Add TLSv1.3 client side external PSK support

Reviewed-by: Rich Salz <rsalz@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/3670)
diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c
index eccc5af..0b82c0e 100644
--- a/ssl/ssl_err.c
+++ b/ssl/ssl_err.c
@@ -594,6 +594,7 @@
     {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_PACKET_LENGTH), "bad packet length"},
     {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_PROTOCOL_VERSION_NUMBER),
     "bad protocol version number"},
+    {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_PSK), "bad psk"},
     {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_PSK_IDENTITY), "bad psk identity"},
     {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_RECORD_TYPE), "bad record type"},
     {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_RSA_ENCRYPT), "bad rsa encrypt"},
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index a46ede1..4bcbfa8 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -419,6 +419,8 @@
         SSL_SESSION_free(s->session);
         s->session = NULL;
     }
+    SSL_SESSION_free(s->psksession);
+    s->psksession = NULL;
 
     s->error = 0;
     s->hit = 0;
@@ -969,6 +971,7 @@
         ssl_clear_bad_session(s);
         SSL_SESSION_free(s->session);
     }
+    SSL_SESSION_free(s->psksession);
 
     clear_ciphers(s);
 
diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h
index b26db6a..7889ab5 100644
--- a/ssl/ssl_locl.h
+++ b/ssl/ssl_locl.h
@@ -485,6 +485,8 @@
                                  * in here? */
     size_t master_key_length;
 
+    /* TLSv1.3 early_secret used for external PSKs */
+    unsigned char early_secret[EVP_MAX_MD_SIZE];
     /*
      * For <=TLS1.2 this is the master_key. For TLS1.3 this is the resumption
      * master secret
@@ -953,6 +955,7 @@
     SSL_psk_server_cb_func psk_server_callback;
 # endif
     SSL_psk_find_session_cb_func psk_find_session_cb;
+    SSL_psk_use_session_cb_func psk_use_session_cb;
 
 # ifndef OPENSSL_NO_SRP
     SRP_CTX srp_ctx;            /* ctx for SRP authentication */
@@ -1103,6 +1106,8 @@
     unsigned char sid_ctx[SSL_MAX_SID_CTX_LENGTH];
     /* This can also be in the session once a session is established */
     SSL_SESSION *session;
+    /* TLSv1.3 PSK session */
+    SSL_SESSION *psksession;
     /* Default generate session ID callback. */
     GEN_SESSION_CB generate_session_id;
     /* Used in SSL3 */
@@ -1124,6 +1129,7 @@
     SSL_psk_server_cb_func psk_server_callback;
 # endif
     SSL_psk_find_session_cb_func psk_find_session_cb;
+    SSL_psk_use_session_cb_func psk_use_session_cb;
     SSL_CTX *ctx;
     /* Verified chain of peer */
     STACK_OF(X509) *verified_chain;
diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c
index 7defbb1..4965231 100644
--- a/ssl/statem/extensions.c
+++ b/ssl/statem/extensions.c
@@ -1234,6 +1234,7 @@
     EVP_MD_CTX *mctx = NULL;
     unsigned char hash[EVP_MAX_MD_SIZE], binderkey[EVP_MAX_MD_SIZE];
     unsigned char finishedkey[EVP_MAX_MD_SIZE], tmpbinder[EVP_MAX_MD_SIZE];
+    unsigned char *early_secret;
     const char resumption_label[] = "res binder";
     const char external_label[] = "ext binder";
     const char *label;
@@ -1248,10 +1249,19 @@
         labelsize = sizeof(resumption_label) - 1;
     }
 
-    /* Generate the early_secret */
+    /*
+     * Generate the early_secret. On the server side we've selected a PSK to
+     * resume with (internal or external) so we always do this. On the client
+     * side we do this for a non-external (i.e. resumption) PSK so that it
+     * is in place for sending early data. For client side external PSK we
+     * generate it but store it away for later use.
+     */
+    if (s->server || !external)
+        early_secret = (unsigned char *)s->early_secret;
+    else
+        early_secret = (unsigned char *)sess->early_secret;
     if (!tls13_generate_secret(s, md, NULL, sess->master_key,
-                               sess->master_key_length,
-                               (unsigned char *)&s->early_secret)) {
+                               sess->master_key_length, early_secret)) {
         SSLerr(SSL_F_TLS_PSK_DO_BINDER, ERR_R_INTERNAL_ERROR);
         goto err;
     }
@@ -1269,7 +1279,7 @@
     }
 
     /* Generate the binder key */
