Skip to content

Commit bbb0153

Browse files
authored
Merge pull request wolfSSL#339 from cconlon/sniBuffer
Add wrapper for wolfSSL_SNI_GetFromBuffer()
2 parents 5da69b4 + 89579f6 commit bbb0153

4 files changed

Lines changed: 278 additions & 0 deletions

File tree

native/com_wolfssl_WolfSSL.c

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

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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,35 @@ 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+
* @throws WolfSSLException if native wolfSSL_SNI_GetFromBuffer() call
1703+
* fails with a negative error code
1704+
*/
1705+
public static native int getSNIFromBuffer(byte[] clientHello,
1706+
byte type, byte[] sni) throws IllegalArgumentException,
1707+
WolfSSLException;
1708+
16801709
/**
16811710
* Returns the enabled cipher suites for native wolfSSL.
16821711
*

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() throws WolfSSLException {
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)