Align TLS 1.2 and 1.3 server session validity checks.
Having that logic in two different places is a nuisance when we go to
add new checks like resumption stuff. Along the way, this adds missing
tests for the ClientHello cipher/session consistency check. (We'll
eventually get it for free once the cipher/resumption change is
unblocked, but get this working in the meantime.)
This also fixes a bug where the session validity checks happened in the
wrong order relative to whether tickets_supported or renew_ticket was
looked at. Fix that by lifting that logic closer to the handshake.
Change-Id: I3f4b59cfe01064f9125277dc5834e62a36e64aae
Reviewed-on: https://boringssl-review.googlesource.com/12230
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/handshake_server.c b/ssl/handshake_server.c
index c8208ad..4c12d09 100644
--- a/ssl/handshake_server.c
+++ b/ssl/handshake_server.c
@@ -730,9 +730,9 @@
client_hello.random_len);
/* Determine whether we are doing session resumption. */
- int send_new_ticket = 0;
- switch (
- ssl_get_prev_session(ssl, &session, &send_new_ticket, &client_hello)) {
+ int tickets_supported = 0, renew_ticket = 0;
+ switch (ssl_get_prev_session(ssl, &session, &tickets_supported,
+ &renew_ticket, &client_hello)) {
case ssl_session_success:
break;
case ssl_session_error:
@@ -741,7 +741,6 @@
ssl->rwstate = SSL_PENDING_SESSION;
goto err;
}
- ssl->s3->hs->ticket_expected = send_new_ticket;
/* The EMS state is needed when making the resumption decision, but
* extensions are not normally parsed until later. This detects the EMS
@@ -754,7 +753,6 @@
TLSEXT_TYPE_extended_master_secret) &&
CBS_len(&ems) == 0;
- int has_session = 0;
if (session != NULL) {
if (session->extended_master_secret &&
!have_extended_master_secret) {
@@ -765,24 +763,23 @@
goto f_err;
}
- has_session =
- /* Only resume if the session's version matches the negotiated
- * version: most clients do not accept a mismatch. */
- ssl->version == session->ssl_version &&
+ if (!ssl_session_is_resumable(ssl, session) ||
/* If the client offers the EMS extension, but the previous session
* didn't use it, then negotiate a new session. */
- have_extended_master_secret == session->extended_master_secret &&
- /* Only resume if the session's cipher is still valid under the
- * current configuration. */
- ssl_is_valid_cipher(ssl, session->cipher);
+ have_extended_master_secret != session->extended_master_secret) {
+ SSL_SESSION_free(session);
+ session = NULL;
+ }
}
- if (has_session) {
+ if (session != NULL) {
/* Use the old session. */
+ ssl->s3->hs->ticket_expected = renew_ticket;
ssl->session = session;
session = NULL;
ssl->s3->session_reused = 1;
} else {
+ ssl->s3->hs->ticket_expected = tickets_supported;
ssl_set_session(ssl, NULL);
if (!ssl_get_new_session(ssl, 1 /* server */)) {
goto err;
diff --git a/ssl/internal.h b/ssl/internal.h
index c964a83..8f4300e 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1651,6 +1651,10 @@
* it has expired. */
int ssl_session_is_time_valid(const SSL *ssl, const SSL_SESSION *session);
+/* ssl_session_is_resumable returns one if |session| is resumable for |ssl| and
+ * zero otherwise. */
+int ssl_session_is_resumable(const SSL *ssl, const SSL_SESSION *session);
+
void ssl_set_session(SSL *ssl, SSL_SESSION *session);
enum ssl_session_result_t {
@@ -1660,14 +1664,13 @@
};
/* ssl_get_prev_session looks up the previous session based on |ctx|. On
- * success, it sets |*out_session| to the session or NULL if none was found. It
- * sets |*out_send_ticket| to whether a ticket should be sent at the end of the
- * handshake. If the session could not be looked up synchronously, it returns
+ * success, it sets |*out_session| to the session or NULL if none was found. If
+ * the session could not be looked up synchronously, it returns
* |ssl_session_retry| and should be called again. Otherwise, it returns
* |ssl_session_error|. */
enum ssl_session_result_t ssl_get_prev_session(
- SSL *ssl, SSL_SESSION **out_session, int *out_send_ticket,
- const struct ssl_early_callback_ctx *ctx);
+ SSL *ssl, SSL_SESSION **out_session, int *out_tickets_supported,
+ int *out_renew_ticket, const struct ssl_early_callback_ctx *ctx);
/* The following flags determine which parts of the session are duplicated. */
#define SSL_SESSION_DUP_AUTH_ONLY 0x0
diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c
index 9f61a5d..faf155c 100644
--- a/ssl/ssl_session.c
+++ b/ssl/ssl_session.c
@@ -633,6 +633,18 @@
return session->timeout > (long)now.tv_sec - session->time;
}
+int ssl_session_is_resumable(const SSL *ssl, const SSL_SESSION *session) {
+ return ssl_session_is_context_valid(ssl, session) &&
+ /* The session must not be expired. */
+ ssl_session_is_time_valid(ssl, session) &&
+ /* Only resume if the session's version matches the negotiated
+ * version. */
+ ssl->version == session->ssl_version &&
+ /* Only resume if the session's cipher is still valid under the
+ * current configuration. */
+ ssl_is_valid_cipher(ssl, session->cipher);
+}
+
/* ssl_lookup_session looks up |session_id| in the session cache and sets
* |*out_session| to an |SSL_SESSION| object if found. The caller takes
* ownership of the result. */
@@ -693,15 +705,8 @@
}
}
- if (session == NULL) {
- return ssl_session_success;
- }
-
- if (!ssl_session_is_context_valid(ssl, session)) {
- /* The client did not offer a suitable ticket or session ID. */
- SSL_SESSION_free(session);
- session = NULL;
- } else if (!ssl_session_is_time_valid(ssl, session)) {
+ if (session != NULL &&
+ !ssl_session_is_time_valid(ssl, session)) {
/* The session was from the cache, so remove it. */
SSL_CTX_remove_session(ssl->initial_ctx, session);
SSL_SESSION_free(session);
@@ -713,8 +718,8 @@
}
enum ssl_session_result_t ssl_get_prev_session(
- SSL *ssl, SSL_SESSION **out_session, int *out_send_ticket,
- const struct ssl_early_callback_ctx *ctx) {
+ SSL *ssl, SSL_SESSION **out_session, int *out_tickets_supported,
+ int *out_renew_ticket, const struct ssl_early_callback_ctx *ctx) {
/* This is used only by servers. */
assert(ssl->server);
SSL_SESSION *session = NULL;
@@ -743,11 +748,8 @@
}
*out_session = session;
- if (session != NULL) {
- *out_send_ticket = renew_ticket;
- } else {
- *out_send_ticket = tickets_supported;
- }
+ *out_tickets_supported = tickets_supported;
+ *out_renew_ticket = renew_ticket;
return ssl_session_success;
}
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 24e4548..c318a9b 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -3133,12 +3133,6 @@
memcpy(session->session_id, session_id, session_id_len);
session->session_id_length = session_id_len;
- if (!ssl_session_is_context_valid(ssl, session) ||
- !ssl_session_is_time_valid(ssl, session)) {
- SSL_SESSION_free(session);
- session = NULL;
- }
-
*out_session = session;
done:
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 446c8dc..c3b6b04 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -769,6 +769,11 @@
// the server believes it has actually negotiated.
SendCipherSuite uint16
+ // SendCipherSuites, if not nil, is the cipher suite list that the
+ // client will send in the ClientHello. This does not affect the cipher
+ // the client believes it has actually offered.
+ SendCipherSuites []uint16
+
// AppDataBeforeHandshake, if not nil, causes application data to be
// sent immediately before the first handshake message.
AppDataBeforeHandshake []byte
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 3f377f3..36dc1e0 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -309,6 +309,10 @@
hello.vers = c.config.Bugs.SendClientVersion
}
+ if c.config.Bugs.SendCipherSuites != nil {
+ hello.cipherSuites = c.config.Bugs.SendCipherSuites
+ }
+
var helloBytes []byte
if c.config.Bugs.SendV2ClientHello {
// Test that the peer left-pads random.
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 8f43e5c..717c420 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -5633,6 +5633,7 @@
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
+ ExpectNewTicket: true,
FilterTicket: func(in []byte) ([]byte, error) {
return SetShimTicketVersion(in, VersionTLS13)
},
@@ -5672,6 +5673,7 @@
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
+ ExpectNewTicket: true,
FilterTicket: func(in []byte) ([]byte, error) {
return SetShimTicketCipherSuite(in, TLS_AES_128_GCM_SHA256)
},
@@ -5691,6 +5693,7 @@
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
+ ExpectNewTicket: true,
FilterTicket: func(in []byte) ([]byte, error) {
return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
},
@@ -5723,6 +5726,46 @@
expectResumeRejected: true,
})
+ // Clients must not advertise a session without also advertising the
+ // cipher.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-UnofferedCipher",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+ Bugs: ProtocolBugs{
+ SendCipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":REQUIRED_CIPHER_MISSING:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "Resume-Server-UnofferedCipher-TLS13",
+ resumeSession: true,
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
+ Bugs: ProtocolBugs{
+ SendCipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
+ },
+ },
+ shouldFail: true,
+ expectedError: ":REQUIRED_CIPHER_MISSING:",
+ })
+
// Sessions may not be resumed at a different cipher.
testCases = append(testCases, testCase{
name: "Resume-Client-CipherMismatch",
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index 0db16d9..c2f8bc5 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -143,11 +143,7 @@
}
if (session != NULL &&
- /* Only resume if the session's version matches. */
- (session->ssl_version != ssl->version ||
- !ssl_client_cipher_list_contains_cipher(
- &client_hello, (uint16_t)SSL_CIPHER_get_id(session->cipher)) ||
- !ssl_is_valid_cipher(ssl, session->cipher))) {
+ !ssl_session_is_resumable(ssl, session)) {
SSL_SESSION_free(session);
session = NULL;
}
@@ -268,7 +264,16 @@
return ssl_hs_error;
}
- if (!ssl->s3->session_reused) {
+ if (ssl->s3->session_reused) {
+ /* Clients may not offer sessions containing unsupported ciphers. */
+ if (!ssl_client_cipher_list_contains_cipher(
+ &client_hello,
+ (uint16_t)SSL_CIPHER_get_id(ssl->s3->new_session->cipher))) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_REQUIRED_CIPHER_MISSING);
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+ return ssl_hs_error;
+ }
+ } else {
const SSL_CIPHER *cipher = choose_tls13_cipher(ssl, &client_hello);
if (cipher == NULL) {
OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER);