Enabling 0-RTT on new Session Tickets.

This adds support for setting 0-RTT mode on tickets minted by
BoringSSL, allowing for testing of the initial handshake knowledge.

BUG=76

Change-Id: Ic199842c03b5401ef122a537fdb7ed9e9a5c635a
Reviewed-on: https://boringssl-review.googlesource.com/12740
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 1a0e052..c230f8c 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2913,6 +2913,11 @@
  * peformed by |ssl|. This includes the pending renegotiation, if any. */
 OPENSSL_EXPORT int SSL_total_renegotiations(const SSL *ssl);
 
+/* SSL_CTX_set_early_data_enabled sets whether early data is allowed to be used
+ * with resumptions using |ctx|. WARNING: This is experimental and may cause
+ * interop failures until fully implemented. */
+OPENSSL_EXPORT void SSL_CTX_set_early_data_enabled(SSL_CTX *ctx, int enabled);
+
 /* SSL_MAX_CERT_LIST_DEFAULT is the default maximum length, in bytes, of a peer
  * certificate chain. */
 #define SSL_MAX_CERT_LIST_DEFAULT (1024 * 100)
@@ -3758,6 +3763,10 @@
 
   uint32_t ticket_age_add;
 
+  /* ticket_max_early_data is the maximum amount of data allowed to be sent as
+   * early data. If zero, 0-RTT is disallowed. */
+  uint32_t ticket_max_early_data;
+
   /* extended_master_secret is true if the master secret in this session was
    * generated using EMS and thus isn't vulnerable to the Triple Handshake
    * attack. */
@@ -4033,6 +4042,10 @@
    * shutdown. */
   unsigned quiet_shutdown:1;
 
+  /* If enable_early_data is non-zero, early data can be sent and accepted over
+   * new connections. */
+  unsigned enable_early_data:1;
+
   /* ocsp_stapling_enabled is only used by client connections and indicates
    * whether OCSP stapling will be requested. */
   unsigned ocsp_stapling_enabled:1;
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index 28828d1..7d9f2ac 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -213,6 +213,7 @@
 #define TLSEXT_TYPE_supported_versions 43
 #define TLSEXT_TYPE_cookie 44
 #define TLSEXT_TYPE_psk_key_exchange_modes 45
+#define TLSEXT_TYPE_ticket_early_data_info 46
 
 /* ExtensionType value from RFC5746 */
 #define TLSEXT_TYPE_renegotiate 0xff01
diff --git a/ssl/internal.h b/ssl/internal.h
index e26fa13..919f5aa 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1148,10 +1148,10 @@
  * it. It writes the parsed extensions to pointers denoted by |ext_types|. On
  * success, it fills in the |out_present| and |out_data| fields and returns one.
  * Otherwise, it sets |*out_alert| to an alert to send and returns zero. Unknown
- * extensions are rejected. */
+ * extensions are rejected unless |ignore_unknown| is 1. */
 int ssl_parse_extensions(const CBS *cbs, uint8_t *out_alert,
                          const SSL_EXTENSION_TYPE *ext_types,
-                         size_t num_ext_types);
+                         size_t num_ext_types, int ignore_unknown);
 
 
 /* SSLKEYLOGFILE functions. */
