Session Ticket app data

Adds application data into the encrypted session ticket

Reviewed-by: Paul Dale <paul.dale@oracle.com>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/3802)
diff --git a/ssl/ssl_asn1.c b/ssl/ssl_asn1.c
index 9327b33..7d39ba1 100644
--- a/ssl/ssl_asn1.c
+++ b/ssl/ssl_asn1.c
@@ -43,6 +43,7 @@
     ASN1_OCTET_STRING *alpn_selected;
     ASN1_OCTET_STRING *tick_nonce;
     uint32_t tlsext_max_fragment_len_mode;
+    ASN1_OCTET_STRING *ticket_appdata;
 } SSL_SESSION_ASN1;
 
 ASN1_SEQUENCE(SSL_SESSION_ASN1) = {
@@ -73,7 +74,8 @@
     ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, max_early_data, ZUINT32, 15),
     ASN1_EXP_OPT(SSL_SESSION_ASN1, alpn_selected, ASN1_OCTET_STRING, 16),
     ASN1_EXP_OPT(SSL_SESSION_ASN1, tick_nonce, ASN1_OCTET_STRING, 17),
-    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, tlsext_max_fragment_len_mode, ZUINT32, 18)
+    ASN1_EXP_OPT_EMBED(SSL_SESSION_ASN1, tlsext_max_fragment_len_mode, ZUINT32, 18),
+    ASN1_EXP_OPT(SSL_SESSION_ASN1, ticket_appdata, ASN1_OCTET_STRING, 19)
 } static_ASN1_SEQUENCE_END(SSL_SESSION_ASN1)
 
 IMPLEMENT_STATIC_ASN1_ENCODE_FUNCTIONS(SSL_SESSION_ASN1)
@@ -123,6 +125,7 @@
 #endif
     ASN1_OCTET_STRING alpn_selected;
     ASN1_OCTET_STRING tick_nonce;
+    ASN1_OCTET_STRING ticket_appdata;
 
     long l;
 
@@ -200,6 +203,12 @@
 
     as.tlsext_max_fragment_len_mode = in->ext.max_fragment_len_mode;
 
+    if (in->ticket_appdata == NULL)
+        as.ticket_appdata = NULL;
+    else
+        ssl_session_oinit(&as.ticket_appdata, &ticket_appdata,
+                          in->ticket_appdata, in->ticket_appdata_len);
+
     return i2d_SSL_SESSION_ASN1(&as, pp);
 
 }
@@ -376,6 +385,15 @@
 
     ret->ext.max_fragment_len_mode = as->tlsext_max_fragment_len_mode;
 
+    if (as->ticket_appdata != NULL) {
+        ret->ticket_appdata = as->ticket_appdata->data;
+        ret->ticket_appdata_len = as->ticket_appdata->length;
+        as->ticket_appdata->data = NULL;
+    } else {
+        ret->ticket_appdata = NULL;
+        ret->ticket_appdata_len = 0;
+    }
+
     M_ASN1_free_of(as, SSL_SESSION_ASN1);
 
     if ((a != NULL) && (*a == NULL))
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index f5219c2..0814fb3 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -5409,3 +5409,14 @@
     ossl_statem_set_in_init(ssl, 1);
     return 1;
 }
+
+int SSL_CTX_set_session_ticket_cb(SSL_CTX *ctx,
+                                  SSL_CTX_generate_session_ticket_fn gen_cb,
+                                  SSL_CTX_decrypt_session_ticket_fn dec_cb,
+                                  void *arg)
+{
+    ctx->generate_ticket_cb = gen_cb;
+    ctx->decrypt_ticket_cb = dec_cb;
+    ctx->ticket_cb_data = arg;
+    return 1;
+}
diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h
index f179efa..9eb5834 100644
--- a/ssl/ssl_locl.h
+++ b/ssl/ssl_locl.h
@@ -591,6 +591,8 @@
 # ifndef OPENSSL_NO_SRP
     char *srp_username;
 # endif
+    unsigned char *ticket_appdata;
+    size_t ticket_appdata_len;
     uint32_t flags;
     CRYPTO_RWLOCK *lock;
 };
