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]
