Export server-side ticket_age skew.

We'll measure this value to guide what tolerance to use in the 0-RTT
anti-replay mechanism. This also fixes a bug where we were previously
minting ticket_age_add-less tickets on the server. Add a check to reject
all those tickets.

BUG=113

Change-Id: I68e690c0794234234e0d0500b4b9a7f79aea641e
Reviewed-on: https://boringssl-review.googlesource.com/14068
Reviewed-by: Steven Valdez <svaldez@google.com>
Commit-Queue: Steven Valdez <svaldez@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 c938a41..2446f8e 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3223,6 +3223,11 @@
  * record with |ssl|. */
 OPENSSL_EXPORT size_t SSL_max_seal_overhead(const SSL *ssl);
 
+/* SSL_get_ticket_age_skew returns the difference, in seconds, between the
+ * client-sent ticket age and the server-computed value in TLS 1.3 server
+ * connections which resumed a session. */
+OPENSSL_EXPORT int32_t SSL_get_ticket_age_skew(const SSL *ssl);
+
 
 /* Deprecated functions. */
 
diff --git a/ssl/internal.h b/ssl/internal.h
index b851e34..1be691c 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1158,10 +1158,9 @@
 
 int ssl_ext_pre_shared_key_parse_serverhello(SSL_HANDSHAKE *hs,
                                              uint8_t *out_alert, CBS *contents);
-int ssl_ext_pre_shared_key_parse_clienthello(SSL_HANDSHAKE *hs,
-                                             SSL_SESSION **out_session,
-                                             CBS *out_binders,
-                                             uint8_t *out_alert, CBS *contents);
+int ssl_ext_pre_shared_key_parse_clienthello(
+    SSL_HANDSHAKE *hs, SSL_SESSION **out_session, CBS *out_binders,
+    uint32_t *out_obfuscated_ticket_age, uint8_t *out_alert, CBS *contents);
 int ssl_ext_pre_shared_key_add_serverhello(SSL_HANDSHAKE *hs, CBB *out);
 
 /* ssl_is_sct_list_valid does a shallow parse of the SCT list in |contents| and
@@ -1679,6 +1678,11 @@
    *     verified Channel ID from the client: a P256 point, (x,y), where
    *     each are big-endian values. */
   uint8_t tlsext_channel_id[64];
+
+  /* ticket_age_skew is the difference, in seconds, between the client-sent
+   * ticket age and the server-computed value in TLS 1.3 server connections
+   * which resumed a session. */
+  int32_t ticket_age_skew;
 } SSL3_STATE;
 
 /* lengths of messages */
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 856fba2..b207e92 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -2557,6 +2557,10 @@
   ctx->grease_enabled = !!enabled;
 }
 
+int32_t SSL_get_ticket_age_skew(const SSL *ssl) {
+  return ssl->s3->ticket_age_skew;
+}
+
 int SSL_clear(SSL *ssl) {
   /* In OpenSSL, reusing a client |SSL| with |SSL_clear| causes the previously
    * established session to be offered the next time around. wpa_supplicant
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index adc7344..4a7fbd3 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -1957,18 +1957,15 @@
   return 1;
 }
 
-int ssl_ext_pre_shared_key_parse_clienthello(SSL_HANDSHAKE *hs,
-                                             SSL_SESSION **out_session,
-                                             CBS *out_binders,
-                                             uint8_t *out_alert,
-                                             CBS *contents) {
+int ssl_ext_pre_shared_key_parse_clienthello(
+    SSL_HANDSHAKE *hs, SSL_SESSION **out_session, CBS *out_binders,
+    uint32_t *out_obfuscated_ticket_age, uint8_t *out_alert, CBS *contents) {
   SSL *const ssl = hs->ssl;
   /* We only process the first PSK identity since we don't support pure PSK. */
-  uint32_t obfuscated_ticket_age;
   CBS identities, ticket, binders;
   if (!CBS_get_u16_length_prefixed(contents, &identities) ||
       !CBS_get_u16_length_prefixed(&identities, &ticket) ||
-      !CBS_get_u32(&identities, &obfuscated_ticket_age) ||
+      !CBS_get_u32(&identities, out_obfuscated_ticket_age) ||
       !CBS_get_u16_length_prefixed(contents, &binders) ||
       CBS_len(&binders) == 0 ||
       CBS_len(contents) != 0) {
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 984136c..804cbbb 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1611,6 +1611,13 @@
     return false;
   }
 
+  if (is_resume && config->expect_ticket_age_skew != 0 &&
+      SSL_get_ticket_age_skew(ssl) != config->expect_ticket_age_skew) {
+    fprintf(stderr, "Ticket age skew was %" PRId32 ", wanted %d\n",
+            SSL_get_ticket_age_skew(ssl), config->expect_ticket_age_skew);
+    return false;
+  }
+
   return true;
 }
 
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index d316ddd..167e872 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -980,6 +980,10 @@
 	// server receives from the client.
 	ExpectTicketAge time.Duration
 