@@ -1025,6 +1027,11 @@
     size_t (*record_padding_cb)(SSL *s, int type, size_t len, void *arg);
     void *record_padding_arg;
     size_t block_padding;
+
+    /* Session ticket appdata */
+    SSL_CTX_generate_session_ticket_fn generate_ticket_cb;
+    SSL_CTX_decrypt_session_ticket_fn decrypt_ticket_cb;
+    void *ticket_cb_data;
 };
 
 struct ssl_st {
@@ -2446,30 +2453,12 @@
 
 __owur int tls1_set_server_sigalgs(SSL *s);
 
-/* Return codes for tls_get_ticket_from_client() and tls_decrypt_ticket() */
-typedef enum ticket_en {
-    /* fatal error, malloc failure */
-    TICKET_FATAL_ERR_MALLOC,
-    /* fatal error, either from parsing or decrypting the ticket */
-    TICKET_FATAL_ERR_OTHER,
-    /* No ticket present */
-    TICKET_NONE,
-    /* Empty ticket present */
-    TICKET_EMPTY,
-    /* the ticket couldn't be decrypted */
-    TICKET_NO_DECRYPT,
-    /* a ticket was successfully decrypted */
-    TICKET_SUCCESS,
-    /* same as above but the ticket needs to be renewed */
-    TICKET_SUCCESS_RENEW
-} TICKET_RETURN;
-
-__owur TICKET_RETURN tls_get_ticket_from_client(SSL *s, CLIENTHELLO_MSG *hello,
-                                                SSL_SESSION **ret);
-__owur TICKET_RETURN tls_decrypt_ticket(SSL *s, const unsigned char *etick,
-                                        size_t eticklen,
-                                        const unsigned char *sess_id,
-                                        size_t sesslen, SSL_SESSION **psess);
+__owur SSL_TICKET_RETURN tls_get_ticket_from_client(SSL *s, CLIENTHELLO_MSG *hello,
+                                                    SSL_SESSION **ret);
+__owur SSL_TICKET_RETURN tls_decrypt_ticket(SSL *s, const unsigned char *etick,
+                                            size_t eticklen,
+                                            const unsigned char *sess_id,
+                                            size_t sesslen, SSL_SESSION **psess);
 
 __owur int tls_use_ticket(SSL *s);
 
diff --git a/ssl/ssl_sess.c b/ssl/ssl_sess.c
index 5d2e171..f78c9cd 100644
--- a/ssl/ssl_sess.c
+++ b/ssl/ssl_sess.c
@@ -134,6 +134,7 @@
     dest->peer_chain = NULL;
     dest->peer = NULL;
     dest->ext.tick_nonce = NULL;
+    dest->ticket_appdata = NULL;
     memset(&dest->ex_data, 0, sizeof(dest->ex_data));
 
     /* We deliberately don't copy the prev and next pointers */
@@ -244,6 +245,13 @@
     }
 #endif
 
+    if (src->ticket_appdata != NULL) {
+        dest->ticket_appdata =
+            OPENSSL_memdup(src->ticket_appdata, src->ticket_appdata_len);
+        if (dest->ticket_appdata == NULL)
+            goto err;
+    }
+
     return dest;
  err:
     SSLerr(SSL_F_SSL_SESSION_DUP, ERR_R_MALLOC_FAILURE);
@@ -471,7 +479,7 @@
     SSL_SESSION *ret = NULL;
     int fatal = 0, discard;
     int try_session_cache = 0;
