Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSL_new_session_ticket() support (1.1.1) #28

Merged
merged 5 commits into from
Apr 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions doc/man3/SSL_CTX_set_num_tickets.pod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -40,21 +42,43 @@ 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. 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<SSL_CTX_sess_set_new_cb(3)> 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

Expand Down
1 change: 1 addition & 0 deletions include/openssl/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1921,6 +1921,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);
Expand Down
8 changes: 5 additions & 3 deletions ssl/record/rec_layer_s3.c
Original file line number Diff line number Diff line change
Expand Up @@ -374,10 +374,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);

/*
Expand Down
13 changes: 13 additions & 0 deletions ssl/ssl_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -2210,6 +2210,19 @@ int SSL_renegotiate_pending(const SSL *s)
return (s->renegotiate != 0);
}

int SSL_new_session_ticket(SSL *s)
{
/* 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;
}

long SSL_ctrl(SSL *s, int cmd, long larg, void *parg)
{
long l;
Expand Down
2 changes: 2 additions & 0 deletions ssl/ssl_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
16 changes: 13 additions & 3 deletions ssl/statem/statem_srvr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -4182,10 +4189,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);
}

Expand Down
163 changes: 163 additions & 0 deletions test/sslapitest.c
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,168 @@ static int test_psk_tickets(void)

return testresult;
}

static int test_extra_tickets(int idx)
{
SSL_CTX *sctx = NULL, *cctx = NULL;
SSL *serverssl = NULL, *clientssl = NULL;
BIO *bretry = BIO_new(bio_s_always_retry());
BIO *tmp = NULL;
int testresult = 0;
int stateful = 0;
size_t nbytes;
unsigned char c, buf[1];

new_called = 0;
do_cache = 1;

if (idx >= 3) {
idx -= 3;
stateful = 1;
}

if (!TEST_ptr(bretry) || !setup_ticket_test(stateful, idx, &sctx, &cctx))
goto end;
SSL_CTX_sess_set_new_cb(sctx, new_session_cb);
/* setup_ticket_test() uses new_cachesession_cb which we don't need. */
SSL_CTX_sess_set_new_cb(cctx, new_session_cb);

if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
&clientssl, NULL, NULL)))
goto end;

/*
* Note that we have new_session_cb on both sctx and cctx, so new_called is
* incremented by both client and server.
*/
if (!TEST_true(create_ssl_connection(serverssl, clientssl,
SSL_ERROR_NONE))
/* Check we got the number of tickets we were expecting */
|| !TEST_int_eq(idx * 2, new_called)
|| !TEST_true(SSL_new_session_ticket(serverssl))
|| !TEST_true(SSL_new_session_ticket(serverssl))
|| !TEST_int_eq(idx * 2, new_called))
goto end;

/* Now try a (real) write to actually send the tickets */
c = '1';
if (!TEST_true(SSL_write_ex(serverssl, &c, 1, &nbytes))
|| !TEST_size_t_eq(1, nbytes)
|| !TEST_int_eq(idx * 2 + 2, new_called)
|| !TEST_true(SSL_read_ex(clientssl, buf, sizeof(buf), &nbytes))
|| !TEST_int_eq(idx * 2 + 4, new_called)
|| !TEST_int_eq(sizeof(buf), nbytes)
|| !TEST_int_eq(c, buf[0])
|| !TEST_false(SSL_read_ex(clientssl, buf, sizeof(buf), &nbytes)))
goto end;

/* Try with only requesting one new ticket, too */
c = '2';
new_called = 0;
if (!TEST_true(SSL_new_session_ticket(serverssl))
|| !TEST_true(SSL_write_ex(serverssl, &c, sizeof(c), &nbytes))
|| !TEST_size_t_eq(sizeof(c), nbytes)
|| !TEST_int_eq(1, new_called)
|| !TEST_true(SSL_read_ex(clientssl, buf, sizeof(buf), &nbytes))
|| !TEST_int_eq(2, new_called)
|| !TEST_size_t_eq(sizeof(buf), nbytes)
|| !TEST_int_eq(c, buf[0]))
goto end;

