Add SSL_new_session_ticket() API
authorBenjamin Kaduk <bkaduk@akamai.com>
Mon, 16 Mar 2020 18:25:58 +0000 (11:25 -0700)
committerBen Kaduk <kaduk@mit.edu>
Fri, 1 May 2020 22:10:11 +0000 (15:10 -0700)
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 <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/11416)

doc/man3/SSL_CTX_set_num_tickets.pod
include/openssl/ssl.h
ssl/record/rec_layer_s3.c
ssl/ssl_lib.c
ssl/ssl_local.h
ssl/statem/statem_srvr.c

index 84afe4a..5d4bd6f 100644 (file)
@@ -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,14 +42,29 @@ 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<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.
@@ -58,7 +75,9 @@ L<ssl(7)>
 
 =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
 
index 5c2affb..f293b03 100644 (file)
@@ -1959,6 +1959,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);
index d419891..bceac72 100644 (file)
@@ -384,10 +384,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);
 
     /*
index 63cbb3d..fde726e 100644 (file)
@@ -2303,6 +2303,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;
index 21bf482..de7e9fd 100644 (file)
@@ -1539,6 +1539,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 */
index b11363d..d1d86ea 100644 (file)
@@ -441,6 +441,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;
 
@@ -531,7 +535,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;
         }
@@ -727,7 +733,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
@@ -4161,10 +4168,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);
     }