Send half-RTT tickets when negotiating 0-RTT.

Once 0-RTT data is added to the current 0-RTT logic, the server will
trigger a write when processing incoming data via SSL_read. This means
SSL_read will block on transport write, which is something we've not
tried to avoid far (assuming no renegotiation).

The specification allows for tickets to be sent at half-RTT by
predicting the client Finished. By doing this we both get the tickets on
the wire sooner and avoid confusing I/O patterns. Moreover, we
anticipate we will need this mode for one of the QUIC stateless reject
patterns.

This is tested by always processing NewSessionTickets in the
ExpectHalfRTTData path on 0-RTT connections. As not other
implementations using BoGo may not do this, this is configurable via the
shim config.

BUG=76

Change-Id: Ia0f56ae63f15078ff1cacceba972d2b99001947f
Reviewed-on: https://boringssl-review.googlesource.com/14371
Reviewed-by: Steven Valdez <svaldez@chromium.org>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Steven Valdez <svaldez@chromium.org>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/ssl/internal.h b/ssl/internal.h
index 14e5e4a..5ef4094 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -972,6 +972,7 @@
   uint8_t server_handshake_secret[EVP_MAX_MD_SIZE];
   uint8_t client_traffic_secret_0[EVP_MAX_MD_SIZE];
   uint8_t server_traffic_secret_0[EVP_MAX_MD_SIZE];
