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,