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]