diff --git a/ssl/s3_both.c b/ssl/s3_both.c
index a0594f4..492884f 100644
--- a/ssl/s3_both.c
+++ b/ssl/s3_both.c
@@ -780,7 +780,7 @@
 
 int ssl_parse_extensions(const CBS *cbs, uint8_t *out_alert,
                          const SSL_EXTENSION_TYPE *ext_types,
-                         size_t num_ext_types) {
+                         size_t num_ext_types, int ignore_unknown) {
   /* Reset everything. */
   for (size_t i = 0; i < num_ext_types; i++) {
     *ext_types[i].out_present = 0;
@@ -807,6 +807,9 @@
     }
 
     if (ext_type == NULL) {
+      if (ignore_unknown) {
+        continue;
+      }
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
       *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
       return 0;
diff --git a/ssl/ssl_asn1.c b/ssl/ssl_asn1.c
index d8a7d8f..c9dfdcc 100644
--- a/ssl/ssl_asn1.c
+++ b/ssl/ssl_asn1.c
@@ -128,6 +128,7 @@
  *     ticketAgeAdd            [21] OCTET STRING OPTIONAL,
  *     isServer                [22] BOOLEAN DEFAULT TRUE,
  *     peerSignatureAlgorithm  [23] INTEGER OPTIONAL,
+ *     ticketMaxEarlyData      [24] INTEGER OPTIONAL,
  * }
  *
  * Note: historically this serialization has included other optional
@@ -180,6 +181,8 @@
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 22;
 static const int kPeerSignatureAlgorithmTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 23;
+static const int kTicketMaxEarlyDataTag =
+    CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 24;
 
 static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data,
                                      size_t *out_len, int for_ticket) {
@@ -392,6 +395,13 @@
     goto err;
   }
 
+  if (in->ticket_max_early_data != 0 &&
+      (!CBB_add_asn1(&session, &child, kTicketMaxEarlyDataTag) ||
+       !CBB_add_asn1_uint64(&child, in->ticket_max_early_data))) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
   if (!CBB_finish(&cbb, out_data, out_len)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     goto err;
@@ -775,6 +785,8 @@
 
   if (!SSL_SESSION_parse_u16(&session, &ret->peer_signature_algorithm,
                              kPeerSignatureAlgorithmTag, 0) ||
+      !SSL_SESSION_parse_u32(&session, &ret->ticket_max_early_data,
+                             kTicketMaxEarlyDataTag, 0) ||
       CBS_len(&session) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
     goto err;
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 2e9f4a6..a60bf81 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -874,6 +874,10 @@
   return ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
 }
 
+void SSL_CTX_set_early_data_enabled(SSL_CTX *ctx, int enabled) {
+  ctx->enable_early_data = !!enabled;
+}
+
 static int bio_retry_reason_to_error(int reason) {
   switch (reason) {
     case BIO_RR_CONNECT:
diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c
index 62ee28e..7adef1a 100644
--- a/ssl/ssl_session.c
+++ b/ssl/ssl_session.c
@@ -276,6 +276,7 @@
         session->original_handshake_hash_len;
     new_session->tlsext_tick_lifetime_hint = session->tlsext_tick_lifetime_hint;
     new_session->ticket_age_add = session->ticket_age_add;
+    new_session->ticket_max_early_data = session->ticket_max_early_data;
     new_session->extended_master_secret = session->extended_master_secret;
   }
 
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index d46a027..a98ff43 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1045,6 +1045,10 @@
     SSL_CTX_set_short_header_enabled(ssl_ctx.get(), 1);
   }
 
+  if (config->enable_early_data) {
+    SSL_CTX_set_early_data_enabled(ssl_ctx.get(), 1);
+  }
+
   return ssl_ctx;
 }
 
@@ -1844,6 +1848,19 @@
               GetTestState(ssl.get())->got_new_session ? "" : " not");
       return false;
     }
+
+    if (expect_new_session) {
+      bool got_early_data_info =
+          GetTestState(ssl.get())->new_session->ticket_max_early_data != 0;
+      if (config->expect_early_data_info != got_early_data_info) {
+        fprintf(
+            stderr,
+            "new session did%s include ticket_early_data_info, but we expected "
+            "the opposite\n",
+            got_early_data_info ? "" : " not");
+        return false;
+      }
+    }
   }
 
   if (out_session) {
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index e527185..029fd4a 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -95,6 +95,7 @@
 	extensionSupportedVersions          uint16 = 43    // draft-ietf-tls-tls13-16
 	extensionCookie                     uint16 = 44    // draft-ietf-tls-tls13-16
 	extensionPSKKeyExchangeModes        uint16 = 45    // draft-ietf-tls-tls13-18
+	extensionTicketEarlyDataInfo        uint16 = 46    // draft-ietf-tls-tls13-18
 	extensionCustom                     uint16 = 1234  // not IANA assigned
 	extensionNextProtoNeg               uint16 = 13172 // not IANA assigned
 	extensionRenegotiationInfo          uint16 = 0xff01
@@ -102,11 +103,6 @@
 	extensionShortHeader                uint16 = 27463 // not IANA assigned
 )
 