-    TICKET_RETURN r;
+    SSL_TICKET_RETURN r;
 
     if (SSL_IS_TLS13(s)) {
         if (!tls_parse_extension(s, TLSEXT_IDX_psk_kex_modes,
@@ -486,20 +494,20 @@
         /* sets s->ext.ticket_expected */
         r = tls_get_ticket_from_client(s, hello, &ret);
         switch (r) {
-        case TICKET_FATAL_ERR_MALLOC:
-        case TICKET_FATAL_ERR_OTHER:
+        case SSL_TICKET_FATAL_ERR_MALLOC:
+        case SSL_TICKET_FATAL_ERR_OTHER:
             fatal = 1;
             SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_SSL_GET_PREV_SESSION,
                      ERR_R_INTERNAL_ERROR);
             goto err;
-        case TICKET_NONE:
-        case TICKET_EMPTY:
+        case SSL_TICKET_NONE:
+        case SSL_TICKET_EMPTY:
             if (hello->session_id_len > 0)
                 try_session_cache = 1;
             break;
-        case TICKET_NO_DECRYPT:
-        case TICKET_SUCCESS:
-        case TICKET_SUCCESS_RENEW:
+        case SSL_TICKET_NO_DECRYPT:
+        case SSL_TICKET_SUCCESS:
+        case SSL_TICKET_SUCCESS_RENEW:
             break;
         }
     }
@@ -806,6 +814,7 @@
 #endif
     OPENSSL_free(ss->ext.alpn_selected);
     OPENSSL_free(ss->ext.tick_nonce);
+    OPENSSL_free(ss->ticket_appdata);
     CRYPTO_THREAD_lock_free(ss->lock);
     OPENSSL_clear_free(ss, sizeof(*ss));
 }
@@ -1269,4 +1278,27 @@
     ctx->app_verify_cookie_cb = cb;
 }
 
+int SSL_SESSION_set1_ticket_appdata(SSL_SESSION *ss, const void *data, size_t len)
+{
+    OPENSSL_free(ss->ticket_appdata);
+    ss->ticket_appdata_len = 0;
+    if (data == NULL || len == 0) {
+        ss->ticket_appdata = NULL;
+        return 1;
+    }
+    ss->ticket_appdata = OPENSSL_memdup(data, len);
+    if (ss->ticket_appdata != NULL) {
+        ss->ticket_appdata_len = len;
+        return 1;
+    }
+    return 0;
+}
+
+int SSL_SESSION_get0_ticket_appdata(SSL_SESSION *ss, void **data, size_t *len)
+{
+    *data = ss->ticket_appdata;
+    *len = ss->ticket_appdata_len;
+    return 1;
+}
+
 IMPLEMENT_PEM_rw(SSL_SESSION, SSL_SESSION, PEM_STRING_SSL_SESSION, SSL_SESSION)
diff --git a/ssl/statem/extensions_srvr.c b/ssl/statem/extensions_srvr.c
index b9692f4..74acdb2 100644
--- a/ssl/statem/extensions_srvr.c
+++ b/ssl/statem/extensions_srvr.c
@@ -1124,13 +1124,13 @@
                                          PACKET_remaining(&identity), NULL, 0,
                                          &sess);
 
-            if (ret == TICKET_FATAL_ERR_MALLOC
-                    || ret == TICKET_FATAL_ERR_OTHER) {
+            if (ret == SSL_TICKET_FATAL_ERR_MALLOC
+                    || ret == SSL_TICKET_FATAL_ERR_OTHER) {
                 SSLfatal(s, SSL_AD_INTERNAL_ERROR,
                          SSL_F_TLS_PARSE_CTOS_PSK, ERR_R_INTERNAL_ERROR);
                 return 0;
             }
-            if (ret == TICKET_NO_DECRYPT)
+            if (ret == SSL_TICKET_NO_DECRYPT)
                 continue;
 
             ticket_age = (uint32_t)ticket_agel;
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c
index 8092684..041089c 100644
--- a/ssl/statem/statem_srvr.c
+++ b/ssl/statem/statem_srvr.c
@@ -3728,6 +3728,10 @@
         s->session->ext.max_early_data = s->max_early_data;
     }
 
+    if (tctx->generate_ticket_cb != NULL &&
+        tctx->generate_ticket_cb(s, tctx->ticket_cb_data) == 0)
+        goto err;
+
     /* get session encoding length */
     slen_full = i2d_SSL_SESSION(s->session, NULL);
     /*
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 8b0d9aa..596fdd4 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -1205,8 +1205,8 @@
  *   s->ctx->ext.ticket_key_cb asked to renew the client's ticket.
  *   Otherwise, s->ext.ticket_expected is set to 0.
  */