-    if (!tls13_hkdf_expand(s, md, s->early_secret, (unsigned char *)label,
+    if (!tls13_hkdf_expand(s, md, early_secret, (unsigned char *)label,
                            labelsize, hash, binderkey, hashsize)) {
         SSLerr(SSL_F_TLS_PSK_DO_BINDER, ERR_R_INTERNAL_ERROR);
         goto err;
diff --git a/ssl/statem/extensions_clnt.c b/ssl/statem/extensions_clnt.c
index 2f6d16e..5733a11 100644
--- a/ssl/statem/extensions_clnt.c
+++ b/ssl/statem/extensions_clnt.c
@@ -791,10 +791,13 @@
 {
 #ifndef OPENSSL_NO_TLS1_3
     uint32_t now, agesec, agems;
-    size_t hashsize, binderoffset, msglen;
-    unsigned char *binder = NULL, *msgstart = NULL;
-    const EVP_MD *md;
+    size_t reshashsize, pskhashsize, binderoffset, msglen, idlen;
+    unsigned char *resbinder = NULL, *pskbinder = NULL, *msgstart = NULL;
+    const unsigned char *id;
+    const EVP_MD *handmd = NULL, *mdres, *mdpsk;
     EXT_RETURN ret = EXT_RETURN_FAIL;
+    SSL_SESSION *psksess = NULL;
+    int dores = 0;
 
     s->session->ext.tick_identity = TLSEXT_PSK_BAD_IDENTITY;
 
@@ -809,76 +812,136 @@
      * so don't add this extension.
      */
     if (s->session->ssl_version != TLS1_3_VERSION
-            || s->session->ext.ticklen == 0)
+            || (s->session->ext.ticklen == 0 && s->psk_use_session_cb == NULL))
         return EXT_RETURN_NOT_SENT;
 
-    if (s->session->cipher == NULL) {
-        SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR);
+    if (s->hello_retry_request)
+        handmd = ssl_handshake_md(s);
+
+    if (s->psk_use_session_cb != NULL
+            && !s->psk_use_session_cb(s, handmd, &id, &idlen, &psksess)) {
+        SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, SSL_R_BAD_PSK);
         goto err;
     }
 
-    md = ssl_md(s->session->cipher->algorithm2);
-    if (md == NULL) {
-        /* Don't recognize this cipher so we can't use the session. Ignore it */
-        return EXT_RETURN_NOT_SENT;
-    }
+    if (s->session->ext.ticklen != 0) {
+        if (s->session->cipher == NULL) {
+            SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR);
+            goto err;
+        }
 
-    if (s->hello_retry_request && md != ssl_handshake_md(s)) {
+        mdres = ssl_md(s->session->cipher->algorithm2);
+        if (mdres == NULL) {
+            /* Don't recognize this cipher so we can't use the session. Ignore it */
+            goto dopsksess;
+        }
+
+        if (s->hello_retry_request && mdres != handmd) {
+            /*
+             * Selected ciphersuite hash does not match the hash for the session so
+             * we can't use it.
+             */
+            goto dopsksess;
+        }
+
         /*
-         * Selected ciphersuite hash does not match the hash for the session so
-         * we can't use it.
+         * Technically the C standard just says time() returns a time_t and says
+         * nothing about the encoding of that type. In practice most implementations
+         * follow POSIX which holds it as an integral type in seconds since epoch.
+         * We've already made the assumption that we can do this in multiple places
+         * in the code, so portability shouldn't be an issue.
          */
-        return EXT_RETURN_NOT_SENT;
-    }
+        now = (uint32_t)time(NULL);
+        agesec = now - (uint32_t)s->session->time;
 
-    /*
-     * Technically the C standard just says time() returns a time_t and says
-     * nothing about the encoding of that type. In practice most implementations
-     * follow POSIX which holds it as an integral type in seconds since epoch.
-     * We've already made the assumption that we can do this in multiple places
-     * in the code, so portability shouldn't be an issue.
-     */
-    now = (uint32_t)time(NULL);
-    agesec = now - (uint32_t)s->session->time;
+        if (s->session->ext.tick_lifetime_hint < agesec) {
+            /* Ticket is too old. Ignore it. */
+            goto dopsksess;
+        }
 
-    if (s->session->ext.tick_lifetime_hint < agesec) {
-        /* Ticket is too old. Ignore it. */
-        return EXT_RETURN_NOT_SENT;
-    }
-
-    /*
-     * Calculate age in ms. We're just doing it to nearest second. Should be
-     * good enough.
-     */
-    agems = agesec * (uint32_t)1000;
-
-    if (agesec != 0 && agems / (uint32_t)1000 != agesec) {
         /*
-         * Overflow. Shouldn't happen unless this is a *really* old session. If
-         * so we just ignore it.
+         * Calculate age in ms. We're just doing it to nearest second. Should be
+         * good enough.
          */
-        return EXT_RETURN_NOT_SENT;
+        agems = agesec * (uint32_t)1000;
+
+        if (agesec != 0 && agems / (uint32_t)1000 != agesec) {
+            /*
+             * Overflow. Shouldn't happen unless this is a *really* old session. If
+             * so we just ignore it.
+             */
+            goto dopsksess;
+        }
+
+        /*
+         * Obfuscate the age. Overflow here is fine, this addition is supposed to
+         * be mod 2^32.
+         */
+        agems += s->session->ext.tick_age_add;
+
+        reshashsize = EVP_MD_size(mdres);
+        dores = 1;
     }
 
-    /*
-     * Obfuscate the age. Overflow here is fine, this addition is supposed to
-     * be mod 2^32.
-     */
-    agems += s->session->ext.tick_age_add;
+ dopsksess:
+    if (!dores && psksess == NULL)
+        return EXT_RETURN_NOT_SENT;
 
-    hashsize = EVP_MD_size(md);
+    if (psksess != NULL) {
+        mdpsk = ssl_md(psksess->cipher->algorithm2);
+        if (mdpsk == NULL) {
+            /*
+             * Don't recognize this cipher so we can't use the session.
+             * If this happens it's an application bug.
+             */
+            SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, SSL_R_BAD_PSK);
+            goto err;
+        }
+
+        if (s->hello_retry_request && mdres != handmd) {
+            /*
+             * Selected ciphersuite hash does not match the hash for the PSK
+             * session. This is an application bug.
+             */
+            SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, SSL_R_BAD_PSK);
+            goto err;
+        }
+
+        pskhashsize = EVP_MD_size(mdpsk);
+    }
 
     /* Create the extension, but skip over the binder for now */
     if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_psk)
             || !WPACKET_start_sub_packet_u16(pkt)
