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 b7c6d465ae00bf9082973a9fffd16a26fce55483
Author: Mark Thomas <[email protected]>
AuthorDate: Tue Sep 22 17:10:27 2026 +0100

    Add client-side PSK support for TLSv1.2
    
    Client side support is required by Tomcat's clustering component
    
    Co-authored-by: GitHub Copilot (GPT-5.6 Sol) <[email protected]>
---
 .../apache/tomcat/jni/PreSharedKeySelector.java    |  16 ++-
 java/org/apache/tomcat/jni/SSLContext.java         |  12 ++-
 native/include/ssl_private.h                       |   4 +
 native/src/sslcontext.c                            | 111 +++++++++++++++++++++
 xdocs/miscellaneous/changelog.xml                  |   4 +
 5 files changed, 143 insertions(+), 4 deletions(-)

diff --git a/java/org/apache/tomcat/jni/PreSharedKeySelector.java 
b/java/org/apache/tomcat/jni/PreSharedKeySelector.java
index 1b4f738f7..2364fdfa1 100644
--- a/java/org/apache/tomcat/jni/PreSharedKeySelector.java
+++ b/java/org/apache/tomcat/jni/PreSharedKeySelector.java
@@ -24,7 +24,7 @@ package org.apache.tomcat.jni;
 public interface PreSharedKeySelector {
 
     /**
-     * Selects the pre-shared key for the provided identity.
+     * Selects the TLSv1.2 pre-shared key on the server side given the 
provided client identity.
      *
      * @param ssl      the SSL instance
      * @param identity the PSK identity provided by the client
@@ -36,7 +36,7 @@ public interface PreSharedKeySelector {
     byte[] select(long ssl, String identity);
 
     /**
-     * Selects the TLSv1.3 pre-shared key and digest for the provided identity.
+     * Selects the TLSv1.3 pre-shared key and digest on the server side given 
the provided client identity.
      * <p>
      * The callback is a little more complex for TLSv1.3. The return value is 
still the pre-shared key but OpenSSL also
      * needs to know which digest to use. Because the OpenSSL API only exposes 
a cipher for this, that is what Tomcat
@@ -51,4 +51,16 @@ public interface PreSharedKeySelector {
      *             random then 16 bytes are recommended for 128-bit ciphers 
and 32 bytes for 256-bit ciphers.
      */
     byte[] select(long ssl, byte[] identity, int[] cipherSuite);
+
+
+    /**
+     * Selects the TLS1v2 identity and pre-shared key that the client will 
present to a server.
+     *
+     * @param ssl      the SSL instance
+     * @param identity a single-element array that must be populated with the 
PSK identity
+     *
+     * @return the pre-shared key, or {@code null} if no key is available
+     */
+    byte[] selectClient(long ssl, String[] identity);
+
 }