-TICKET_RETURN tls_get_ticket_from_client(SSL *s, CLIENTHELLO_MSG *hello,
-                                         SSL_SESSION **ret)
+SSL_TICKET_RETURN tls_get_ticket_from_client(SSL *s, CLIENTHELLO_MSG *hello,
+                                             SSL_SESSION **ret)
 {
     int retv;
     size_t size;
@@ -1221,11 +1221,11 @@
      * resumption.
      */
     if (s->version <= SSL3_VERSION || !tls_use_ticket(s))
-        return TICKET_NONE;
+        return SSL_TICKET_NONE;
 
     ticketext = &hello->pre_proc_exts[TLSEXT_IDX_session_ticket];
     if (!ticketext->present)
-        return TICKET_NONE;
+        return SSL_TICKET_NONE;
 
     size = PACKET_remaining(&ticketext->data);
     if (size == 0) {
@@ -1234,7 +1234,7 @@
          * one.
          */
         s->ext.ticket_expected = 1;
-        return TICKET_EMPTY;
+        return SSL_TICKET_EMPTY;
     }
     if (s->ext.session_secret_cb) {
         /*
@@ -1243,25 +1243,49 @@
          * abbreviated handshake based on external mechanism to
          * calculate the master secret later.
          */
-        return TICKET_NO_DECRYPT;
+        return SSL_TICKET_NO_DECRYPT;
     }
 
     retv = tls_decrypt_ticket(s, PACKET_data(&ticketext->data), size,
                               hello->session_id, hello->session_id_len, ret);
+
+    /*
+     * If set, the decrypt_ticket_cb() is always called regardless of the
+     * return from tls_decrypt_ticket(). The callback is responsible for
+     * checking |retv| before it performs any action
+     */
+    if (s->session_ctx->decrypt_ticket_cb != NULL) {
+        size_t keyname_len = size;
+
+        if (keyname_len > TLSEXT_KEYNAME_LENGTH)
+            keyname_len = TLSEXT_KEYNAME_LENGTH;
+        retv = s->session_ctx->decrypt_ticket_cb(s, *ret,
+                                                 PACKET_data(&ticketext->data),
+                                                 keyname_len,
+                                                 retv, s->session_ctx->ticket_cb_data);
+    }
+
     switch (retv) {
-    case TICKET_NO_DECRYPT:
+    case SSL_TICKET_NO_DECRYPT:
         s->ext.ticket_expected = 1;
-        return TICKET_NO_DECRYPT;
+        return SSL_TICKET_NO_DECRYPT;
 
-    case TICKET_SUCCESS:
-        return TICKET_SUCCESS;
+    case SSL_TICKET_SUCCESS:
+        return SSL_TICKET_SUCCESS;
 
-    case TICKET_SUCCESS_RENEW:
+    case SSL_TICKET_SUCCESS_RENEW:
         s->ext.ticket_expected = 1;
-        return TICKET_SUCCESS;
+        return SSL_TICKET_SUCCESS;
+
+    case SSL_TICKET_EMPTY:
+        s->ext.ticket_expected = 1;
+        return SSL_TICKET_EMPTY;
+
+    case SSL_TICKET_NONE:
+        return SSL_TICKET_NONE;
 
     default:
-        return TICKET_FATAL_ERR_OTHER;
+        return SSL_TICKET_FATAL_ERR_OTHER;
     }
 }
 
@@ -1275,15 +1299,15 @@
  *   psess: (output) on return, if a ticket was decrypted, then this is set to
  *       point to the resulting session.
  */