-// TLS ticket extension numbers
-const (
-	ticketExtensionCustom uint16 = 1234 // not IANA assigned
-)
-
 // TLS signaling cipher suite values
 const (
 	scsvRenegotiation uint16 = 0x00ff
@@ -959,6 +955,15 @@
 	// receipt of a NewSessionTicket message.
 	ExpectNoNewSessionTicket bool
 
+	// SendTicketEarlyDataInfo, if non-zero, is the maximum amount of data that we
+	// will accept as early data, and gets sent in the ticket_early_data_info
+	// extension of the NewSessionTicket message.
+	SendTicketEarlyDataInfo uint32
+
+	// ExpectTicketEarlyDataInfo, if true, means that the client will fail upon
+	// absence of the ticket_early_data_info extension.
+	ExpectTicketEarlyDataInfo bool
+
 	// ExpectTicketAge, if non-zero, is the expected age of the ticket that the
 	// server receives from the client.
 	ExpectTicketAge time.Duration
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 3e22465..62a46bf 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -1451,6 +1451,10 @@
 				return errors.New("tls: no GREASE ticket extension found")
 			}
 
+			if c.config.Bugs.ExpectTicketEarlyDataInfo && newSessionTicket.earlyDataInfo == 0 {
+				return errors.New("tls: no ticket_early_data_info extension found")
+			}
+
 			if c.config.Bugs.ExpectNoNewSessionTicket {
 				return errors.New("tls: received unexpected NewSessionTicket")
 			}
@@ -1765,6 +1769,7 @@
 	m := &newSessionTicketMsg{
 		version:         c.vers,
 		ticketLifetime:  uint32(24 * time.Hour / time.Second),
+		earlyDataInfo:   c.config.Bugs.SendTicketEarlyDataInfo,
 		customExtension: c.config.Bugs.CustomTicketExtension,
 		ticketAgeAdd:    ticketAgeAdd,
 	}
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 01f673b..a431cb5 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -2008,6 +2008,7 @@
 	ticketLifetime     uint32
 	ticketAgeAdd       uint32
 	ticket             []byte
+	earlyDataInfo      uint32
 	customExtension    string
 	hasGREASEExtension bool
 }
@@ -2031,8 +2032,12 @@
 
 	if m.version >= VersionTLS13 {
 		extensions := body.addU16LengthPrefixed()
+		if m.earlyDataInfo > 0 {
+			extensions.addU16(extensionTicketEarlyDataInfo)
+			extensions.addU16LengthPrefixed().addU32(m.earlyDataInfo)
+		}
 		if len(m.customExtension) > 0 {
-			extensions.addU16(ticketExtensionCustom)
+			extensions.addU16(extensionCustom)
 			extensions.addU16LengthPrefixed().addBytes([]byte(m.customExtension))
 		}
 	}
@@ -2078,28 +2083,37 @@
 		if len(data) < 2 {
 			return false
 		}
-		extsLength := int(data[0])<<8 + int(data[1])
+
+		extensionsLength := int(data[0])<<8 | int(data[1])
 		data = data[2:]
