This hangs a few bits off SSL_SESSION and populates them on receipt of a NewSessionTicket. Since the current session may already be in the cache, and as such should not be changed, we dup the session using a new ssl_session_dup() and modify that one. I added an include_ticket parameter which we could use if we should need the SSL_SESSION_dup() API.
The actual parse function is directly modeled after RFC 8446, section 4.6.1, with the EVP_Digest() for the session_id matching what's done in ssl3_get_new_session_ticket(). As always with PHH messages, there is a bit of a DOS question. For now the general rate limiting of 100 PHH messages per hour is all we have. Index: ssl_locl.h =================================================================== RCS file: /cvs/src/lib/libssl/ssl_locl.h,v retrieving revision 1.427 diff -u -p -r1.427 ssl_locl.h --- ssl_locl.h 2 Oct 2022 16:36:41 -0000 1.427 +++ ssl_locl.h 13 Oct 2022 13:21:17 -0000 @@ -507,6 +507,10 @@ struct ssl_session_st { size_t tlsext_ticklen; /* Session ticket length */ uint32_t tlsext_tick_lifetime_hint; /* Session lifetime hint in seconds */ + /* RFC 8446 info */ + uint32_t tlsext_tick_age_add; + struct tls13_secret resumption_master_secret; + CRYPTO_EX_DATA ex_data; /* application specific data */ /* These are used to make removal of session-ids more @@ -1311,6 +1315,7 @@ int ssl_security_cert_chain(const SSL *s int ssl_security_shared_group(const SSL *ssl, uint16_t group_id); int ssl_security_supported_group(const SSL *ssl, uint16_t group_id); +SSL_SESSION *ssl_session_dup(SSL_SESSION *src, int include_ticket); int ssl_get_new_session(SSL *s, int session); int ssl_get_prev_session(SSL *s, CBS *session_id, CBS *ext_block, int *alert); Index: ssl_sess.c =================================================================== RCS file: /cvs/src/lib/libssl/ssl_sess.c,v retrieving revision 1.118 diff -u -p -r1.118 ssl_sess.c --- ssl_sess.c 2 Oct 2022 16:36:41 -0000 1.118 +++ ssl_sess.c 13 Oct 2022 13:21:17 -0000 @@ -222,6 +222,12 @@ SSL_SESSION_new(void) return (NULL); } + if (!tls13_secret_init(&ss->resumption_master_secret, 256)) { + SSLerrorx(ERR_R_MALLOC_FAILURE); + free(ss); + return (NULL); + } + ss->verify_result = 1; /* avoid 0 (= X509_V_OK) just in case */ ss->references = 1; ss->timeout = 60 * 5 + 4; /* 5 minutes 4 seconds timeout by default */ @@ -242,6 +248,108 @@ SSL_SESSION_new(void) return (ss); } +SSL_SESSION * +ssl_session_dup(SSL_SESSION *src, int include_ticket) +{ + SSL_SESSION *dest; + CBS cbs; + + if ((dest = calloc(1, sizeof(*dest))) == NULL) { + SSLerrorx(ERR_R_MALLOC_FAILURE); + goto err; + } + + if (!tls13_secret_init(&dest->resumption_master_secret, 256)) { + SSLerrorx(ERR_R_MALLOC_FAILURE); + goto err; + } + + CBS_init(&cbs, src->master_key, src->master_key_length); + if (!CBS_write_bytes(&cbs, dest->master_key, sizeof(dest->master_key), + &dest->master_key_length)) + goto err; + + CBS_init(&cbs, src->session_id, src->session_id_length); + if (!CBS_write_bytes(&cbs, dest->session_id, sizeof(dest->session_id), + &dest->session_id_length)) + goto err; + + CBS_init(&cbs, src->sid_ctx, src->sid_ctx_length); + if (!CBS_write_bytes(&cbs, dest->sid_ctx, sizeof(dest->sid_ctx), + &dest->sid_ctx_length)) + goto err; + + if (src->peer_cert != NULL) { + if (!X509_up_ref(src->peer_cert)) + goto err; + dest->peer_cert = src->peer_cert; + } + dest->peer_cert_type = src->peer_cert_type; + + dest->verify_result = src->verify_result; + + dest->timeout = src->timeout; + dest->time = src->time; + dest->references = 1; + + dest->cipher = src->cipher; + dest->cipher_id = src->cipher_id; + + if (src->ciphers != NULL) { + if ((dest->ciphers = sk_SSL_CIPHER_dup(src->ciphers)) == NULL) + goto err; + } + + if (include_ticket) { + if (src->tlsext_tick != NULL) { + CBS_init(&cbs, src->tlsext_tick, src->tlsext_ticklen); + if (!CBS_stow(&cbs, &dest->tlsext_tick, + &dest->tlsext_ticklen)) + goto err; + dest->tlsext_tick_lifetime_hint = + src->tlsext_tick_lifetime_hint; + } + + /* XXX - include TLSv1.3 NST here? */ + } + + if (!CRYPTO_new_ex_data(CRYPTO_EX_INDEX_SSL_SESSION, dest, + &dest->ex_data)) + goto err; + + if (!CRYPTO_dup_ex_data(CRYPTO_EX_INDEX_SSL_SESSION, &dest->ex_data, + &src->ex_data)) + goto err; + + /* Omit prev/next: the new session gets its own slot in the cache. */ + + dest->not_resumable = src->not_resumable; + + if (src->tlsext_ecpointformatlist != NULL) { + CBS_init(&cbs, src->tlsext_ecpointformatlist, + src->tlsext_ecpointformatlist_length); + if (!CBS_stow(&cbs, &dest->tlsext_ecpointformatlist, + &dest->tlsext_ecpointformatlist_length)) + goto err; + } + + if (src->tlsext_supportedgroups != NULL) { + if ((dest->tlsext_supportedgroups = calloc(sizeof(uint16_t), + src->tlsext_supportedgroups_length)) == NULL) + goto err; + memcpy(dest->tlsext_supportedgroups, src->tlsext_supportedgroups, + sizeof(uint16_t) * src->tlsext_supportedgroups_length); + dest->tlsext_supportedgroups_length = + src->tlsext_supportedgroups_length; + } + + return dest; + + err: + SSL_SESSION_free(dest); + return NULL; +} + const unsigned char * SSL_SESSION_get_id(const SSL_SESSION *ss, unsigned int *len) { @@ -771,6 +879,8 @@ SSL_SESSION_free(SSL_SESSION *ss) free(ss->tlsext_tick); free(ss->tlsext_ecpointformatlist); free(ss->tlsext_supportedgroups); + + tls13_secret_cleanup(&ss->resumption_master_secret); freezero(ss, sizeof(*ss)); } Index: tls13_lib.c =================================================================== RCS file: /cvs/src/lib/libssl/tls13_lib.c,v retrieving revision 1.72 diff -u -p -r1.72 tls13_lib.c --- tls13_lib.c 2 Oct 2022 16:36:42 -0000 1.72 +++ tls13_lib.c 13 Oct 2022 13:21:17 -0000 @@ -25,6 +25,13 @@ #include "tls13_internal.h" /* + * RFC 8446, section 4.6.1. Servers must not indicate a lifetime longer than + * 7 days and clients must not cache tickets for longer than 7 days. + */ + +#define TLS13_MAX_TICKET_LIFETIME (7 * 24 * 3600) + +/* * Downgrade sentinels - RFC 8446 section 4.1.3, magic values which must be set * by the server in server random if it is willing to downgrade but supports * TLSv1.3 @@ -328,6 +335,98 @@ tls13_key_update_recv(struct tls13_ctx * return tls13_send_alert(ctx->rl, alert); } +/* RFC 8446 section 4.6.1 */ +static ssize_t +tls13_new_session_ticket_recv(struct tls13_ctx *ctx, CBS *cbs) +{ + int alert = TLS13_ALERT_DECODE_ERROR; + uint32_t ticket_lifetime, ticket_age_add; + CBS ticket_nonce, ticket; + SSL_SESSION *sess; + int session_id_length; + struct tls13_secrets *secrets = ctx->hs->tls13.secrets; + struct tls13_secret nonce; + + memset(&nonce, 0, sizeof(nonce)); + + if (ctx->mode != TLS13_HS_CLIENT) { + alert = TLS13_ALERT_UNEXPECTED_MESSAGE; + goto err; + } + + if (!CBS_get_u32(cbs, &ticket_lifetime)) + goto err; + if (!CBS_get_u32(cbs, &ticket_age_add)) + goto err; + if (!CBS_get_u8_length_prefixed(cbs, &ticket_nonce)) + goto err; + if (!CBS_get_u16_length_prefixed(cbs, &ticket)) + goto err; + /* Extensions only contain early_data, which we ignore. */ + if (!tlsext_client_parse(ctx->ssl, SSL_TLSEXT_MSG_NST, cbs, &alert)) + goto err; + + if (CBS_len(cbs) != 0) + goto err; + + /* Servers MUST NOT use any value larger than 7 days. */ + if (ticket_lifetime > TLS13_MAX_TICKET_LIFETIME) { + alert = TLS13_ALERT_ILLEGAL_PARAMETER; + goto err; + } + /* Zero indicates that the ticket should be discarded immediately. */ + if (ticket_lifetime == 0) { + alert = 0; + goto err; + } + + alert = TLS13_ALERT_INTERNAL_ERROR; + + /* + * Create new session instead of modifying the current session. + * The current session could already be in the session cache. + */ + if ((sess = ssl_session_dup(ctx->ssl->session, 0)) == NULL) + goto err; + SSL_SESSION_free(ctx->ssl->session); + ctx->ssl->session = sess; + + sess->time = time(NULL); + + sess->tlsext_tick_lifetime_hint = ticket_lifetime; + sess->tlsext_tick_age_add = ticket_age_add; + + if (!CBS_stow(&ticket, &sess->tlsext_tick, &sess->tlsext_ticklen)) + goto err; + + if (!EVP_Digest(CBS_data(&ticket), CBS_len(&ticket), + sess->session_id, &session_id_length, EVP_sha256(), NULL)) + goto err; + sess->session_id_length = session_id_length; + + if (!CBS_stow(&ticket_nonce, &nonce.data, &nonce.len)) + goto err; + + if (!tls13_derive_secret(&sess->resumption_master_secret, + secrets->digest, &secrets->resumption_master, "resumption", + &nonce)) + goto err; + + tls13_secret_cleanup(&nonce); + + ssl_update_cache(ctx->ssl, SSL_SESS_CACHE_CLIENT); + + return TLS13_IO_SUCCESS; + + err: + tls13_secret_cleanup(&nonce); + + if (alert == 0) + return TLS13_IO_SUCCESS; + + return tls13_send_alert(ctx->rl, alert); +} + ssize_t tls13_phh_received_cb(void *cb_arg) { @@ -354,7 +453,7 @@ tls13_phh_received_cb(void *cb_arg) ret = tls13_key_update_recv(ctx, &cbs); break; case TLS13_MT_NEW_SESSION_TICKET: - /* XXX do nothing for now and ignore this */ + ret = tls13_new_session_ticket_recv(ctx, &cbs); break; case TLS13_MT_CERTIFICATE_REQUEST: /* XXX add support if we choose to advertise this */