Enforce the ticket_age parameter for 0-RTT.
For now just hard-code a tolerance of 1 minute.
SSL_get_early_data_reason and SSL_get_ticket_age_skew will allow us to
tune this.
Bug: 113
Change-Id: I85a530494d5405a3e11198d49bfa9cfd355f4f35
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/35886
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index b936efa..63350ea 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3342,6 +3342,8 @@
ssl_early_data_channel_id,
// The connection negotiated token binding, which is incompatible with 0-RTT.
ssl_early_data_token_binding,
+ // The client and server ticket age were too far apart.
+ ssl_early_data_ticket_age_skew,
};
// SSL_get_early_data_reason returns details why 0-RTT was accepted or rejected
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index e7e5dec..ea9516b 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -415,6 +415,8 @@
return "channel_id";
case ssl_early_data_token_binding:
return "token_binding";
+ case ssl_early_data_ticket_age_skew:
+ return "ticket_age_skew";
}
abort();
diff --git a/ssl/test/runner/fuzzer_mode.json b/ssl/test/runner/fuzzer_mode.json
index 78639e2..836b7ba 100644
--- a/ssl/test/runner/fuzzer_mode.json
+++ b/ssl/test/runner/fuzzer_mode.json
@@ -47,6 +47,7 @@
"*-EarlyData-RejectUnfinishedWrite-Client-*": "Trial decryption does not work with the NULL cipher.",
"EarlyData-Reject*-Client-*": "Trial decryption does not work with the NULL cipher.",
"CustomExtensions-Server-EarlyDataOffered": "Trial decryption does not work with the NULL cipher.",
+ "*-TicketAgeSkew-*-Reject": "Trial decryption does not work with the NULL cipher.",
"Renegotiate-Client-BadExt*": "Fuzzer mode does not check renegotiation_info.",
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index f5f0285..de65b28 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -11330,6 +11330,96 @@
},
})
+ // Test that ticket age skew up to 60 seconds in either direction is accepted.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-TicketAgeSkew-Forward-60-Accept",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketAge: 70 * time.Second,
+ SendEarlyData: [][]byte{{1, 2, 3, 4}},
+ ExpectEarlyDataAccepted: true,
+ ExpectHalfRTTData: [][]byte{{254, 253, 252, 251}},
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-resumption-delay", "10",
+ "-expect-ticket-age-skew", "60",
+ // 0-RTT is accepted.
+ "-enable-early-data",
+ "-on-resume-expect-accept-early-data",
+ "-on-resume-expect-early-data-reason", "accept",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-TicketAgeSkew-Backward-60-Accept",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketAge: 10 * time.Second,
+ SendEarlyData: [][]byte{{1, 2, 3, 4}},
+ ExpectEarlyDataAccepted: true,
+ ExpectHalfRTTData: [][]byte{{254, 253, 252, 251}},
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-resumption-delay", "70",
+ "-expect-ticket-age-skew", "-60",
+ // 0-RTT is accepted.
+ "-enable-early-data",
+ "-on-resume-expect-accept-early-data",
+ "-on-resume-expect-early-data-reason", "accept",
+ },
+ })
+
+ // Test that ticket age skew beyond 60 seconds in either direction is rejected.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-TicketAgeSkew-Forward-61-Reject",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketAge: 71 * time.Second,
+ SendEarlyData: [][]byte{{1, 2, 3, 4}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-resumption-delay", "10",
+ "-expect-ticket-age-skew", "61",
+ // 0-RTT is rejected.
+ "-enable-early-data",
+ "-expect-reject-early-data",
+ "-on-resume-expect-early-data-reason", "ticket_age_skew",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-TicketAgeSkew-Backward-61-Reject",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketAge: 10 * time.Second,
+ SendEarlyData: [][]byte{{1, 2, 3, 4}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-resumption-delay", "71",
+ "-expect-ticket-age-skew", "-61",
+ // 0-RTT is rejected.
+ "-enable-early-data",
+ "-expect-reject-early-data",
+ "-on-resume-expect-early-data-reason", "ticket_age_skew",
+ },
+ })
+
testCases = append(testCases, testCase{
testType: clientTest,
name: "TLS13-SendTicketEarlyDataInfo",
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index 82c9689..8f31704 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -53,6 +53,12 @@
static const uint8_t kZeroes[EVP_MAX_MD_SIZE] = {0};
+// Allow a minute of ticket age skew in either direction. This covers
+// transmission delays in ClientHello and NewSessionTicket, as well as
+// drift between client and server clock rate since the ticket was issued.
+// See RFC 8446, section 8.3.
+static const int32_t kMaxTicketAgeSkewSeconds = 60;
+
static int resolve_ecdhe_secret(SSL_HANDSHAKE *hs, bool *out_need_retry,
SSL_CLIENT_HELLO *client_hello) {
SSL *const ssl = hs->ssl;
@@ -433,6 +439,10 @@
// a fresh session.
hs->new_session =
SSL_SESSION_dup(session.get(), SSL_SESSION_DUP_AUTH_ONLY);
+ if (hs->new_session == nullptr) {
+ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+ return ssl_hs_error;
+ }
if (!ssl->enable_early_data) {
ssl->s3->early_data_reason = ssl_early_data_disabled;
@@ -449,16 +459,14 @@
} else if (MakeConstSpan(ssl->s3->alpn_selected) != session->early_alpn) {
// The negotiated ALPN must match the one in the ticket.
ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch;
+ } else if (ssl->s3->ticket_age_skew < -kMaxTicketAgeSkewSeconds ||
+ kMaxTicketAgeSkewSeconds < ssl->s3->ticket_age_skew) {
+ ssl->s3->early_data_reason = ssl_early_data_ticket_age_skew;
} else {
ssl->s3->early_data_reason = ssl_early_data_accepted;
ssl->s3->early_data_accepted = true;
}
- if (hs->new_session == NULL) {
- ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
- return ssl_hs_error;
- }
-
ssl->s3->session_reused = true;
// Resumption incorporates fresh key material, so refresh the timeout.