-TICKET_RETURN tls_decrypt_ticket(SSL *s, const unsigned char *etick,
-                                 size_t eticklen, const unsigned char *sess_id,
-                                 size_t sesslen, SSL_SESSION **psess)
+SSL_TICKET_RETURN tls_decrypt_ticket(SSL *s, const unsigned char *etick,
+                                     size_t eticklen, const unsigned char *sess_id,
+                                     size_t sesslen, SSL_SESSION **psess)
 {
     SSL_SESSION *sess;
     unsigned char *sdec;
     const unsigned char *p;
     int slen, renew_ticket = 0, declen;
-    TICKET_RETURN ret = TICKET_FATAL_ERR_OTHER;
+    SSL_TICKET_RETURN ret = SSL_TICKET_FATAL_ERR_OTHER;
     size_t mlen;
     unsigned char tick_hmac[EVP_MAX_MD_SIZE];
     HMAC_CTX *hctx = NULL;
@@ -1292,17 +1316,17 @@
 
     /* Need at least keyname + iv */
     if (eticklen < TLSEXT_KEYNAME_LENGTH + EVP_MAX_IV_LENGTH) {
-        ret = TICKET_NO_DECRYPT;
+        ret = SSL_TICKET_NO_DECRYPT;
         goto err;
     }
 
     /* Initialize session ticket encryption and HMAC contexts */
     hctx = HMAC_CTX_new();
     if (hctx == NULL)
-        return TICKET_FATAL_ERR_MALLOC;
+        return SSL_TICKET_FATAL_ERR_MALLOC;
     ctx = EVP_CIPHER_CTX_new();
     if (ctx == NULL) {
-        ret = TICKET_FATAL_ERR_MALLOC;
+        ret = SSL_TICKET_FATAL_ERR_MALLOC;
         goto err;
     }
     if (tctx->ext.ticket_key_cb) {
@@ -1313,7 +1337,7 @@
         if (rv < 0)
             goto err;
         if (rv == 0) {
-            ret = TICKET_NO_DECRYPT;
+            ret = SSL_TICKET_NO_DECRYPT;
             goto err;
         }
         if (rv == 2)
@@ -1322,7 +1346,7 @@
         /* Check key name matches */
         if (memcmp(etick, tctx->ext.tick_key_name,
                    TLSEXT_KEYNAME_LENGTH) != 0) {
-            ret = TICKET_NO_DECRYPT;
+            ret = SSL_TICKET_NO_DECRYPT;
             goto err;
         }
         if (HMAC_Init_ex(hctx, tctx->ext.tick_hmac_key,
@@ -1345,7 +1369,7 @@
     /* Sanity check ticket length: must exceed keyname + IV + HMAC */
     if (eticklen <=
         TLSEXT_KEYNAME_LENGTH + EVP_CIPHER_CTX_iv_length(ctx) + mlen) {
-        ret = TICKET_NO_DECRYPT;
+        ret = SSL_TICKET_NO_DECRYPT;
         goto err;
     }
     eticklen -= mlen;
@@ -1357,7 +1381,7 @@
     HMAC_CTX_free(hctx);
     if (CRYPTO_memcmp(tick_hmac, etick + eticklen, mlen)) {
         EVP_CIPHER_CTX_free(ctx);
-        return TICKET_NO_DECRYPT;
+        return SSL_TICKET_NO_DECRYPT;
     }
     /* Attempt to decrypt session data */
     /* Move p after IV to start of encrypted ticket, update length */
@@ -1368,12 +1392,12 @@
                                           (int)eticklen) <= 0) {
         EVP_CIPHER_CTX_free(ctx);
         OPENSSL_free(sdec);
-        return TICKET_FATAL_ERR_OTHER;
+        return SSL_TICKET_FATAL_ERR_OTHER;
     }
     if (EVP_DecryptFinal(ctx, sdec + slen, &declen) <= 0) {
         EVP_CIPHER_CTX_free(ctx);
         OPENSSL_free(sdec);
-        return TICKET_NO_DECRYPT;
+        return SSL_TICKET_NO_DECRYPT;
     }
     slen += declen;
     EVP_CIPHER_CTX_free(ctx);
@@ -1387,7 +1411,7 @@
         /* Some additional consistency checks */
         if (slen != 0 || sess->session_id_length != 0) {
             SSL_SESSION_free(sess);
-            return TICKET_NO_DECRYPT;
+            return SSL_TICKET_NO_DECRYPT;
         }
         /*
          * The session ID, if non-empty, is used by some clients to detect
@@ -1400,15 +1424,15 @@
         sess->session_id_length = sesslen;
         *psess = sess;
         if (renew_ticket)
-            return TICKET_SUCCESS_RENEW;
+            return SSL_TICKET_SUCCESS_RENEW;
         else
-            return TICKET_SUCCESS;
+            return SSL_TICKET_SUCCESS;
     }
     ERR_clear_error();
     /*
      * For session parse failure, indicate that we need to send a new ticket.
      */
-    return TICKET_NO_DECRYPT;
+    return SSL_TICKET_NO_DECRYPT;
  err:
     EVP_CIPHER_CTX_free(ctx);
     HMAC_CTX_free(hctx);