-		if len(data) < extsLength {
+		if extensionsLength != len(data) {
 			return false
 		}
-		extensions := data[:extsLength]
-		data = data[extsLength:]
 
-		for len(extensions) > 0 {
-			if len(extensions) < 4 {
+		for len(data) != 0 {
+			if len(data) < 4 {
 				return false
 			}
-			extValue := uint16(extensions[0])<<8 | uint16(extensions[1])
-			extLength := int(extensions[2])<<8 | int(extensions[3])
-			if len(extensions) < 4+extLength {
+			extension := uint16(data[0])<<8 | uint16(data[1])
+			length := int(data[2])<<8 | int(data[3])
+			data = data[4:]
+			if len(data) < length {
 				return false
 			}
-			extensions = extensions[4+extLength:]
 
-			if isGREASEValue(extValue) {
-				m.hasGREASEExtension = true
+			switch extension {
+			case extensionTicketEarlyDataInfo:
+				if length != 4 {
+					return false
+				}
+				m.earlyDataInfo = uint32(data[0])<<24 | uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3])
+			default:
+				if isGREASEValue(extension) {
+					m.hasGREASEExtension = true
+				}
 			}
+
+			data = data[length:]
 		}
 	}
 
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 56026b2..5c3d914 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -8379,6 +8379,33 @@
 		expectedLocalError: "tls: invalid ticket age",
 	})
 
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-SendTicketEarlyDataInfo",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendTicketEarlyDataInfo: 16384,
+			},
+		},
+		flags: []string{
+			"-expect-early-data-info",
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-ExpectTicketEarlyDataInfo",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectTicketEarlyDataInfo: true,
+			},
+		},
+		flags: []string{
+			"-enable-early-data",
+		},
+	})
 }
 
 func addChangeCipherSpecTests() {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 22e4c9c..a06b5e5 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -81,8 +81,10 @@
   { "-tls-unique", &TestConfig::tls_unique },
   { "-expect-ticket-renewal", &TestConfig::expect_ticket_renewal },
   { "-expect-no-session", &TestConfig::expect_no_session },
+  { "-expect-early-data-info", &TestConfig::expect_early_data_info },
   { "-use-ticket-callback", &TestConfig::use_ticket_callback },
   { "-renew-ticket", &TestConfig::renew_ticket },
+  { "-enable-early-data", &TestConfig::enable_early_data },
   { "-enable-client-custom-extension",
     &TestConfig::enable_client_custom_extension },
   { "-enable-server-custom-extension",
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 882cddc..1307d56 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -83,8 +83,10 @@
   bool tls_unique = false;
   bool expect_ticket_renewal = false;
   bool expect_no_session = false;
+  bool expect_early_data_info = false;
   bool use_ticket_callback = false;
   bool renew_ticket = false;
+  bool enable_early_data = false;
   bool enable_client_custom_extension = false;
   bool enable_server_custom_extension = false;
   bool custom_extension_skip = false;
diff --git a/ssl/tls13_both.c b/ssl/tls13_both.c
index 5a058b1..7347fc4 100644
--- a/ssl/tls13_both.c
+++ b/ssl/tls13_both.c
@@ -243,7 +243,8 @@
 
     uint8_t alert;
     if (!ssl_parse_extensions(&extensions, &alert, ext_types,
-                              OPENSSL_ARRAY_SIZE(ext_types))) {
+                              OPENSSL_ARRAY_SIZE(ext_types),
+                              0 /* reject unknown */)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
       goto err;
     }
diff --git a/ssl/tls13_client.c b/ssl/tls13_client.c
index 20b80ed..1d8bc54 100644
--- a/ssl/tls13_client.c
+++ b/ssl/tls13_client.c
@@ -78,7 +78,8 @@
 
   uint8_t alert;
   if (!ssl_parse_extensions(&extensions, &alert, ext_types,
-                            OPENSSL_ARRAY_SIZE(ext_types))) {
+                            OPENSSL_ARRAY_SIZE(ext_types),
+                            0 /* reject unknown */)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
@@ -211,7 +212,8 @@
 
   uint8_t alert;
   if (!ssl_parse_extensions(&extensions, &alert, ext_types,
-                            OPENSSL_ARRAY_SIZE(ext_types))) {
+                            OPENSSL_ARRAY_SIZE(ext_types),
+                            0 /* reject unknown */)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
@@ -659,6 +661,30 @@
     return 0;
   }
 
