Adding code to send session as PSK Identity. BUG=75 Change-Id: Ied864cfccbc0e68d71c55c5ab563da27b7253463 Reviewed-on: https://boringssl-review.googlesource.com/9043 Reviewed-by: David Benjamin <davidben@google.com> Commit-Queue: David Benjamin <davidben@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/ssl/handshake_client.c b/ssl/handshake_client.c index 34c1adc..d7b1a2c 100644 --- a/ssl/handshake_client.c +++ b/ssl/handshake_client.c
@@ -514,7 +514,7 @@ * of the new established_session due to False Start. The caller may * have taken a reference to the temporary session. */ ssl->s3->established_session = - SSL_SESSION_dup(ssl->s3->new_session, 1 /* include ticket */); + SSL_SESSION_dup(ssl->s3->new_session, SSL_SESSION_DUP_ALL); if (ssl->s3->established_session == NULL) { /* Do not stay in SSL_ST_OK, to avoid confusing |SSL_in_init| * callers. */ @@ -605,6 +605,16 @@ if (!CBB_add_u16(&child, ssl_cipher_get_value(cipher))) { return 0; } + /* Add PSK ciphers for TLS 1.3 resumption. */ + if (ssl->session != NULL && + ssl->method->version_from_wire(ssl->session->ssl_version) >= + TLS1_3_VERSION) { + uint16_t resumption_cipher; + if (ssl_cipher_get_ecdhe_psk_cipher(cipher, &resumption_cipher) && + !CBB_add_u16(&child, resumption_cipher)) { + return 0; + } + } } /* If all ciphers were disabled, return the error to the caller. */ @@ -708,10 +718,10 @@ if (ssl->session != NULL) { uint16_t session_version = ssl->method->version_from_wire(ssl->session->ssl_version); - struct timeval now; - ssl_get_current_time(ssl, &now); - if (ssl->session->session_id_length == 0 || ssl->session->not_resumable || - ssl->session->timeout < (long)now.tv_sec - ssl->session->time || + if ((session_version < TLS1_3_VERSION && + ssl->session->session_id_length == 0) || + ssl->session->not_resumable || + !ssl_session_is_time_valid(ssl, ssl->session) || session_version < min_version || session_version > max_version) { SSL_set_session(ssl, NULL); } @@ -885,18 +895,11 @@ goto f_err; } - assert(ssl->session == NULL || ssl->session->session_id_length > 0); if (!ssl->s3->initial_handshake_complete && ssl->session != NULL && + ssl->session->session_id_length != 0 && CBS_mem_equal(&session_id, ssl->session->session_id, ssl->session->session_id_length)) { - if (ssl->sid_ctx_length != ssl->session->sid_ctx_length || - memcmp(ssl->session->sid_ctx, ssl->sid_ctx, ssl->sid_ctx_length)) { - /* actually a client application bug */ - al = SSL_AD_ILLEGAL_PARAMETER; - OPENSSL_PUT_ERROR(SSL, - SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT); - goto f_err; - } + ssl->s3->session_reused = 1; } else { /* The session wasn't resumed. Create a fresh SSL_SESSION to * fill out. */ @@ -946,6 +949,13 @@ OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_VERSION_NOT_RETURNED); goto f_err; } + if (!ssl_session_is_context_valid(ssl, ssl->session)) { + /* This is actually a client application bug. */ + al = SSL_AD_ILLEGAL_PARAMETER; + OPENSSL_PUT_ERROR(SSL, + SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT); + goto f_err; + } } else { ssl->s3->new_session->cipher = c; } @@ -1935,8 +1945,7 @@ /* The server is sending a new ticket for an existing session. Sessions are * immutable once established, so duplicate all but the ticket of the * existing session. */ - session = SSL_SESSION_dup(ssl->session, - 0 /* Don't duplicate session ticket */); + session = SSL_SESSION_dup(ssl->session, SSL_SESSION_INCLUDE_NONAUTH); if (session == NULL) { /* This should never happen. */ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
diff --git a/ssl/handshake_server.c b/ssl/handshake_server.c index 4e7aae2..4818fe5 100644 --- a/ssl/handshake_server.c +++ b/ssl/handshake_server.c
@@ -713,6 +713,7 @@ ssl->session = session; session = NULL; ssl->verify_result = ssl->session->verify_result; + ssl->s3->session_reused = 1; } else { SSL_set_session(ssl, NULL); if (!ssl_get_new_session(ssl, 1 /* server */)) {
diff --git a/ssl/internal.h b/ssl/internal.h index 4fcf4b9..ade9416 100644 --- a/ssl/internal.h +++ b/ssl/internal.h
@@ -241,6 +241,11 @@ /* ssl_cipher_get_value returns the cipher suite id of |cipher|. */ uint16_t ssl_cipher_get_value(const SSL_CIPHER *cipher); +/* ssl_cipher_get_resumption_cipher returns the cipher suite id of the cipher + * matching |cipher| with PSK enabled. */ +int ssl_cipher_get_ecdhe_psk_cipher(const SSL_CIPHER *cipher, + uint16_t *out_cipher); + /* ssl_cipher_get_key_type returns the |EVP_PKEY_*| value corresponding to the * server key used in |cipher| or |EVP_PKEY_NONE| if there is none. */ int ssl_cipher_get_key_type(const SSL_CIPHER *cipher); @@ -848,6 +853,18 @@ * 0 for the Client Finished. */ int tls13_finished_mac(SSL *ssl, uint8_t *out, size_t *out_len, int is_server); +/* tls13_resumption_psk calculates the PSK to use for the resumption of + * |session| and stores the result in |out|. It returns one on success, and + * zero on failure. */ +int tls13_resumption_psk(SSL *ssl, uint8_t *out, size_t out_len, + const SSL_SESSION *session); + +/* tls13_resumption_context derives the context to be used for the handshake + * transcript on the resumption of |session|. It returns one on success, and + * zero on failure. */ +int tls13_resumption_context(SSL *ssl, uint8_t *out, size_t out_len, + const SSL_SESSION *session); + /* Handshake functions. */ @@ -938,6 +955,13 @@ uint8_t *out_alert, CBS *contents); int ssl_ext_key_share_add_serverhello(SSL *ssl, CBB *out); +int ssl_ext_pre_shared_key_parse_serverhello(SSL *ssl, uint8_t *out_alert, + CBS *contents); +int ssl_ext_pre_shared_key_parse_clienthello(SSL *ssl, + SSL_SESSION **out_session, + uint8_t *out_alert, CBS *contents); +int ssl_ext_pre_shared_key_add_serverhello(SSL *ssl, CBB *out); + int ssl_add_client_hello_body(SSL *ssl, CBB *body); @@ -1232,6 +1256,14 @@ int ssl_get_new_session(SSL *ssl, int is_server); int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session); +/* ssl_session_is_context_valid returns one if |session|'s session ID context + * matches the one set on |ssl| and zero otherwise. */ +int ssl_session_is_context_valid(const SSL *ssl, const SSL_SESSION *session); + +/* ssl_session_is_time_valid returns one if |session| is still valid and zero if + * it has expired. */ +int ssl_session_is_time_valid(const SSL *ssl, const SSL_SESSION *session); + enum ssl_session_result_t { ssl_session_success, ssl_session_error, @@ -1248,11 +1280,18 @@ SSL *ssl, SSL_SESSION **out_session, int *out_send_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 +#define SSL_SESSION_INCLUDE_TICKET 0x1 +#define SSL_SESSION_INCLUDE_NONAUTH 0x2 +#define SSL_SESSION_DUP_ALL \ + (SSL_SESSION_INCLUDE_TICKET | SSL_SESSION_INCLUDE_NONAUTH) + /* SSL_SESSION_dup returns a newly-allocated |SSL_SESSION| with a copy of the * fields in |session| or NULL on error. The new session is non-resumable and * must be explicitly marked resumable once it has been filled in. */ OPENSSL_EXPORT SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, - int include_ticket); + int dup_flags); void ssl_cipher_preference_list_free( struct ssl_cipher_preference_list_st *cipher_list);
diff --git a/ssl/ssl_cipher.c b/ssl/ssl_cipher.c index 3810667..079c823 100644 --- a/ssl/ssl_cipher.c +++ b/ssl/ssl_cipher.c
@@ -1660,6 +1660,30 @@ return id & 0xffff; } +int ssl_cipher_get_ecdhe_psk_cipher(const SSL_CIPHER *cipher, + uint16_t *out_cipher) { + switch (cipher->id) { + case TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + case TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + case TLS1_CK_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: + *out_cipher = TLS1_CK_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 & 0xffff; + return 1; + + case TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case TLS1_CK_ECDHE_PSK_WITH_AES_128_GCM_SHA256: + *out_cipher = TLS1_CK_ECDHE_PSK_WITH_AES_128_GCM_SHA256 & 0xffff; + return 1; + + case TLS1_CK_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case TLS1_CK_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case TLS1_CK_ECDHE_PSK_WITH_AES_256_GCM_SHA384: + *out_cipher = TLS1_CK_ECDHE_PSK_WITH_AES_256_GCM_SHA384 & 0xffff; + return 1; + } + return 0; +} + int SSL_CIPHER_is_AES(const SSL_CIPHER *cipher) { return (cipher->algorithm_enc & SSL_AES) != 0; }
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 1a950ad..e7830a1 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c
@@ -2162,7 +2162,7 @@ } int SSL_session_reused(const SSL *ssl) { - return ssl->session != NULL; + return ssl->s3->session_reused; } const COMP_METHOD *SSL_get_current_compression(SSL *ssl) { return NULL; }
diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c index 1d634d8..e26866b 100644 --- a/ssl/ssl_session.c +++ b/ssl/ssl_session.c
@@ -175,22 +175,23 @@ return session; } -SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int include_ticket) { +SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int dup_flags) { SSL_SESSION *new_session = SSL_SESSION_new(); if (new_session == NULL) { goto err; } new_session->ssl_version = session->ssl_version; - new_session->key_exchange_info = session->key_exchange_info; + new_session->sid_ctx_length = session->sid_ctx_length; + memcpy(new_session->sid_ctx, session->sid_ctx, session->sid_ctx_length); + + /* Copy the key material. */ new_session->master_key_length = session->master_key_length; memcpy(new_session->master_key, session->master_key, session->master_key_length); - new_session->session_id_length = session->session_id_length; - memcpy(new_session->session_id, session->session_id, - session->session_id_length); - new_session->sid_ctx_length = session->sid_ctx_length; - memcpy(new_session->sid_ctx, session->sid_ctx, session->sid_ctx_length); + new_session->cipher = session->cipher; + + /* Copy authentication state. */ if (session->psk_identity != NULL) { new_session->psk_identity = BUF_strdup(session->psk_identity); if (new_session->psk_identity == NULL) { @@ -208,26 +209,15 @@ } } new_session->verify_result = session->verify_result; - new_session->timeout = session->timeout; - new_session->time = session->time; - new_session->cipher = session->cipher; - /* The new_session does not get a copy of the ex_data. */ - if (session->tlsext_hostname != NULL) { - new_session->tlsext_hostname = BUF_strdup(session->tlsext_hostname); - if (new_session->tlsext_hostname == NULL) { + + new_session->ocsp_response_length = session->ocsp_response_length; + if (session->ocsp_response != NULL) { + new_session->ocsp_response = BUF_memdup(session->ocsp_response, + session->ocsp_response_length); + if (new_session->ocsp_response == NULL) { goto err; } } - if (include_ticket) { - if (session->tlsext_tick != NULL) { - new_session->tlsext_tick = - BUF_memdup(session->tlsext_tick, session->tlsext_ticklen); - if (new_session->tlsext_tick == NULL) { - goto err; - } - } - new_session->tlsext_ticklen = session->tlsext_ticklen; - } new_session->tlsext_signed_cert_timestamp_list_length = session->tlsext_signed_cert_timestamp_list_length; @@ -239,25 +229,52 @@ goto err; } } - new_session->ocsp_response_length = session->ocsp_response_length; - if (session->ocsp_response != NULL) { - new_session->ocsp_response = BUF_memdup(session->ocsp_response, - session->ocsp_response_length); - if (new_session->ocsp_response == NULL) { - goto err; - } - } + memcpy(new_session->peer_sha256, session->peer_sha256, SHA256_DIGEST_LENGTH); - memcpy(new_session->original_handshake_hash, - session->original_handshake_hash, - session->original_handshake_hash_len); - new_session->original_handshake_hash_len = - session->original_handshake_hash_len; - new_session->tlsext_tick_lifetime_hint = session->tlsext_tick_lifetime_hint; - new_session->ticket_flags = session->ticket_flags; - new_session->ticket_age_add = session->ticket_age_add; - new_session->extended_master_secret = session->extended_master_secret; new_session->peer_sha256_valid = session->peer_sha256_valid; + + /* Copy non-authentication connection properties. */ + if (dup_flags & SSL_SESSION_INCLUDE_NONAUTH) { + new_session->session_id_length = session->session_id_length; + memcpy(new_session->session_id, session->session_id, + session->session_id_length); + + new_session->key_exchange_info = session->key_exchange_info; + new_session->timeout = session->timeout; + new_session->time = session->time; + + if (session->tlsext_hostname != NULL) { + new_session->tlsext_hostname = BUF_strdup(session->tlsext_hostname); + if (new_session->tlsext_hostname == NULL) { + goto err; + } + } + + memcpy(new_session->original_handshake_hash, + session->original_handshake_hash, + session->original_handshake_hash_len); + new_session->original_handshake_hash_len = + session->original_handshake_hash_len; + new_session->tlsext_tick_lifetime_hint = session->tlsext_tick_lifetime_hint; + new_session->ticket_flags = session->ticket_flags; + new_session->ticket_age_add = session->ticket_age_add; + new_session->extended_master_secret = session->extended_master_secret; + } + + /* Copy the ticket. */ + if (dup_flags & SSL_SESSION_INCLUDE_TICKET) { + if (session->tlsext_tick != NULL) { + new_session->tlsext_tick = + BUF_memdup(session->tlsext_tick, session->tlsext_ticklen); + if (new_session->tlsext_tick == NULL) { + goto err; + } + } + new_session->tlsext_ticklen = session->tlsext_ticklen; + } + + /* The new_session does not get a copy of the ex_data. */ + new_session->not_resumable = 1; return new_session; @@ -564,6 +581,25 @@ return ret; } +int ssl_session_is_context_valid(const SSL *ssl, const SSL_SESSION *session) { + if (session == NULL) { + return 0; + } + + return session->sid_ctx_length == ssl->sid_ctx_length && + memcmp(session->sid_ctx, ssl->sid_ctx, ssl->sid_ctx_length) == 0; +} + +int ssl_session_is_time_valid(const SSL *ssl, const SSL_SESSION *session) { + if (session == NULL) { + return 0; + } + + struct timeval now; + ssl_get_current_time(ssl, &now); + return session->timeout >= (long)now.tv_sec - session->time; +} + /* 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. */ @@ -576,7 +612,7 @@ return ssl_session_success; } - SSL_SESSION *session; + SSL_SESSION *session = NULL; /* Try the internal cache, if it exists. */ if (!(ssl->initial_ctx->session_cache_mode & SSL_SESS_CACHE_NO_INTERNAL_LOOKUP)) { @@ -592,39 +628,48 @@ } /* TODO(davidben): This should probably move it to the front of the list. */ CRYPTO_MUTEX_unlock_read(&ssl->initial_ctx->lock); - - if (session != NULL) { - *out_session = session; - return ssl_session_success; - } } /* Fall back to the external cache, if it exists. */ - if (ssl->initial_ctx->get_session_cb == NULL) { - return ssl_session_success; + if (session == NULL && + ssl->initial_ctx->get_session_cb != NULL) { + int copy = 1; + session = ssl->initial_ctx->get_session_cb(ssl, (uint8_t *)session_id, + session_id_len, ©); + + if (session == SSL_magic_pending_session_ptr()) { + return ssl_session_retry; + } + + /* Increment reference count now if the session callback asks us to do so + * (note that if the session structures returned by the callback are shared + * between threads, it must handle the reference count itself [i.e. copy == + * 0], or things won't be thread-safe). */ + if (copy) { + SSL_SESSION_up_ref(session); + } + + /* Add the externally cached session to the internal cache if necessary. */ + if (session != NULL && + !(ssl->initial_ctx->session_cache_mode & + SSL_SESS_CACHE_NO_INTERNAL_STORE)) { + SSL_CTX_add_session(ssl->initial_ctx, session); + } } - int copy = 1; - session = ssl->initial_ctx->get_session_cb(ssl, (uint8_t *)session_id, - session_id_len, ©); + if (session == NULL) { return ssl_session_success; } - if (session == SSL_magic_pending_session_ptr()) { - return ssl_session_retry; - } - /* Increment reference count now if the session callback asks us to do so - * (note that if the session structures returned by the callback are shared - * between threads, it must handle the reference count itself [i.e. copy == - * 0], or things won't be thread-safe). */ - if (copy) { - SSL_SESSION_up_ref(session); - } - - /* Add the externally cached session to the internal cache if necessary. */ - if (!(ssl->initial_ctx->session_cache_mode & - SSL_SESS_CACHE_NO_INTERNAL_STORE)) { - SSL_CTX_add_session(ssl->initial_ctx, session); + 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)) { + /* The session was from the cache, so remove it. */ + SSL_CTX_remove_session(ssl->initial_ctx, session); + SSL_SESSION_free(session); + session = NULL; } *out_session = session; @@ -647,7 +692,6 @@ ssl->version > SSL3_VERSION && SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_session_ticket, &ticket, &ticket_len); - int from_cache = 0; if (tickets_supported && ticket_len > 0) { if (!tls_process_ticket(ssl, &session, &renew_ticket, ticket, ticket_len, ctx->session_id, ctx->session_id_len)) { @@ -660,35 +704,14 @@ if (lookup_ret != ssl_session_success) { return lookup_ret; } - from_cache = 1; - } - - if (session == NULL || - session->sid_ctx_length != ssl->sid_ctx_length || - memcmp(session->sid_ctx, ssl->sid_ctx, ssl->sid_ctx_length) != 0) { - /* The client did not offer a suitable ticket or session ID. If supported, - * the new session should use a ticket. */ - goto no_session; - } - - struct timeval now; - ssl_get_current_time(ssl, &now); - if (session->timeout < (long)now.tv_sec - session->time) { - if (from_cache) { - /* The session was from the cache, so remove it. */ - SSL_CTX_remove_session(ssl->initial_ctx, session); - } - goto no_session; } *out_session = session; - *out_send_ticket = renew_ticket; - return ssl_session_success; - -no_session: - *out_session = NULL; - *out_send_ticket = tickets_supported; - SSL_SESSION_free(session); + if (session != NULL) { + *out_send_ticket = renew_ticket; + } else { + *out_send_ticket = tickets_supported; + } return ssl_session_success; }
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index 4c4f6d9..f73c37d 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc
@@ -1301,7 +1301,7 @@ } SSL_SESSION *session0 = SSL_get_session(client.get()); - ScopedSSL_SESSION session1(SSL_SESSION_dup(session0, 1)); + ScopedSSL_SESSION session1(SSL_SESSION_dup(session0, SSL_SESSION_DUP_ALL)); if (!session1) { return false; } @@ -1796,11 +1796,6 @@ static const uint8_t kContext2[] = {2}; for (uint16_t version : kVersions) { - // TODO(davidben): Enable this when TLS 1.3 resumption is implemented. - if (version == TLS1_3_VERSION) { - continue; - } - ScopedSSL_CTX server_ctx(SSL_CTX_new(TLS_method())); ScopedSSL_CTX client_ctx(SSL_CTX_new(TLS_method())); if (!server_ctx || !client_ctx || @@ -1864,11 +1859,6 @@ } for (uint16_t version : kVersions) { - // TODO(davidben): Enable this when TLS 1.3 resumption is implemented. - if (version == TLS1_3_VERSION) { - continue; - } - ScopedSSL_CTX server_ctx(SSL_CTX_new(TLS_method())); ScopedSSL_CTX client_ctx(SSL_CTX_new(TLS_method())); if (!server_ctx || !client_ctx ||
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c index bb345b4..dc6c6dd 100644 --- a/ssl/t1_lib.c +++ b/ssl/t1_lib.c
@@ -1030,7 +1030,10 @@ * without upstream's 3c3f0259238594d77264a78944d409f2127642c4. */ if (!ssl->s3->initial_handshake_complete && ssl->session != NULL && - ssl->session->tlsext_tick != NULL) { + ssl->session->tlsext_tick != NULL && + /* Don't send TLS 1.3 session tickets in the ticket extension. */ + ssl->method->version_from_wire(ssl->session->ssl_version) < + TLS1_3_VERSION) { ticket_data = ssl->session->tlsext_tick; ticket_len = ssl->session->tlsext_ticklen; } @@ -1428,7 +1431,7 @@ } /* Session resumption uses the original session information. */ - if (ssl->session == NULL && + if (!ssl->s3->session_reused && !CBS_stow( contents, &ssl->s3->new_session->tlsext_signed_cert_timestamp_list, @@ -1447,7 +1450,7 @@ static int ext_sct_add_serverhello(SSL *ssl, CBB *out) { /* The extension shouldn't be sent when resuming sessions. */ - if (ssl->session != NULL || + if (ssl->s3->session_reused || ssl->ctx->signed_cert_timestamp_list_length == 0) { return 1; } @@ -1972,6 +1975,89 @@ } +/* Pre Shared Key + * + * https://tools.ietf.org/html/draft-ietf-tls-tls13-14 */ + +static int ext_pre_shared_key_add_clienthello(SSL *ssl, CBB *out) { + uint16_t min_version, max_version; + if (!ssl_get_version_range(ssl, &min_version, &max_version)) { + return 0; + } + + if (max_version < TLS1_3_VERSION || ssl->session == NULL || + ssl->method->version_from_wire(ssl->session->ssl_version) < + TLS1_3_VERSION) { + return 1; + } + + CBB contents, identities, identity; + if (!CBB_add_u16(out, TLSEXT_TYPE_pre_shared_key) || + !CBB_add_u16_length_prefixed(out, &contents) || + !CBB_add_u16_length_prefixed(&contents, &identities) || + !CBB_add_u16_length_prefixed(&identities, &identity) || + !CBB_add_bytes(&identity, ssl->session->tlsext_tick, + ssl->session->tlsext_ticklen)) { + return 0; + } + + return CBB_flush(out); +} + +int ssl_ext_pre_shared_key_parse_serverhello(SSL *ssl, uint8_t *out_alert, + CBS *contents) { + uint16_t psk_id; + if (!CBS_get_u16(contents, &psk_id) || + CBS_len(contents) != 0) { + *out_alert = SSL_AD_DECODE_ERROR; + return 0; + } + + if (psk_id != 0) { + *out_alert = SSL_AD_UNKNOWN_PSK_IDENTITY; + return 0; + } + + return 1; +} + +int ssl_ext_pre_shared_key_parse_clienthello(SSL *ssl, + SSL_SESSION **out_session, + uint8_t *out_alert, + CBS *contents) { + CBS identities, identity; + if (!CBS_get_u16_length_prefixed(contents, &identities) || + !CBS_get_u16_length_prefixed(&identities, &identity) || + CBS_len(contents) != 0) { + *out_alert = SSL_AD_DECODE_ERROR; + return 0; + } + + /* TLS 1.3 session tickets are renewed separately as part of the + * NewSessionTicket. */ + int renew; + return tls_process_ticket(ssl, out_session, &renew, CBS_data(&identity), + CBS_len(&identity), NULL, 0); +} + +int ssl_ext_pre_shared_key_add_serverhello(SSL *ssl, CBB *out) { + if (!ssl->s3->session_reused) { + return 1; + } + + CBB contents; + if (!CBB_add_u16(out, TLSEXT_TYPE_pre_shared_key) || + !CBB_add_u16_length_prefixed(out, &contents) || + /* We only consider the first identity for resumption */ + !CBB_add_u16(&contents, 0) || + !CBB_flush(out)) { + return 0; + } + + return 1; +} + + /* Key Share * * https://tools.ietf.org/html/draft-ietf-tls-tls13-12 */ @@ -2361,6 +2447,14 @@ ignore_parse_clienthello, dont_add_serverhello, }, + { + TLSEXT_TYPE_pre_shared_key, + NULL, + ext_pre_shared_key_add_clienthello, + forbid_parse_serverhello, + ignore_parse_clienthello, + dont_add_serverhello, + }, /* The final extension must be non-empty. WebSphere Application Server 7.0 is * intolerant to the last extension being zero-length. See * https://crbug.com/363583. */ @@ -2786,6 +2880,10 @@ *out_renew_ticket = 0; *out_session = NULL; + if (SSL_get_options(ssl) & SSL_OP_NO_TICKET) { + goto done; + } + if (session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH) { goto done; } @@ -2875,6 +2973,12 @@ 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/bssl_shim.cc b/ssl/test/bssl_shim.cc index 2a4db6b..13229e9 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc
@@ -101,6 +101,7 @@ // operation has been retried. unsigned private_key_retries = 0; bool got_new_session = false; + ScopedSSL_SESSION new_session; bool ticket_decrypt_done = false; bool alpn_select_done = false; }; @@ -645,8 +646,7 @@ static int NewSessionCallback(SSL *ssl, SSL_SESSION *session) { GetTestState(ssl)->got_new_session = true; - // BoringSSL passes a reference to |session|. - SSL_SESSION_free(session); + GetTestState(ssl)->new_session.reset(session); return 1; } @@ -1621,7 +1621,7 @@ } if (out_session) { - out_session->reset(SSL_get1_session(ssl.get())); + *out_session = std::move(GetTestState(ssl.get())->new_session); } ret = DoShutdown(ssl.get());
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go index 45d3e13..d064919 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go
@@ -2313,9 +2313,6 @@ expectedClientError = ":WRONG_CIPHER_RETURNED:" } - // TODO(davidben,svaldez): Implement resumption for TLS 1.3. - resumeSession := ver.version < VersionTLS13 - testCases = append(testCases, testCase{ testType: serverTest, protocol: protocol, @@ -2336,7 +2333,7 @@ certFile: certFile, keyFile: keyFile, flags: flags, - resumeSession: resumeSession, + resumeSession: true, shouldFail: shouldServerFail, expectedError: expectedServerError, }) @@ -2358,7 +2355,7 @@ }, }, flags: flags, - resumeSession: resumeSession, + resumeSession: true, shouldFail: shouldClientFail, expectedError: expectedClientError, }) @@ -3426,9 +3423,7 @@ base64.StdEncoding.EncodeToString(testOCSPResponse), "-verify-peer", }, - // TODO(davidben): Enable this when resumption is implemented - // in TLS 1.3. - resumeSession: false, + resumeSession: true, }) tests = append(tests, testCase{ testType: serverTest, @@ -3441,9 +3436,7 @@ "-ocsp-response", base64.StdEncoding.EncodeToString(testOCSPResponse), }, - // TODO(davidben): Enable this when resumption is implemented - // in TLS 1.3. - resumeSession: false, + resumeSession: true, }) // Certificate verification tests. @@ -3474,9 +3467,7 @@ flag, "-expect-verify-result", }, - // TODO(davidben): Enable this when resumption is - // implemented in TLS 1.3. - resumeSession: vers.version != VersionTLS13, + resumeSession: true, }) tests = append(tests, testCase{ testType: testType, @@ -3507,9 +3498,7 @@ "-verify-fail", "-expect-verify-result", }, - // TODO(davidben): Enable this when resumption is - // implemented in TLS 1.3. - resumeSession: vers.version != VersionTLS13, + resumeSession: true, }) } @@ -3822,7 +3811,6 @@ func addDDoSCallbackTests() { // DDoS callback. - // TODO(davidben): Implement DDoS resumption tests for TLS 1.3. for _, resume := range []bool{false, true} { suffix := "Resume" if resume { @@ -3838,17 +3826,15 @@ flags: []string{"-install-ddos-callback"}, resumeSession: resume, }) - if !resume { - testCases = append(testCases, testCase{ - testType: serverTest, - name: "Server-DDoS-OK-" + suffix + "-TLS13", - config: Config{ - MaxVersion: VersionTLS13, - }, - flags: []string{"-install-ddos-callback"}, - resumeSession: resume, - }) - } + testCases = append(testCases, testCase{ + testType: serverTest, + name: "Server-DDoS-OK-" + suffix + "-TLS13", + config: Config{ + MaxVersion: VersionTLS13, + }, + flags: []string{"-install-ddos-callback"}, + resumeSession: resume, + }) failFlag := "-fail-ddos-callback" if resume { @@ -3865,19 +3851,17 @@ shouldFail: true, expectedError: ":CONNECTION_REJECTED:", }) - if !resume { - testCases = append(testCases, testCase{ - testType: serverTest, - name: "Server-DDoS-Reject-" + suffix + "-TLS13", - config: Config{ - MaxVersion: VersionTLS13, - }, - flags: []string{"-install-ddos-callback", failFlag}, - resumeSession: resume, - shouldFail: true, - expectedError: ":CONNECTION_REJECTED:", - }) - } + testCases = append(testCases, testCase{ + testType: serverTest, + name: "Server-DDoS-Reject-" + suffix + "-TLS13", + config: Config{ + MaxVersion: VersionTLS13, + }, + flags: []string{"-install-ddos-callback", failFlag}, + resumeSession: resume, + shouldFail: true, + expectedError: ":CONNECTION_REJECTED:", + }) } } @@ -4223,9 +4207,6 @@ continue } - // TODO(davidben): Implement resumption in TLS 1.3. - resumeSession := ver.version < VersionTLS13 - // Test that duplicate extensions are rejected. testCases = append(testCases, testCase{ testType: clientTest, @@ -4297,7 +4278,7 @@ ServerName: "example.com", }, flags: []string{"-expect-server-name", "example.com"}, - resumeSession: resumeSession, + resumeSession: true, }) // Test ALPN. @@ -4314,7 +4295,7 @@ }, expectedNextProto: "foo", expectedNextProtoType: alpn, - resumeSession: resumeSession, + resumeSession: true, }) testCases = append(testCases, testCase{ testType: clientTest, @@ -4345,7 +4326,7 @@ }, expectedNextProto: "foo", expectedNextProtoType: alpn, - resumeSession: resumeSession, + resumeSession: true, }) testCases = append(testCases, testCase{ testType: serverTest, @@ -4356,7 +4337,7 @@ }, flags: []string{"-decline-alpn"}, expectNoNextProto: true, - resumeSession: resumeSession, + resumeSession: true, }) // Test ALPN in async mode as well to ensure that extensions callbacks are only @@ -4375,7 +4356,7 @@ }, expectedNextProto: "foo", expectedNextProtoType: alpn, - resumeSession: resumeSession, + resumeSession: true, }) var emptyString string @@ -4430,7 +4411,7 @@ }, expectedNextProto: "foo", expectedNextProtoType: alpn, - resumeSession: resumeSession, + resumeSession: true, }) testCases = append(testCases, testCase{ testType: serverTest, @@ -4449,7 +4430,7 @@ }, expectedNextProto: "foo", expectedNextProtoType: alpn, - resumeSession: resumeSession, + resumeSession: true, }) // Test that negotiating both NPN and ALPN is forbidden. @@ -4503,66 +4484,65 @@ } // Test ticket behavior. - // - // TODO(davidben): Add TLS 1.3 versions of these. + + // Resume with a corrupt ticket. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "CorruptTicket-" + ver.name, + config: Config{ + MaxVersion: ver.version, + Bugs: ProtocolBugs{ + CorruptTicket: true, + }, + }, + resumeSession: true, + expectResumeRejected: true, + }) + // Test the ticket callback, with and without renewal. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TicketCallback-" + ver.name, + config: Config{ + MaxVersion: ver.version, + }, + resumeSession: true, + flags: []string{"-use-ticket-callback"}, + }) + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TicketCallback-Renew-" + ver.name, + config: Config{ + MaxVersion: ver.version, + Bugs: ProtocolBugs{ + ExpectNewTicket: true, + }, + }, + flags: []string{"-use-ticket-callback", "-renew-ticket"}, + resumeSession: true, + }) + + // Test that the ticket callback is only called once when everything before + // it in the ClientHello is asynchronous. This corrupts the ticket so + // certificate selection callbacks run. + testCases = append(testCases, testCase{ + testType: serverTest, + name: "TicketCallback-SingleCall-" + ver.name, + config: Config{ + MaxVersion: ver.version, + Bugs: ProtocolBugs{ + CorruptTicket: true, + }, + }, + resumeSession: true, + expectResumeRejected: true, + flags: []string{ + "-use-ticket-callback", + "-async", + }, + }) + + // Resume with an oversized session id. if ver.version < VersionTLS13 { - // Resume with a corrupt ticket. - testCases = append(testCases, testCase{ - testType: serverTest, - name: "CorruptTicket-" + ver.name, - config: Config{ - MaxVersion: ver.version, - Bugs: ProtocolBugs{ - CorruptTicket: true, - }, - }, - resumeSession: true, - expectResumeRejected: true, - }) - // Test the ticket callback, with and without renewal. - testCases = append(testCases, testCase{ - testType: serverTest, - name: "TicketCallback-" + ver.name, - config: Config{ - MaxVersion: ver.version, - }, - resumeSession: true, - flags: []string{"-use-ticket-callback"}, - }) - testCases = append(testCases, testCase{ - testType: serverTest, - name: "TicketCallback-Renew-" + ver.name, - config: Config{ - MaxVersion: ver.version, - Bugs: ProtocolBugs{ - ExpectNewTicket: true, - }, - }, - flags: []string{"-use-ticket-callback", "-renew-ticket"}, - resumeSession: true, - }) - - // Test that the ticket callback is only called once when everything before - // it in the ClientHello is asynchronous. This corrupts the ticket so - // certificate selection callbacks run. - testCases = append(testCases, testCase{ - testType: serverTest, - name: "TicketCallback-SingleCall-" + ver.name, - config: Config{ - MaxVersion: ver.version, - Bugs: ProtocolBugs{ - CorruptTicket: true, - }, - }, - resumeSession: true, - expectResumeRejected: true, - flags: []string{ - "-use-ticket-callback", - "-async", - }, - }) - - // Resume with an oversized session id. testCases = append(testCases, testCase{ testType: serverTest, name: "OversizedSessionId-" + ver.name, @@ -4674,7 +4654,7 @@ "-expect-signed-cert-timestamps", base64.StdEncoding.EncodeToString(testSCTList), }, - resumeSession: resumeSession, + resumeSession: true, }) testCases = append(testCases, testCase{ name: "SendSCTListOnResume-" + ver.name, @@ -4689,7 +4669,7 @@ "-expect-signed-cert-timestamps", base64.StdEncoding.EncodeToString(testSCTList), }, - resumeSession: resumeSession, + resumeSession: true, }) testCases = append(testCases, testCase{ name: "SignedCertificateTimestampList-Server-" + ver.name, @@ -4702,7 +4682,7 @@ base64.StdEncoding.EncodeToString(testSCTList), }, expectedSCTList: testSCTList, - resumeSession: resumeSession, + resumeSession: true, }) } @@ -4874,14 +4854,7 @@ func addResumptionVersionTests() { for _, sessionVers := range tlsVersions { - // TODO(davidben,svaldez): Implement resumption in TLS 1.3. - if sessionVers.version >= VersionTLS13 { - continue - } for _, resumeVers := range tlsVersions { - if resumeVers.version >= VersionTLS13 { - continue - } cipher := TLS_RSA_WITH_AES_128_CBC_SHA if sessionVers.version >= VersionTLS13 || resumeVers.version >= VersionTLS13 { // TLS 1.3 only shares ciphers with TLS 1.2, so @@ -4916,6 +4889,14 @@ expectedResumeVersion: resumeVers.version, }) } else { + var localError, error string + if (resumeVers.version >= VersionTLS13) != (sessionVers.version >= VersionTLS13) { + // TLS 1.3 sessions are incompatible with TLS 1.2 sessions. + localError = "didResume is false, but we expected the opposite" + } else { + error = ":OLD_SESSION_VERSION_NOT_RETURNED:" + } + testCases = append(testCases, testCase{ protocol: protocol, name: "Resume-Client-Mismatch" + suffix, @@ -4934,7 +4915,8 @@ }, expectedResumeVersion: resumeVers.version, shouldFail: true, - expectedError: ":OLD_SESSION_VERSION_NOT_RETURNED:", + expectedLocalError: localError, + expectedError: error, }) } @@ -4977,7 +4959,6 @@ } } - // TODO(davidben): This test should have a TLS 1.3 variant later. testCases = append(testCases, testCase{ name: "Resume-Client-CipherMismatch", resumeSession: true, @@ -4995,6 +4976,24 @@ shouldFail: true, expectedError: ":OLD_SESSION_CIPHER_NOT_RETURNED:", }) + + testCases = append(testCases, testCase{ + name: "Resume-Client-CipherMismatch-TLS13", + resumeSession: true, + config: Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + }, + resumeConfig: &Config{ + MaxVersion: VersionTLS13, + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + Bugs: ProtocolBugs{ + SendCipherSuite: TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, + }, + }, + shouldFail: true, + expectedError: ":OLD_SESSION_CIPHER_NOT_RETURNED:", + }) } func addRenegotiationTests() {
diff --git a/ssl/tls13_client.c b/ssl/tls13_client.c index 61e1414..d58f72d 100644 --- a/ssl/tls13_client.c +++ b/ssl/tls13_client.c
@@ -151,8 +151,8 @@ } /* Parse out the extensions. */ - int have_key_share = 0; - CBS key_share; + int have_key_share = 0, have_pre_shared_key = 0; + CBS key_share, pre_shared_key; while (CBS_len(&extensions) != 0) { uint16_t type; CBS extension; @@ -173,6 +173,15 @@ key_share = extension; have_key_share = 1; break; + case TLSEXT_TYPE_pre_shared_key: + if (have_pre_shared_key) { + OPENSSL_PUT_ERROR(SSL, SSL_R_DUPLICATE_EXTENSION); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); + return ssl_hs_error; + } + pre_shared_key = extension; + have_pre_shared_key = 1; + break; default: OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION); ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION); @@ -183,10 +192,48 @@ assert(ssl->s3->have_version); memcpy(ssl->s3->server_random, CBS_data(&server_random), SSL3_RANDOM_SIZE); - SSL_set_session(ssl, NULL); - if (!ssl_get_new_session(ssl, 0)) { - ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); - return ssl_hs_error; + uint8_t alert = SSL_AD_DECODE_ERROR; + if (have_pre_shared_key) { + if (ssl->session == NULL) { + OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION); + return ssl_hs_error; + } + + if (!ssl_ext_pre_shared_key_parse_serverhello(ssl, &alert, + &pre_shared_key)) { + ssl3_send_alert(ssl, SSL3_AL_FATAL, alert); + return ssl_hs_error; + } + + if (ssl->session->ssl_version != ssl->version) { + OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_VERSION_NOT_RETURNED); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); + return ssl_hs_error; + } + + if (!ssl_session_is_context_valid(ssl, ssl->session)) { + /* This is actually a client application bug. */ + OPENSSL_PUT_ERROR(SSL, + SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); + return ssl_hs_error; + } + + ssl->s3->session_reused = 1; + /* Only authentication information carries over in TLS 1.3. */ + ssl->s3->new_session = + SSL_SESSION_dup(ssl->session, SSL_SESSION_DUP_AUTH_ONLY); + if (ssl->s3->new_session == NULL) { + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); + return ssl_hs_error; + } + SSL_set_session(ssl, NULL); + } else { + if (!ssl_get_new_session(ssl, 0)) { + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); + return ssl_hs_error; + } } const SSL_CIPHER *cipher = SSL_get_cipher_by_value(cipher_suite); @@ -196,15 +243,26 @@ return ssl_hs_error; } - /* Check if the cipher is disabled. */ - if ((cipher->algorithm_mkey & ssl->cert->mask_k) || - (cipher->algorithm_auth & ssl->cert->mask_a) || - SSL_CIPHER_get_min_version(cipher) > ssl3_protocol_version(ssl) || - SSL_CIPHER_get_max_version(cipher) < ssl3_protocol_version(ssl) || - !sk_SSL_CIPHER_find(ssl_get_ciphers_by_id(ssl), NULL, cipher)) { - OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED); - ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); - return ssl_hs_error; + if (!ssl->s3->session_reused) { + /* Check if the cipher is disabled. */ + if ((cipher->algorithm_mkey & ssl->cert->mask_k) || + (cipher->algorithm_auth & ssl->cert->mask_a) || + SSL_CIPHER_get_min_version(cipher) > ssl3_protocol_version(ssl) || + SSL_CIPHER_get_max_version(cipher) < ssl3_protocol_version(ssl) || + !sk_SSL_CIPHER_find(ssl_get_ciphers_by_id(ssl), NULL, cipher)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); + return ssl_hs_error; + } + } else { + uint16_t resumption_cipher; + if (!ssl_cipher_get_ecdhe_psk_cipher(ssl->s3->new_session->cipher, + &resumption_cipher) || + resumption_cipher != ssl_cipher_get_value(cipher)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_CIPHER_NOT_RETURNED); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER); + return ssl_hs_error; + } } ssl->s3->new_session->cipher = cipher; @@ -212,18 +270,35 @@ /* The PRF hash is now known. Set up the key schedule. */ static const uint8_t kZeroes[EVP_MAX_MD_SIZE] = {0}; - size_t hash_len = + size_t resumption_ctx_len = EVP_MD_size(ssl_get_handshake_digest(ssl_get_algorithm_prf(ssl))); - if (!tls13_init_key_schedule(ssl, kZeroes, hash_len)) { + if (ssl->s3->session_reused) { + uint8_t resumption_ctx[EVP_MAX_MD_SIZE]; + if (!tls13_resumption_context(ssl, resumption_ctx, resumption_ctx_len, + ssl->s3->new_session) || + !tls13_init_key_schedule(ssl, resumption_ctx, resumption_ctx_len)) { + return ssl_hs_error; + } + } else if (!tls13_init_key_schedule(ssl, kZeroes, resumption_ctx_len)) { return ssl_hs_error; } /* Resolve PSK and incorporate it into the secret. */ if (cipher->algorithm_auth == SSL_aPSK) { - /* TODO(davidben): Support PSK. */ - OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); - return ssl_hs_error; - } else if (!tls13_advance_key_schedule(ssl, kZeroes, hash_len)) { + if (!ssl->s3->session_reused) { + OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); + return ssl_hs_error; + } + + uint8_t resumption_psk[EVP_MAX_MD_SIZE]; + if (!tls13_resumption_psk(ssl, resumption_psk, hs->hash_len, + ssl->s3->new_session) || + !tls13_advance_key_schedule(ssl, resumption_psk, hs->hash_len)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); + return ssl_hs_error; + } + } else if (!tls13_advance_key_schedule(ssl, kZeroes, hs->hash_len)) { return ssl_hs_error; } @@ -237,7 +312,6 @@ uint8_t *dhe_secret; size_t dhe_secret_len; - uint8_t alert = SSL_AD_DECODE_ERROR; if (!ssl_ext_key_share_parse_serverhello(ssl, &dhe_secret, &dhe_secret_len, &alert, &key_share)) { ssl3_send_alert(ssl, SSL3_AL_FATAL, alert); @@ -255,7 +329,7 @@ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION); return ssl_hs_error; } - if (!tls13_advance_key_schedule(ssl, kZeroes, hash_len)) { + if (!tls13_advance_key_schedule(ssl, kZeroes, hs->hash_len)) { return ssl_hs_error; } } @@ -568,8 +642,9 @@ } int tls13_process_new_session_ticket(SSL *ssl) { - SSL_SESSION *session = SSL_SESSION_dup(ssl->s3->established_session, - 0 /* don't include ticket */); + SSL_SESSION *session = + SSL_SESSION_dup(ssl->s3->established_session, + SSL_SESSION_INCLUDE_NONAUTH); if (session == NULL) { return 0; }
diff --git a/ssl/tls13_enc.c b/ssl/tls13_enc.c index 70b041a..518d2d3 100644 --- a/ssl/tls13_enc.c +++ b/ssl/tls13_enc.c
@@ -351,6 +351,28 @@ return 1; } +static const char kTLS13LabelResumptionPSK[] = "resumption psk"; +static const char kTLS13LabelResumptionContext[] = "resumption context"; + +int tls13_resumption_psk(SSL *ssl, uint8_t *out, size_t out_len, + const SSL_SESSION *session) { + const EVP_MD *digest = ssl_get_handshake_digest(ssl_get_algorithm_prf(ssl)); + return hkdf_expand_label(out, digest, session->master_key, + session->master_key_length, + (const uint8_t *)kTLS13LabelResumptionPSK, + strlen(kTLS13LabelResumptionPSK), NULL, 0, out_len); +} + +int tls13_resumption_context(SSL *ssl, uint8_t *out, size_t out_len, + const SSL_SESSION *session) { + const EVP_MD *digest = ssl_get_handshake_digest(ssl_get_algorithm_prf(ssl)); + return hkdf_expand_label(out, digest, session->master_key, + session->master_key_length, + (const uint8_t *)kTLS13LabelResumptionContext, + strlen(kTLS13LabelResumptionContext), NULL, 0, + out_len); +} + int tls13_export_keying_material(SSL *ssl, uint8_t *out, size_t out_len, const char *label, size_t label_len, const uint8_t *context, size_t context_len,
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c index a1aeeea..1f4475e 100644 --- a/ssl/tls13_server.c +++ b/ssl/tls13_server.c
@@ -58,9 +58,14 @@ return tls13_advance_key_schedule(ssl, kZeroes, hs->hash_len); } - /* TODO(davidben): Support PSK. */ - OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR); - return 0; + uint8_t resumption_psk[EVP_MAX_MD_SIZE]; + if (!tls13_resumption_psk(ssl, resumption_psk, hs->hash_len, + ssl->s3->new_session) || + !tls13_advance_key_schedule(ssl, resumption_psk, hs->hash_len)) { + return 0; + } + + return 1; } static int resolve_ecdhe_secret(SSL *ssl, int *out_need_retry, @@ -123,10 +128,45 @@ } memcpy(ssl->s3->client_random, client_hello.random, client_hello.random_len); - SSL_set_session(ssl, NULL); - if (!ssl_get_new_session(ssl, 1 /* server */)) { - ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); - return ssl_hs_error; + uint8_t alert = SSL_AD_DECODE_ERROR; + SSL_SESSION *session = NULL; + CBS pre_shared_key; + if (ssl_early_callback_get_extension(&client_hello, &pre_shared_key, + TLSEXT_TYPE_pre_shared_key) && + !ssl_ext_pre_shared_key_parse_clienthello(ssl, &session, &alert, + &pre_shared_key)) { + ssl3_send_alert(ssl, SSL3_AL_FATAL, alert); + return 0; + } + + uint16_t resumption_cipher; + if (session != NULL && + /* We currently only support ECDHE-PSK resumption. */ + ((session->ticket_flags & SSL_TICKET_ALLOW_DHE_RESUMPTION) == 0 || + /* Only resume if the session's version matches. */ + session->ssl_version != ssl->version || + !ssl_cipher_get_ecdhe_psk_cipher(session->cipher, &resumption_cipher) || + !ssl_client_cipher_list_contains_cipher(&client_hello, + resumption_cipher))) { + SSL_SESSION_free(session); + session = NULL; + } + + if (session == NULL) { + if (!ssl_get_new_session(ssl, 1 /* server */)) { + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); + return ssl_hs_error; + } + } else { + /* Only authentication information carries over in TLS 1.3. */ + ssl->s3->new_session = SSL_SESSION_dup(session, SSL_SESSION_DUP_AUTH_ONLY); + if (ssl->s3->new_session == NULL) { + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); + return ssl_hs_error; + } + ssl->s3->session_reused = 1; + ssl->verify_result = session->verify_result; + SSL_SESSION_free(session); } if (ssl->ctx->dos_protection_cb != NULL && @@ -156,17 +196,19 @@ } static enum ssl_hs_wait_t do_select_parameters(SSL *ssl, SSL_HANDSHAKE *hs) { - /* Call |cert_cb| to update server certificates if required. */ - if (ssl->cert->cert_cb != NULL) { - int rv = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg); - if (rv == 0) { - OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR); - ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); - return ssl_hs_error; - } - if (rv < 0) { - hs->state = state_select_parameters; - return ssl_hs_x509_lookup; + if (!ssl->s3->session_reused) { + /* Call |cert_cb| to update server certificates if required. */ + if (ssl->cert->cert_cb != NULL) { + int rv = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg); + if (rv == 0) { + OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR); + return ssl_hs_error; + } + if (rv < 0) { + hs->state = state_select_parameters; + return ssl_hs_x509_lookup; + } } } @@ -178,25 +220,45 @@ return ssl_hs_error; } - const SSL_CIPHER *cipher = - ssl3_choose_cipher(ssl, &client_hello, ssl_get_cipher_preferences(ssl)); - if (cipher == NULL) { - OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER); - ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE); - return ssl_hs_error; - } + if (!ssl->s3->session_reused) { + const SSL_CIPHER *cipher = + ssl3_choose_cipher(ssl, &client_hello, ssl_get_cipher_preferences(ssl)); + if (cipher == NULL) { + OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE); + return ssl_hs_error; + } - ssl->s3->new_session->cipher = cipher; - ssl->s3->tmp.new_cipher = cipher; + ssl->s3->new_session->cipher = cipher; + ssl->s3->tmp.new_cipher = cipher; + } else { + uint16_t resumption_cipher; + if (!ssl_cipher_get_ecdhe_psk_cipher(ssl->s3->new_session->cipher, + &resumption_cipher)) { + OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE); + return ssl_hs_error; + } + ssl->s3->tmp.new_cipher = SSL_get_cipher_by_value(resumption_cipher); + } ssl->method->received_flight(ssl); /* The PRF hash is now known. Set up the key schedule and hash the * ClientHello. */ - size_t hash_len = + size_t resumption_ctx_len = EVP_MD_size(ssl_get_handshake_digest(ssl_get_algorithm_prf(ssl))); - if (!tls13_init_key_schedule(ssl, kZeroes, hash_len)) { - return ssl_hs_error; + if (ssl->s3->session_reused) { + uint8_t resumption_ctx[EVP_MAX_MD_SIZE]; + if (!tls13_resumption_context(ssl, resumption_ctx, resumption_ctx_len, + ssl->s3->new_session) || + !tls13_init_key_schedule(ssl, resumption_ctx, resumption_ctx_len)) { + return ssl_hs_error; + } + } else { + if (!tls13_init_key_schedule(ssl, kZeroes, resumption_ctx_len)) { + return ssl_hs_error; + } } /* Resolve PSK and incorporate it into the secret. */ @@ -285,6 +347,7 @@ !CBB_add_bytes(&body, ssl->s3->server_random, SSL3_RANDOM_SIZE) || !CBB_add_u16(&body, ssl_cipher_get_value(ssl->s3->tmp.new_cipher)) || !CBB_add_u16_length_prefixed(&body, &extensions) || + !ssl_ext_pre_shared_key_add_serverhello(ssl, &extensions) || !ssl_ext_key_share_add_serverhello(ssl, &extensions) || !ssl->method->finish_message(ssl, &cbb)) { CBB_cleanup(&cbb); @@ -428,7 +491,7 @@ SSL_HANDSHAKE *hs) { if (!ssl->s3->tmp.cert_request) { /* Skip this state. */ - hs->state = state_process_client_certificate_verify; + hs->state = state_process_client_finished; return ssl_hs_ok; }