Updating NewSessionTicket message and updating PSK to Draft 15.

BUG=77

Change-Id: Id8c45e98c4c22cdd437cbba1e9375239e123b261
Reviewed-on: https://boringssl-review.googlesource.com/10763
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 175160a..c4d7940 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3715,7 +3715,6 @@
 
   uint32_t tlsext_tick_lifetime_hint; /* Session lifetime hint in seconds */
 
-  uint32_t ticket_flags;
   uint32_t ticket_age_add;
 
   /* extended_master_secret is true if the master secret in this session was
diff --git a/ssl/internal.h b/ssl/internal.h
index 9a78a46..4719acb 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1272,10 +1272,12 @@
 extern const SSL3_ENC_METHOD TLSv1_enc_data;
 extern const SSL3_ENC_METHOD SSLv3_enc_data;
 
-/* From draft-ietf-tls-tls13-14, used in determining ticket validity. */
-#define SSL_TICKET_ALLOW_EARLY_DATA 1
-#define SSL_TICKET_ALLOW_DHE_RESUMPTION 2
-#define SSL_TICKET_ALLOW_PSK_RESUMPTION 4
+/* From draft-ietf-tls-tls13-15, used in determining PSK modes. */
+#define SSL_PSK_KE        0x0
+#define SSL_PSK_DHE_KE    0x1
+
+#define SSL_PSK_AUTH      0x0
+#define SSL_PSK_SIGN_AUTH 0x1
 
 CERT *ssl_cert_new(void);
 CERT *ssl_cert_dup(CERT *cert);
diff --git a/ssl/ssl_asn1.c b/ssl/ssl_asn1.c
index ba3b10e..eeea826 100644
--- a/ssl/ssl_asn1.c
+++ b/ssl/ssl_asn1.c
@@ -121,7 +121,6 @@
  *     extendedMasterSecret    [17] BOOLEAN OPTIONAL,
  *     keyExchangeInfo         [18] INTEGER OPTIONAL,
  *     certChain               [19] SEQUENCE OF Certificate OPTIONAL,
- *     ticketFlags             [20] INTEGER OPTIONAL,
  *     ticketAgeAdd            [21] OCTET STRING OPTIONAL,
  * }
  *
@@ -131,7 +130,9 @@
  *     keyArg                  [0] IMPLICIT OCTET STRING OPTIONAL,
  *     pskIdentityHint         [7] OCTET STRING OPTIONAL,
  *     compressionMethod       [11] OCTET STRING OPTIONAL,
- *     srpUsername             [12] OCTET STRING OPTIONAL, */
+ *     srpUsername             [12] OCTET STRING OPTIONAL,
+ *     ticketFlags             [20] INTEGER OPTIONAL,
+ */
 
 static const unsigned kVersion = 1;
 
@@ -167,8 +168,6 @@
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 18;
 static const int kCertChainTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 19;
-static const int kTicketFlagsTag =
-    CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 20;
 static const int kTicketAgeAddTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 21;
 
@@ -347,14 +346,6 @@
     }
   }
 
-  if (in->ticket_flags > 0) {
-    if (!CBB_add_asn1(&session, &child, kTicketFlagsTag) ||
-        !CBB_add_asn1_uint64(&child, in->ticket_flags)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
-    }
-  }
-
   if (in->ticket_age_add_valid) {
     if (!CBB_add_asn1(&session, &child, kTicketAgeAddTag) ||
         !CBB_add_asn1(&child, &child2, CBS_ASN1_OCTETSTRING) ||
@@ -687,9 +678,7 @@
 
   CBS age_add;
   int age_add_present;
-  if (!SSL_SESSION_parse_u32(&session, &ret->ticket_flags,
-                             kTicketFlagsTag, 0) ||
-      !CBS_get_optional_asn1_octet_string(&session, &age_add, &age_add_present,
+  if (!CBS_get_optional_asn1_octet_string(&session, &age_add, &age_add_present,
                                           kTicketAgeAddTag) ||
       (age_add_present &&
        !CBS_get_u32(&age_add, &ret->ticket_age_add)) ||
diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c
index c081476..c2396b1 100644
--- a/ssl/ssl_session.c
+++ b/ssl/ssl_session.c
@@ -258,7 +258,6 @@
     new_session->original_handshake_hash_len =
         session->original_handshake_hash_len;
     new_session->tlsext_tick_lifetime_hint = session->tlsext_tick_lifetime_hint;
-    new_session->ticket_flags = session->ticket_flags;
     new_session->ticket_age_add = session->ticket_age_add;
     new_session->extended_master_secret = session->extended_master_secret;
   }
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 048b629..0da212b 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -2019,7 +2019,7 @@
 
 /* Pre Shared Key
  *
- * https://tools.ietf.org/html/draft-ietf-tls-tls13-14 */
+ * https://tools.ietf.org/html/draft-ietf-tls-tls13-15 */
 
 static int ext_pre_shared_key_add_clienthello(SSL *ssl, CBB *out) {
   uint16_t min_version, max_version;
@@ -2035,12 +2035,16 @@
     return 1;
   }
 
-  CBB contents, identities, identity;
+  CBB contents, identity, ke_modes, auth_modes, ticket;
   if (!CBB_add_u16(out, TLSEXT_TYPE_pre_shared_key) ||
       !CBB_add_u16_length_prefixed(out, &contents) ||
-      !CBB_add_u16_length_prefixed(&contents, &identities) ||
-      !CBB_add_u16_length_prefixed(&identities, &identity) ||
-      !CBB_add_bytes(&identity, ssl->session->tlsext_tick,
+      !CBB_add_u16_length_prefixed(&contents, &identity) ||
+      !CBB_add_u8_length_prefixed(&identity, &ke_modes) ||
+      !CBB_add_u8(&ke_modes, SSL_PSK_DHE_KE) ||
+      !CBB_add_u8_length_prefixed(&identity, &auth_modes) ||
+      !CBB_add_u8(&auth_modes, SSL_PSK_AUTH) ||
+      !CBB_add_u16_length_prefixed(&identity, &ticket) ||
+      !CBB_add_bytes(&ticket, ssl->session->tlsext_tick,
                      ssl->session->tlsext_ticklen)) {
     return 0;
   }
@@ -2069,19 +2073,30 @@
                                              SSL_SESSION **out_session,
                                              uint8_t *out_alert,
                                              CBS *contents) {
-  CBS identities, identity;
-  if (!CBS_get_u16_length_prefixed(contents, &identities) ||
-      !CBS_get_u16_length_prefixed(&identities, &identity) ||
-      CBS_len(contents) != 0) {
+  /* We only process the first PSK identity since we don't support pure PSK. */
+  CBS identity, ke_modes, auth_modes, ticket;
+  if (!CBS_get_u16_length_prefixed(contents, &identity) ||
+      !CBS_get_u8_length_prefixed(&identity, &ke_modes) ||
+      !CBS_get_u8_length_prefixed(&identity, &auth_modes) ||
+      !CBS_get_u16_length_prefixed(&identity, &ticket) ||
+      CBS_len(&identity) != 0) {
     *out_alert = SSL_AD_DECODE_ERROR;
     return 0;
   }
 
+  /* We only support tickets with PSK_DHE_KE and PSK_AUTH. */
+  if (memchr(CBS_data(&ke_modes), SSL_PSK_DHE_KE, CBS_len(&ke_modes)) == NULL ||
+      memchr(CBS_data(&auth_modes), SSL_PSK_AUTH, CBS_len(&auth_modes)) ==
+          NULL) {
+    *out_session = NULL;
+    return 1;
+  }
+
   /* TLS 1.3 session tickets are renewed separately as part of the
    * NewSessionTicket. */
   int renew;
-  return tls_process_ticket(ssl, out_session, &renew, CBS_data(&identity),
-                            CBS_len(&identity), NULL, 0);
+  return tls_process_ticket(ssl, out_session, &renew, CBS_data(&ticket),
+                            CBS_len(&ticket), NULL, 0);
 }
 
 int ssl_ext_pre_shared_key_add_serverhello(SSL *ssl, CBB *out) {
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 1d16a7a..599ce02 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -189,11 +189,16 @@
 	SRTP_AES128_CM_HMAC_SHA1_32        = 0x0002
 )
 
-// TicketFlags values (see draft-ietf-tls-tls13-14, section 4.4.1)
+// PskKeyExchangeMode values (see draft-ietf-tls-tls13-15)
 const (
-	ticketAllowEarlyData     = 1
-	ticketAllowDHEResumption = 2
-	ticketAllowPSKResumption = 4
+	pskKEMode    = 0
+	pskDHEKEMode = 1
+)
+
+// PskAuthenticationMode values (see draft-ietf-tls-tls13-15)
+const (
+	pskAuthMode     = 0
+	pskSignAuthMode = 1
 )
 
 // ConnectionState records basic TLS details about the connection.
@@ -243,8 +248,6 @@
 	ocspResponse         []byte
 	ticketCreationTime   time.Time
 	ticketExpiration     time.Time
-	ticketFlags          uint32
-	ticketAgeAdd         uint32
 }
 
 // ClientSessionCache is a cache of ClientSessionState objects that can be used
@@ -914,6 +917,13 @@
 	// session ticket.
 	SendEmptySessionTicket bool
 
+	// SnedPSKKeyExchangeModes, if present, determines the PSK key exchange modes
+	// to send.
+	SendPSKKeyExchangeModes []byte
+
+	// SendPSKAuthModes, if present, determines the PSK auth modes to send.
+	SendPSKAuthModes []byte
+
 	// FailIfSessionOffered, if true, causes the server to fail any
 	// connections where the client offers a non-empty session ID or session
 	// ticket.
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 1fd0200..84e1eb8 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -1402,6 +1402,23 @@
 				return nil
 			}
 
+			var foundKE, foundAuth bool
+			for _, mode := range newSessionTicket.keModes {
+				if mode == pskDHEKEMode {
+					foundKE = true
+				}
+			}
+			for _, mode := range newSessionTicket.authModes {
+				if mode == pskAuthMode {
+					foundAuth = true
+				}
+			}
+
+			// Ignore the ticket if the server preferences do not match a mode we implement.
+			if !foundKE || !foundAuth {
+				return nil
+			}
+
 			session := &ClientSessionState{
 				sessionTicket:      newSessionTicket.ticket,
 				vers:               c.vers,
@@ -1412,8 +1429,6 @@
 				ocspResponse:       c.ocspResponse,
 				ticketCreationTime: c.config.time(),
 				ticketExpiration:   c.config.time().Add(time.Duration(newSessionTicket.ticketLifetime) * time.Second),
-				ticketFlags:        newSessionTicket.ticketFlags,
-				ticketAgeAdd:       newSessionTicket.ticketAgeAdd,
 			}
 
 			cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
@@ -1693,17 +1708,19 @@
 		peerCertificatesRaw = append(peerCertificatesRaw, cert.Raw)
 	}
 
-	var ageAdd uint32
-	if err := binary.Read(c.config.rand(), binary.LittleEndian, &ageAdd); err != nil {
-		return err
-	}
-
 	// TODO(davidben): Allow configuring these values.
 	m := &newSessionTicketMsg{
 		version:        c.vers,
 		ticketLifetime: uint32(24 * time.Hour / time.Second),
-		ticketFlags:    ticketAllowDHEResumption | ticketAllowPSKResumption,
-		ticketAgeAdd:   ageAdd,
+		keModes:        []byte{pskDHEKEMode},
+		authModes:      []byte{pskAuthMode},
+	}
+
+	if len(c.config.Bugs.SendPSKKeyExchangeModes) != 0 {
+		m.keModes = c.config.Bugs.SendPSKKeyExchangeModes
+	}
+	if len(c.config.Bugs.SendPSKAuthModes) != 0 {
+		m.authModes = c.config.Bugs.SendPSKAuthModes
 	}
 
 	state := sessionState{
@@ -1713,8 +1730,6 @@
 		certificates:       peerCertificatesRaw,
 		ticketCreationTime: c.config.time(),
 		ticketExpiration:   c.config.time().Add(time.Duration(m.ticketLifetime) * time.Second),
-		ticketFlags:        m.ticketFlags,
-		ticketAgeAdd:       ageAdd,
 	}
 
 	if !c.config.Bugs.SendEmptySessionTicket {
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index c658e72..f03e169 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -261,10 +261,20 @@
 		if session.vers >= VersionTLS13 || c.config.Bugs.SendBothTickets {
 			// TODO(nharper): Support sending more
 			// than one PSK identity.
-			if session.ticketFlags&ticketAllowDHEResumption != 0 || c.config.Bugs.SendBothTickets {
-				hello.pskIdentities = [][]uint8{ticket}
-				hello.cipherSuites = append(hello.cipherSuites, ecdhePSKSuite(session.cipherSuite))
+			psk := pskIdentity{
+				keModes:   []byte{pskDHEKEMode},
+				authModes: []byte{pskAuthMode},
+				ticket:    ticket,
 			}
+			if len(c.config.Bugs.SendPSKKeyExchangeModes) != 0 {
+				psk.keModes = c.config.Bugs.SendPSKKeyExchangeModes
+			}
+			if len(c.config.Bugs.SendPSKAuthModes) != 0 {
+				psk.authModes = c.config.Bugs.SendPSKAuthModes
+			}
+
+			hello.pskIdentities = []pskIdentity{psk}
+			hello.cipherSuites = append(hello.cipherSuites, ecdhePSKSuite(session.cipherSuite))
 		}
 
 		if session.vers < VersionTLS13 || c.config.Bugs.SendBothTickets {
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 0b45a4d..e7a1235 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -124,6 +124,12 @@
 	keyExchange []byte
 }
 
+type pskIdentity struct {
+	keModes   []byte
+	authModes []byte
+	ticket    []uint8
+}
+
 type clientHelloMsg struct {
 	raw       []byte
 	isDTLS    bool
@@ -143,7 +149,7 @@
 	hasKeyShares            bool
 	keyShares               []keyShareEntry
 	trailingKeyShareData    bool
-	pskIdentities           [][]uint8
+	pskIdentities           []pskIdentity
 	hasEarlyData            bool
 	earlyDataContext        []byte
 	ticketSupported         bool
@@ -185,7 +191,7 @@
 		m.hasKeyShares == m1.hasKeyShares &&
 		eqKeyShareEntryLists(m.keyShares, m1.keyShares) &&
 		m.trailingKeyShareData == m1.trailingKeyShareData &&
-		eqByteSlices(m.pskIdentities, m1.pskIdentities) &&
+		eqPSKIdentityLists(m.pskIdentities, m1.pskIdentities) &&
 		m.hasEarlyData == m1.hasEarlyData &&
 		bytes.Equal(m.earlyDataContext, m1.earlyDataContext) &&
 		m.ticketSupported == m1.ticketSupported &&
@@ -316,8 +322,9 @@
 
 		pskIdentities := pskExtension.addU16LengthPrefixed()
 		for _, psk := range m.pskIdentities {
-			pskIdentity := pskIdentities.addU16LengthPrefixed()
-			pskIdentity.addBytes(psk)
+			pskIdentities.addU8LengthPrefixed().addBytes(psk.keModes)
+			pskIdentities.addU8LengthPrefixed().addBytes(psk.authModes)
+			pskIdentities.addU16LengthPrefixed().addBytes(psk.ticket)
 		}
 	}
 	if m.hasEarlyData {
@@ -612,15 +619,39 @@
 			}
 			d := data[2:length]
 			for len(d) > 0 {
+				var psk pskIdentity
+
+				if len(d) < 1 {
+					return false
+				}
+				keModesLen := int(d[0])
+				d = d[1:]
+				if len(d) < keModesLen {
+					return false
+				}
+				psk.keModes = d[:keModesLen]
+				d = d[keModesLen:]
+
+				if len(d) < 1 {
+					return false
+				}
+				authModesLen := int(d[0])
+				d = d[1:]
+				if len(d) < authModesLen {
+					return false
+				}
+				psk.authModes = d[:authModesLen]
+				d = d[authModesLen:]
 				if len(d) < 2 {
 					return false
 				}
 				pskLen := int(d[0])<<8 | int(d[1])
 				d = d[2:]
+
 				if len(d) < pskLen {
 					return false
 				}
-				psk := d[:pskLen]
+				psk.ticket = d[:pskLen]
 				m.pskIdentities = append(m.pskIdentities, psk)
 				d = d[pskLen:]
 			}
@@ -1781,8 +1812,8 @@
 	raw            []byte
 	version        uint16
 	ticketLifetime uint32
-	ticketFlags    uint32
-	ticketAgeAdd   uint32
+	keModes        []byte
+	authModes      []byte
 	ticket         []byte
 }
 
@@ -1797,16 +1828,20 @@
 	body := ticketMsg.addU24LengthPrefixed()
 	body.addU32(m.ticketLifetime)
 	if m.version >= VersionTLS13 {
-		body.addU32(m.ticketFlags)
-		body.addU32(m.ticketAgeAdd)
+		body.addU8LengthPrefixed().addBytes(m.keModes)
+		body.addU8LengthPrefixed().addBytes(m.authModes)
+	}
+
+	ticket := body.addU16LengthPrefixed()
+	ticket.addBytes(m.ticket)
+
+	if m.version >= VersionTLS13 {
 		// Send no extensions.
 		//
 		// TODO(davidben): Add an option to send a custom extension to
 		// test we correctly ignore unknown ones.
 		body.addU16(0)
 	}
-	ticket := body.addU16LengthPrefixed()
-	ticket.addBytes(m.ticket)
 
 	m.raw = ticketMsg.finish()
 	return m.raw
@@ -1822,31 +1857,58 @@
 	data = data[8:]
 
 	if m.version >= VersionTLS13 {
-		if len(data) < 10 {
+		if len(data) < 1 {
 			return false
 		}
-		m.ticketFlags = uint32(data[0])<<24 | uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3])
-		m.ticketAgeAdd = uint32(data[4])<<24 | uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7])
-		extsLength := int(data[8])<<8 + int(data[9])
-		data = data[10:]
-		if len(data) < extsLength {
+		keModesLength := int(data[0])
+		if len(data)-1 < keModesLength {
 			return false
 		}
-		data = data[extsLength:]
+		m.keModes = data[1 : 1+keModesLength]
+		data = data[1+keModesLength:]
+
+		if len(data) < 1 {
+			return false
+		}
+		authModesLength := int(data[0])
+		if len(data)-1 < authModesLength {
+			return false
+		}
+		m.authModes = data[1 : 1+authModesLength]
+		data = data[1+authModesLength:]
 	}
 
 	if len(data) < 2 {
 		return false
 	}
 	ticketLen := int(data[0])<<8 + int(data[1])
-	if len(data)-2 != ticketLen {
+	data = data[2:]
+	if len(data) < ticketLen {
 		return false
 	}
+
 	if m.version >= VersionTLS13 && ticketLen == 0 {
 		return false
 	}
 
-	m.ticket = data[2:]
+	m.ticket = data[:ticketLen]
+	data = data[ticketLen:]
+
+	if m.version >= VersionTLS13 {
+		if len(data) < 2 {
+			return false
+		}
+		extsLength := int(data[0])<<8 + int(data[1])
+		data = data[2:]
+		if len(data) < extsLength {
+			return false
+		}
+		data = data[extsLength:]
+	}
+
+	if len(data) > 0 {
+		return false
+	}
 
 	return true
 }
@@ -2071,3 +2133,16 @@
 	return true
 
 }