+  /* Parse out the extensions. */
+  int have_early_data_info = 0;
+  CBS early_data_info;
+  const SSL_EXTENSION_TYPE ext_types[] = {
+      {TLSEXT_TYPE_ticket_early_data_info, &have_early_data_info,
+       &early_data_info},
+  };
+
+  uint8_t alert;
+  if (!ssl_parse_extensions(&extensions, &alert, ext_types,
+                            OPENSSL_ARRAY_SIZE(ext_types),
+                            1 /* ignore unknown */)) {
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+    return ssl_hs_error;
+  }
+
+  if (have_early_data_info) {
+    if (!CBS_get_u32(&early_data_info, &session->ticket_max_early_data) ||
+        CBS_len(&early_data_info) != 0) {
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+      return ssl_hs_error;
+    }
+  }
+
   session->ticket_age_add_valid = 1;
   session->not_resumable = 0;
 
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index cdf78e6..750e47f 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -29,6 +29,11 @@
 #include "internal.h"
 
 
+/* kMaxEarlyDataAccepted is the advertised number of plaintext bytes of early
+ * data that will be accepted. This value should be slightly below
+ * kMaxEarlyDataSkipped in tls_record.c, which is measured in ciphertext. */
+static const size_t kMaxEarlyDataAccepted = 14336;
+
 enum server_hs_state_t {
   state_process_client_hello = 0,
   state_select_parameters,
@@ -657,9 +662,6 @@
     goto err;
   }
 
-  /* TODO(svaldez): Add support for sending 0RTT through TicketEarlyDataInfo
-   * extension. */
-
   CBB cbb, body, ticket, extensions;
   if (!ssl->method->init_message(ssl, &cbb, &body,
                                  SSL3_MT_NEW_SESSION_TICKET) ||
@@ -671,6 +673,18 @@
     goto err;
   }
 
+  if (ssl->ctx->enable_early_data) {
+    session->ticket_max_early_data = kMaxEarlyDataAccepted;
+
+    CBB early_data_info;
+    if (!CBB_add_u16(&extensions, TLSEXT_TYPE_ticket_early_data_info) ||
+        !CBB_add_u16_length_prefixed(&extensions, &early_data_info) ||
+        !CBB_add_u32(&early_data_info, session->ticket_max_early_data) ||
+        !CBB_flush(&extensions)) {
+      goto err;
+    }
+  }
+
   /* Add a fake extension. See draft-davidben-tls-grease-01. */
   if (!CBB_add_u16(&extensions,
                    ssl_get_grease_value(ssl, ssl_grease_ticket_extension)) ||
diff --git a/ssl/tls_record.c b/ssl/tls_record.c
index 0039a02..362b0c2 100644
--- a/ssl/tls_record.c
+++ b/ssl/tls_record.c
@@ -125,10 +125,11 @@
  * forever. */
 static const uint8_t kMaxEmptyRecords = 32;
 
-/* kMaxEarlyDataSkipped is the maximum amount of data processed when skipping
- * over early data. Without this limit an attacker could send records at a
- * faster rate than we can process and cause trial decryption to loop
- * forever. */
+/* kMaxEarlyDataSkipped is the maximum number of rejected early data bytes that
+ * will be skipped. Without this limit an attacker could send records at a
+ * faster rate than we can process and cause trial decryption to loop forever.
+ * This value should be slightly above kMaxEarlyDataAccepted in tls13_server.c,
+ * which is measured in plaintext. */
 static const size_t kMaxEarlyDataSkipped = 16384;
 
 /* kMaxWarningAlerts is the number of consecutive warning alerts that will be