diff --git a/java/org/apache/tomcat/jni/SSLContext.java 
b/java/org/apache/tomcat/jni/SSLContext.java
index 7d7663fc4..89fb454fe 100644
--- a/java/org/apache/tomcat/jni/SSLContext.java
+++ b/java/org/apache/tomcat/jni/SSLContext.java
@@ -472,7 +472,7 @@ public final class SSLContext {
     public static native void setCertVerifyCallback(long ctx, 
CertificateVerifier verifier);
 
     /**
-     * Allow to hook {@link PreSharedKeySelector} into the TLSv1.2 handshake 
processing. This will call
+     * Sets the TLSv1.2 server-side pre-shared key callback to a {@link 
PreSharedKeySelector} instance. This will call
      * {@code SSL_CTX_set_psk_server_callback}.
      *
      * @param ctx      Server context to use.
@@ -481,7 +481,7 @@ 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
+     * Sets the TLSv1.3 server-side pre-shared key callback to a {@link 
PreSharedKeySelector} instance. This will call
      * {@code SSL_CTX_set_psk_find_session_callback}.
      *
      * @param ctx      Server context to use.
@@ -489,6 +489,14 @@ public final class SSLContext {
      */
     public static native void setPskFindSessionCallback(long ctx, 
PreSharedKeySelector selector);
 
+    /**
+     * Sets the TLSv1.2 client-side pre-shared key callback.
+     *
+     * @param ctx      Client context to use
+     * @param selector pre-shared key selector
+     */
+    public static native void setPskClientCallback(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 b9baac9d5..05cd683b1 100644
--- a/native/include/ssl_private.h
+++ b/native/include/ssl_private.h
@@ -203,6 +203,10 @@ struct tcn_ssl_ctxt_t {
     jobject psk_find_session_selector;
     jmethodID psk_find_session_selector_method;
 
+    /* TLSv1.2 client-side pre-shared key selector */
+    jobject psk_client_selector;
+    jmethodID psk_client_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 cb0dd165d..6558eb7fa 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_client_selector) {
+            JNIEnv *e;
+            tcn_get_java_env(&e);
+            (*e)->DeleteGlobalRef(e, c->psk_client_selector);
+            c->psk_client_selector = NULL;
+        }
+        c->psk_client_selector_method = NULL;
+
         if (c->psk_find_session_selector) {
             JNIEnv *e;
             tcn_get_java_env(&e);
@@ -1548,6 +1556,109 @@ cleanup:
 }
 #endif
 
+#ifndef OPENSSL_NO_PSK
+static unsigned int SSL_psk_client(SSL *ssl, const char *hint, char *identity, 
unsigned int max_identity_len,
+                                   unsigned char *psk, unsigned int 
max_psk_len)
+{
+    tcn_ssl_ctxt_t *c = SSL_get_app_data2(ssl);
+    JNIEnv *e;
+    jobjectArray identity_array = NULL;
+    jstring identity_string = NULL;
+    jbyteArray key = NULL;
+    const char *identity_utf = NULL;
+    jsize key_len;
+    size_t identity_len;
+    unsigned int result = 0;
+
+    UNREFERENCED(hint);
+    if (c == NULL || c->psk_client_selector == NULL || 
c->psk_client_selector_method == NULL ||
+            tcn_get_java_env(&e) != JNI_OK) {
+        return 0;
+    }
+
+    identity_array = (*e)->NewObjectArray(e, 1, (*e)->FindClass(e, 
"java/lang/String"), NULL);
+    if (identity_array == NULL) {
+        goto cleanup;
+    }
+    key = (*e)->CallObjectMethod(e, c->psk_client_selector, 
c->psk_client_selector_method, P2J(ssl), identity_array);
+    if ((*e)->ExceptionCheck(e) || key == NULL) {
+        goto cleanup;
+    }
+    identity_string = (*e)->GetObjectArrayElement(e, identity_array, 0);
+    if (identity_string == NULL) {
+        goto cleanup;
+    }
+    identity_utf = (*e)->GetStringUTFChars(e, identity_string, NULL);
+    if (identity_utf == NULL) {
+        goto cleanup;
+    }
+    identity_len = strlen(identity_utf);
+    key_len = (*e)->GetArrayLength(e, key);
+    if (identity_len + 1 > max_identity_len || key_len <= 0 || (unsigned int) 
key_len > max_psk_len) {
+        goto cleanup;
+    }
+    memcpy(identity, identity_utf, identity_len + 1);
+    (*e)->GetByteArrayRegion(e, key, 0, key_len, (jbyte *)psk);
+    if (!(*e)->ExceptionCheck(e)) {
+        result = (unsigned int) key_len;
+    }
+
+cleanup:
+    if (identity_utf != NULL) {
+        (*e)->ReleaseStringUTFChars(e, identity_string, identity_utf);
+    }
+    if ((*e)->ExceptionCheck(e)) {
+        (*e)->ExceptionClear(e);
+    }
+    if (key != NULL) {
+        (*e)->DeleteLocalRef(e, key);
+    }
+    if (identity_string != NULL) {
+        (*e)->DeleteLocalRef(e, identity_string);
+    }
+    if (identity_array != NULL) {
+        (*e)->DeleteLocalRef(e, identity_array);
+    }
+    return result;
+}
+#endif
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setPskClientCallback)(TCN_STDARGS, jlong 
ctx, jobject selector)
+{
+#ifdef OPENSSL_NO_PSK
+    UNREFERENCED(o);
+    UNREFERENCED(ctx);
+    UNREFERENCED(selector);
+    tcn_Throw(e, "OpenSSL does not support PSK");
+#else
+    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, "selectClient", 
"(J[Ljava/lang/String;)[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_client_callback(c->ctx, selector == NULL ? NULL : 
SSL_psk_client);
+    if (c->psk_client_selector != NULL) {
+        (*e)->DeleteGlobalRef(e, c->psk_client_selector);
+    }
+    c->psk_client_selector = new_selector;
+    c->psk_client_selector_method = new_method;
+#endif
+}
+
 TCN_IMPLEMENT_CALL(void, SSLContext, setPskServerCallback)(TCN_STDARGS, jlong 
ctx, jobject selector)
 {
 #ifdef OPENSSL_NO_PSK
diff --git a/xdocs/miscellaneous/changelog.xml 
b/xdocs/miscellaneous/changelog.xml
index f25ec930b..ffc8e622a 100644
--- a/xdocs/miscellaneous/changelog.xml
+++ b/xdocs/miscellaneous/changelog.xml
@@ -37,6 +37,10 @@
       Add support for configuring server-side TLSv1.2 and TLSv1.3 pre-shared
       key callbacks. (markt)
     </add>
+    <add>
+      Add support for configuring client-side TLSv1.2 pre-shared key callback.
+      (markt)
+    </add>
   </changelog>
 </section>
 <section name="2.0.16" rtext="2026-09-07">


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

Reply via email to