Skip to content

Commit ff6cd1d

Browse files
committed
JNI: add wrapper for wolfSSL_SNI_GetFromBuffer()
1 parent ef35e7a commit ff6cd1d

4 files changed

Lines changed: 271 additions & 0 deletions

File tree

native/com_wolfssl_WolfSSL.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2638,3 +2638,101 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSL_getErrno
26382638
return 0;
26392639
#endif
26402640
}
2641+
2642+
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSL_getSNIFromBuffer
2643+
(JNIEnv* jenv, jclass jcl, jbyteArray clientHello, jbyte type, jbyteArray sni)
2644+
{
2645+
#ifdef HAVE_SNI
2646+
int ret = 0;
2647+
byte* clientHelloBuf = NULL;
2648+
byte* sniBuf = NULL;
2649+
unsigned int inOutSz = 0;
2650+
int helloSz = 0;
2651+
jclass excClass = NULL;
2652+
char errMsg[WOLFSSL_MAX_ERROR_SZ];
2653+
(void)jcl;
2654+
2655+
if (jenv == NULL || clientHello == NULL || sni == NULL) {
2656+
excClass = (*jenv)->FindClass(jenv,
2657+
"java/lang/IllegalArgumentException");
2658+
if ((*jenv)->ExceptionOccurred(jenv)) {
2659+
(*jenv)->ExceptionDescribe(jenv);
2660+
(*jenv)->ExceptionClear(jenv);
2661+
return (jint)BAD_FUNC_ARG;
2662+
}
2663+
(*jenv)->ThrowNew(jenv, excClass,
2664+
"clientHello and sni must be non-null");
2665+
return (jint)BAD_FUNC_ARG;
2666+
}
2667+
2668+
inOutSz = (unsigned int)(*jenv)->GetArrayLength(jenv, sni);
2669+
if (inOutSz == 0) {
2670+
return (jint)0;
2671+
}
2672+
2673+
helloSz = (*jenv)->GetArrayLength(jenv, clientHello);
2674+
2675+
clientHelloBuf = (byte*)(*jenv)->GetByteArrayElements(jenv,
2676+
clientHello, NULL);
2677+
if (clientHelloBuf == NULL) {
2678+
if ((*jenv)->ExceptionOccurred(jenv)) {
2679+
(*jenv)->ExceptionDescribe(jenv);
2680+
(*jenv)->ExceptionClear(jenv);
2681+
}
2682+
return (jint)MEMORY_E;
2683+
}
2684+
2685+
sniBuf = (byte*)(*jenv)->GetByteArrayElements(jenv, sni, NULL);
2686+
if (sniBuf == NULL) {
2687+
(*jenv)->ReleaseByteArrayElements(jenv, clientHello,
2688+
(jbyte*)clientHelloBuf, JNI_ABORT);
2689+
if ((*jenv)->ExceptionOccurred(jenv)) {
2690+
(*jenv)->ExceptionDescribe(jenv);
2691+
(*jenv)->ExceptionClear(jenv);
2692+
}
2693+
return (jint)MEMORY_E;
2694+
}
2695+
2696+
ret = wolfSSL_SNI_GetFromBuffer(clientHelloBuf, (unsigned int)helloSz,
2697+
(byte)type, sniBuf, &inOutSz);
2698+
2699+
if (ret == WOLFSSL_SUCCESS) {
2700+
/* copy back SNI data to Java array */
2701+
(*jenv)->ReleaseByteArrayElements(jenv, sni, (jbyte*)sniBuf, 0);
2702+
(*jenv)->ReleaseByteArrayElements(jenv, clientHello,
2703+
(jbyte*)clientHelloBuf, JNI_ABORT);
2704+
2705+
return (jint)inOutSz;
2706+
2707+
} else {
2708+
/* discard, don't copy back */
2709+
(*jenv)->ReleaseByteArrayElements(jenv, sni, (jbyte*)sniBuf, JNI_ABORT);
2710+
(*jenv)->ReleaseByteArrayElements(jenv, clientHello,
2711+
(jbyte*)clientHelloBuf, JNI_ABORT);
2712+
2713+
if (ret < 0) {
2714+
XSNPRINTF(errMsg, sizeof(errMsg),
2715+
"wolfSSL_SNI_GetFromBuffer() failed: %d", ret);
2716+
excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLException");
2717+
if ((*jenv)->ExceptionOccurred(jenv)) {
2718+
(*jenv)->ExceptionDescribe(jenv);
2719+
(*jenv)->ExceptionClear(jenv);
2720+
return (jint)ret;
2721+
}
2722+
(*jenv)->ThrowNew(jenv, excClass, errMsg);
2723+
return (jint)ret;
2724+
}
2725+
}
2726+
2727+
/* ret == 0: no SNI found, not an error */
2728+
return (jint)0;
2729+
2730+
#else
2731+
(void)jenv;
2732+
(void)jcl;
2733+
(void)clientHello;
2734+
(void)type;
2735+
(void)sni;
2736+
return (jint)NOT_COMPILED_IN;
2737+
#endif /* HAVE_SNI */
2738+
}

