Skip to content

Commit 5acfd5f

Browse files
author
Alex Choulos
committed
Tag SSL_CREDENTIALs with an id and record the selected id at handshake completion
1 parent 3bbd9de commit 5acfd5f

8 files changed

Lines changed: 258 additions & 6 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2026 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.internal.tcnative;
17+
18+
import org.junit.jupiter.api.BeforeAll;
19+
import org.junit.jupiter.api.Test;
20+
21+
import java.io.File;
22+
23+
import static org.junit.jupiter.api.Assertions.assertEquals;
24+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
25+
import static org.junit.jupiter.api.Assertions.assertThrows;
26+
import static org.junit.jupiter.api.Assertions.assertTrue;
27+
28+
public class SSLCredentialIdTest {
29+
30+
@BeforeAll
31+
public static void loadNativeLib() throws Exception {
32+
String testClassesRoot = SSLCredentialIdTest.class.getProtectionDomain().getCodeSource().getLocation().getFile();
33+
File[] directories = new File(testClassesRoot + File.separator + "META-INF" + File.separator + "native")
34+
.listFiles();
35+
if (directories == null || directories.length != 1) {
36+
throw new IllegalStateException("Could not find platform specific native directory");
37+
}
38+
String libName = System.mapLibraryName("netty_tcnative")
39+
// Fix the filename (this is needed for macOS).
40+
.replace(".dylib", ".jnilib");
41+
System.load(directories[0].getAbsoluteFile() + File.separator + libName);
42+
Library.initialize();
43+
}
44+
45+
@Test
46+
public void newCredentialsHavePositiveDistinctIds() throws Exception {
47+
long x509 = SSLCredential.newX509();
48+
long delegated = SSLCredential.newDelegated();
49+
try {
50+
long x509Id = SSLCredential.getId(x509);
51+
long delegatedId = SSLCredential.getId(delegated);
52+
assertTrue(x509Id > 0);
53+
assertTrue(delegatedId > 0);
54+
assertNotEquals(x509Id, delegatedId);
55+
} finally {
56+
SSLCredential.free(x509);
57+
SSLCredential.free(delegated);
58+
}
59+
}
60+
61+
@Test
62+
public void idCannotBeReassigned() throws Exception {
63+
long cred = SSLCredential.newX509();
64+
try {
65+
long id = SSLCredential.getId(cred);
66+
assertThrows(IllegalStateException.class, () -> SSLCredential.setId0(cred, id + 1));
67+
assertEquals(id, SSLCredential.getId(cred));
68+
} finally {
69+
SSLCredential.free(cred);
70+
}
71+
}
72+
73+
@Test
74+
public void nonPositiveIdIsRejected() throws Exception {
75+
long cred = SSLCredential.newX509();
76+
try {
77+
assertThrows(IllegalArgumentException.class, () -> SSLCredential.setId0(cred, 0));
78+
assertThrows(IllegalArgumentException.class, () -> SSLCredential.setId0(cred, -1));
79+
} finally {
80+
SSLCredential.free(cred);
81+
}
82+
}
83+
84+
@Test
85+
public void freshSslHasNoSelectedCredentialId() throws Exception {
86+
long ctx = SSLContext.make(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_MODE_SERVER);
87+
try {
88+
long ssl = SSL.newSSL(ctx, true);
89+
try {
90+
assertEquals(0, SSL.getSelectedCredentialId(ssl));
91+
} finally {
92+
SSL.freeSSL(ssl);
93+
}
94+
} finally {
95+
SSLContext.free(ctx);
96+
}
97+
}
98+
}

