This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat-native.git

commit 4d334f720a88da477d4ff87f17f4ceb559526df1
Author: Mark Thomas <[email protected]>
AuthorDate: Mon Sep 21 14:40:44 2026 +0100

    Add PSK support for TLS 1.3
    
    Co-authored-by: GitHub Copilot (GPT-5.6 Sol) <[email protected]>
---
 .../apache/tomcat/jni/PreSharedKeySelector.java    |  14 +-
 java/org/apache/tomcat/jni/SSLContext.java         |   9 ++
 native/include/ssl_private.h                       |   4 +
 native/src/sslcontext.c                            | 149 +++++++++++++++++++++
 xdocs/miscellaneous/changelog.xml                  |   4 +-
 5 files changed, 177 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/tomcat/jni/PreSharedKeySelector.java 
b/java/org/apache/tomcat/jni/PreSharedKeySelector.java
index 0cafc2a90..ef701ed7f 100644
--- a/java/org/apache/tomcat/jni/PreSharedKeySelector.java
+++ b/java/org/apache/tomcat/jni/PreSharedKeySelector.java
@@ -17,7 +17,8 @@
 package org.apache.tomcat.jni;
 
 /**
- * Is called during a TLSv1.2 handshake and hooked into OpenSSL via {@code 
SSL_CTX_set_psk_server_callback}.
+ * Is called during a TLS handshake and hooked into OpenSSL via {@code 
SSL_CTX_set_psk_server_callback} for TLSv1.2 and
+ * {@code SSL_CTX_set_psk_find_session_callback} for TLSv1.3.
  */
 public interface PreSharedKeySelector {
 
@@ -30,4 +31,15 @@ public interface PreSharedKeySelector {
      * @return the pre-shared key, or {@code null} if the identity is not 
recognized
      */
     byte[] select(long ssl, String identity);
+
+    /**
+     * Selects the TLSv1.3 pre-shared key and cipher suite for the provided 
identity.
+     *
+     * @param ssl         the SSL instance
+     * @param identity    the PSK identity provided by the client
+     * @param cipherSuite a single-element array that must be populated with 
the IANA cipher suite identifier
+     *
+     * @return the pre-shared key, or {@code null} if the identity is not 
recognized
+     */
+    byte[] select(long ssl, byte[] identity, int[] cipherSuite);
 }
diff --git a/java/org/apache/tomcat/jni/SSLContext.java 
b/java/org/apache/tomcat/jni/SSLContext.java
index fa56214fe..7d7663fc4 100644
--- a/java/org/apache/tomcat/jni/SSLContext.java
+++ b/java/org/apache/tomcat/jni/SSLContext.java
@@ -480,6 +480,15 @@ public final class SSLContext {
      */
     public static native void setPskServerCallback(long ctx, 
PreSharedKeySelector selector);
 
+    /**
+     * Allow to hook {@link PreSharedKeySelector} into the TLSv1.3 handshake 
processing. This will call
+     * {@code SSL_CTX_set_psk_find_session_callback}.
+     *
+     * @param ctx      Server context to use.
+     * @param selector the selector to call during handshake, or {@code null} 
to remove the current selector
+     */
+    public static native void setPskFindSessionCallback(long ctx, 
PreSharedKeySelector selector);
+
     /**
      * Set application layer protocol for application layer protocol 
negotiation extension
      *
diff --git a/native/include/ssl_private.h b/native/include/ssl_private.h
index 8b6575cab..b9baac9d5 100644
--- a/native/include/ssl_private.h
+++ b/native/include/ssl_private.h
@@ -199,6 +199,10 @@ struct tcn_ssl_ctxt_t {
     jobject psk_selector;
     jmethodID psk_selector_method;
 
+    /* TLSv1.3 server-side pre-shared key selector */
+    jobject psk_find_session_selector;
+    jmethodID psk_find_session_selector_method;
+
     /* Holds the alpn protocols, each of them prefixed with the len of the 
protocol */
     unsigned char   *alpn_proto_data;
     unsigned int    alpn_proto_len;
diff --git a/native/src/sslcontext.c b/native/src/sslcontext.c
index c9ce9c73a..cb0dd165d 100644
--- a/native/src/sslcontext.c
+++ b/native/src/sslcontext.c
@@ -69,6 +69,14 @@ static apr_status_t ssl_context_cleanup(void *data)
         }
         c->psk_selector_method = NULL;
 
+        if (c->psk_find_session_selector) {
+            JNIEnv *e;
+            tcn_get_java_env(&e);
+            (*e)->DeleteGlobalRef(e, c->psk_find_session_selector);
+            c->psk_find_session_selector = NULL;
+        }
+        c->psk_find_session_selector_method = NULL;
+
         if (c->alpn_proto_data) {
             free(c->alpn_proto_data);
             c->alpn_proto_data = NULL;
@@ -1579,6 +1587,147 @@ TCN_IMPLEMENT_CALL(void, SSLContext, 
setPskServerCallback)(TCN_STDARGS, jlong ct
 #endif
 }
 
+#if defined(HAVE_TLSV1_3) && !defined(LIBRESSL_VERSION_NUMBER)
+static int SSL_psk_find_session(SSL *ssl, const unsigned char *identity, 
size_t identity_len, SSL_SESSION **sess)
+{
+    tcn_ssl_ctxt_t *c = SSL_get_app_data2(ssl);
+    JNIEnv *e;
+    jbyteArray identity_array = NULL;
+    jintArray cipher_suite_array = NULL;
+    jbyteArray key = NULL;
+    jint cipher_suite;
+    jsize key_len = 0;
+    unsigned char cipher_id[2];
+    unsigned char *key_data = NULL;
+    const SSL_CIPHER *cipher;
+    SSL_SESSION *session = NULL;
+    int result = 0;
+
+    *sess = NULL;
+    if (c == NULL || c->psk_find_session_selector == NULL || 
c->psk_find_session_selector_method == NULL ||
+            identity == NULL || tcn_get_java_env(&e) != JNI_OK) {
+        return 0;
+    }
+
+    identity_array = (*e)->NewByteArray(e, (jsize)identity_len);
+    if (identity_array == NULL) {
+        goto cleanup;
+    }
+    (*e)->SetByteArrayRegion(e, identity_array, 0, (jsize)identity_len, (const 
jbyte *)identity);
+    if ((*e)->ExceptionCheck(e)) {
+        goto cleanup;
+    }
+
+    cipher_suite_array = (*e)->NewIntArray(e, 1);
+    if (cipher_suite_array == NULL) {
+        goto cleanup;
+    }
+
+    key = (*e)->CallObjectMethod(e, c->psk_find_session_selector, 
c->psk_find_session_selector_method, P2J(ssl),
+            identity_array, cipher_suite_array);
+    if ((*e)->ExceptionCheck(e)) {
+        goto cleanup;
+    }
+    if (key == NULL) {
+        result = 1;
+        goto cleanup;
+    }
+
+    (*e)->GetIntArrayRegion(e, cipher_suite_array, 0, 1, &cipher_suite);
+    if ((*e)->ExceptionCheck(e) || cipher_suite <= 0 || cipher_suite > 0xffff) 
{
+        goto cleanup;
+    }
+    cipher_id[0] = (unsigned char)(cipher_suite >> 8);
+    cipher_id[1] = (unsigned char)cipher_suite;
+    cipher = SSL_CIPHER_find(ssl, cipher_id);
+    if (cipher == NULL || strcmp(SSL_CIPHER_get_version(cipher), "TLSv1.3") != 
0) {
+        goto cleanup;
+    }
+
+    key_len = (*e)->GetArrayLength(e, key);
+    if (key_len <= 0) {
+        goto cleanup;
+    }
+    key_data = OPENSSL_malloc((size_t)key_len);
+    if (key_data == NULL) {
+        goto cleanup;
+    }
+    (*e)->GetByteArrayRegion(e, key, 0, key_len, (jbyte *)key_data);
+    if ((*e)->ExceptionCheck(e)) {
+        goto cleanup;
+    }
+
+    session = SSL_SESSION_new();
+    if (session == NULL || !SSL_SESSION_set1_master_key(session, key_data, 
(size_t)key_len) ||
+            !SSL_SESSION_set_cipher(session, cipher) ||
+            !SSL_SESSION_set_protocol_version(session, TLS1_3_VERSION)) {
+        goto cleanup;
+    }
+
+    *sess = session;
+    session = NULL;
+    result = 1;
+
+cleanup:
+    if ((*e)->ExceptionCheck(e)) {
+        (*e)->ExceptionClear(e);
+    }
+    SSL_SESSION_free(session);
+    if (key_data != NULL) {
+        OPENSSL_clear_free(key_data, (size_t)key_len);
+    }
+    if (key != NULL) {
+        (*e)->DeleteLocalRef(e, key);
+    }
+    if (cipher_suite_array != NULL) {
+        (*e)->DeleteLocalRef(e, cipher_suite_array);
+    }
+    if (identity_array != NULL) {
+        (*e)->DeleteLocalRef(e, identity_array);
+    }
+    return result;
+}
+#endif
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setPskFindSessionCallback)(TCN_STDARGS, 
jlong ctx, jobject selector)
+{
+#if defined(HAVE_TLSV1_3) && !defined(LIBRESSL_VERSION_NUMBER)
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jobject new_selector = NULL;
+    jmethodID new_method = NULL;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+
+    if (selector != NULL) {
+        jclass selector_class = (*e)->GetObjectClass(e, selector);
+        new_method = (*e)->GetMethodID(e, selector_class, "select", 
"(J[B[I)[B");
+        (*e)->DeleteLocalRef(e, selector_class);
+        if (new_method == NULL) {
+            return;
+        }
+
+        new_selector = (*e)->NewGlobalRef(e, selector);
+        if (new_selector == NULL) {
+            return;
+        }
+    }
+
+    SSL_CTX_set_psk_find_session_callback(c->ctx, selector == NULL ? NULL : 
SSL_psk_find_session);
+
+    if (c->psk_find_session_selector != NULL) {
+        (*e)->DeleteGlobalRef(e, c->psk_find_session_selector);
+    }
+    c->psk_find_session_selector = new_selector;
+    c->psk_find_session_selector_method = new_method;
+#else
+    UNREFERENCED(o);
+    UNREFERENCED(ctx);
+    UNREFERENCED(selector);
+    tcn_Throw(e, "OpenSSL does not support TLSv1.3 PSK");
+#endif
+}
+
 TCN_IMPLEMENT_CALL(jboolean, SSLContext, setSessionIdContext)(TCN_STDARGS, 
jlong ctx, jbyteArray sidCtx)
 {
     tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
diff --git a/xdocs/miscellaneous/changelog.xml 
b/xdocs/miscellaneous/changelog.xml
index 2eeaa964a..0ffb364db 100644
--- a/xdocs/miscellaneous/changelog.xml
+++ b/xdocs/miscellaneous/changelog.xml
@@ -52,8 +52,8 @@
       Make Windows 10 / 11 the default target version for Windows builds. 
(rjung)
     </update>
     <add>
-      Add support for configuring a server-side TLSv1.2 pre-shared key
-      callback. (markt)
+      Add support for configuring server-side TLSv1.2 and TLSv1.3 pre-shared
+      key callbacks. (markt)
     </add>
     <fix>
       Fix a potential crash when negotiating ALPN. (markt)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to