-            || !WPACKET_start_sub_packet_u16(pkt)
-            || !WPACKET_sub_memcpy_u16(pkt, s->session->ext.tick,
-                                       s->session->ext.ticklen)
-            || !WPACKET_put_bytes_u32(pkt, agems)
-            || !WPACKET_close(pkt)
+            || !WPACKET_start_sub_packet_u16(pkt)) {
+        SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+
+    if (dores) {
+        if (!WPACKET_sub_memcpy_u16(pkt, s->session->ext.tick,
+                                           s->session->ext.ticklen)
+                || !WPACKET_put_bytes_u32(pkt, agems)) {
+            SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR);
+            goto err;
+        }
+    }
+
+    if (psksess != NULL) {
+        if (!WPACKET_sub_memcpy_u16(pkt, id, idlen)
+                || !WPACKET_put_bytes_u32(pkt, 0)) {
+            SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR);
+            goto err;
+        }
+    }
+
+    if (!WPACKET_close(pkt)
             || !WPACKET_get_total_written(pkt, &binderoffset)
             || !WPACKET_start_sub_packet_u16(pkt)
-            || !WPACKET_sub_allocate_bytes_u8(pkt, hashsize, &binder)
+            || (dores
+                && !WPACKET_sub_allocate_bytes_u8(pkt, reshashsize, &resbinder))
+            || (psksess != NULL
+                && !WPACKET_sub_allocate_bytes_u8(pkt, pskhashsize, &pskbinder))
             || !WPACKET_close(pkt)
             || !WPACKET_close(pkt)
             || !WPACKET_get_total_written(pkt, &msglen)
@@ -893,13 +956,24 @@
 
     msgstart = WPACKET_get_curr(pkt) - msglen;
 
-    if (tls_psk_do_binder(s, md, msgstart, binderoffset, NULL, binder,
-                          s->session, 1, 0) != 1) {
+    if (dores && tls_psk_do_binder(s, mdres, msgstart, binderoffset, NULL,
+                                   resbinder, s->session, 1, 0) != 1) {
         SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR);
         goto err;
     }
 
-    s->session->ext.tick_identity = 0;
+    if (psksess != NULL && tls_psk_do_binder(s, mdpsk, msgstart,
+                                                binderoffset, NULL, pskbinder,
+                                                psksess, 1, 1) != 1) {
+        SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+
+    if (dores)
+        s->session->ext.tick_identity = 0;
+    s->psksession = psksess;
+    if (psksess != NULL)
+        s->psksession->ext.tick_identity = (dores ? 1 : 0);
 
     ret = EXT_RETURN_SENT;
  err:
@@ -1508,12 +1582,24 @@
         return 0;
     }
 
-    if (s->session->ext.tick_identity != (int)identity) {
+    if (s->session->ext.tick_identity == (int)identity) {
+        s->hit = 1;
+        SSL_SESSION_free(s->psksession);
+        s->psksession = NULL;
+        return 1;
+    }
+
+    if (s->psksession == NULL
+            || s->psksession->ext.tick_identity != (int)identity) {
         *al = SSL_AD_ILLEGAL_PARAMETER;
         SSLerr(SSL_F_TLS_PARSE_STOC_PSK, SSL_R_BAD_PSK_IDENTITY);
         return 0;
     }
 
+    SSL_SESSION_free(s->session);
+    s->session = s->psksession;
+    s->psksession = NULL;
+    memcpy(s->early_secret, s->session->early_secret, EVP_MAX_MD_SIZE);
     s->hit = 1;
 #endif