From 1e6f11a7ff72bea394e6651b512e435fbc95d323 Mon Sep 17 00:00:00 2001 From: Steven Valdez Date: Wed, 27 Jul 2016 11:10:52 -0400 Subject: [PATCH] Adding NewSessionTicket. We will now send tickets as a server and accept them as a client. Correctly offering and resuming them in the handshake will be implemented in a follow-up. Now that we're actually processing draft 14 tickets, bump the draft version. Change-Id: I304320a29c4ffe564fa9c00642a4ace96ff8d871 Reviewed-on: https://boringssl-review.googlesource.com/8982 Reviewed-by: David Benjamin Commit-Queue: David Benjamin CQ-Verified: CQ bot account: commit-bot@chromium.org --- include/openssl/ssl.h | 10 +++- ssl/handshake_client.c | 2 +- ssl/handshake_server.c | 105 ++++------------------------------------- ssl/internal.h | 9 ++++ ssl/ssl_asn1.c | 42 +++++++++++++++-- ssl/ssl_session.c | 95 +++++++++++++++++++++++++++++++++++-- ssl/test/bssl_shim.cc | 25 +++++++++- ssl/tls13_both.c | 3 +- ssl/tls13_client.c | 35 ++++++++++++++ ssl/tls13_server.c | 55 ++++++++++++++++++++- 10 files changed, 272 insertions(+), 109 deletions(-) diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 0ec3b585..90db2caa 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -572,7 +572,7 @@ OPENSSL_EXPORT int DTLSv1_handle_timeout(SSL *ssl); #define DTLS1_VERSION 0xfeff #define DTLS1_2_VERSION 0xfefd -#define TLS1_3_DRAFT_VERSION 13 +#define TLS1_3_DRAFT_VERSION 14 /* SSL_CTX_set_min_version sets the minimum protocol version for |ctx| to * |version|. */ @@ -3702,7 +3702,10 @@ struct ssl_session_st { uint8_t original_handshake_hash[EVP_MAX_MD_SIZE]; unsigned original_handshake_hash_len; - uint32_t tlsext_tick_lifetime_hint; /* Session lifetime hint in seconds */ + uint32_t ticket_lifetime_hint; /* Session lifetime hint in seconds */ + + uint32_t ticket_flags; + uint32_t ticket_age_add; /* extended_master_secret is true if the master secret in this session was * generated using EMS and thus isn't vulnerable to the Triple Handshake @@ -3714,6 +3717,9 @@ struct ssl_session_st { /* not_resumable is used to indicate that session resumption is disallowed. */ unsigned not_resumable:1; + + /* ticket_age_add_valid is non-zero if |ticket_age_add| is valid. */ + unsigned ticket_age_add_valid:1; }; /* ssl_cipher_preference_list_st contains a list of SSL_CIPHERs with diff --git a/ssl/handshake_client.c b/ssl/handshake_client.c index 396a66d4..38fb3af7 100644 --- a/ssl/handshake_client.c +++ b/ssl/handshake_client.c @@ -1953,7 +1953,7 @@ static int ssl3_get_new_session_ticket(SSL *ssl) { OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); goto err; } - session->tlsext_tick_lifetime_hint = ticket_lifetime_hint; + session->ticket_lifetime_hint = ticket_lifetime_hint; /* Generate a session ID for this session based on the session ticket. We use * the session ID mechanism for detecting ticket resumption. This also fits in diff --git a/ssl/handshake_server.c b/ssl/handshake_server.c index dbf34e98..4e4681a0 100644 --- a/ssl/handshake_server.c +++ b/ssl/handshake_server.c @@ -1867,107 +1867,22 @@ static int ssl3_send_new_session_ticket(SSL *ssl) { return ssl->method->write_message(ssl); } - /* Serialize the SSL_SESSION to be encoded into the ticket. */ - uint8_t *session = NULL; - size_t session_len; - if (!SSL_SESSION_to_bytes_for_ticket( - ssl->session != NULL ? ssl->session : ssl->s3->new_session, - &session, &session_len)) { - return -1; - } - - EVP_CIPHER_CTX ctx; - EVP_CIPHER_CTX_init(&ctx); - HMAC_CTX hctx; - HMAC_CTX_init(&hctx); - - int ret = -1; CBB cbb, body, ticket; - if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_NEW_SESSION_TICKET) || + if (!ssl->method->init_message(ssl, &cbb, &body, + SSL3_MT_NEW_SESSION_TICKET) || /* Ticket lifetime hint (advisory only): We leave this unspecified for * resumed session (for simplicity), and guess that tickets for new * sessions will live as long as their sessions. */ - !CBB_add_u32(&body, ssl->session != NULL ? 0 : - ssl->s3->new_session->timeout) || - !CBB_add_u16_length_prefixed(&body, &ticket)) { - goto err; - } - - /* If the session is too long, emit a dummy value rather than abort the - * connection. */ - const size_t max_ticket_overhead = - 16 + EVP_MAX_IV_LENGTH + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE; - if (session_len > 0xffff - max_ticket_overhead) { - static const char kTicketPlaceholder[] = "TICKET TOO LARGE"; - - if (!CBB_add_bytes(&ticket, (const uint8_t *)kTicketPlaceholder, - strlen(kTicketPlaceholder)) || - !ssl->method->finish_message(ssl, &cbb)) { - goto err; - } - - ssl->state = SSL3_ST_SW_SESSION_TICKET_B; - ret = 1; - goto err; - } - - /* Initialize HMAC and cipher contexts. If callback present it does all the - * work otherwise use generated values from parent ctx. */ - SSL_CTX *tctx = ssl->initial_ctx; - uint8_t iv[EVP_MAX_IV_LENGTH]; - uint8_t key_name[16]; - if (tctx->tlsext_ticket_key_cb != NULL) { - if (tctx->tlsext_ticket_key_cb(ssl, key_name, iv, &ctx, &hctx, - 1 /* encrypt */) < 0) { - goto err; - } - } else { - if (!RAND_bytes(iv, 16) || - !EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL, - tctx->tlsext_tick_aes_key, iv) || - !HMAC_Init_ex(&hctx, tctx->tlsext_tick_hmac_key, 16, tlsext_tick_md(), - NULL)) { - goto err; - } - memcpy(key_name, tctx->tlsext_tick_key_name, 16); - } - - uint8_t *ptr; - if (!CBB_add_bytes(&ticket, key_name, 16) || - !CBB_add_bytes(&ticket, iv, EVP_CIPHER_CTX_iv_length(&ctx)) || - !CBB_reserve(&ticket, &ptr, session_len + EVP_MAX_BLOCK_LENGTH)) { - goto err; - } - - int len; - size_t total = 0; - if (!EVP_EncryptUpdate(&ctx, ptr + total, &len, session, session_len)) { - goto err; - } - total += len; - if (!EVP_EncryptFinal_ex(&ctx, ptr + total, &len)) { - goto err; - } - total += len; - if (!CBB_did_write(&ticket, total)) { - goto err; - } - - unsigned hlen; - if (!HMAC_Update(&hctx, CBB_data(&ticket), CBB_len(&ticket)) || - !CBB_reserve(&ticket, &ptr, EVP_MAX_MD_SIZE) || - !HMAC_Final(&hctx, ptr, &hlen) || - !CBB_did_write(&ticket, hlen) || + !CBB_add_u32(&body, + ssl->session != NULL ? 0 : ssl->s3->new_session->timeout) || + !CBB_add_u16_length_prefixed(&body, &ticket) || + !ssl_encrypt_ticket(ssl, &ticket, ssl->session != NULL + ? ssl->session + : ssl->s3->new_session) || !ssl->method->finish_message(ssl, &cbb)) { - goto err; + return 0; } ssl->state = SSL3_ST_SW_SESSION_TICKET_B; - ret = ssl->method->write_message(ssl); - -err: - OPENSSL_free(session); - EVP_CIPHER_CTX_cleanup(&ctx); - HMAC_CTX_cleanup(&hctx); - return ret; + return ssl->method->write_message(ssl); } diff --git a/ssl/internal.h b/ssl/internal.h index 22b5566e..360853fd 100644 --- a/ssl/internal.h +++ b/ssl/internal.h @@ -891,6 +891,8 @@ struct ssl_handshake_st { uint8_t *cert_context; size_t cert_context_len; + + uint8_t session_tickets_sent; } /* SSL_HANDSHAKE */; SSL_HANDSHAKE *ssl_handshake_new(enum ssl_hs_wait_t (*do_handshake)(SSL *ssl)); @@ -925,6 +927,7 @@ int tls13_prepare_certificate(SSL *ssl); enum ssl_private_key_result_t tls13_prepare_certificate_verify( SSL *ssl, int is_first_run); int tls13_prepare_finished(SSL *ssl); +int tls13_process_new_session_ticket(SSL *ssl); int ssl_ext_key_share_parse_serverhello(SSL *ssl, uint8_t **out_secret, size_t *out_secret_len, @@ -1202,12 +1205,18 @@ typedef struct dtls1_state_st { extern const SSL3_ENC_METHOD TLSv1_enc_data; extern const SSL3_ENC_METHOD SSLv3_enc_data; +/* From draft-ietf-tls-tls13-14, used in determining ticket validity. */ +#define SSL_TICKET_ALLOW_EARLY_DATA 1 +#define SSL_TICKET_ALLOW_DHE_RESUMPTION 2 +#define SSL_TICKET_ALLOW_PSK_RESUMPTION 4 + int ssl_clear_bad_session(SSL *ssl); CERT *ssl_cert_new(void); CERT *ssl_cert_dup(CERT *cert); void ssl_cert_clear_certs(CERT *c); void ssl_cert_free(CERT *c); int ssl_get_new_session(SSL *ssl, int is_server); +int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session); enum ssl_session_result_t { ssl_session_success, diff --git a/ssl/ssl_asn1.c b/ssl/ssl_asn1.c index 41987f87..b1c6a093 100644 --- a/ssl/ssl_asn1.c +++ b/ssl/ssl_asn1.c @@ -120,6 +120,8 @@ * extendedMasterSecret [17] BOOLEAN OPTIONAL, * keyExchangeInfo [18] INTEGER OPTIONAL, * certChain [19] SEQUENCE OF Certificate OPTIONAL, + * ticketFlags [20] INTEGER OPTIONAL, + * ticketAgeAdd [21] OCTET STRING OPTIONAL, * } * * Note: historically this serialization has included other optional @@ -164,6 +166,10 @@ static const int kKeyExchangeInfoTag = CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 18; static const int kCertChainTag = CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 19; +static const int kTicketFlagsTag = + CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 20; +static const int kTicketAgeAddTag = + CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 21; static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data, size_t *out_len, int for_ticket) { @@ -255,9 +261,9 @@ static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data, } } - if (in->tlsext_tick_lifetime_hint > 0) { + if (in->ticket_lifetime_hint > 0) { if (!CBB_add_asn1(&session, &child, kTicketLifetimeHintTag) || - !CBB_add_asn1_uint64(&child, in->tlsext_tick_lifetime_hint)) { + !CBB_add_asn1_uint64(&child, in->ticket_lifetime_hint)) { OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); goto err; } @@ -341,6 +347,23 @@ static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data, } } + if (in->ticket_flags > 0) { + if (!CBB_add_asn1(&session, &child, kTicketFlagsTag) || + !CBB_add_asn1_uint64(&child, in->ticket_flags)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); + goto err; + } + } + + if (in->ticket_age_add_valid) { + if (!CBB_add_asn1(&session, &child, kTicketAgeAddTag) || + !CBB_add_asn1(&child, &child2, CBS_ASN1_OCTETSTRING) || + !CBB_add_u32(&child2, in->ticket_age_add)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); + goto err; + } + } + if (!CBB_finish(&cbb, out_data, out_len)) { OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); goto err; @@ -573,7 +596,7 @@ static SSL_SESSION *SSL_SESSION_parse(CBS *cbs) { kHostNameTag) || !SSL_SESSION_parse_string(&session, &ret->psk_identity, kPSKIdentityTag) || - !SSL_SESSION_parse_u32(&session, &ret->tlsext_tick_lifetime_hint, + !SSL_SESSION_parse_u32(&session, &ret->ticket_lifetime_hint, kTicketLifetimeHintTag, 0) || !SSL_SESSION_parse_octet_string(&session, &ret->tlsext_tick, &ret->tlsext_ticklen, kTicketTag)) { @@ -652,6 +675,19 @@ static SSL_SESSION *SSL_SESSION_parse(CBS *cbs) { } } + CBS age_add; + int age_add_present; + if (!SSL_SESSION_parse_u32(&session, &ret->ticket_flags, + kTicketFlagsTag, 0) || + !CBS_get_optional_asn1_octet_string(&session, &age_add, &age_add_present, + kTicketAgeAddTag) || + (age_add_present && + !CBS_get_u32(&age_add, &ret->ticket_age_add)) || + CBS_len(&age_add) != 0) { + goto err; + } + ret->ticket_age_add_valid = age_add_present; + if (CBS_len(&session) != 0) { OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION); goto err; diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c index 55534006..cb1edb95 100644 --- a/ssl/ssl_session.c +++ b/ssl/ssl_session.c @@ -219,8 +219,8 @@ SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int include_ticket) { } if (include_ticket) { if (session->tlsext_tick != NULL) { - new_session->tlsext_tick = BUF_memdup(session->tlsext_tick, - session->tlsext_ticklen); + new_session->tlsext_tick = + BUF_memdup(session->tlsext_tick, session->tlsext_ticklen); if (new_session->tlsext_tick == NULL) { goto err; } @@ -252,7 +252,9 @@ SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int include_ticket) { 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_lifetime_hint = session->ticket_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; new_session->not_resumable = 1; @@ -468,6 +470,93 @@ err: return 0; } +int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session) { + int ret = 0; + + /* Serialize the SSL_SESSION to be encoded into the ticket. */ + uint8_t *session_buf = NULL; + size_t session_len; + if (!SSL_SESSION_to_bytes_for_ticket(session, &session_buf, &session_len)) { + return -1; + } + + EVP_CIPHER_CTX ctx; + EVP_CIPHER_CTX_init(&ctx); + HMAC_CTX hctx; + HMAC_CTX_init(&hctx); + + /* If the session is too long, emit a dummy value rather than abort the + * connection. */ + static const size_t kMaxTicketOverhead = + 16 + EVP_MAX_IV_LENGTH + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE; + if (session_len > 0xffff - kMaxTicketOverhead) { + static const char kTicketPlaceholder[] = "TICKET TOO LARGE"; + if (CBB_add_bytes(out, (const uint8_t *)kTicketPlaceholder, + strlen(kTicketPlaceholder))) { + ret = 1; + } + goto err; + } + + /* Initialize HMAC and cipher contexts. If callback present it does all the + * work otherwise use generated values from parent ctx. */ + SSL_CTX *tctx = ssl->initial_ctx; + uint8_t iv[EVP_MAX_IV_LENGTH]; + uint8_t key_name[16]; + if (tctx->tlsext_ticket_key_cb != NULL) { + if (tctx->tlsext_ticket_key_cb(ssl, key_name, iv, &ctx, &hctx, + 1 /* encrypt */) < 0) { + goto err; + } + } else { + if (!RAND_bytes(iv, 16) || + !EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL, + tctx->tlsext_tick_aes_key, iv) || + !HMAC_Init_ex(&hctx, tctx->tlsext_tick_hmac_key, 16, tlsext_tick_md(), + NULL)) { + goto err; + } + memcpy(key_name, tctx->tlsext_tick_key_name, 16); + } + + uint8_t *ptr; + if (!CBB_add_bytes(out, key_name, 16) || + !CBB_add_bytes(out, iv, EVP_CIPHER_CTX_iv_length(&ctx)) || + !CBB_reserve(out, &ptr, session_len + EVP_MAX_BLOCK_LENGTH)) { + goto err; + } + + int len; + size_t total = 0; + if (!EVP_EncryptUpdate(&ctx, ptr + total, &len, session_buf, session_len)) { + goto err; + } + total += len; + if (!EVP_EncryptFinal_ex(&ctx, ptr + total, &len)) { + goto err; + } + total += len; + if (!CBB_did_write(out, total)) { + goto err; + } + + unsigned hlen; + if (!HMAC_Update(&hctx, CBB_data(out), CBB_len(out)) || + !CBB_reserve(out, &ptr, EVP_MAX_MD_SIZE) || + !HMAC_Final(&hctx, ptr, &hlen) || + !CBB_did_write(out, hlen)) { + goto err; + } + + ret = 1; + +err: + OPENSSL_free(session_buf); + EVP_CIPHER_CTX_cleanup(&ctx); + HMAC_CTX_cleanup(&hctx); + return ret; +} + /* 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. */ diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc index dd7d3a5d..9cc1277d 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc @@ -1028,6 +1028,14 @@ static int DoSendFatalAlert(SSL *ssl, uint8_t alert) { return ret; } +static uint16_t GetProtocolVersion(const SSL *ssl) { + uint16_t version = SSL_version(ssl); + if (!SSL_is_dtls(ssl)) { + return version; + } + return 0x0201 + ~version; +} + // CheckHandshakeProperties checks, immediately after |ssl| completes its // initial handshake (or False Starts), whether all the properties are // consistent with the test configuration and invariants. @@ -1057,8 +1065,8 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume) { bool expect_new_session = !config->expect_no_session && (!SSL_session_reused(ssl) || config->expect_ticket_renewal) && - /* TODO(svaldez): Implement Session Resumption. */ - SSL_version(ssl) != TLS1_3_VERSION; + // Session tickets are sent post-handshake in TLS 1.3. + GetProtocolVersion(ssl) < TLS1_3_VERSION; if (expect_new_session != GetTestState(ssl)->got_new_session) { fprintf(stderr, "new session was%s cached, but we expected the opposite\n", @@ -1569,11 +1577,24 @@ static bool DoExchange(ScopedSSL_SESSION *out_session, SSL_CTX *ssl_ctx, if (!config->is_server && !config->false_start && !config->implicit_handshake && + // Session tickets are sent post-handshake in TLS 1.3. + GetProtocolVersion(ssl.get()) < TLS1_3_VERSION && GetTestState(ssl.get())->got_new_session) { fprintf(stderr, "new session was established after the handshake\n"); return false; } + if (GetProtocolVersion(ssl.get()) >= TLS1_3_VERSION && !config->is_server) { + bool expect_new_session = + !config->expect_no_session && !config->shim_shuts_down; + if (expect_new_session != GetTestState(ssl.get())->got_new_session) { + fprintf(stderr, + "new session was%s cached, but we expected the opposite\n", + GetTestState(ssl.get())->got_new_session ? "" : " not"); + return false; + } + } + if (out_session) { out_session->reset(SSL_get1_session(ssl.get())); } diff --git a/ssl/tls13_both.c b/ssl/tls13_both.c index cb3d9276..83e0c3b4 100644 --- a/ssl/tls13_both.c +++ b/ssl/tls13_both.c @@ -458,8 +458,7 @@ int tls13_post_handshake(SSL *ssl) { if (ssl->s3->tmp.message_type == SSL3_MT_NEW_SESSION_TICKET && !ssl->server) { - // TODO(svaldez): Handle NewSessionTicket. - return 1; + return tls13_process_new_session_ticket(ssl); } // TODO(svaldez): Handle post-handshake authentication. diff --git a/ssl/tls13_client.c b/ssl/tls13_client.c index 1ad17802..376e0ac3 100644 --- a/ssl/tls13_client.c +++ b/ssl/tls13_client.c @@ -566,3 +566,38 @@ enum ssl_hs_wait_t tls13_client_handshake(SSL *ssl) { return ssl_hs_ok; } + +int tls13_process_new_session_ticket(SSL *ssl) { + SSL_SESSION *session = SSL_SESSION_dup(ssl->s3->established_session, + 0 /* don't include ticket */); + if (session == NULL) { + return 0; + } + + CBS cbs, extensions, ticket; + CBS_init(&cbs, ssl->init_msg, ssl->init_num); + if (!CBS_get_u32(&cbs, &session->ticket_lifetime_hint) || + !CBS_get_u32(&cbs, &session->ticket_flags) || + !CBS_get_u32(&cbs, &session->ticket_age_add) || + !CBS_get_u16_length_prefixed(&cbs, &extensions) || + !CBS_get_u16_length_prefixed(&cbs, &ticket) || + !CBS_stow(&ticket, &session->tlsext_tick, &session->tlsext_ticklen) || + CBS_len(&cbs) != 0) { + SSL_SESSION_free(session); + ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR); + OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); + return 0; + } + + session->ticket_age_add_valid = 1; + session->not_resumable = 0; + + if (ssl->ctx->new_session_cb != NULL && + ssl->ctx->new_session_cb(ssl, session)) { + /* |new_session_cb|'s return value signals that it took ownership. */ + return 1; + } + + SSL_SESSION_free(session); + return 1; +} diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c index d8443382..b15d56db 100644 --- a/ssl/tls13_server.c +++ b/ssl/tls13_server.c @@ -43,6 +43,8 @@ enum server_hs_state_t { state_process_client_certificate, state_process_client_certificate_verify, state_process_client_finished, + state_send_new_session_ticket, + state_flush_new_session_ticket, state_done, }; @@ -506,10 +508,55 @@ static enum ssl_hs_wait_t do_process_client_finished(SSL *ssl, } ssl->method->received_flight(ssl); - hs->state = state_done; + hs->state = state_send_new_session_ticket; return ssl_hs_ok; } +static enum ssl_hs_wait_t do_send_new_session_ticket(SSL *ssl, + SSL_HANDSHAKE *hs) { + SSL_SESSION *session = ssl->s3->new_session; + session->ticket_lifetime_hint = session->timeout; + session->ticket_flags = SSL_TICKET_ALLOW_DHE_RESUMPTION; + if (!RAND_bytes((uint8_t *)&session->ticket_age_add, + sizeof(session->ticket_age_add))) { + return 0; + } + session->ticket_age_add_valid = 1; + + CBB cbb, body, ticket; + if (!ssl->method->init_message(ssl, &cbb, &body, + SSL3_MT_NEW_SESSION_TICKET) || + !CBB_add_u32(&body, session->ticket_lifetime_hint) || + !CBB_add_u32(&body, session->ticket_flags) || + !CBB_add_u32(&body, session->ticket_age_add) || + !CBB_add_u16(&body, 0 /* no ticket extensions */) || + !CBB_add_u16_length_prefixed(&body, &ticket) || + !ssl_encrypt_ticket(ssl, &ticket, session) || + !ssl->method->finish_message(ssl, &cbb)) { + CBB_cleanup(&cbb); + return ssl_hs_error; + } + + hs->session_tickets_sent++; + + hs->state = state_flush_new_session_ticket; + return ssl_hs_write_message; +} + +/* TLS 1.3 recommends single-use tickets, so issue multiple tickets in case the + * client makes several connections before getting a renewal. */ +static const int kNumTickets = 2; + +static enum ssl_hs_wait_t do_flush_new_session_ticket(SSL *ssl, + SSL_HANDSHAKE *hs) { + if (hs->session_tickets_sent >= kNumTickets) { + hs->state = state_done; + } else { + hs->state = state_send_new_session_ticket; + } + return ssl_hs_flush; +} + enum ssl_hs_wait_t tls13_server_handshake(SSL *ssl) { SSL_HANDSHAKE *hs = ssl->s3->hs; @@ -562,6 +609,12 @@ enum ssl_hs_wait_t tls13_server_handshake(SSL *ssl) { case state_process_client_finished: ret = do_process_client_finished(ssl, hs); break; + case state_send_new_session_ticket: + ret = do_send_new_session_ticket(ssl, hs); + break; + case state_flush_new_session_ticket: + ret = do_flush_new_session_ticket(ssl, hs); + break; case state_done: ret = ssl_hs_ok; break;