native/com_wolfssl_WolfSSL.h

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/java/com/wolfssl/WolfSSL.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,32 @@ public static native int getPkcs8TraditionalOffset(byte[] in, long idx,
16771677
*/
16781678
public static native long getLibVersionHex();
16791679

1680+
/**
1681+
* Retrieves the Server Name Indication (SNI) from a raw ClientHello
1682+
* message buffer.
1683+
*
1684+
* This is a utility method that does not require an active SSL/TLS session
1685+
* or context. It can be used to inspect the SNI before setting up the
1686+
* TLS session, for example for SNI-based routing.
1687+
*
1688+
* Wraps native wolfSSL_SNI_GetFromBuffer().
1689+
*
1690+
* @param clientHello raw Client Hello message bytes
1691+
* @param type SNI type to retrieve, typically
1692+
* {@link #WOLFSSL_SNI_HOST_NAME}
1693+
* @param sni output buffer where SNI value will be written. On
1694+
* success, the SNI data will be placed at the beginning
1695+
* of this array.
1696+
*
1697+
* @return number of bytes written into sni on success, 0 if no SNI
1698+
* extension was found in Client Hello, {@link #NOT_COMPILED_IN} if
1699+
* native wolfSSL was compiled without SNI support, negative value
1700+
* on error.
1701+
* @throws IllegalArgumentException if clientHello or sni is null
1702+
*/
1703+
public static native int getSNIFromBuffer(byte[] clientHello, byte type,
1704+
byte[] sni);
1705+
16801706
/**
16811707
* Returns the enabled cipher suites for native wolfSSL.
16821708
*

src/test/com/wolfssl/test/WolfSSLTest.java

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.junit.BeforeClass;
2626
import static org.junit.Assert.*;
2727

28+
import java.nio.charset.StandardCharsets;
2829
import java.util.Arrays;
2930
import java.util.List;
3031

@@ -56,6 +57,7 @@ public void testWolfSSL() throws WolfSSLException {
5657
test_WolfSSL_Method_Allocators(lib);
5758
test_WolfSSL_getLibVersionHex();
5859
test_WolfSSL_getErrno();
60+
test_WolfSSL_getSNIFromBuffer();
5961
testGetCiphersAvailableIana();
6062
test_isLibraryLoadSkippedReturnsFalseByDefault();
6163
test_SystemPropertyNotSetByDefault();
@@ -236,6 +238,143 @@ public void test_WolfSSL_getErrno() {
236238
System.out.println("\t\t\t... passed");
237239
}
238240

241+
public void test_WolfSSL_getSNIFromBuffer() {
242+
System.out.print("\tgetSNIFromBuffer()");
243+
244+
/* Minimal TLS 1.2 ClientHello with SNI extension for "www.example.com".
245+
* This is a hand crafted minimal valid ClientHello message. */
246+
String hostname = "www.example.com";
247+
byte[] hostBytes = hostname.getBytes(StandardCharsets.UTF_8);
248+
int hostLen = hostBytes.length;
249+
250+
/* SNI extension: type(2) + len(2) + sni_list_len(2) + sni_type(1) +
251+
* sni_len(2) + sni_data */
252+
int sniExtLen = 2 + 2 + 2 + 1 + 2 + hostLen;
253+
/* Extensions block: ext_len(2) + sni_ext */
254+
int extBlockLen = 2 + sniExtLen;
255+
256+
/* ClientHello body:
257+
* version(2) + random(32) + sessId_len(1) + cipher_suites_len(2) +
258+
* one_suite(2) + comp_len(1) + comp_null(1) + extensions */
259+
int chBodyLen = 2 + 32 + 1 + 2 + 2 + 1 + 1 + extBlockLen;
260+
261+
/* Handshake header: type(1) + length(3) */
262+
int hsLen = 1 + 3 + chBodyLen;
263+
264+
/* TLS record: type(1) + version(2) + length(2) */
265+
int totalLen = 1 + 2 + 2 + hsLen;
266+
267+
byte[] clientHello = new byte[totalLen];
268+
int offset = 0;
269+
270+
/* TLS record header */
271+
clientHello[offset++] = 0x16; /* handshake */
272+
clientHello[offset++] = 0x03; /* TLS 1.0 */
273+
clientHello[offset++] = 0x01;
274+
clientHello[offset++] = (byte)((hsLen >> 8) & 0xFF);
275+
clientHello[offset++] = (byte)(hsLen & 0xFF);
276+
277+
/* Handshake header */
278+
clientHello[offset++] = 0x01; /* client_hello */
279+
clientHello[offset++] = 0x00; /* length (3B) */
280+
clientHello[offset++] = (byte)((chBodyLen >> 8) & 0xFF);
281+
clientHello[offset++] = (byte)(chBodyLen & 0xFF);
282+
283+
/* ClientHello body */
284+
clientHello[offset++] = 0x03; /* TLS 1.2 */
285+
clientHello[offset++] = 0x03;
286+
287+
/* 32 bytes random (zeros for test) */
288+
offset += 32;
289+
290+
/* Session ID length = 0 */
291+
clientHello[offset++] = 0x00;
292+
293+
/* Cipher suites: length=2, one suite */
294+
clientHello[offset++] = 0x00;
295+
clientHello[offset++] = 0x02;
296+
clientHello[offset++] = (byte)0xC0;
297+
clientHello[offset++] = 0x2F;
298+
299+
/* Compression: length=1, null */
300+
clientHello[offset++] = 0x01;
301+
clientHello[offset++] = 0x00;
302+
303+
/* Extensions length */
304+
int extTotalLen = sniExtLen;
305+
clientHello[offset++] = (byte)((extTotalLen >> 8) & 0xFF);
306+
clientHello[offset++] = (byte)(extTotalLen & 0xFF);
307+
308+
/* SNI extension type = 0x0000 */
309+
clientHello[offset++] = 0x00;
310+
clientHello[offset++] = 0x00;
311+
312+
/* SNI extension data length */
313+
int sniDataLen = 2 + 1 + 2 + hostLen;
314+
clientHello[offset++] = (byte)((sniDataLen >> 8) & 0xFF);
315+
clientHello[offset++] = (byte)(sniDataLen & 0xFF);
316+
317+
/* SNI list length */
318+
int sniListLen = 1 + 2 + hostLen;
319+
clientHello[offset++] = (byte)((sniListLen >> 8) & 0xFF);
320+
clientHello[offset++] = (byte)(sniListLen & 0xFF);
321+
322+
/* SNI type: host_name = 0 */
323+
clientHello[offset++] = 0x00;
324+
325+
/* SNI host name length */
326+
clientHello[offset++] = (byte)((hostLen >> 8) & 0xFF);
327+
clientHello[offset++] = (byte)(hostLen & 0xFF);
328+
329+
/* SNI host name data */
330+
System.arraycopy(hostBytes, 0, clientHello, offset, hostLen);
331+
offset += hostLen;
332+
333+
byte[] sniOut = new byte[256];
334+
335+
int ret = WolfSSL.getSNIFromBuffer(clientHello,
336+
(byte)WolfSSL.WOLFSSL_SNI_HOST_NAME, sniOut);
337+
338+
if (ret == WolfSSL.NOT_COMPILED_IN) {
339+
System.out.println("\t\t... skipped");
340+
return;
341+
}
342+
343+
if (ret <= 0) {
344+
System.out.println("\t\t... failed");
345+
fail("getSNIFromBuffer() returned: " + ret);
346+
}
347+
348+
String extracted = new String(sniOut, 0, ret, StandardCharsets.UTF_8);
349+
if (!hostname.equals(extracted)) {
350+
System.out.println("\t\t... failed");
351+
fail("getSNIFromBuffer() expected [" + hostname + "] got [" +
352+
extracted + "]");
353+
}
354+
355+
/* Test null clientHello throws exception */
356+
try {
357+
WolfSSL.getSNIFromBuffer(null, (byte)WolfSSL.WOLFSSL_SNI_HOST_NAME,
358+
sniOut);
359+
System.out.println("\t\t... failed");
360+
fail("Expected IllegalArgumentException for null clientHello");
361+
} catch (IllegalArgumentException e) {
362+
/* expected */
363+
}
364+
365+
/* Test null sni output buffer throws exception */
366+
try {
367+
WolfSSL.getSNIFromBuffer(clientHello,
368+
(byte)WolfSSL.WOLFSSL_SNI_HOST_NAME, null);
369+
System.out.println("\t\t... failed");
370+
fail("Expected IllegalArgumentException for null sni");
371+
} catch (IllegalArgumentException e) {
372+
/* expected */
373+
}
374+
375+
System.out.println("\t\t... passed");
376+
}
377+
239378
public void test_isLibraryLoadSkippedReturnsFalseByDefault() {
240379

241380
System.out.print(

0 commit comments

Comments
 (0)