+
+func eqPSKIdentityLists(x, y []pskIdentity) bool {
+	if len(x) != len(y) {
+		return false
+	}
+	for i, v := range x {
+		if !bytes.Equal(y[i].keModes, v.keModes) || !bytes.Equal(y[i].authModes, v.authModes) || !bytes.Equal(y[i].ticket, v.ticket) {
+			return false
+		}
+	}
+	return true
+
+}
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index a09dc27..934e052 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -400,10 +400,34 @@
 
 	pskIdentities := hs.clientHello.pskIdentities
 	if len(pskIdentities) == 0 && len(hs.clientHello.sessionTicket) > 0 && c.config.Bugs.AcceptAnySession {
-		pskIdentities = [][]uint8{hs.clientHello.sessionTicket}
+		psk := pskIdentity{
+			keModes:   []byte{pskDHEKEMode},
+			authModes: []byte{pskAuthMode},
+			ticket:    hs.clientHello.sessionTicket,
+		}
+		pskIdentities = []pskIdentity{psk}
 	}
 	for i, pskIdentity := range pskIdentities {
-		sessionState, ok := c.decryptTicket(pskIdentity)
+		foundKE := false
+		foundAuth := false
+
+		for _, keMode := range pskIdentity.keModes {
+			if keMode == pskDHEKEMode {
+				foundKE = true
+			}
+		}
+
+		for _, authMode := range pskIdentity.authModes {
+			if authMode == pskAuthMode {
+				foundAuth = true
+			}
+		}
+
+		if !foundKE || !foundAuth {
+			continue
+		}
+
+		sessionState, ok := c.decryptTicket(pskIdentity.ticket)
 		if !ok {
 			continue
 		}
@@ -411,9 +435,6 @@
 			if sessionState.vers != c.vers && c.config.Bugs.AcceptAnySession {
 				continue
 			}
-			if sessionState.ticketFlags&ticketAllowDHEResumption == 0 {
-				continue
-			}
 			if sessionState.ticketExpiration.Before(c.config.time()) {
 				continue
 			}
@@ -1090,7 +1111,7 @@
 
 	ticket := hs.clientHello.sessionTicket
 	if len(ticket) == 0 && len(hs.clientHello.pskIdentities) > 0 && c.config.Bugs.AcceptAnySession {
-		ticket = hs.clientHello.pskIdentities[0]
+		ticket = hs.clientHello.pskIdentities[0].ticket
 	}
 	if len(ticket) > 0 {
 		if c.config.SessionTicketsDisabled {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 17cc507..f536ceb 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -2118,19 +2118,6 @@
 			expectMessageDropped: true,
 		},
 		{
-			// In TLS 1.2 and below, empty NewSessionTicket messages
-			// mean the server changed its mind on sending a ticket.
-			name: "SendEmptySessionTicket",
-			config: Config{
-				MaxVersion: VersionTLS12,
-				Bugs: ProtocolBugs{
-					SendEmptySessionTicket: true,
-					FailIfSessionOffered:   true,
-				},
-			},
-			flags: []string{"-expect-no-session"},
-		},
-		{
 			name:        "BadHelloRequest-1",
 			renegotiate: 1,
 			config: Config{
@@ -7572,6 +7559,105 @@
 	})
 }
 
+func addSessionTicketTests() {
+	testCases = append(testCases, testCase{
+		// In TLS 1.2 and below, empty NewSessionTicket messages
+		// mean the server changed its mind on sending a ticket.
+		name: "SendEmptySessionTicket",
+		config: Config{
+			MaxVersion: VersionTLS12,
+			Bugs: ProtocolBugs{
+				SendEmptySessionTicket: true,
+			},
+		},
+		flags: []string{"-expect-no-session"},
+	})
+
+	// Test that the server ignores unknown PSK modes.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-SendUnknownModeSessionTicket-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendPSKKeyExchangeModes: []byte{0x1a, pskDHEKEMode, 0x2a},
+				SendPSKAuthModes:        []byte{0x1a, pskAuthMode, 0x2a},
+			},
+		},
+		resumeSession:         true,
+		expectedResumeVersion: VersionTLS13,
+	})
+
+	// Test that the server declines sessions with no matching key exchange mode.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-SendBadKEModeSessionTicket-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendPSKKeyExchangeModes: []byte{0x1a},
+			},
+		},
+		resumeSession:        true,
+		expectResumeRejected: true,
+	})
+
+	// Test that the server declines sessions with no matching auth mode.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-SendBadAuthModeSessionTicket-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendPSKAuthModes: []byte{0x1a},
+			},
+		},
+		resumeSession:        true,
+		expectResumeRejected: true,
+	})
+
+	// Test that the client ignores unknown PSK modes.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-SendUnknownModeSessionTicket-Client",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendPSKKeyExchangeModes: []byte{0x1a, pskDHEKEMode, 0x2a},
+				SendPSKAuthModes:        []byte{0x1a, pskAuthMode, 0x2a},
+			},
+		},
+		resumeSession:         true,
+		expectedResumeVersion: VersionTLS13,
+	})
+
+	// Test that the client ignores tickets with no matching key exchange mode.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-SendBadKEModeSessionTicket-Client",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendPSKKeyExchangeModes: []byte{0x1a},
+			},
+		},
+		flags: []string{"-expect-no-session"},
+	})
+
+	// Test that the client ignores tickets with no matching auth mode.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-SendBadAuthModeSessionTicket-Client",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendPSKAuthModes: []byte{0x1a},
+			},
+		},
+		flags: []string{"-expect-no-session"},
+	})
+}
+
 func addChangeCipherSpecTests() {
 	// Test missing ChangeCipherSpecs.
 	testCases = append(testCases, testCase{
@@ -8642,6 +8728,7 @@
 	addCurveTests()
 	addCECPQ1Tests()
 	addDHEGroupSizeTests()
+	addSessionTicketTests()
 	addTLS13RecordTests()
 	addAllStateMachineCoverageTests()
 	addChangeCipherSpecTests()
diff --git a/ssl/tls13_client.c b/ssl/tls13_client.c
index ee73f73..aa80b69 100644
--- a/ssl/tls13_client.c
+++ b/ssl/tls13_client.c
@@ -645,14 +645,16 @@
     return 0;
   }
 
-  CBS cbs, extensions, ticket;
+  CBS cbs, ke_modes, auth_modes, ticket, extensions;
   CBS_init(&cbs, ssl->init_msg, ssl->init_num);
   if (!CBS_get_u32(&cbs, &session->tlsext_tick_lifetime_hint) ||
-      !CBS_get_u32(&cbs, &session->ticket_flags) ||
-      !CBS_get_u32(&cbs, &session->ticket_age_add) ||
-      !CBS_get_u16_length_prefixed(&cbs, &extensions) ||
+      !CBS_get_u8_length_prefixed(&cbs, &ke_modes) ||
+      CBS_len(&ke_modes) == 0 ||
+      !CBS_get_u8_length_prefixed(&cbs, &auth_modes) ||
+      CBS_len(&auth_modes) == 0 ||
       !CBS_get_u16_length_prefixed(&cbs, &ticket) ||
       !CBS_stow(&ticket, &session->tlsext_tick, &session->tlsext_ticklen) ||
+      !CBS_get_u16_length_prefixed(&cbs, &extensions) ||
       CBS_len(&cbs) != 0) {
     SSL_SESSION_free(session);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
@@ -660,10 +662,13 @@
     return 0;
   }
 
-  session->ticket_age_add_valid = 1;
   session->not_resumable = 0;
 
-  if (ssl->ctx->new_session_cb != NULL &&
+  /* Ignore the ticket unless the server preferences are compatible with us. */
+  if (memchr(CBS_data(&ke_modes), SSL_PSK_DHE_KE, CBS_len(&ke_modes)) != NULL &&
+      memchr(CBS_data(&auth_modes), SSL_PSK_AUTH, CBS_len(&auth_modes)) !=
+          NULL &&
+      ssl->ctx->new_session_cb != NULL &&
       ssl->ctx->new_session_cb(ssl, session)) {
     /* |new_session_cb|'s return value signals that it took ownership. */
     return 1;
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index 53e5363..9e146e6 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -141,10 +141,8 @@
 
   uint16_t resumption_cipher;
   if (session != NULL &&
-      /* We currently only support ECDHE-PSK resumption. */
-      ((session->ticket_flags & SSL_TICKET_ALLOW_DHE_RESUMPTION) == 0 ||
-       /* Only resume if the session's version matches. */
-       session->ssl_version != ssl->version ||
+      /* Only resume if the session's version matches. */
+      (session->ssl_version != ssl->version ||
        !ssl_cipher_get_ecdhe_psk_cipher(session->cipher, &resumption_cipher) ||
        !ssl_client_cipher_list_contains_cipher(&client_hello,
                                                resumption_cipher))) {
@@ -556,22 +554,21 @@
                                                      SSL_HANDSHAKE *hs) {
   SSL_SESSION *session = ssl->s3->new_session;
   session->tlsext_tick_lifetime_hint = session->timeout;
-  session->ticket_flags = SSL_TICKET_ALLOW_DHE_RESUMPTION;
-  if (!RAND_bytes((uint8_t *)&session->ticket_age_add,
-                  sizeof(session->ticket_age_add))) {
-    return 0;
-  }
-  session->ticket_age_add_valid = 1;
 
-  CBB cbb, body, ticket;
+  /* TODO(svaldez): Add support for sending 0RTT through TicketEarlyDataInfo
+   * extension. */
+
+  CBB cbb, body, ke_modes, auth_modes, ticket;
   if (!ssl->method->init_message(ssl, &cbb, &body,
                                  SSL3_MT_NEW_SESSION_TICKET) ||
       !CBB_add_u32(&body, session->tlsext_tick_lifetime_hint) ||
-      !CBB_add_u32(&body, session->ticket_flags) ||
-      !CBB_add_u32(&body, session->ticket_age_add) ||
-      !CBB_add_u16(&body, 0 /* no ticket extensions */) ||
+      !CBB_add_u8_length_prefixed(&body, &ke_modes) ||
+      !CBB_add_u8(&ke_modes, SSL_PSK_DHE_KE) ||
+      !CBB_add_u8_length_prefixed(&body, &auth_modes) ||
+      !CBB_add_u8(&auth_modes, SSL_PSK_AUTH) ||
       !CBB_add_u16_length_prefixed(&body, &ticket) ||
       !ssl_encrypt_ticket(ssl, &ticket, session) ||
+      !CBB_add_u16(&body, 0 /* no ticket extensions */) ||
       !ssl->method->finish_message(ssl, &cbb)) {
     CBB_cleanup(&cbb);
     return ssl_hs_error;