From dda31e31ecc24b0bb773f47168b7b144978bbd66 Mon Sep 17 00:00:00 2001 From: Alex Choulos Date: Wed, 7 Oct 2026 12:08:44 -0400 Subject: [PATCH 1/5] Cache SSL_CREDENTIAL selected during handshake so getSelectedCredential works post-handshake SSL_get0_selected_credential reads the handshake state, which BoringSSL frees before SSL_do_handshake returns, so SSL.getSelectedCredential always returned 0 once the handshake completed. Sample the credential in ssl_info_callback on SSL_CB_HANDSHAKE_DONE, hold a ref in tcn_ssl_state_t and release it in free_ssl_state. On renegotiation the previous ref is released and replaced, so the cache reflects the most recent completed handshake. getSelectedCredential tries the live lookup first (non-NULL only while the handshake state exists) and falls back to the cached credential. --- .../java/io/netty/internal/tcnative/SSL.java | 5 ++++- openssl-dynamic/src/main/c/ssl.c | 14 ++++++++++++++ openssl-dynamic/src/main/c/ssl_private.h | 4 ++++ openssl-dynamic/src/main/c/sslcontext.c | 16 ++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java index f28df2b61..e2e496619 100644 --- a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java +++ b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java @@ -997,7 +997,10 @@ public static AsyncTask getAsyncTask(long ssl) { *

This is a BoringSSL-specific feature. See * SSL_get0_selected_credential * for detailed documentation.

- * + * + *

Once a handshake has completed this returns the credential selected during the most recent completed + * handshake. The returned pointer is borrowed and stays valid until {@link #freeSSL(long)}; do not free it.

+ * * @param ssl the SSL instance (SSL *) * @return the selected credential (SSL_CREDENTIAL *) or {@code 0} if none * @throws Exception if an error occurred diff --git a/openssl-dynamic/src/main/c/ssl.c b/openssl-dynamic/src/main/c/ssl.c index 3aec22371..93fa8f5b1 100644 --- a/openssl-dynamic/src/main/c/ssl.c +++ b/openssl-dynamic/src/main/c/ssl.c @@ -933,6 +933,13 @@ static void free_ssl_state(JNIEnv* e, tcn_ssl_state_t* state) { tcn_ssl_task_free(e, state->ssl_task); state->ssl_task = NULL; +#ifdef OPENSSL_IS_BORINGSSL + if (state->selected_credential != NULL) { + SSL_CREDENTIAL_free((SSL_CREDENTIAL*) state->selected_credential); + state->selected_credential = NULL; + } +#endif + // Free the tcn_ssl_state_t itself as it was allocated via OPENSSL_malloc(...) before // // https://github.com/netty/netty-tcnative/issues/532 @@ -2763,6 +2770,13 @@ TCN_IMPLEMENT_CALL(jlong, SSL, getSelectedCredential)(TCN_STDARGS, jlong ssl) { SSL *ssl_ = J2P(ssl, SSL *); TCN_CHECK_NULL(ssl_, ssl, 0); const SSL_CREDENTIAL* credential = SSL_get0_selected_credential(ssl_); + if (credential == NULL) { + // Handshake state is gone once the handshake completed, use the credential cached by ssl_info_callback. + tcn_ssl_state_t* state = tcn_SSL_get_app_state(ssl_); + if (state != NULL) { + credential = state->selected_credential; + } + } if (credential == NULL) { return 0; } diff --git a/openssl-dynamic/src/main/c/ssl_private.h b/openssl-dynamic/src/main/c/ssl_private.h index da52b9148..a05953009 100644 --- a/openssl-dynamic/src/main/c/ssl_private.h +++ b/openssl-dynamic/src/main/c/ssl_private.h @@ -411,6 +411,10 @@ struct tcn_ssl_state_t { // len < sk_CRYPTO_BUFFER_num(chain) check (both locals are 0/NULL there). int task_array_len; int task_chain_num; +#ifdef OPENSSL_IS_BORINGSSL + // Ref-counted credential sampled in SSL_CB_HANDSHAKE_DONE, as the selection is gone once the handshake completes. + const SSL_CREDENTIAL *selected_credential; +#endif }; #define TCN_GET_SSL_CTX(ssl, C) \ diff --git a/openssl-dynamic/src/main/c/sslcontext.c b/openssl-dynamic/src/main/c/sslcontext.c index d66538f1c..b3092f51e 100644 --- a/openssl-dynamic/src/main/c/sslcontext.c +++ b/openssl-dynamic/src/main/c/sslcontext.c @@ -189,6 +189,22 @@ static void ssl_info_callback(const SSL *ssl, int where, int ret) { state->handshakeCount++; } } +#ifdef OPENSSL_IS_BORINGSSL + if (0 != (where & SSL_CB_HANDSHAKE_DONE)) { + // The selection lives in the handshake state, which BoringSSL frees before SSL_do_handshake returns, so it + // must be sampled here. On renegotiation the previous ref is replaced by the most recent selection. + if ((state = tcn_SSL_get_app_state(ssl)) != NULL) { + const SSL_CREDENTIAL* credential = SSL_get0_selected_credential(ssl); + if (credential != NULL) { + SSL_CREDENTIAL_up_ref((SSL_CREDENTIAL*) credential); + if (state->selected_credential != NULL) { + SSL_CREDENTIAL_free((SSL_CREDENTIAL*) state->selected_credential); + } + state->selected_credential = credential; + } + } + } +#endif } /* Initialize server context */ From 3bbd9debb23d657e3fb2e0f5880292b23307ea38 Mon Sep 17 00:00:00 2001 From: Alex Choulos Date: Thu, 8 Oct 2026 14:55:56 -0400 Subject: [PATCH 2/5] Revert "Cache SSL_CREDENTIAL selected during handshake so getSelectedCredential works post-handshake" This reverts commit dda31e31ecc24b0bb773f47168b7b144978bbd66. --- .../java/io/netty/internal/tcnative/SSL.java | 5 +---- openssl-dynamic/src/main/c/ssl.c | 14 -------------- openssl-dynamic/src/main/c/ssl_private.h | 4 ---- openssl-dynamic/src/main/c/sslcontext.c | 16 ---------------- 4 files changed, 1 insertion(+), 38 deletions(-) diff --git a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java index e2e496619..f28df2b61 100644 --- a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java +++ b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java @@ -997,10 +997,7 @@ public static AsyncTask getAsyncTask(long ssl) { *

This is a BoringSSL-specific feature. See * SSL_get0_selected_credential * for detailed documentation.

- * - *

Once a handshake has completed this returns the credential selected during the most recent completed - * handshake. The returned pointer is borrowed and stays valid until {@link #freeSSL(long)}; do not free it.

- * + * * @param ssl the SSL instance (SSL *) * @return the selected credential (SSL_CREDENTIAL *) or {@code 0} if none * @throws Exception if an error occurred diff --git a/openssl-dynamic/src/main/c/ssl.c b/openssl-dynamic/src/main/c/ssl.c index 93fa8f5b1..3aec22371 100644 --- a/openssl-dynamic/src/main/c/ssl.c +++ b/openssl-dynamic/src/main/c/ssl.c @@ -933,13 +933,6 @@ static void free_ssl_state(JNIEnv* e, tcn_ssl_state_t* state) { tcn_ssl_task_free(e, state->ssl_task); state->ssl_task = NULL; -#ifdef OPENSSL_IS_BORINGSSL - if (state->selected_credential != NULL) { - SSL_CREDENTIAL_free((SSL_CREDENTIAL*) state->selected_credential); - state->selected_credential = NULL; - } -#endif - // Free the tcn_ssl_state_t itself as it was allocated via OPENSSL_malloc(...) before // // https://github.com/netty/netty-tcnative/issues/532 @@ -2770,13 +2763,6 @@ TCN_IMPLEMENT_CALL(jlong, SSL, getSelectedCredential)(TCN_STDARGS, jlong ssl) { SSL *ssl_ = J2P(ssl, SSL *); TCN_CHECK_NULL(ssl_, ssl, 0); const SSL_CREDENTIAL* credential = SSL_get0_selected_credential(ssl_); - if (credential == NULL) { - // Handshake state is gone once the handshake completed, use the credential cached by ssl_info_callback. - tcn_ssl_state_t* state = tcn_SSL_get_app_state(ssl_); - if (state != NULL) { - credential = state->selected_credential; - } - } if (credential == NULL) { return 0; } diff --git a/openssl-dynamic/src/main/c/ssl_private.h b/openssl-dynamic/src/main/c/ssl_private.h index a05953009..da52b9148 100644 --- a/openssl-dynamic/src/main/c/ssl_private.h +++ b/openssl-dynamic/src/main/c/ssl_private.h @@ -411,10 +411,6 @@ struct tcn_ssl_state_t { // len < sk_CRYPTO_BUFFER_num(chain) check (both locals are 0/NULL there). int task_array_len; int task_chain_num; -#ifdef OPENSSL_IS_BORINGSSL - // Ref-counted credential sampled in SSL_CB_HANDSHAKE_DONE, as the selection is gone once the handshake completes. - const SSL_CREDENTIAL *selected_credential; -#endif }; #define TCN_GET_SSL_CTX(ssl, C) \ diff --git a/openssl-dynamic/src/main/c/sslcontext.c b/openssl-dynamic/src/main/c/sslcontext.c index b3092f51e..d66538f1c 100644 --- a/openssl-dynamic/src/main/c/sslcontext.c +++ b/openssl-dynamic/src/main/c/sslcontext.c @@ -189,22 +189,6 @@ static void ssl_info_callback(const SSL *ssl, int where, int ret) { state->handshakeCount++; } } -#ifdef OPENSSL_IS_BORINGSSL - if (0 != (where & SSL_CB_HANDSHAKE_DONE)) { - // The selection lives in the handshake state, which BoringSSL frees before SSL_do_handshake returns, so it - // must be sampled here. On renegotiation the previous ref is replaced by the most recent selection. - if ((state = tcn_SSL_get_app_state(ssl)) != NULL) { - const SSL_CREDENTIAL* credential = SSL_get0_selected_credential(ssl); - if (credential != NULL) { - SSL_CREDENTIAL_up_ref((SSL_CREDENTIAL*) credential); - if (state->selected_credential != NULL) { - SSL_CREDENTIAL_free((SSL_CREDENTIAL*) state->selected_credential); - } - state->selected_credential = credential; - } - } - } -#endif } /* Initialize server context */ From 5acfd5f929a8cb5501f9cd4961838462964b46fa Mon Sep 17 00:00:00 2001 From: Alex Choulos Date: Thu, 8 Oct 2026 14:59:51 -0400 Subject: [PATCH 3/5] Tag SSL_CREDENTIALs with an id and record the selected id at handshake completion --- .../tcnative/SSLCredentialIdTest.java | 98 +++++++++++++++++++ .../java/io/netty/internal/tcnative/SSL.java | 20 ++++ .../internal/tcnative/SSLCredential.java | 48 ++++++++- openssl-dynamic/src/main/c/ssl.c | 19 ++++ openssl-dynamic/src/main/c/ssl_private.h | 3 + openssl-dynamic/src/main/c/sslcontext.c | 10 ++ openssl-dynamic/src/main/c/sslcredential.c | 62 +++++++++++- openssl-dynamic/src/main/c/sslcredential.h | 4 + 8 files changed, 258 insertions(+), 6 deletions(-) create mode 100644 boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java diff --git a/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java b/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java new file mode 100644 index 000000000..5f1963fa7 --- /dev/null +++ b/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2026 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.internal.tcnative; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SSLCredentialIdTest { + + @BeforeAll + public static void loadNativeLib() throws Exception { + String testClassesRoot = SSLCredentialIdTest.class.getProtectionDomain().getCodeSource().getLocation().getFile(); + File[] directories = new File(testClassesRoot + File.separator + "META-INF" + File.separator + "native") + .listFiles(); + if (directories == null || directories.length != 1) { + throw new IllegalStateException("Could not find platform specific native directory"); + } + String libName = System.mapLibraryName("netty_tcnative") + // Fix the filename (this is needed for macOS). + .replace(".dylib", ".jnilib"); + System.load(directories[0].getAbsoluteFile() + File.separator + libName); + Library.initialize(); + } + + @Test + public void newCredentialsHavePositiveDistinctIds() throws Exception { + long x509 = SSLCredential.newX509(); + long delegated = SSLCredential.newDelegated(); + try { + long x509Id = SSLCredential.getId(x509); + long delegatedId = SSLCredential.getId(delegated); + assertTrue(x509Id > 0); + assertTrue(delegatedId > 0); + assertNotEquals(x509Id, delegatedId); + } finally { + SSLCredential.free(x509); + SSLCredential.free(delegated); + } + } + + @Test + public void idCannotBeReassigned() throws Exception { + long cred = SSLCredential.newX509(); + try { + long id = SSLCredential.getId(cred); + assertThrows(IllegalStateException.class, () -> SSLCredential.setId0(cred, id + 1)); + assertEquals(id, SSLCredential.getId(cred)); + } finally { + SSLCredential.free(cred); + } + } + + @Test + public void nonPositiveIdIsRejected() throws Exception { + long cred = SSLCredential.newX509(); + try { + assertThrows(IllegalArgumentException.class, () -> SSLCredential.setId0(cred, 0)); + assertThrows(IllegalArgumentException.class, () -> SSLCredential.setId0(cred, -1)); + } finally { + SSLCredential.free(cred); + } + } + + @Test + public void freshSslHasNoSelectedCredentialId() throws Exception { + long ctx = SSLContext.make(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_MODE_SERVER); + try { + long ssl = SSL.newSSL(ctx, true); + try { + assertEquals(0, SSL.getSelectedCredentialId(ssl)); + } finally { + SSL.freeSSL(ssl); + } + } finally { + SSLContext.free(ctx); + } + } +} diff --git a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java index f28df2b61..370861cec 100644 --- a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java +++ b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java @@ -1004,6 +1004,26 @@ public static AsyncTask getAsyncTask(long ssl) { */ public static native long getSelectedCredential(long ssl) throws Exception; + /** + * Get the id of the credential selected for an SSL instance, as assigned by {@link SSLCredential#newX509()} or + * {@link SSLCredential#newDelegated()}. + * + *

Returns: + *

    + *
  • during a handshake that has already selected a credential, the id of that credential;
  • + *
  • otherwise, the id recorded by the most recent completed handshake;
  • + *
  • {@code 0} when no credential was selected, when the credential came from a legacy API, or when no + * handshake has completed yet.
  • + *
+ * + *

This is a BoringSSL-specific feature.

+ * + * @param ssl the SSL instance (SSL *) + * @return the selected credential id, or {@code 0} + * @throws Exception if an error occurred + */ + public static native long getSelectedCredentialId(long ssl) throws Exception; + /** * Get the name of the group used by ssl's most recently completed handshake, or {@code null} if not applicable. * diff --git a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLCredential.java b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLCredential.java index c0581085c..f908d22db 100644 --- a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLCredential.java +++ b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLCredential.java @@ -15,6 +15,8 @@ */ package io.netty.internal.tcnative; +import java.util.concurrent.atomic.AtomicLong; + /** * SSL_CREDENTIAL management for BoringSSL. * @@ -32,6 +34,8 @@ */ public final class SSLCredential { + private static final AtomicLong NEXT_ID = new AtomicLong(); + private SSLCredential() { } /** @@ -44,7 +48,43 @@ private SSLCredential() { } * @return the SSL_CREDENTIAL instance (SSL_CREDENTIAL *) * @throws Exception if an error occurred */ - public static native long newX509() throws Exception; + public static long newX509() throws Exception { + return assignId(newX509Native()); + } + + private static native long newX509Native() throws Exception; + + private static long assignId(long cred) throws Exception { + try { + setId0(cred, NEXT_ID.incrementAndGet()); + } catch (Throwable t) { + // Don't leak the credential if tagging fails. + free(cred); + throw t; + } + return cred; + } + + /** + * Assign the id of an SSL_CREDENTIAL. A credential can only be assigned an id once. + * + * @param cred the SSL_CREDENTIAL instance (SSL_CREDENTIAL *) + * @param id the id, which must be positive + * @throws IllegalArgumentException if {@code id} is not positive + * @throws IllegalStateException if the credential already has an id + */ + static native void setId0(long cred, long id); + + /** + * Get the id that was assigned to an SSL_CREDENTIAL when it was created by {@link #newX509()} or + * {@link #newDelegated()}. + * + *

This is a BoringSSL-specific feature.

+ * + * @param cred the SSL_CREDENTIAL instance (SSL_CREDENTIAL *) + * @return the id, or {@code 0} if none was assigned + */ + public static native long getId(long cred); /** * Increment the reference count of an SSL_CREDENTIAL. @@ -184,7 +224,11 @@ private SSLCredential() { } * @return the delegated SSL_CREDENTIAL instance (SSL_CREDENTIAL *) * @throws Exception if an error occurred */ - public static native long newDelegated() throws Exception; + public static long newDelegated() throws Exception { + return assignId(newDelegatedNative()); + } + + private static native long newDelegatedNative() throws Exception; /** * Set the delegated credential for an SSL_CREDENTIAL. diff --git a/openssl-dynamic/src/main/c/ssl.c b/openssl-dynamic/src/main/c/ssl.c index 3aec22371..2f2b18b38 100644 --- a/openssl-dynamic/src/main/c/ssl.c +++ b/openssl-dynamic/src/main/c/ssl.c @@ -44,6 +44,7 @@ #include "apr_portable.h" #include "ssl_private.h" #include "ssl.h" +#include "sslcredential.h" #define SSL_CLASSNAME "io/netty/internal/tcnative/SSL" @@ -2773,6 +2774,23 @@ TCN_IMPLEMENT_CALL(jlong, SSL, getSelectedCredential)(TCN_STDARGS, jlong ssl) { #endif } +TCN_IMPLEMENT_CALL(jlong, SSL, getSelectedCredentialId)(TCN_STDARGS, jlong ssl) { +#ifdef OPENSSL_IS_BORINGSSL + SSL *ssl_ = J2P(ssl, SSL *); + TCN_CHECK_NULL(ssl_, ssl, 0); + const SSL_CREDENTIAL* credential = SSL_get0_selected_credential(ssl_); + if (credential != NULL) { + // Handshake in progress: report the live selection. + return tcn_SSL_CREDENTIAL_get_id(credential); + } + tcn_ssl_state_t* state = tcn_SSL_get_app_state(ssl_); + return state == NULL ? 0 : state->selected_credential_id; +#else + tcn_ThrowUnsupportedOperationException(e, "SSL_CREDENTIAL API not available."); + return 0; +#endif +} + // JNI Method Registration Table Begin static const JNINativeMethod method_table[] = { { TCN_METHOD_TABLE_ENTRY(bioLengthByteBuffer, (J)I, SSL) }, @@ -2854,6 +2872,7 @@ static const JNINativeMethod method_table[] = { { TCN_METHOD_TABLE_ENTRY(setRenegotiateMode, (JI)V, SSL) }, { TCN_METHOD_TABLE_ENTRY(addCredential, (JJ)V, SSL) }, { TCN_METHOD_TABLE_ENTRY(getSelectedCredential, (J)J, SSL) }, + { TCN_METHOD_TABLE_ENTRY(getSelectedCredentialId, (J)J, SSL) }, { TCN_METHOD_TABLE_ENTRY(getGroupName, (J)Ljava/lang/String;, SSL) } }; diff --git a/openssl-dynamic/src/main/c/ssl_private.h b/openssl-dynamic/src/main/c/ssl_private.h index da52b9148..d065936bb 100644 --- a/openssl-dynamic/src/main/c/ssl_private.h +++ b/openssl-dynamic/src/main/c/ssl_private.h @@ -411,6 +411,9 @@ struct tcn_ssl_state_t { // len < sk_CRYPTO_BUFFER_num(chain) check (both locals are 0/NULL there). int task_array_len; int task_chain_num; +#ifdef OPENSSL_IS_BORINGSSL + jlong selected_credential_id; +#endif }; #define TCN_GET_SSL_CTX(ssl, C) \ diff --git a/openssl-dynamic/src/main/c/sslcontext.c b/openssl-dynamic/src/main/c/sslcontext.c index d66538f1c..911fe7a5b 100644 --- a/openssl-dynamic/src/main/c/sslcontext.c +++ b/openssl-dynamic/src/main/c/sslcontext.c @@ -37,6 +37,7 @@ #include "ssl_private.h" #include #include "sslcontext.h" +#include "sslcredential.h" #include "cert_compress.h" #define SSLCONTEXT_CLASSNAME "io/netty/internal/tcnative/SSLContext" @@ -189,6 +190,15 @@ static void ssl_info_callback(const SSL *ssl, int where, int ret) { state->handshakeCount++; } } +#ifdef OPENSSL_IS_BORINGSSL + if (0 != (where & SSL_CB_HANDSHAKE_DONE)) { + // BoringSSL frees the handshake state (and its selected credential) before SSL_do_handshake returns, + // so record the selection here. Assign unconditionally so a later handshake without one clears it. + if ((state = tcn_SSL_get_app_state(ssl)) != NULL) { + state->selected_credential_id = tcn_SSL_CREDENTIAL_get_id(SSL_get0_selected_credential(ssl)); + } + } +#endif } /* Initialize server context */ diff --git a/openssl-dynamic/src/main/c/sslcredential.c b/openssl-dynamic/src/main/c/sslcredential.c index 18f25709c..eab9e5ebd 100644 --- a/openssl-dynamic/src/main/c/sslcredential.c +++ b/openssl-dynamic/src/main/c/sslcredential.c @@ -37,12 +37,24 @@ static void throw_openssl_error(JNIEnv* env, const char* msg) { ERR_error_string_n(err, err_buf, sizeof(err_buf)); tcn_Throw(env, "%s: %s", msg, err_buf); } + +static int tcn_SSL_CREDENTIAL_id_idx = -1; + +typedef char tcn_SSL_CREDENTIAL_id_requires_64bit_pointers[sizeof(void*) >= sizeof(jlong) ? 1 : -1]; + +// The id is a scalar packed into the ex_data slot, so no free callback is needed. +jlong tcn_SSL_CREDENTIAL_get_id(const SSL_CREDENTIAL* cred) { + if (cred == NULL || tcn_SSL_CREDENTIAL_id_idx < 0) { + return 0; + } + return (jlong)(intptr_t) SSL_CREDENTIAL_get_ex_data(cred, tcn_SSL_CREDENTIAL_id_idx); +} #endif // Core SSL_CREDENTIAL functions -TCN_IMPLEMENT_CALL(jlong, SSLCredential, newX509)(TCN_STDARGS) { +TCN_IMPLEMENT_CALL(jlong, SSLCredential, newX509Native)(TCN_STDARGS) { #ifdef OPENSSL_IS_BORINGSSL SSL_CREDENTIAL* cred = SSL_CREDENTIAL_new_x509(); TCN_CHECK_NULL(cred, credential, 0); @@ -74,6 +86,40 @@ TCN_IMPLEMENT_CALL(void, SSLCredential, free)(TCN_STDARGS, jlong cred) { #endif } +TCN_IMPLEMENT_CALL(void, SSLCredential, setId0)(TCN_STDARGS, jlong cred, jlong id) { +#ifdef OPENSSL_IS_BORINGSSL + SSL_CREDENTIAL* c = (SSL_CREDENTIAL*)(intptr_t)cred; + TCN_CHECK_NULL(c, credential, /* void */); + if (id <= 0) { + tcn_ThrowIllegalArgumentException(e, "credential id must be positive"); + return; + } + if (tcn_SSL_CREDENTIAL_get_id(c) != 0) { + jclass ise = (*e)->FindClass(e, "java/lang/IllegalStateException"); + if (ise != NULL) { + (*e)->ThrowNew(e, ise, "credential already has an id"); + } + return; + } + if (!SSL_CREDENTIAL_set_ex_data(c, tcn_SSL_CREDENTIAL_id_idx, (void*)(intptr_t) id)) { + throw_openssl_error(e, "Failed to set SSL_CREDENTIAL id"); + } +#else + tcn_ThrowUnsupportedOperationException(e, "SSL_CREDENTIAL API not available."); +#endif +} + +TCN_IMPLEMENT_CALL(jlong, SSLCredential, getId)(TCN_STDARGS, jlong cred) { +#ifdef OPENSSL_IS_BORINGSSL + SSL_CREDENTIAL* c = (SSL_CREDENTIAL*)(intptr_t)cred; + TCN_CHECK_NULL(c, credential, 0); + return tcn_SSL_CREDENTIAL_get_id(c); +#else + tcn_ThrowUnsupportedOperationException(e, "SSL_CREDENTIAL API not available."); + return 0; +#endif +} + // SSL_CREDENTIAL configuration methods TCN_IMPLEMENT_CALL(void, SSLCredential, setPrivateKey)(TCN_STDARGS, jlong cred, jlong key) { #ifdef OPENSSL_IS_BORINGSSL @@ -294,7 +340,7 @@ TCN_IMPLEMENT_CALL(void, SSLCredential, setTrustAnchorId)(TCN_STDARGS, jlong cre } // Delegated credentials -TCN_IMPLEMENT_CALL(jlong, SSLCredential, newDelegated)(TCN_STDARGS) { +TCN_IMPLEMENT_CALL(jlong, SSLCredential, newDelegatedNative)(TCN_STDARGS) { #ifdef OPENSSL_IS_BORINGSSL SSL_CREDENTIAL* credential = SSL_CREDENTIAL_new_delegated(); if (credential == NULL) { @@ -343,9 +389,11 @@ TCN_IMPLEMENT_CALL(void, SSLCredential, setDelegatedCredential)(TCN_STDARGS, jlo // JNI Method Registration Table Begin static const JNINativeMethod method_table[] = { // Core functions - { TCN_METHOD_TABLE_ENTRY(newX509, ()J, SSLCredential) }, + { TCN_METHOD_TABLE_ENTRY(newX509Native, ()J, SSLCredential) }, { TCN_METHOD_TABLE_ENTRY(upRef, (J)V, SSLCredential) }, { TCN_METHOD_TABLE_ENTRY(free, (J)V, SSLCredential) }, + { TCN_METHOD_TABLE_ENTRY(setId0, (JJ)V, SSLCredential) }, + { TCN_METHOD_TABLE_ENTRY(getId, (J)J, SSLCredential) }, // Configuration { TCN_METHOD_TABLE_ENTRY(setPrivateKey, (JJ)V, SSLCredential) }, @@ -360,7 +408,7 @@ static const JNINativeMethod method_table[] = { { TCN_METHOD_TABLE_ENTRY(setTrustAnchorId, (J[B)V, SSLCredential) }, // Delegated credentials - { TCN_METHOD_TABLE_ENTRY(newDelegated, ()J, SSLCredential) }, + { TCN_METHOD_TABLE_ENTRY(newDelegatedNative, ()J, SSLCredential) }, { TCN_METHOD_TABLE_ENTRY(setDelegatedCredential, (J[B)V, SSLCredential) } }; @@ -371,6 +419,12 @@ static const jint method_table_size = sizeof(method_table) / sizeof(method_table // IMPORTANT: If you add any NETTY_JNI_UTIL_LOAD_CLASS or NETTY_JNI_UTIL_FIND_CLASS calls you also need to update // Library to reflect that. jint netty_internal_tcnative_SSLCredential_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) { +#ifdef OPENSSL_IS_BORINGSSL + tcn_SSL_CREDENTIAL_id_idx = SSL_CREDENTIAL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + if (tcn_SSL_CREDENTIAL_id_idx < 0) { + return JNI_ERR; + } +#endif if (netty_jni_util_register_natives(env, packagePrefix, SSLCREDENTIAL_CLASSNAME, diff --git a/openssl-dynamic/src/main/c/sslcredential.h b/openssl-dynamic/src/main/c/sslcredential.h index c38eb8e7b..942ab4b4b 100644 --- a/openssl-dynamic/src/main/c/sslcredential.h +++ b/openssl-dynamic/src/main/c/sslcredential.h @@ -27,6 +27,10 @@ extern "C" { jint netty_internal_tcnative_SSLCredential_JNI_OnLoad(JNIEnv* env, const char* packagePrefix); void netty_internal_tcnative_SSLCredential_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix); +#ifdef OPENSSL_IS_BORINGSSL +jlong tcn_SSL_CREDENTIAL_get_id(const SSL_CREDENTIAL* cred); +#endif + #ifdef __cplusplus } #endif From 86cecc7ebe694a8738a471b85375fa91b58c724a Mon Sep 17 00:00:00 2001 From: Alex Choulos Date: Fri, 9 Oct 2026 11:48:52 -0400 Subject: [PATCH 4/5] Assign credential ids inside newX509/newDelegated so no existing native is renamed --- .../tcnative/SSLCredentialIdTest.java | 24 -------- .../internal/tcnative/SSLCredential.java | 37 +------------ openssl-dynamic/src/main/c/sslcredential.c | 55 ++++++++----------- 3 files changed, 24 insertions(+), 92 deletions(-) diff --git a/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java b/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java index 5f1963fa7..dce8c22ad 100644 --- a/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java +++ b/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java @@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; public class SSLCredentialIdTest { @@ -58,29 +57,6 @@ public void newCredentialsHavePositiveDistinctIds() throws Exception { } } - @Test - public void idCannotBeReassigned() throws Exception { - long cred = SSLCredential.newX509(); - try { - long id = SSLCredential.getId(cred); - assertThrows(IllegalStateException.class, () -> SSLCredential.setId0(cred, id + 1)); - assertEquals(id, SSLCredential.getId(cred)); - } finally { - SSLCredential.free(cred); - } - } - - @Test - public void nonPositiveIdIsRejected() throws Exception { - long cred = SSLCredential.newX509(); - try { - assertThrows(IllegalArgumentException.class, () -> SSLCredential.setId0(cred, 0)); - assertThrows(IllegalArgumentException.class, () -> SSLCredential.setId0(cred, -1)); - } finally { - SSLCredential.free(cred); - } - } - @Test public void freshSslHasNoSelectedCredentialId() throws Exception { long ctx = SSLContext.make(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_MODE_SERVER); diff --git a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLCredential.java b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLCredential.java index f908d22db..0e3dde6fd 100644 --- a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLCredential.java +++ b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLCredential.java @@ -15,8 +15,6 @@ */ package io.netty.internal.tcnative; -import java.util.concurrent.atomic.AtomicLong; - /** * SSL_CREDENTIAL management for BoringSSL. * @@ -34,8 +32,6 @@ */ public final class SSLCredential { - private static final AtomicLong NEXT_ID = new AtomicLong(); - private SSLCredential() { } /** @@ -48,32 +44,7 @@ private SSLCredential() { } * @return the SSL_CREDENTIAL instance (SSL_CREDENTIAL *) * @throws Exception if an error occurred */ - public static long newX509() throws Exception { - return assignId(newX509Native()); - } - - private static native long newX509Native() throws Exception; - - private static long assignId(long cred) throws Exception { - try { - setId0(cred, NEXT_ID.incrementAndGet()); - } catch (Throwable t) { - // Don't leak the credential if tagging fails. - free(cred); - throw t; - } - return cred; - } - - /** - * Assign the id of an SSL_CREDENTIAL. A credential can only be assigned an id once. - * - * @param cred the SSL_CREDENTIAL instance (SSL_CREDENTIAL *) - * @param id the id, which must be positive - * @throws IllegalArgumentException if {@code id} is not positive - * @throws IllegalStateException if the credential already has an id - */ - static native void setId0(long cred, long id); + public static native long newX509() throws Exception; /** * Get the id that was assigned to an SSL_CREDENTIAL when it was created by {@link #newX509()} or @@ -224,11 +195,7 @@ private static long assignId(long cred) throws Exception { * @return the delegated SSL_CREDENTIAL instance (SSL_CREDENTIAL *) * @throws Exception if an error occurred */ - public static long newDelegated() throws Exception { - return assignId(newDelegatedNative()); - } - - private static native long newDelegatedNative() throws Exception; + public static native long newDelegated() throws Exception; /** * Set the delegated credential for an SSL_CREDENTIAL. diff --git a/openssl-dynamic/src/main/c/sslcredential.c b/openssl-dynamic/src/main/c/sslcredential.c index eab9e5ebd..a7e6b05b8 100644 --- a/openssl-dynamic/src/main/c/sslcredential.c +++ b/openssl-dynamic/src/main/c/sslcredential.c @@ -24,6 +24,7 @@ #include "tcn.h" +#include "apr_atomic.h" #include "ssl_private.h" #include "sslcredential.h" @@ -39,26 +40,38 @@ static void throw_openssl_error(JNIEnv* env, const char* msg) { } static int tcn_SSL_CREDENTIAL_id_idx = -1; - -typedef char tcn_SSL_CREDENTIAL_id_requires_64bit_pointers[sizeof(void*) >= sizeof(jlong) ? 1 : -1]; +static volatile apr_uint32_t tcn_SSL_CREDENTIAL_next_id = 0; // The id is a scalar packed into the ex_data slot, so no free callback is needed. jlong tcn_SSL_CREDENTIAL_get_id(const SSL_CREDENTIAL* cred) { if (cred == NULL || tcn_SSL_CREDENTIAL_id_idx < 0) { return 0; } - return (jlong)(intptr_t) SSL_CREDENTIAL_get_ex_data(cred, tcn_SSL_CREDENTIAL_id_idx); + return (jlong)(uintptr_t) SSL_CREDENTIAL_get_ex_data(cred, tcn_SSL_CREDENTIAL_id_idx); +} + +static SSL_CREDENTIAL* assign_id(JNIEnv* e, SSL_CREDENTIAL* cred) { + apr_uint32_t id; + do { + id = apr_atomic_inc32(&tcn_SSL_CREDENTIAL_next_id) + 1; + } while (id == 0); + if (!SSL_CREDENTIAL_set_ex_data(cred, tcn_SSL_CREDENTIAL_id_idx, (void*)(uintptr_t) id)) { + SSL_CREDENTIAL_free(cred); + throw_openssl_error(e, "Failed to set SSL_CREDENTIAL id"); + return NULL; + } + return cred; } #endif // Core SSL_CREDENTIAL functions -TCN_IMPLEMENT_CALL(jlong, SSLCredential, newX509Native)(TCN_STDARGS) { +TCN_IMPLEMENT_CALL(jlong, SSLCredential, newX509)(TCN_STDARGS) { #ifdef OPENSSL_IS_BORINGSSL SSL_CREDENTIAL* cred = SSL_CREDENTIAL_new_x509(); TCN_CHECK_NULL(cred, credential, 0); - return (jlong)(intptr_t)cred; + return (jlong)(intptr_t)assign_id(e, cred); #else tcn_ThrowUnsupportedOperationException(e, "SSL_CREDENTIAL API not available."); return 0; @@ -86,29 +99,6 @@ TCN_IMPLEMENT_CALL(void, SSLCredential, free)(TCN_STDARGS, jlong cred) { #endif } -TCN_IMPLEMENT_CALL(void, SSLCredential, setId0)(TCN_STDARGS, jlong cred, jlong id) { -#ifdef OPENSSL_IS_BORINGSSL - SSL_CREDENTIAL* c = (SSL_CREDENTIAL*)(intptr_t)cred; - TCN_CHECK_NULL(c, credential, /* void */); - if (id <= 0) { - tcn_ThrowIllegalArgumentException(e, "credential id must be positive"); - return; - } - if (tcn_SSL_CREDENTIAL_get_id(c) != 0) { - jclass ise = (*e)->FindClass(e, "java/lang/IllegalStateException"); - if (ise != NULL) { - (*e)->ThrowNew(e, ise, "credential already has an id"); - } - return; - } - if (!SSL_CREDENTIAL_set_ex_data(c, tcn_SSL_CREDENTIAL_id_idx, (void*)(intptr_t) id)) { - throw_openssl_error(e, "Failed to set SSL_CREDENTIAL id"); - } -#else - tcn_ThrowUnsupportedOperationException(e, "SSL_CREDENTIAL API not available."); -#endif -} - TCN_IMPLEMENT_CALL(jlong, SSLCredential, getId)(TCN_STDARGS, jlong cred) { #ifdef OPENSSL_IS_BORINGSSL SSL_CREDENTIAL* c = (SSL_CREDENTIAL*)(intptr_t)cred; @@ -340,14 +330,14 @@ TCN_IMPLEMENT_CALL(void, SSLCredential, setTrustAnchorId)(TCN_STDARGS, jlong cre } // Delegated credentials -TCN_IMPLEMENT_CALL(jlong, SSLCredential, newDelegatedNative)(TCN_STDARGS) { +TCN_IMPLEMENT_CALL(jlong, SSLCredential, newDelegated)(TCN_STDARGS) { #ifdef OPENSSL_IS_BORINGSSL SSL_CREDENTIAL* credential = SSL_CREDENTIAL_new_delegated(); if (credential == NULL) { throw_openssl_error(e, "Failed to create delegated SSL_CREDENTIAL"); return 0; } - return (jlong)(intptr_t)credential; + return (jlong)(intptr_t)assign_id(e, credential); #else tcn_ThrowUnsupportedOperationException(e, "SSL_CREDENTIAL API not available."); return 0; @@ -389,10 +379,9 @@ TCN_IMPLEMENT_CALL(void, SSLCredential, setDelegatedCredential)(TCN_STDARGS, jlo // JNI Method Registration Table Begin static const JNINativeMethod method_table[] = { // Core functions - { TCN_METHOD_TABLE_ENTRY(newX509Native, ()J, SSLCredential) }, + { TCN_METHOD_TABLE_ENTRY(newX509, ()J, SSLCredential) }, { TCN_METHOD_TABLE_ENTRY(upRef, (J)V, SSLCredential) }, { TCN_METHOD_TABLE_ENTRY(free, (J)V, SSLCredential) }, - { TCN_METHOD_TABLE_ENTRY(setId0, (JJ)V, SSLCredential) }, { TCN_METHOD_TABLE_ENTRY(getId, (J)J, SSLCredential) }, // Configuration @@ -408,7 +397,7 @@ static const JNINativeMethod method_table[] = { { TCN_METHOD_TABLE_ENTRY(setTrustAnchorId, (J[B)V, SSLCredential) }, // Delegated credentials - { TCN_METHOD_TABLE_ENTRY(newDelegatedNative, ()J, SSLCredential) }, + { TCN_METHOD_TABLE_ENTRY(newDelegated, ()J, SSLCredential) }, { TCN_METHOD_TABLE_ENTRY(setDelegatedCredential, (J[B)V, SSLCredential) } }; From f39f2e1fa60ede7a5577d8b6ed8d370a0b525576 Mon Sep 17 00:00:00 2001 From: Alex Choulos Date: Fri, 9 Oct 2026 15:21:19 -0400 Subject: [PATCH 5/5] Make recording the selected credential opt-in per context and store the id in existing padding --- .../internal/tcnative/SSLCredentialIdTest.java | 1 + .../main/java/io/netty/internal/tcnative/SSL.java | 7 ++++--- .../io/netty/internal/tcnative/SSLContext.java | 11 +++++++++++ openssl-dynamic/src/main/c/ssl_private.h | 6 +++--- openssl-dynamic/src/main/c/sslcontext.c | 14 ++++++++++++-- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java b/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java index dce8c22ad..f7cf24d37 100644 --- a/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java +++ b/boringssl-static/src/test/java/io/netty/internal/tcnative/SSLCredentialIdTest.java @@ -61,6 +61,7 @@ public void newCredentialsHavePositiveDistinctIds() throws Exception { public void freshSslHasNoSelectedCredentialId() throws Exception { long ctx = SSLContext.make(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_MODE_SERVER); try { + SSLContext.setRecordSelectedCredential(ctx, true); long ssl = SSL.newSSL(ctx, true); try { assertEquals(0, SSL.getSelectedCredentialId(ssl)); diff --git a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java index 370861cec..93a818dc2 100644 --- a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java +++ b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java @@ -1011,9 +1011,10 @@ public static AsyncTask getAsyncTask(long ssl) { *

Returns: *

    *
  • during a handshake that has already selected a credential, the id of that credential;
  • - *
  • otherwise, the id recorded by the most recent completed handshake;
  • - *
  • {@code 0} when no credential was selected, when the credential came from a legacy API, or when no - * handshake has completed yet.
  • + *
  • otherwise, the id recorded by the most recent completed handshake, if + * {@link SSLContext#setRecordSelectedCredential(long, boolean)} is enabled;
  • + *
  • {@code 0} when no credential was selected, when the credential came from a legacy API, when recording is + * disabled, or when no handshake has completed yet.
  • *
* *

This is a BoringSSL-specific feature.

diff --git a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLContext.java b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLContext.java index 6d5d2e70e..f4a995dff 100644 --- a/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLContext.java +++ b/openssl-classes/src/main/java/io/netty/internal/tcnative/SSLContext.java @@ -683,6 +683,17 @@ public static void setAlpnProtos(long ctx, String[] alpnProtos, int selectorFail */ public static native void setUseTasks(long ctx, boolean useTasks); + /** + * Enable or disable recording the credential selected by each completed handshake, so that it can be obtained + * via {@link SSL#getSelectedCredentialId(long)} after the handshake. Disabled by default. + * + *

This only has an effect when using BoringSSL.

+ * + * @param ctx context to use + * @param record {@code true} to enable, {@code false} to disable. + */ + public static native void setRecordSelectedCredential(long ctx, boolean record); + /** * Adds a certificate compression algorithm to the given {@link SSLContext} or throws an * exception if certificate compression is not supported or the algorithm not recognized. diff --git a/openssl-dynamic/src/main/c/ssl_private.h b/openssl-dynamic/src/main/c/ssl_private.h index d065936bb..47e936564 100644 --- a/openssl-dynamic/src/main/c/ssl_private.h +++ b/openssl-dynamic/src/main/c/ssl_private.h @@ -389,6 +389,7 @@ struct tcn_ssl_ctxt_t { unsigned char context_id[SHA_DIGEST_LENGTH]; int use_tasks; + int record_selected_credential; }; // Store the callback to run and also if it was consumed via SSL.getTask(...). @@ -404,6 +405,8 @@ void tcn_ssl_task_free(JNIEnv*, tcn_ssl_task_t*); typedef struct tcn_ssl_state_t tcn_ssl_state_t; struct tcn_ssl_state_t { int handshakeCount; + // Fills the padding before ctx so recording the selected credential adds no per-connection memory. + apr_uint32_t selected_credential_id; tcn_ssl_ctxt_t *ctx; tcn_ssl_task_t* ssl_task; tcn_ssl_verify_config_t verify_config; @@ -411,9 +414,6 @@ struct tcn_ssl_state_t { // len < sk_CRYPTO_BUFFER_num(chain) check (both locals are 0/NULL there). int task_array_len; int task_chain_num; -#ifdef OPENSSL_IS_BORINGSSL - jlong selected_credential_id; -#endif }; #define TCN_GET_SSL_CTX(ssl, C) \ diff --git a/openssl-dynamic/src/main/c/sslcontext.c b/openssl-dynamic/src/main/c/sslcontext.c index 911fe7a5b..c96fbd628 100644 --- a/openssl-dynamic/src/main/c/sslcontext.c +++ b/openssl-dynamic/src/main/c/sslcontext.c @@ -194,8 +194,9 @@ static void ssl_info_callback(const SSL *ssl, int where, int ret) { if (0 != (where & SSL_CB_HANDSHAKE_DONE)) { // BoringSSL frees the handshake state (and its selected credential) before SSL_do_handshake returns, // so record the selection here. Assign unconditionally so a later handshake without one clears it. - if ((state = tcn_SSL_get_app_state(ssl)) != NULL) { - state->selected_credential_id = tcn_SSL_CREDENTIAL_get_id(SSL_get0_selected_credential(ssl)); + if ((state = tcn_SSL_get_app_state(ssl)) != NULL && state->ctx->record_selected_credential != 0) { + state->selected_credential_id = + (apr_uint32_t) tcn_SSL_CREDENTIAL_get_id(SSL_get0_selected_credential(ssl)); } } #endif @@ -2917,6 +2918,14 @@ TCN_IMPLEMENT_CALL(void, SSLContext, setUseTasks)(TCN_STDARGS, jlong ctx, jboole c->use_tasks = useTasks == JNI_TRUE ? 1 : 0; } +TCN_IMPLEMENT_CALL(void, SSLContext, setRecordSelectedCredential)(TCN_STDARGS, jlong ctx, jboolean record) { + tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); + + TCN_CHECK_NULL(c, ctx, /* void */); + + c->record_selected_credential = record == JNI_TRUE ? 1 : 0; +} + TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCurvesList0)(TCN_STDARGS, jlong ctx, jstring curves) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); @@ -3113,6 +3122,7 @@ static const JNINativeMethod fixed_method_table[] = { { TCN_METHOD_TABLE_ENTRY(disableOcsp, (J)V, SSLContext) }, { TCN_METHOD_TABLE_ENTRY(getSslCtx, (J)J, SSLContext) }, { TCN_METHOD_TABLE_ENTRY(setUseTasks, (JZ)V, SSLContext) }, + { TCN_METHOD_TABLE_ENTRY(setRecordSelectedCredential, (JZ)V, SSLContext) }, { TCN_METHOD_TABLE_ENTRY(setNumTickets, (JI)Z, SSLContext) }, { TCN_METHOD_TABLE_ENTRY(setCurvesList0, (JLjava/lang/String;)Z, SSLContext) }, { TCN_METHOD_TABLE_ENTRY(setMaxCertList, (JI)V, SSLContext) },