+  uint8_t expected_client_finished[EVP_MAX_MD_SIZE];
 
   union {
     /* sent is a bitset where the bits correspond to elements of kExtensions
@@ -1152,7 +1153,11 @@
 
 int tls13_process_certificate(SSL_HANDSHAKE *hs, int allow_anonymous);
 int tls13_process_certificate_verify(SSL_HANDSHAKE *hs);
-int tls13_process_finished(SSL_HANDSHAKE *hs);
+
+/* tls13_process_finished processes the current message as a Finished message
+ * from the peer. If |use_saved_value| is one, the verify_data is compared
+ * against |hs->expected_client_finished| rather than computed fresh. */
+int tls13_process_finished(SSL_HANDSHAKE *hs, int use_saved_value);
 
 int tls13_add_certificate(SSL_HANDSHAKE *hs);
 enum ssl_private_key_result_t tls13_add_certificate_verify(SSL_HANDSHAKE *hs,
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 65a8797..964d5e7 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -1180,9 +1180,9 @@
 	// Finished message.
 	SendHalfRTTData [][]byte
 
-	// ExpectHalfRTTData causes a TLS 1.3 client to read application
-	// data after reading the server's Finished message and before
-	// sending any other handshake messages. It checks that the
+	// ExpectHalfRTTData causes a TLS 1.3 client, if 0-RTT was accepted, to
+	// read application data after reading the server's Finished message and
+	// before sending any subsequent handshake messages. It checks that the
 	// application data it reads matches what is provided in
 	// ExpectHalfRTTData and errors if the number of records or their
 	// content do not match.
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index ffe4f34..830977d 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -1425,6 +1425,43 @@
 	return n + m, c.out.setErrorLocked(err)
 }
 
+func (c *Conn) processTLS13NewSessionTicket(newSessionTicket *newSessionTicketMsg, cipherSuite *cipherSuite) error {
+	if c.config.Bugs.ExpectGREASE && !newSessionTicket.hasGREASEExtension {
+		return errors.New("tls: no GREASE ticket extension found")
+	}
+
+	if c.config.Bugs.ExpectTicketEarlyDataInfo && newSessionTicket.maxEarlyDataSize == 0 {
+		return errors.New("tls: no ticket_early_data_info extension found")
+	}
+
+	if c.config.Bugs.ExpectNoNewSessionTicket {
+		return errors.New("tls: received unexpected NewSessionTicket")
+	}
+
+	if c.config.ClientSessionCache == nil || newSessionTicket.ticketLifetime == 0 {
+		return nil
+	}
+
+	session := &ClientSessionState{
+		sessionTicket:      newSessionTicket.ticket,
+		vers:               c.vers,
+		cipherSuite:        cipherSuite.id,
+		masterSecret:       c.resumptionSecret,
+		serverCertificates: c.peerCertificates,
+		sctList:            c.sctList,
+		ocspResponse:       c.ocspResponse,
+		ticketCreationTime: c.config.time(),
+		ticketExpiration:   c.config.time().Add(time.Duration(newSessionTicket.ticketLifetime) * time.Second),
+		ticketAgeAdd:       newSessionTicket.ticketAgeAdd,
+		maxEarlyDataSize:   newSessionTicket.maxEarlyDataSize,
+		earlyALPN:          c.clientProtocol,
+	}
+
+	cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
+	c.config.ClientSessionCache.Put(cacheKey, session)
+	return nil
+}
+
 func (c *Conn) handlePostHandshakeMessage() error {
 	msg, err := c.readHandshake()
 	if err != nil {
@@ -1449,40 +1486,7 @@
 
 	if c.isClient {
 		if newSessionTicket, ok := msg.(*newSessionTicketMsg); ok {
-			if c.config.Bugs.ExpectGREASE && !newSessionTicket.hasGREASEExtension {
-				return errors.New("tls: no GREASE ticket extension found")
-			}
-
-			if c.config.Bugs.ExpectTicketEarlyDataInfo && newSessionTicket.maxEarlyDataSize == 0 {
-				return errors.New("tls: no ticket_early_data_info extension found")
-			}
-
-			if c.config.Bugs.ExpectNoNewSessionTicket {
-				return errors.New("tls: received unexpected NewSessionTicket")
-			}
-
-			if c.config.ClientSessionCache == nil || newSessionTicket.ticketLifetime == 0 {
-				return nil
-			}
-
-			session := &ClientSessionState{
-				sessionTicket:      newSessionTicket.ticket,
-				vers:               c.vers,
-				cipherSuite:        c.cipherSuite.id,
-				masterSecret:       c.resumptionSecret,
-				serverCertificates: c.peerCertificates,
-				sctList:            c.sctList,
-				ocspResponse:       c.ocspResponse,
-				ticketCreationTime: c.config.time(),
-				ticketExpiration:   c.config.time().Add(time.Duration(newSessionTicket.ticketLifetime) * time.Second),
-				ticketAgeAdd:       newSessionTicket.ticketAgeAdd,
-				maxEarlyDataSize:   newSessionTicket.maxEarlyDataSize,
-				earlyALPN:          c.clientProtocol,
-			}
-
-			cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
-			c.config.ClientSessionCache.Put(cacheKey, session)
-			return nil
+			return c.processTLS13NewSessionTicket(newSessionTicket, c.cipherSuite)
 		}
 	}
 
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index a3fc14a..23aa701 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -861,15 +861,32 @@
 
 	// If we're expecting 0.5-RTT messages from the server, read them
 	// now.
-	for _, expectedMsg := range c.config.Bugs.ExpectHalfRTTData {
-		if err := c.readRecord(recordTypeApplicationData); err != nil {
-			return err
+	if encryptedExtensions.extensions.hasEarlyData {
+		// BoringSSL will always send two tickets half-RTT when
+		// negotiating 0-RTT.
+		for i := 0; i < shimConfig.HalfRTTTickets; i++ {
+			msg, err := c.readHandshake()
+			if err != nil {
+				return fmt.Errorf("tls: error reading half-RTT ticket: %s", err)
+			}
+			newSessionTicket, ok := msg.(*newSessionTicketMsg)
+			if !ok {
+				return errors.New("tls: expected half-RTT ticket")
+			}
+			if err := c.processTLS13NewSessionTicket(newSessionTicket, hs.suite); err != nil {
+				return err
+			}
 		}
-		if !bytes.Equal(c.input.data[c.input.off:], expectedMsg) {
-			return errors.New("ExpectHalfRTTData: did not get expected message")
+		for _, expectedMsg := range c.config.Bugs.ExpectHalfRTTData {
+			if err := c.readRecord(recordTypeApplicationData); err != nil {
+				return err
+			}
+			if !bytes.Equal(c.input.data[c.input.off:], expectedMsg) {
+				return errors.New("ExpectHalfRTTData: did not get expected message")
+			}
+			c.in.freeBlock(c.input)
+			c.input = nil
 		}
-		c.in.freeBlock(c.input)
-		c.input = nil
 	}
 
 	// Send EndOfEarlyData and then switch write key to handshake
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index eeb6235..f7e39ab 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -83,9 +83,16 @@
 	// “:NO_SHARED_CIPHER:” (a BoringSSL error string) to something
 	// like “SSL_ERROR_NO_CYPHER_OVERLAP”.
 	ErrorMap map[string]string
+
+	// HalfRTTTickets is the number of half-RTT tickets the client should
+	// expect before half-RTT data when testing 0-RTT.
+	HalfRTTTickets int
 }
 
-var shimConfig ShimConfiguration
+// Setup shimConfig defaults aligning with BoringSSL.
+var shimConfig ShimConfiguration = ShimConfiguration{
+	HalfRTTTickets: 2,
+}
 
 type testCert int
 
diff --git a/ssl/tls13_both.c b/ssl/tls13_both.c
index 4a16437..e334a6c 100644
--- a/ssl/tls13_both.c
+++ b/ssl/tls13_both.c
@@ -410,12 +410,21 @@
   return ret;
 }
 
-int tls13_process_finished(SSL_HANDSHAKE *hs) {
+int tls13_process_finished(SSL_HANDSHAKE *hs, int use_saved_value) {
   SSL *const ssl = hs->ssl;
-  uint8_t verify_data[EVP_MAX_MD_SIZE];
+  uint8_t verify_data_buf[EVP_MAX_MD_SIZE];
+  const uint8_t *verify_data;
   size_t verify_data_len;
-  if (!tls13_finished_mac(hs, verify_data, &verify_data_len, !ssl->server)) {
-    return 0;
+  if (use_saved_value) {
+    assert(ssl->server);
+    verify_data = hs->expected_client_finished;
+    verify_data_len = hs->hash_len;
+  } else {
+    if (!tls13_finished_mac(hs, verify_data_buf, &verify_data_len,
+                            !ssl->server)) {
+      return 0;
+    }
+    verify_data = verify_data_buf;
   }
 
   int finished_ok =
diff --git a/ssl/tls13_client.c b/ssl/tls13_client.c
index 16aedc6..9f586f5 100644
--- a/ssl/tls13_client.c
+++ b/ssl/tls13_client.c
@@ -466,7 +466,7 @@
 static enum ssl_hs_wait_t do_process_server_finished(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   if (!ssl_check_message_type(ssl, SSL3_MT_FINISHED) ||
-      !tls13_process_finished(hs) ||
+      !tls13_process_finished(hs, 0 /* don't use saved value */) ||
       !ssl_hash_current_message(hs) ||
       /* Update the secret to the master secret and derive traffic keys. */
       !tls13_advance_key_schedule(hs, kZeroes, hs->hash_len) ||
diff --git a/ssl/tls13_enc.c b/ssl/tls13_enc.c
index 16efd85..3a7009c 100644
--- a/ssl/tls13_enc.c
+++ b/ssl/tls13_enc.c
@@ -311,13 +311,11 @@
 
 int tls13_finished_mac(SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len,
                        int is_server) {
-  SSL *const ssl = hs->ssl;
-
   const uint8_t *traffic_secret;
-  if (is_server == ssl->server) {
-    traffic_secret = ssl->s3->write_traffic_secret;
+  if (is_server) {
+    traffic_secret = hs->server_handshake_secret;
   } else {
-    traffic_secret = ssl->s3->read_traffic_secret;
+    traffic_secret = hs->client_handshake_secret;
   }
 
   uint8_t context_hash[EVP_MAX_MD_SIZE];
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index 8b71da0..5211dff 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -136,6 +136,68 @@
   return best;
 }
 
+static int add_new_session_tickets(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  /* TLS 1.3 recommends single-use tickets, so issue multiple tickets in case
+   * the client makes several connections before getting a renewal. */
+  static const int kNumTickets = 2;
+
+  SSL_SESSION *session = hs->new_session;
+  CBB cbb;
+  CBB_zero(&cbb);
+
+  /* Rebase the session timestamp so that it is measured from ticket
+   * issuance. */
+  ssl_session_rebase_time(ssl, session);
+
+  for (int i = 0; i < kNumTickets; i++) {
+    if (!RAND_bytes((uint8_t *)&session->ticket_age_add, 4)) {
+      goto err;
+    }
+    session->ticket_age_add_valid = 1;
+
+    CBB body, ticket, extensions;
+    if (!ssl->method->init_message(ssl, &cbb, &body,
+                                   SSL3_MT_NEW_SESSION_TICKET) ||
+        !CBB_add_u32(&body, session->timeout) ||
+        !CBB_add_u32(&body, session->ticket_age_add) ||
+        !CBB_add_u16_length_prefixed(&body, &ticket) ||
+        !ssl_encrypt_ticket(ssl, &ticket, session) ||
+        !CBB_add_u16_length_prefixed(&body, &extensions)) {
+      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)) ||
+        !CBB_add_u16(&extensions, 0 /* empty */)) {
+      goto err;
+    }
+
+    if (!ssl_add_message_cbb(ssl, &cbb)) {
+      goto err;
+    }
+  }
+
+  return 1;
+
+err:
+  CBB_cleanup(&cbb);
+  return 0;
+}
+
 static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
   /* At this point, most ClientHello extensions have already been processed by
    * the common handshake logic. Resolve the remaining non-PSK parameters. */
@@ -567,6 +629,37 @@
     return ssl_hs_error;
   }
 
+  if (ssl->early_data_accepted) {
+    /* If accepting 0-RTT, we send tickets half-RTT. This gets the tickets on
+     * the wire sooner and also avoids triggering a write on |SSL_read| when
+     * processing the client Finished. This requires computing the client
+     * Finished early. See draft-ietf-tls-tls13-18, section 4.5.1. */
+    size_t finished_len;
+    if (!tls13_finished_mac(hs, hs->expected_client_finished, &finished_len,
+                            0 /* client */)) {
+      return ssl_hs_error;
+    }
+
+    if (finished_len != hs->hash_len) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
+
+    /* Feed the predicted Finished into the transcript. This allows us to derive
+     * the resumption secret early and send half-RTT tickets.
+     *
+     * TODO(davidben): This will need to be updated for DTLS 1.3. */
+    assert(!SSL_is_dtls(hs->ssl));
+    uint8_t header[4] = {SSL3_MT_FINISHED, 0, 0, hs->hash_len};
+    if (!SSL_TRANSCRIPT_update(&hs->transcript, header, sizeof(header)) ||
+        !SSL_TRANSCRIPT_update(&hs->transcript, hs->expected_client_finished,
+                               hs->hash_len) ||
+        !tls13_derive_resumption_secret(hs) ||
+        !add_new_session_tickets(hs)) {
+      return ssl_hs_error;
+    }
+  }
+
   hs->tls13_state = state_read_second_client_flight;
   return ssl_hs_flush;
 }
@@ -581,7 +674,6 @@
     hs->tls13_state = state_process_end_of_early_data;
     return ssl_hs_read_end_of_early_data;
   }
-
   hs->tls13_state = state_process_end_of_early_data;
   return ssl_hs_ok;
 }
@@ -592,7 +684,8 @@
                              hs->hash_len)) {
     return ssl_hs_error;
   }
-  hs->tls13_state = state_process_client_certificate;
+  hs->tls13_state = ssl->early_data_accepted ? state_process_client_finished
+                                             : state_process_client_certificate;
   return ssl_hs_read_message;
 }
 
@@ -659,30 +752,33 @@
 static enum ssl_hs_wait_t do_process_client_finished(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   if (!ssl_check_message_type(ssl, SSL3_MT_FINISHED) ||
-      !tls13_process_finished(hs) ||
-      !ssl_hash_current_message(hs) ||
+      /* If early data was accepted, we've already computed the client Finished
+       * and derived the resumption secret. */
+      !tls13_process_finished(hs, ssl->early_data_accepted) ||
       /* evp_aead_seal keys have already been switched. */
       !tls13_set_traffic_key(ssl, evp_aead_open, hs->client_traffic_secret_0,
-                             hs->hash_len) ||
-      !tls13_derive_resumption_secret(hs)) {
+                             hs->hash_len)) {
     return ssl_hs_error;
   }
 
   ssl->method->received_flight(ssl);
 
-  /* Rebase the session timestamp so that it is measured from ticket
-   * issuance. */
-  ssl_session_rebase_time(ssl, hs->new_session);
-  hs->tls13_state = state_send_new_session_ticket;
+  if (!ssl->early_data_accepted) {
+    if (!ssl_hash_current_message(hs) ||
+        !tls13_derive_resumption_secret(hs)) {
+      return ssl_hs_error;
+    }
+
+    /* We send post-handshake tickets as part of the handshake in 1-RTT. */
+    hs->tls13_state = state_send_new_session_ticket;
+    return ssl_hs_ok;
+  }
+
+  hs->tls13_state = state_done;
   return ssl_hs_ok;
 }
 
 static enum ssl_hs_wait_t do_send_new_session_ticket(SSL_HANDSHAKE *hs) {
-  /* TLS 1.3 recommends single-use tickets, so issue multiple tickets in case the
-   * client makes several connections before getting a renewal. */
-  static const int kNumTickets = 2;
-
-  SSL *const ssl = hs->ssl;
   /* If the client doesn't accept resumption with PSK_DHE_KE, don't send a
    * session ticket. */
   if (!hs->accept_psk_mode) {
@@ -690,57 +786,12 @@
     return ssl_hs_ok;
   }
 
-  SSL_SESSION *session = hs->new_session;
-  CBB cbb;
-  CBB_zero(&cbb);
-
-  for (int i = 0; i < kNumTickets; i++) {
-    if (!RAND_bytes((uint8_t *)&session->ticket_age_add, 4)) {
-      goto err;
-    }
-    session->ticket_age_add_valid = 1;
-
-    CBB body, ticket, extensions;
-    if (!ssl->method->init_message(ssl, &cbb, &body,
-                                   SSL3_MT_NEW_SESSION_TICKET) ||
-        !CBB_add_u32(&body, session->timeout) ||
-        !CBB_add_u32(&body, session->ticket_age_add) ||
-        !CBB_add_u16_length_prefixed(&body, &ticket) ||
-        !ssl_encrypt_ticket(ssl, &ticket, session) ||
-        !CBB_add_u16_length_prefixed(&body, &extensions)) {
-      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)) ||
-        !CBB_add_u16(&extensions, 0 /* empty */)) {
-      goto err;
-    }
-
-    if (!ssl_add_message_cbb(ssl, &cbb)) {
-      goto err;
-    }
+  if (!add_new_session_tickets(hs)) {
+    return ssl_hs_error;
   }
 
   hs->tls13_state = state_done;
   return ssl_hs_flush;
-
-err:
-  CBB_cleanup(&cbb);
-  return ssl_hs_error;
 }
 
 enum ssl_hs_wait_t tls13_server_handshake(SSL_HANDSHAKE *hs) {