From 7a7ca59aae664d7c876f409cfee2179c6d5c7f13 Mon Sep 17 00:00:00 2001 From: Benjamin Kaduk Date: Mon, 16 Mar 2020 11:25:58 -0700 Subject: QUIC: Add SSL_new_session_ticket() API This API requests that the TLS stack generate a (TLS 1.3) NewSessionTicket message the next time it is safe to do so (i.e., we do not have other data pending write, which could be mid-record). For efficiency, defer actually generating/writing the ticket until there is other data to write, to avoid producing server-to-client traffic when not needed. Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/11416) (cherry picked from commit 3bfacb5fd4679812a7b9ec61d296b1add64669c0) --- doc/man3/SSL_CTX_set_num_tickets.pod | 27 +++++++++++++++++++++++---- include/openssl/ssl.h | 1 + ssl/record/rec_layer_s3.c | 8 +++++--- ssl/ssl_lib.c | 9 +++++++++ ssl/ssl_local.h | 2 ++ ssl/statem/statem_srvr.c | 16 +++++++++++++--- 6 files changed, 53 insertions(+), 10 deletions(-) diff --git a/doc/man3/SSL_CTX_set_num_tickets.pod b/doc/man3/SSL_CTX_set_num_tickets.pod index bc031008f2..8c67b83989 100644 --- a/doc/man3/SSL_CTX_set_num_tickets.pod +++ b/doc/man3/SSL_CTX_set_num_tickets.pod @@ -5,7 +5,8 @@ SSL_set_num_tickets, SSL_get_num_tickets, SSL_CTX_set_num_tickets, -SSL_CTX_get_num_tickets +SSL_CTX_get_num_tickets, +SSL_new_session_ticket - control the number of TLSv1.3 session tickets that are issued =head1 SYNOPSIS @@ -16,6 +17,7 @@ SSL_CTX_get_num_tickets size_t SSL_get_num_tickets(SSL *s); int SSL_CTX_set_num_tickets(SSL_CTX *ctx, size_t num_tickets); size_t SSL_CTX_get_num_tickets(SSL_CTX *ctx); + int SSL_new_session_ticket(SSL *s); =head1 DESCRIPTION @@ -40,21 +42,38 @@ handshake then SSL_set_num_tickets() can be called again prior to calling SSL_verify_client_post_handshake() to update the number of tickets that will be sent. +To issue tickets after other events (such as application-layer changes), +SSL_new_session_ticket() is used by a server application to request that a new +ticket be sent when it is safe to do so. New tickets are only allowed to be +sent in this manner after the initial handshake has completed, and only for TLS +1.3 connections. The ticket generation and transmission are delayed until the +server is starting a new write operation, so that it is bundled with other +application data being written and properly aligned to a record boundary. +SSL_new_session_ticket() can be called more than once to request additional +tickets be sent; all such requests are queued and written together when it is +safe to do so. Note that a successful return from SSL_new_session_ticket() +indicates only that the request to send a ticket was processed, not that the +ticket itself was sent. To be notified when the ticket itself is sent, a +new-session callback can be registered with L that +will be invoked as the ticket or tickets are generated. + SSL_CTX_get_num_tickets() and SSL_get_num_tickets() return the number of tickets set by a previous call to SSL_CTX_set_num_tickets() or SSL_set_num_tickets(), or 2 if no such call has been made. =head1 RETURN VALUES -SSL_CTX_set_num_tickets() and SSL_set_num_tickets() return 1 on success or 0 on -failure. +SSL_CTX_set_num_tickets(), SSL_set_num_tickets(), and +SSL_new_session_ticket() return 1 on success or 0 on failure. SSL_CTX_get_num_tickets() and SSL_get_num_tickets() return the number of tickets that have been previously set. =head1 HISTORY -These functions were added in OpenSSL 1.1.1. +SSL_new_session_ticket() was added in OpenSSL 3.0.0. +SSL_set_num_tickets(), SSL_get_num_tickets(), SSL_CTX_set_num_tickets(), and +SSL_CTX_get_num_tickets() were added in OpenSSL 1.1.1. =head1 COPYRIGHT diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index c1fe0c24bc..63ee3baae7 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -1931,6 +1931,7 @@ int SSL_get_key_update_type(const SSL *s); int SSL_renegotiate(SSL *s); int SSL_renegotiate_abbreviated(SSL *s); __owur int SSL_renegotiate_pending(const SSL *s); +int SSL_new_session_ticket(SSL *s); int SSL_shutdown(SSL *s); __owur int SSL_verify_client_post_handshake(SSL *s); void SSL_CTX_set_post_handshake_auth(SSL_CTX *ctx, int val); diff --git a/ssl/record/rec_layer_s3.c b/ssl/record/rec_layer_s3.c index 1db1712a09..fe746249a7 100644 --- a/ssl/record/rec_layer_s3.c +++ b/ssl/record/rec_layer_s3.c @@ -387,10 +387,12 @@ int ssl3_write_bytes(SSL *s, int type, const void *buf_, size_t len, s->rlayer.wnum = 0; /* - * If we are supposed to be sending a KeyUpdate then go into init unless we - * have writes pending - in which case we should finish doing that first. + * If we are supposed to be sending a KeyUpdate or NewSessionTicket then go + * into init unless we have writes pending - in which case we should finish + * doing that first. */ - if (wb->left == 0 && s->key_update != SSL_KEY_UPDATE_NONE) + if (wb->left == 0 && (s->key_update != SSL_KEY_UPDATE_NONE + || s->ext.extra_tickets_expected > 0)) ossl_statem_set_in_init(s, 1); /* diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index c268b769f0..ec35f1f22f 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -2232,6 +2232,15 @@ int SSL_renegotiate_pending(const SSL *s) return (s->renegotiate != 0); } +int SSL_new_session_ticket(SSL *s) +{ + if (SSL_in_init(s) || SSL_IS_FIRST_HANDSHAKE(s) || !s->server + || !SSL_IS_TLS13(s)) + return 0; + s->ext.extra_tickets_expected++; + return 1; +} + long SSL_ctrl(SSL *s, int cmd, long larg, void *parg) { long l; diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h index f9b6a5cc9d..2ac1fa0059 100644 --- a/ssl/ssl_local.h +++ b/ssl/ssl_local.h @@ -1320,6 +1320,8 @@ struct ssl_st { /* RFC4507 session ticket expected to be received or sent */ int ticket_expected; + /* TLS 1.3 tickets requested by the application. */ + int extra_tickets_expected; # ifndef OPENSSL_NO_EC size_t ecpointformats_len; /* our list */ diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c index 83a4663620..b21e519041 100644 --- a/ssl/statem/statem_srvr.c +++ b/ssl/statem/statem_srvr.c @@ -437,6 +437,10 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL *s) st->hand_state = TLS_ST_SW_CERT_REQ; return WRITE_TRAN_CONTINUE; } + if (s->ext.extra_tickets_expected > 0) { + st->hand_state = TLS_ST_SW_SESSION_TICKET; + return WRITE_TRAN_CONTINUE; + } /* Try to read from the client instead */ return WRITE_TRAN_FINISHED; @@ -527,7 +531,9 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL *s) * Following an initial handshake we send the number of tickets we have * been configured for. */ - if (s->hit || s->num_tickets <= s->sent_tickets) { + if (!SSL_IS_FIRST_HANDSHAKE(s) && s->ext.extra_tickets_expected > 0) { + return WRITE_TRAN_CONTINUE; + } else if (s->hit || s->num_tickets <= s->sent_tickets) { /* We've written enough tickets out. */ st->hand_state = TLS_ST_OK; } @@ -723,7 +729,8 @@ WORK_STATE ossl_statem_server_pre_work(SSL *s, WORK_STATE wst) return WORK_FINISHED_CONTINUE; case TLS_ST_SW_SESSION_TICKET: - if (SSL_IS_TLS13(s) && s->sent_tickets == 0) { + if (SSL_IS_TLS13(s) && s->sent_tickets == 0 + && s->ext.extra_tickets_expected == 0) { /* * Actually this is the end of the handshake, but we're going * straight into writing the session ticket out. So we finish off @@ -4194,10 +4201,13 @@ int tls_construct_new_session_ticket(SSL *s, WPACKET *pkt) /* * Increment both |sent_tickets| and |next_ticket_nonce|. |sent_tickets| * gets reset to 0 if we send more tickets following a post-handshake - * auth, but |next_ticket_nonce| does not. + * auth, but |next_ticket_nonce| does not. If we're sending extra + * tickets, decrement the count of pending extra tickets. */ s->sent_tickets++; s->next_ticket_nonce++; + if (s->ext.extra_tickets_expected > 0) + s->ext.extra_tickets_expected--; ssl_update_cache(s, SSL_SESS_CACHE_SERVER); } -- 2.35.3