+	// SendTicketAge, if non-zero, is the ticket age to be sent by the
+	// client.
+	SendTicketAge time.Duration
+
 	// 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/handshake_client.go b/ssl/test/runner/handshake_client.go
index eb072ad..bf38c1a 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -270,6 +270,9 @@
 			// TODO(nharper): Support sending more
 			// than one PSK identity.
 			ticketAge := uint32(c.config.time().Sub(session.ticketCreationTime) / time.Millisecond)
+			if c.config.Bugs.SendTicketAge != 0 {
+				ticketAge = uint32(c.config.Bugs.SendTicketAge / time.Millisecond)
+			}
 			psk := pskIdentity{
 				ticket:              ticket,
 				obfuscatedTicketAge: session.ticketAgeAdd + ticketAge,
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 55685b0..d90485c 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -8601,6 +8601,38 @@
 		expectedLocalError: "tls: invalid ticket age",
 	})
 
+	// Test that the server's ticket age skew reporting works.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-TicketAgeSkew-Forward",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendTicketAge: 15 * time.Second,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-resumption-delay", "10",
+			"-expect-ticket-age-skew", "5",
+		},
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-TicketAgeSkew-Backward",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendTicketAge: 5 * time.Second,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-resumption-delay", "10",
+			"-expect-ticket-age-skew", "-5",
+		},
+	})
+
 	testCases = append(testCases, testCase{
 		testType: clientTest,
 		name:     "TLS13-SendTicketEarlyDataInfo",
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index fefe376..e581581 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -180,6 +180,7 @@
   { "-resumption-delay", &TestConfig::resumption_delay },
   { "-max-send-fragment", &TestConfig::max_send_fragment },
   { "-read-size", &TestConfig::read_size },
+  { "-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew },
 };
 
 const Flag<std::vector<int>> kIntVectorFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index ee3d462..7057b48 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -133,6 +133,7 @@
   int read_size = 0;
   bool expect_session_id = false;
   bool expect_no_session_id = false;
+  int expect_ticket_age_skew = 0;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index e7cc296..59c126e 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -163,6 +163,7 @@
   /* Decode the ticket if we agree on a PSK key exchange mode. */
   uint8_t alert = SSL_AD_DECODE_ERROR;
   SSL_SESSION *session = NULL;
+  uint32_t client_ticket_age = 0;
   CBS pre_shared_key, binders;
   if (hs->accept_psk_mode &&
       ssl_client_hello_get_extension(&client_hello, &pre_shared_key,
@@ -177,18 +178,46 @@
     }
 
     if (!ssl_ext_pre_shared_key_parse_clienthello(hs, &session, &binders,
-                                                  &alert, &pre_shared_key)) {
+                                                  &client_ticket_age, &alert,
+                                                  &pre_shared_key)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
       return ssl_hs_error;
     }
   }
 
   if (session != NULL &&
-      !ssl_session_is_resumable(hs, session)) {
+      (!ssl_session_is_resumable(hs, session) ||
+       /* Historically, some TLS 1.3 tickets were missing ticket_age_add. */
+       !session->ticket_age_add_valid)) {
     SSL_SESSION_free(session);
     session = NULL;
   }
 
+  if (session != NULL) {
+    /* Recover the client ticket age and convert to seconds. */
+    client_ticket_age -= session->ticket_age_add;
+    client_ticket_age /= 1000;
+
+    struct OPENSSL_timeval now;
+    ssl_get_current_time(ssl, &now);
+
+    /* Compute the server ticket age in seconds. */
+    assert(now.tv_sec >= session->time);
+    uint64_t server_ticket_age = now.tv_sec - session->time;
+
+    /* To avoid overflowing |hs->ticket_age_skew|, we will not resume
+     * 68-year-old sessions. */
+    if (server_ticket_age > INT32_MAX) {
+      SSL_SESSION_free(session);
+      session = NULL;
+    } else {
+      /* TODO(davidben,svaldez): Measure this value to decide on tolerance. For
+       * now, accept all values. https://crbug.com/boringssl/113. */
+      ssl->s3->ticket_age_skew =
+          (int32_t)client_ticket_age - (int32_t)server_ticket_age;
+    }
+  }
+
   /* Set up the new session, either using the original one as a template or
    * creating a fresh one. */
   if (session == NULL) {
@@ -565,6 +594,7 @@
     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,