Commit 4abb70d327 for openssl.org

commit 4abb70d32719d15a1db1868484123a1f630b720b
Author: Viktor Dukhovni <viktor@openssl.org>
Date:   Sun Jul 12 00:35:56 2026 +1000

    PSK: Handle non-empty client sid_ctx

    - Do not reject external PSKs when client-side sid_ctx is set, fixed
      by copying the sid_ctx into the pending session.

    Tests added for this and also the server side sid_ctx issues.

    Reviewed-by: Bob Beck <beck@openssl.org>
    Reviewed-by: Norbert Pocs <norbertp@openssl.org>
    MergeDate: Tue Jul 21 14:12:06 2026
    (Merged from https://github.com/openssl/openssl/pull/31964)

diff --git a/ssl/statem/extensions_clnt.c b/ssl/statem/extensions_clnt.c
index e3e713f5f6..f17fe2c495 100644
--- a/ssl/statem/extensions_clnt.c
+++ b/ssl/statem/extensions_clnt.c
@@ -2415,6 +2415,7 @@ int tls_parse_stoc_psk(SSL_CONNECTION *s, PACKET *pkt,
     size_t chainidx)
 {
 #ifndef OPENSSL_NO_TLS1_3
+    SSL_SESSION *sesstmp;
     unsigned int identity;

     if (!PACKET_get_net_2(pkt, &identity) || PACKET_remaining(pkt) != 0) {
@@ -2456,6 +2457,25 @@ int tls_parse_stoc_psk(SSL_CONNECTION *s, PACKET *pkt,
         || s->psksession->ext.max_early_data == 0)
         memcpy(s->early_secret, s->psksession->early_secret, EVP_MAX_MD_SIZE);

+    /*
+     * The psk_use_session_cb()/psk_client_callback() may reuse
+     * the session across connections we can't mutate it directly.
+     */
+    if ((sesstmp = ssl_session_dup(s->psksession, 0)) == NULL) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    SSL_SESSION_free(s->psksession);
+    s->psksession = sesstmp;
+
+    /*
+     * s->psksession (now our private copy) was built by the callback, not via
+     * ssl_get_new_session(), so it was never stamped with our own sid_ctx. Do
+     * so now, to avoid rejection of the PSK session in tls_process_server_hello().
+     */
+    memcpy(s->psksession->sid_ctx, s->sid_ctx, s->sid_ctx_length);
+    s->psksession->sid_ctx_length = s->sid_ctx_length;
+
     SSL_SESSION_free(s->session);
     s->session = s->psksession;
     s->psksession = NULL;
diff --git a/test/sslapitest.c b/test/sslapitest.c
index d751385fd2..6fc18a0dc0 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -6546,6 +6546,165 @@ end:
     return testresult;
 }

+/*
+ * A server with SSL_VERIFY_PEER set but no session ID context configured
+ * must still accept a TLS 1.3 external PSK connection: the session was
+ * just resolved via the application's own callback for this identity, not
+ * read back out of a shared cache, so the sid_ctx check that guards
+ * against cross-context cache reuse does not apply to it.
+ */
+static int test_tls13_psk_verify_peer_no_sid_ctx(void)
+{
+    SSL_CTX *sctx = NULL, *cctx = NULL;
+    SSL *serverssl = NULL, *clientssl = NULL;
+    int testresult = 0;
+
+    if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
+            TLS_client_method(), TLS1_VERSION, 0, &sctx, &cctx, NULL, NULL))
+        || !TEST_true(SSL_CTX_set_ciphersuites(cctx, "TLS_AES_128_GCM_SHA256")))
+        goto end;
+
+    SSL_CTX_set_verify(sctx, SSL_VERIFY_PEER, NULL);
+
+    SSL_CTX_set_psk_use_session_callback(cctx, use_session_cb);
+    SSL_CTX_set_psk_find_session_callback(sctx, find_session_cb);
+    srvid = pskid;
+    use_session_cb_cnt = 0;
+    find_session_cb_cnt = 0;
+
+    if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+            NULL, NULL)))
+        goto end;
+
+    clientpsk = create_a_psk(clientssl, SHA256_DIGEST_LENGTH);
+    if (!TEST_ptr(clientpsk) || !TEST_true(SSL_SESSION_up_ref(clientpsk)))
+        goto end;
+    serverpsk = clientpsk;
+
+    if (!TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))
+        || !TEST_true(SSL_session_reused(clientssl))
+        || !TEST_true(SSL_session_reused(serverssl)))
+        goto end;
+
+    testresult = 1;
+end:
+    SSL_SESSION_free(clientpsk);
+    SSL_SESSION_free(serverpsk);
+    clientpsk = serverpsk = NULL;
+    SSL_free(serverssl);
+    SSL_free(clientssl);
+    SSL_CTX_free(sctx);
+    SSL_CTX_free(cctx);
+    return testresult;
+}
+
+/*
+ * A server with SSL_VERIFY_PEER set but no session ID context configured
+ * must not issue a session ticket after a full (non-PSK) handshake: any
+ * such ticket would be a poison pill, since resuming it would hit exactly
+ * the sid_ctx check that a fresh external PSK is exempted from above.
+ */
+static int test_tls13_psk_verify_peer_no_ticket(void)
+{
+    SSL_CTX *sctx = NULL, *cctx = NULL;
+    SSL *serverssl = NULL, *clientssl = NULL;
+    SSL_SESSION *sess = NULL;
+    int testresult = 0;
+
+    if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
+            TLS_client_method(), TLS1_VERSION, 0, &sctx, &cctx, cert, privkey)))
+        goto end;
+
+    SSL_CTX_set_verify(sctx, SSL_VERIFY_PEER, NULL);
+
+    if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+            NULL, NULL))
+        || !TEST_true(create_ssl_connection(serverssl, clientssl,
+            SSL_ERROR_NONE)))
+        goto end;
+
+    sess = SSL_get1_session(clientssl);
+    if (!TEST_ptr(sess) || !TEST_false(SSL_SESSION_has_ticket(sess)))
+        goto end;
+
+    testresult = 1;
+end:
+    SSL_SESSION_free(sess);
+    SSL_free(serverssl);
+    SSL_free(clientssl);
+    SSL_CTX_free(sctx);
+    SSL_CTX_free(cctx);
+    return testresult;
+}
+
+/*
+ * A client with its own session ID context configured must still be able
+ * to resume a TLS 1.3 external PSK obtained via the legacy
+ * psk_use_session_cb()/psk_client_callback() callbacks. s->psksession is
+ * never routed through ssl_get_new_session(), so, unlike an ordinary
+ * session, it was never stamped with the client's own sid_ctx; without
+ * that stamp tls_process_server_hello()'s own sid_ctx self-consistency
+ * check fatally rejects marking it reused.
+ *
+ * Test 0: new style callback (psk_use_session_cb()/psk_find_session_cb()).
+ * Test 1: old style callback (psk_client_callback()/psk_server_callback()).
+ */
+static int test_tls13_psk_client_sid_ctx(int idx)
+{
+    SSL_CTX *sctx = NULL, *cctx = NULL;
+    SSL *serverssl = NULL, *clientssl = NULL;
+    int sess_id_ctx = 1;
+    int testresult = 0;
+
+    if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
+            TLS_client_method(), TLS1_VERSION, 0, &sctx, &cctx, NULL, NULL))
+        || !TEST_true(SSL_CTX_set_ciphersuites(cctx, "TLS_AES_128_GCM_SHA256"))
+        || !TEST_true(SSL_CTX_set_session_id_context(cctx,
+            (void *)&sess_id_ctx, sizeof(sess_id_ctx))))
+        goto end;
+
+    srvid = pskid;
+    if (idx == 0) {
+        SSL_CTX_set_psk_use_session_callback(cctx, use_session_cb);
+        SSL_CTX_set_psk_find_session_callback(sctx, find_session_cb);
+        use_session_cb_cnt = 0;
+        find_session_cb_cnt = 0;
+    }
+#ifndef OPENSSL_NO_PSK
+    else {
+        SSL_CTX_set_psk_client_callback(cctx, psk_client_cb);
+        SSL_CTX_set_psk_server_callback(sctx, psk_server_cb);
+        psk_client_cb_cnt = 0;
+        psk_server_cb_cnt = 0;
+    }
+#endif
+
+    if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+            NULL, NULL)))
+        goto end;
+
+    clientpsk = create_a_psk(clientssl, SHA256_DIGEST_LENGTH);
+    if (!TEST_ptr(clientpsk) || !TEST_true(SSL_SESSION_up_ref(clientpsk)))
+        goto end;
+    serverpsk = clientpsk;
+
+    if (!TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))
+        || !TEST_true(SSL_session_reused(clientssl))
+        || !TEST_true(SSL_session_reused(serverssl)))
+        goto end;
+
+    testresult = 1;
+end:
+    SSL_SESSION_free(clientpsk);
+    SSL_SESSION_free(serverpsk);
+    clientpsk = serverpsk = NULL;
+    SSL_free(serverssl);
+    SSL_free(clientssl);
+    SSL_CTX_free(sctx);
+    SSL_CTX_free(cctx);
+    return testresult;
+}
+
 #ifndef OSSL_NO_USABLE_TLS1_3
 /*
  * Test TLS1.3 connection establishment succeeds with various configurations of
@@ -15325,9 +15484,13 @@ int setup_tests(void)
     ADD_ALL_TESTS(test_tls13_ciphersuite, 4);
 #ifdef OPENSSL_NO_PSK
     ADD_ALL_TESTS(test_tls13_psk, 1);
+    ADD_ALL_TESTS(test_tls13_psk_client_sid_ctx, 1);
 #else
     ADD_ALL_TESTS(test_tls13_psk, 4);
+    ADD_ALL_TESTS(test_tls13_psk_client_sid_ctx, 2);
 #endif /* OPENSSL_NO_PSK */
+    ADD_TEST(test_tls13_psk_verify_peer_no_sid_ctx);
+    ADD_TEST(test_tls13_psk_verify_peer_no_ticket);
 #ifndef OSSL_NO_USABLE_TLS1_3
     ADD_ALL_TESTS(test_tls13_no_dhe_kex, 8);
 #endif /* OSSL_NO_USABLE_TLS1_3 */