/* Do it again but use dummy writes to drive the ticket generation */
c = '3';
new_called = 0;
if (!TEST_true(SSL_new_session_ticket(serverssl))
|| !TEST_true(SSL_new_session_ticket(serverssl))
|| !TEST_true(SSL_write_ex(serverssl, &c, 0, &nbytes))
|| !TEST_size_t_eq(0, nbytes)
|| !TEST_int_eq(2, new_called)
|| !TEST_false(SSL_read_ex(clientssl, buf, sizeof(buf), &nbytes))
|| !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 = '5';
new_called = 0;
tmp = SSL_get_wbio(serverssl);
if (!TEST_ptr(tmp) || !TEST_true(BIO_up_ref(tmp))) {
tmp = NULL;
goto end;
}
SSL_set0_wbio(serverssl, bretry);
bretry = NULL;
if (!TEST_false(SSL_write_ex(serverssl, &c, 1, &nbytes))
|| !TEST_int_eq(SSL_get_error(serverssl, 0), SSL_ERROR_WANT_WRITE)
|| !TEST_size_t_eq(nbytes, 0))
goto end;
/* 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
* 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 */
if (!TEST_true(SSL_write_ex(serverssl, &c, 1, &nbytes))
|| !TEST_size_t_eq(1, nbytes)
|| !TEST_int_eq(0, new_called)
|| !TEST_true(SSL_read_ex(clientssl, buf, sizeof(buf), &nbytes))
|| !TEST_int_eq(0, new_called)
|| !TEST_int_eq(sizeof(buf), nbytes)
|| !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 = '6';
if (!TEST_true(SSL_write_ex(serverssl, &c, 1, &nbytes))
|| !TEST_size_t_eq(1, nbytes)
|| !TEST_int_eq(2, new_called)
|| !TEST_true(SSL_read_ex(clientssl, buf, sizeof(buf), &nbytes))
|| !TEST_int_eq(4, new_called)
|| !TEST_int_eq(sizeof(buf), nbytes)
|| !TEST_int_eq(c, buf[0])
|| !TEST_false(SSL_read_ex(clientssl, buf, sizeof(buf), &nbytes)))
goto end;

SSL_shutdown(clientssl);
SSL_shutdown(serverssl);
testresult = 1;

end:
BIO_free(bretry);
BIO_free(tmp);
SSL_free(serverssl);
SSL_free(clientssl);
SSL_CTX_free(sctx);
SSL_CTX_free(cctx);
clientssl = serverssl = NULL;
sctx = cctx = NULL;
return testresult;
}
#endif

#define USE_NULL 0
Expand Down Expand Up @@ -7115,6 +7277,7 @@ int setup_tests(void)
ADD_ALL_TESTS(test_stateful_tickets, 3);
ADD_ALL_TESTS(test_stateless_tickets, 3);
ADD_TEST(test_psk_tickets);
ADD_ALL_TESTS(test_extra_tickets, 6);
#endif
ADD_ALL_TESTS(test_ssl_set_bio, TOTAL_SSL_SET_BIO_TESTS);
ADD_TEST(test_ssl_bio_pop_next_bio);
Expand Down
1 change: 1 addition & 0 deletions util/libssl.num
Original file line number Diff line number Diff line change
Expand Up @@ -514,3 +514,4 @@ SSL_set_quic_transport_version 20012 1_1_1i EXIST::FUNCTION:QUIC
SSL_get_peer_quic_transport_version 20013 1_1_1i EXIST::FUNCTION:QUIC
SSL_get_quic_transport_version 20014 1_1_1i EXIST::FUNCTION:QUIC
SSL_set_quic_early_data_enabled 20015 1_1_1j EXIST::FUNCTION:QUIC
SSL_new_session_ticket 20016 1_1_1k EXIST::FUNCTION:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ok if we assume that this function won't eventually be added to 1.1.1. Do we think it will be?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new API implements a new feature, so it would take an OTC vote to approve an exception policy in order for it to make it to 1.1.1. I don't expect that to happen (though I can't definitively rule it out).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair.