From aab57fbb5e3cffa659c05d301db7d2d588c59d0f Mon Sep 17 00:00:00 2001 From: kaduk Date: Fri, 9 Apr 2021 14:16:29 -0700 Subject: [PATCH] QUIC: SSL_new_session_ticket() support (#26) * Let SSL_new_session_ticket() work immediately The initial implementation always deferred the generation of the requested ticket(s) until the next application write, but this means that the ticket cannot be written at all until there is application data ready to write. In some scenarios this application data may never arrive or may take a long time to arrive, so (when already at a record boundary) allow the application to explicitly call SSL_do_handshake() after SSL_new_session_ticket() to force an immediate write, even when there is no application data available. The default behavior remains to defer the generation of the ticket and coalesce the network traffic for the ticket and application data. * Test new SSL_new_session_ticket() functionality Now that we can become "in init" directly after the call, test the various scenarios where explicit SSL_do_handshake() calls can come into play. * Update SSL_new_session_ticket() manual for triggered send Document the recently added functionality. (cherry picked from commit 4fb1ff7ee88caed42acdd944347a4ff272b351a3) --- doc/man3/SSL_CTX_set_num_tickets.pod | 27 ++++++++++++++++----------- ssl/ssl_lib.c | 6 +++++- test/sslapitest.c | 26 +++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/doc/man3/SSL_CTX_set_num_tickets.pod b/doc/man3/SSL_CTX_set_num_tickets.pod index 8c67b839895ad..8fafc777eac57 100644 --- a/doc/man3/SSL_CTX_set_num_tickets.pod +++ b/doc/man3/SSL_CTX_set_num_tickets.pod @@ -45,17 +45,22 @@ 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. +sent in this manner after the initial handshake has completed, and only for +TLS 1.3 connections. By default, 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. If the connection was at a record boundary when +SSL_new_session_ticket() was called, the ticket can be sent immediately +(without waiting for the next application write) by calling +SSL_do_handshake(). 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 and triggered by SSL_write() or +SSL_do_handshake(). 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 diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 4ae2e7ecc0b22..f816e44551034 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -2234,10 +2234,14 @@ int SSL_renegotiate_pending(const SSL *s) int SSL_new_session_ticket(SSL *s) { - if (SSL_in_init(s) || SSL_IS_FIRST_HANDSHAKE(s) || !s->server + /* If we are in init because we're sending tickets, okay to send more. */ + if ((SSL_in_init(s) && s->ext.extra_tickets_expected == 0) + || SSL_IS_FIRST_HANDSHAKE(s) || !s->server || !SSL_IS_TLS13(s)) return 0; s->ext.extra_tickets_expected++; + if (s->rlayer.wbuf[0].left == 0 && !SSL_in_init(s)) + ossl_statem_set_in_init(s, 1); return 1; } diff --git a/test/sslapitest.c b/test/sslapitest.c index aca627c5b683e..b8fc35c146a59 100644 --- a/test/sslapitest.c +++ b/test/sslapitest.c @@ -1693,11 +1693,22 @@ static int test_extra_tickets(int idx) || !TEST_int_eq(4, new_called)) goto end; + /* Once more, but with SSL_do_handshake() to drive the ticket generation */ + c = '4'; + new_called = 0; + if (!TEST_true(SSL_new_session_ticket(serverssl)) + || !TEST_true(SSL_new_session_ticket(serverssl)) + || !TEST_true(SSL_do_handshake(serverssl)) + || !TEST_int_eq(2, new_called) + || !TEST_false(SSL_read_ex(clientssl, buf, sizeof(buf), &nbytes)) + || !TEST_int_eq(4, new_called)) + goto end; + /* * Use the always-retry BIO to exercise the logic that forces ticket * generation to wait until a record boundary. */ - c = '4'; + c = '5'; new_called = 0; tmp = SSL_get_wbio(serverssl); if (!TEST_ptr(tmp) || !TEST_true(BIO_up_ref(tmp))) { @@ -1713,9 +1724,14 @@ static int test_extra_tickets(int idx) /* Restore a BIO that will let the write succeed */ SSL_set0_wbio(serverssl, tmp); tmp = NULL; - /* These calls should just queue the request and not send anything. */ + /* + * These calls should just queue the request and not send anything + * even if we explicitly try to hit the state machine. + */ if (!TEST_true(SSL_new_session_ticket(serverssl)) || !TEST_true(SSL_new_session_ticket(serverssl)) + || !TEST_int_eq(0, new_called) + || !TEST_true(SSL_do_handshake(serverssl)) || !TEST_int_eq(0, new_called)) goto end; /* Re-do the write; still no tickets sent */ @@ -1728,8 +1744,12 @@ static int test_extra_tickets(int idx) || !TEST_int_eq(c, buf[0]) || !TEST_false(SSL_read_ex(clientssl, buf, sizeof(buf), &nbytes))) goto end; + /* Even trying to hit the state machine now will still not send tickets */ + if (!TEST_true(SSL_do_handshake(serverssl)) + || !TEST_int_eq(0, new_called)) + goto end; /* Now the *next* write should send the tickets */ - c = '5'; + c = '6'; if (!TEST_true(SSL_write_ex(serverssl, &c, 1, &nbytes)) || !TEST_size_t_eq(1, nbytes) || !TEST_int_eq(2, new_called)