‎openssl-classes/src/main/java/io/netty/internal/tcnative/SSL.java‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,26 @@ public static AsyncTask getAsyncTask(long ssl) {
10041004
*/
10051005
public static native long getSelectedCredential(long ssl) throws Exception;
10061006

1007+
/**
1008+
* Get the id of the credential selected for an SSL instance, as assigned by {@link SSLCredential#newX509()} or
1009+
* {@link SSLCredential#newDelegated()}.
1010+
*
1011+
* <p>Returns:
1012+
* <ul>
1013+
* <li>during a handshake that has already selected a credential, the id of that credential;</li>
1014+
* <li>otherwise, the id recorded by the most recent completed handshake;</li>
1015+
* <li>{@code 0} when no credential was selected, when the credential came from a legacy API, or when no
1016+
* handshake has completed yet.</li>
1017+
* </ul>
1018+
*
1019+
* <p>This is a BoringSSL-specific feature.</p>
1020+
*
1021+
* @param ssl the SSL instance (SSL *)
1022+
* @return the selected credential id, or {@code 0}
1023+
* @throws Exception if an error occurred
1024+
*/
1025+
public static native long getSelectedCredentialId(long ssl) throws Exception;
1026+
10071027
/**
10081028
* Get the name of the group used by ssl's most recently completed handshake, or {@code null} if not applicable.
10091029
*

‎openssl-classes/src/main/java/io/netty/internal/tcnative/SSLCredential.java‎

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package io.netty.internal.tcnative;
1717

18+
import java.util.concurrent.atomic.AtomicLong;
19+
1820
/**
1921
* SSL_CREDENTIAL management for BoringSSL.
2022
*
@@ -32,6 +34,8 @@
3234
*/
3335
public final class SSLCredential {
3436

37+
private static final AtomicLong NEXT_ID = new AtomicLong();
38+
3539
private SSLCredential() { }
3640

3741
/**
@@ -44,7 +48,43 @@ private SSLCredential() { }
4448
* @return the SSL_CREDENTIAL instance (SSL_CREDENTIAL *)
4549
* @throws Exception if an error occurred
4650
*/
47-
public static native long newX509() throws Exception;
51+
public static long newX509() throws Exception {
52+
return assignId(newX509Native());
53+
}
54+
55+
private static native long newX509Native() throws Exception;
56+
57+
private static long assignId(long cred) throws Exception {
58+
try {
59+
setId0(cred, NEXT_ID.incrementAndGet());
60+
} catch (Throwable t) {
61+
// Don't leak the credential if tagging fails.
62+
free(cred);
63+
throw t;
64+
}
65+
return cred;
66+
}
67+
68+
/**
69+
* Assign the id of an SSL_CREDENTIAL. A credential can only be assigned an id once.
70+
*
71+
* @param cred the SSL_CREDENTIAL instance (SSL_CREDENTIAL *)
72+
* @param id the id, which must be positive
73+
* @throws IllegalArgumentException if {@code id} is not positive
74+
* @throws IllegalStateException if the credential already has an id
75+
*/
76+
static native void setId0(long cred, long id);
77+
78+
/**
79+
* Get the id that was assigned to an SSL_CREDENTIAL when it was created by {@link #newX509()} or
80+
* {@link #newDelegated()}.
81+
*
82+
* <p>This is a BoringSSL-specific feature.</p>
83+
*
84+
* @param cred the SSL_CREDENTIAL instance (SSL_CREDENTIAL *)
85+
* @return the id, or {@code 0} if none was assigned
86+
*/
87+
public static native long getId(long cred);
4888

4989
/**
5090
* Increment the reference count of an SSL_CREDENTIAL.
@@ -184,7 +224,11 @@ private SSLCredential() { }
184224
* @return the delegated SSL_CREDENTIAL instance (SSL_CREDENTIAL *)
185225
* @throws Exception if an error occurred
186226
*/
187-
public static native long newDelegated() throws Exception;
227+
public static long newDelegated() throws Exception {
228+
return assignId(newDelegatedNative());
229+
}
230+
231+
private static native long newDelegatedNative() throws Exception;
188232

189233
/**
190234
* Set the delegated credential for an SSL_CREDENTIAL.

‎openssl-dynamic/src/main/c/ssl.c‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "apr_portable.h"
4545
#include "ssl_private.h"
4646
#include "ssl.h"
47+
#include "sslcredential.h"
4748

4849
#define SSL_CLASSNAME "io/netty/internal/tcnative/SSL"
4950

@@ -2773,6 +2774,23 @@ TCN_IMPLEMENT_CALL(jlong, SSL, getSelectedCredential)(TCN_STDARGS, jlong ssl) {
27732774
#endif
27742775
}
27752776

2777+
TCN_IMPLEMENT_CALL(jlong, SSL, getSelectedCredentialId)(TCN_STDARGS, jlong ssl) {
2778+
#ifdef OPENSSL_IS_BORINGSSL
2779+
SSL *ssl_ = J2P(ssl, SSL *);
2780+
TCN_CHECK_NULL(ssl_, ssl, 0);
2781+
const SSL_CREDENTIAL* credential = SSL_get0_selected_credential(ssl_);
2782+
if (credential != NULL) {
2783+
// Handshake in progress: report the live selection.
2784+
return tcn_SSL_CREDENTIAL_get_id(credential);
2785+
}
2786+
tcn_ssl_state_t* state = tcn_SSL_get_app_state(ssl_);
2787+
return state == NULL ? 0 : state->selected_credential_id;
2788+
#else
2789+
tcn_ThrowUnsupportedOperationException(e, "SSL_CREDENTIAL API not available.");
2790+
return 0;
2791+
#endif
2792+
}
2793+
27762794
// JNI Method Registration Table Begin
27772795
static const JNINativeMethod method_table[] = {
27782796
{ TCN_METHOD_TABLE_ENTRY(bioLengthByteBuffer, (J)I, SSL) },
@@ -2854,6 +2872,7 @@ static const JNINativeMethod method_table[] = {
28542872
{ TCN_METHOD_TABLE_ENTRY(setRenegotiateMode, (JI)V, SSL) },
28552873
{ TCN_METHOD_TABLE_ENTRY(addCredential, (JJ)V, SSL) },
28562874
{ TCN_METHOD_TABLE_ENTRY(getSelectedCredential, (J)J, SSL) },
2875+
{ TCN_METHOD_TABLE_ENTRY(getSelectedCredentialId, (J)J, SSL) },
28572876
{ TCN_METHOD_TABLE_ENTRY(getGroupName, (J)Ljava/lang/String;, SSL) }
28582877
};
28592878

‎openssl-dynamic/src/main/c/ssl_private.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,9 @@ struct tcn_ssl_state_t {
411411
// len < sk_CRYPTO_BUFFER_num(chain) check (both locals are 0/NULL there).
412412
int task_array_len;
413413
int task_chain_num;
414+
#ifdef OPENSSL_IS_BORINGSSL
415+
jlong selected_credential_id;
416+
#endif
414417
};
415418

416419
#define TCN_GET_SSL_CTX(ssl, C) \

‎openssl-dynamic/src/main/c/sslcontext.c‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "ssl_private.h"
3838
#include <stdint.h>
3939
#include "sslcontext.h"
40+
#include "sslcredential.h"
4041
#include "cert_compress.h"
4142

4243
#define SSLCONTEXT_CLASSNAME "io/netty/internal/tcnative/SSLContext"
@@ -189,6 +190,15 @@ static void ssl_info_callback(const SSL *ssl, int where, int ret) {
189190
state->handshakeCount++;
190191
}
191192
}
193+
#ifdef OPENSSL_IS_BORINGSSL
194+
if (0 != (where & SSL_CB_HANDSHAKE_DONE)) {
195+
// BoringSSL frees the handshake state (and its selected credential) before SSL_do_handshake returns,
196+
// so record the selection here. Assign unconditionally so a later handshake without one clears it.
197+
if ((state = tcn_SSL_get_app_state(ssl)) != NULL) {
198+
state->selected_credential_id = tcn_SSL_CREDENTIAL_get_id(SSL_get0_selected_credential(ssl));
199+
}
200+
}
201+
#endif
192202
}
193203

194204
/* Initialize server context */

‎openssl-dynamic/src/main/c/sslcredential.c‎

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,24 @@ static void throw_openssl_error(JNIEnv* env, const char* msg) {
3737
ERR_error_string_n(err, err_buf, sizeof(err_buf));
3838
tcn_Throw(env, "%s: %s", msg, err_buf);
3939
}
40+
41+
static int tcn_SSL_CREDENTIAL_id_idx = -1;
42+
43+
typedef char tcn_SSL_CREDENTIAL_id_requires_64bit_pointers[sizeof(void*) >= sizeof(jlong) ? 1 : -1];
44+
45+
// The id is a scalar packed into the ex_data slot, so no free callback is needed.
46+
jlong tcn_SSL_CREDENTIAL_get_id(const SSL_CREDENTIAL* cred) {
47+
if (cred == NULL || tcn_SSL_CREDENTIAL_id_idx < 0) {
48+
return 0;
49+
}
50+
return (jlong)(intptr_t) SSL_CREDENTIAL_get_ex_data(cred, tcn_SSL_CREDENTIAL_id_idx);
51+
}
4052
#endif
4153

4254

4355

4456
// Core SSL_CREDENTIAL functions
45-
TCN_IMPLEMENT_CALL(jlong, SSLCredential, newX509)(TCN_STDARGS) {
57+
TCN_IMPLEMENT_CALL(jlong, SSLCredential, newX509Native)(TCN_STDARGS) {
4658
#ifdef OPENSSL_IS_BORINGSSL
4759
SSL_CREDENTIAL* cred = SSL_CREDENTIAL_new_x509();
4860
TCN_CHECK_NULL(cred, credential, 0);
@@ -74,6 +86,40 @@ TCN_IMPLEMENT_CALL(void, SSLCredential, free)(TCN_STDARGS, jlong cred) {
7486
#endif
7587
}
7688

89+
TCN_IMPLEMENT_CALL(void, SSLCredential, setId0)(TCN_STDARGS, jlong cred, jlong id) {
90+
#ifdef OPENSSL_IS_BORINGSSL
91+
SSL_CREDENTIAL* c = (SSL_CREDENTIAL*)(intptr_t)cred;
92+
TCN_CHECK_NULL(c, credential, /* void */);
93+
if (id <= 0) {
94+
tcn_ThrowIllegalArgumentException(e, "credential id must be positive");
95+
return;
96+
}
97+
if (tcn_SSL_CREDENTIAL_get_id(c) != 0) {
98+
jclass ise = (*e)->FindClass(e, "java/lang/IllegalStateException");
99+
if (ise != NULL) {
100+
(*e)->ThrowNew(e, ise, "credential already has an id");
101+
}
102+
return;
103+
}
104+
if (!SSL_CREDENTIAL_set_ex_data(c, tcn_SSL_CREDENTIAL_id_idx, (void*)(intptr_t) id)) {
105+
throw_openssl_error(e, "Failed to set SSL_CREDENTIAL id");
106+
}
107+
#else
108+
tcn_ThrowUnsupportedOperationException(e, "SSL_CREDENTIAL API not available.");
109+
#endif
110+
}
111+
112+
TCN_IMPLEMENT_CALL(jlong, SSLCredential, getId)(TCN_STDARGS, jlong cred) {
113+
#ifdef OPENSSL_IS_BORINGSSL
114+
SSL_CREDENTIAL* c = (SSL_CREDENTIAL*)(intptr_t)cred;
115+
TCN_CHECK_NULL(c, credential, 0);
116+
return tcn_SSL_CREDENTIAL_get_id(c);
117+
#else
118+
tcn_ThrowUnsupportedOperationException(e, "SSL_CREDENTIAL API not available.");
119+
return 0;
120+
#endif
121+
}
122+
77123
// SSL_CREDENTIAL configuration methods
78124
TCN_IMPLEMENT_CALL(void, SSLCredential, setPrivateKey)(TCN_STDARGS, jlong cred, jlong key) {
79125
#ifdef OPENSSL_IS_BORINGSSL
@@ -294,7 +340,7 @@ TCN_IMPLEMENT_CALL(void, SSLCredential, setTrustAnchorId)(TCN_STDARGS, jlong cre
294340
}
295341

296342
// Delegated credentials
297-
TCN_IMPLEMENT_CALL(jlong, SSLCredential, newDelegated)(TCN_STDARGS) {
343+
TCN_IMPLEMENT_CALL(jlong, SSLCredential, newDelegatedNative)(TCN_STDARGS) {
298344
#ifdef OPENSSL_IS_BORINGSSL
299345
SSL_CREDENTIAL* credential = SSL_CREDENTIAL_new_delegated();
300346
if (credential == NULL) {
@@ -343,9 +389,11 @@ TCN_IMPLEMENT_CALL(void, SSLCredential, setDelegatedCredential)(TCN_STDARGS, jlo
343389
// JNI Method Registration Table Begin
344390
static const JNINativeMethod method_table[] = {
345391
// Core functions
346-
{ TCN_METHOD_TABLE_ENTRY(newX509, ()J, SSLCredential) },
392+
{ TCN_METHOD_TABLE_ENTRY(newX509Native, ()J, SSLCredential) },
347393
{ TCN_METHOD_TABLE_ENTRY(upRef, (J)V, SSLCredential) },
348394
{ TCN_METHOD_TABLE_ENTRY(free, (J)V, SSLCredential) },
395+
{ TCN_METHOD_TABLE_ENTRY(setId0, (JJ)V, SSLCredential) },
396+
{ TCN_METHOD_TABLE_ENTRY(getId, (J)J, SSLCredential) },
349397

350398
// Configuration
351399
{ TCN_METHOD_TABLE_ENTRY(setPrivateKey, (JJ)V, SSLCredential) },
@@ -360,7 +408,7 @@ static const JNINativeMethod method_table[] = {
360408
{ TCN_METHOD_TABLE_ENTRY(setTrustAnchorId, (J[B)V, SSLCredential) },
361409

362410
// Delegated credentials
363-
{ TCN_METHOD_TABLE_ENTRY(newDelegated, ()J, SSLCredential) },
411+
{ TCN_METHOD_TABLE_ENTRY(newDelegatedNative, ()J, SSLCredential) },
364412
{ TCN_METHOD_TABLE_ENTRY(setDelegatedCredential, (J[B)V, SSLCredential) }
365413
};
366414

@@ -371,6 +419,12 @@ static const jint method_table_size = sizeof(method_table) / sizeof(method_table
371419
// IMPORTANT: If you add any NETTY_JNI_UTIL_LOAD_CLASS or NETTY_JNI_UTIL_FIND_CLASS calls you also need to update
372420
// Library to reflect that.
373421
jint netty_internal_tcnative_SSLCredential_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) {
422+
#ifdef OPENSSL_IS_BORINGSSL
423+
tcn_SSL_CREDENTIAL_id_idx = SSL_CREDENTIAL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
424+
if (tcn_SSL_CREDENTIAL_id_idx < 0) {
425+
return JNI_ERR;
426+
}
427+
#endif
374428
if (netty_jni_util_register_natives(env,
375429
packagePrefix,
376430
SSLCREDENTIAL_CLASSNAME,

‎openssl-dynamic/src/main/c/sslcredential.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ extern "C" {
2727
jint netty_internal_tcnative_SSLCredential_JNI_OnLoad(JNIEnv* env, const char* packagePrefix);
2828
void netty_internal_tcnative_SSLCredential_JNI_OnUnLoad(JNIEnv* env, const char* packagePrefix);
2929

30+
#ifdef OPENSSL_IS_BORINGSSL
31+
jlong tcn_SSL_CREDENTIAL_get_id(const SSL_CREDENTIAL* cred);
32+
#endif
33+
3034
#ifdef __cplusplus
3135
}
3236
#endif

0 commit comments

Comments
 (0)