Skip to content

Commit 0e9a858

Browse files
authored
Merge pull request wolfSSL#305 from cconlon/alpnGrease
Fix ALPN to support non-ASCII protocol names
2 parents 8822d49 + dc8d1ac commit 0e9a858

5 files changed

Lines changed: 188 additions & 14 deletions

File tree

native/com_wolfssl_WolfSSLSession.c

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5451,29 +5451,91 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_useALPN
54515451
(JNIEnv* jenv, jobject jcl, jlong sslPtr, jstring protocols, jint options)
54525452
{
54535453
int ret = SSL_FAILURE;
5454+
#ifdef HAVE_ALPN
5455+
WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr;
5456+
char* protoList = NULL;
5457+
jsize protocolsCharLen = 0;
5458+
jsize protocolsUtfLen = 0;
5459+
(void)jcl;
5460+
5461+
if (jenv == NULL || ssl == NULL || protocols == NULL || options < 0) {
5462+
return BAD_FUNC_ARG;
5463+
}
5464+
5465+
/* GetStringLength returns character count (Unicode code units),
5466+
* GetStringUTFLength returns byte count in modified UTF-8 encoding.
5467+
* For non-ASCII characters (> 127), UTF-8 uses multiple bytes per char.
5468+
* GetStringUTFRegion expects character count, not byte count. */
5469+
protocolsCharLen = (*jenv)->GetStringLength(jenv, protocols);
5470+
protocolsUtfLen = (*jenv)->GetStringUTFLength(jenv, protocols);
5471+
if (protocolsCharLen == 0 || protocolsUtfLen == 0) {
5472+
return BAD_FUNC_ARG;
5473+
}
5474+
5475+
/* Allocate UTF-8 byte size + 1 to guarantee null terminated */
5476+
protoList = (char*)XMALLOC(protocolsUtfLen + 1, NULL,
5477+
DYNAMIC_TYPE_TMP_BUFFER);
5478+
if (protoList == NULL) {
5479+
return MEMORY_E;
5480+
}
5481+
5482+
/* GetStringUTFRegion() does not need to be freed/released.
5483+
* Third param is start index, fourth is character count (not bytes). */
5484+
(*jenv)->GetStringUTFRegion(jenv, protocols, 0, protocolsCharLen,
5485+
protoList);
5486+
if ((*jenv)->ExceptionCheck(jenv)) {
5487+
XFREE(protoList, NULL, DYNAMIC_TYPE_TMP_BUFFER);
5488+
return SSL_FAILURE;
5489+
}
5490+
protoList[protocolsUtfLen] = '\0';
5491+
5492+
ret = wolfSSL_UseALPN(ssl, protoList, protocolsUtfLen, (int)options);
5493+
5494+
XFREE(protoList, NULL, DYNAMIC_TYPE_TMP_BUFFER);
5495+
#else
5496+
(void)jenv;
5497+
(void)jcl;
5498+
(void)sslPtr;
5499+
(void)protocols;
5500+
(void)options;
5501+
ret = NOT_COMPILED_IN;
5502+
#endif
5503+
5504+
return (jint)ret;
5505+
}
5506+
5507+
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_useALPNByteArray
5508+
(JNIEnv* jenv, jobject jcl, jlong sslPtr, jbyteArray protocols, jint options)
5509+
{
5510+
int ret = SSL_FAILURE;
54545511
#ifdef HAVE_ALPN
54555512
WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr;
54565513
char* protoList = NULL;
54575514
jsize protocolsLen = 0;
54585515
(void)jcl;
54595516

5460-
if (jenv == NULL || ssl == 0 || protocols == NULL || options < 0) {
5517+
if (jenv == NULL || ssl == NULL || protocols == NULL || options < 0) {
54615518
return BAD_FUNC_ARG;
54625519
}
54635520

5464-
protocolsLen = (*jenv)->GetStringUTFLength(jenv, protocols);
5521+
protocolsLen = (*jenv)->GetArrayLength(jenv, protocols);
54655522
if (protocolsLen == 0) {
54665523
return BAD_FUNC_ARG;
54675524
}
54685525

5469-
/* Allocate size + 1 to guarantee we are null terminated */
5526+
/* Allocate size + 1 to guarantee null terminated */
54705527
protoList = (char*)XMALLOC(protocolsLen + 1, NULL, DYNAMIC_TYPE_TMP_BUFFER);
54715528
if (protoList == NULL) {
54725529
return MEMORY_E;
54735530
}
54745531

5475-
/* GetStringUTFRegion() does not need to be freed/released */
5476-
(*jenv)->GetStringUTFRegion(jenv, protocols, 0, protocolsLen, protoList);
5532+
/* Copy byte array directly */
5533+
(*jenv)->GetByteArrayRegion(jenv, protocols, 0, protocolsLen,
5534+
(jbyte*)protoList);
5535+
if ((*jenv)->ExceptionCheck(jenv)) {
5536+
XFREE(protoList, NULL, DYNAMIC_TYPE_TMP_BUFFER);
5537+
return SSL_FAILURE;
5538+
}
54775539
protoList[protocolsLen] = '\0';
54785540

54795541
ret = wolfSSL_UseALPN(ssl, protoList, protocolsLen, (int)options);

native/com_wolfssl_WolfSSLSession.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/WolfSSLSession.java

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,8 @@ private native int setTlsHmacInner(long ssl, byte[] inner, long sz,
694694
private native int sslSetAlpnProtos(long ssl, byte[] alpnProtos);
695695
private native byte[] sslGet0AlpnSelected(long ssl);
696696
private native int useALPN(long ssl, String protocols, int options);
697+
private native int useALPNByteArray(long ssl, byte[] protocols,
698+
int options);
697699
private native int setALPNSelectCb(long ssl);
698700
private native int setTls13SecretCb(long ssl);
699701
private native int setSessionTicketCb(long ssl);
@@ -5504,8 +5506,11 @@ public int useALPN(byte[] alpnProtos) throws IllegalStateException {
55045506
*/
55055507
public int useALPN(String[] protocols, int options) {
55065508

5507-
/* all protocols, comma delimited */
5508-
StringBuilder allProtocols = new StringBuilder();
5509+
int i;
5510+
int totalLen = 0;
5511+
byte[][] protoBytes = null;
5512+
byte[] allProtocols = null;
5513+
int offset = 0;
55095514

55105515
confirmObjectIsActive();
55115516

@@ -5515,19 +5520,38 @@ public int useALPN(String[] protocols, int options) {
55155520
() -> "entered useALPN(String[], int)");
55165521
}
55175522

5518-
if (protocols == null) {
5523+
if (protocols == null || protocols.length == 0) {
55195524
return WolfSSL.BAD_FUNC_ARG;
55205525
}
55215526

5522-
for (int i = 0; i < protocols.length; i++) {
5523-
if (i != 0) {
5524-
allProtocols.append(",");
5527+
/* Convert each protocol String to bytes using ISO-8859-1 (Latin-1).
5528+
* This preserves byte values 0-255, which is required for ALPN
5529+
* protocol names that contain non-ASCII bytes (eg GREASE values). */
5530+
protoBytes = new byte[protocols.length][];
5531+
for (i = 0; i < protocols.length; i++) {
5532+
if (protocols[i] == null) {
5533+
return WolfSSL.BAD_FUNC_ARG;
55255534
}
5526-
allProtocols.append(protocols[i]);
5535+
protoBytes[i] = protocols[i].getBytes(StandardCharsets.ISO_8859_1);
5536+
totalLen += protoBytes[i].length;
5537+
if (i > 0) {
5538+
totalLen += 1; /* comma separator */
5539+
}
5540+
}
5541+
5542+
/* Build comma-delimited byte array of all protocols */
5543+
allProtocols = new byte[totalLen];
5544+
for (i = 0; i < protoBytes.length; i++) {
5545+
if (i > 0) {
5546+
allProtocols[offset++] = (byte)',';
5547+
}
5548+
System.arraycopy(protoBytes[i], 0, allProtocols, offset,
5549+
protoBytes[i].length);
5550+
offset += protoBytes[i].length;
55275551
}
55285552

55295553
synchronized (sslLock) {
5530-
return useALPN(this.sslPtr, allProtocols.toString(), options);
5554+
return useALPNByteArray(this.sslPtr, allProtocols, options);
55315555
}
55325556
}
55335557

@@ -5574,7 +5598,11 @@ public String getAlpnSelectedString() throws IllegalStateException {
55745598
alpnSelectedBytes = getAlpnSelected();
55755599

55765600
if (alpnSelectedBytes != null) {
5577-
return new String(alpnSelectedBytes, StandardCharsets.UTF_8);
5601+
/* Use ISO-8859-1 (Latin-1) to preserve raw byte values 0-255.
5602+
* This is needed for ALPN protocol names containing non-ASCII
5603+
* bytes (eg GREASE values). UTF-8 would corrupt bytes > 127 that
5604+
* are not valid UTF-8 sequences. */
5605+
return new String(alpnSelectedBytes, StandardCharsets.ISO_8859_1);
55785606
} else {
55795607
return null;
55805608
}

src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.io.ByteArrayInputStream;
4444
import java.io.StringWriter;
4545
import java.io.PrintWriter;
46+
import java.nio.charset.StandardCharsets;
4647
import java.net.Socket;
4748
import java.net.ServerSocket;
4849
import java.net.SocketAddress;
@@ -967,6 +968,26 @@ public void testClientServerThreadedAlpnSelectCallback() throws Exception {
967968
cArgs.setAlpnList(new String[] {"h2", "http/1.1"});
968969
alpnClientServerRunner(sArgs, cArgs, true);
969970

971+
/* Successful test:
972+
* ALPN with GREASE bytes (RFC 8701) containing non-ASCII values.
973+
* Tests that bytes > 127 are preserved correctly through useALPN()
974+
* and getAlpnSelectedString(). */
975+
byte[] greaseBytes = new byte[] {
976+
(byte)0x0A, (byte)0x1A, (byte)0x2A, (byte)0x3A,
977+
(byte)0x4A, (byte)0x5A, (byte)0x6A, (byte)0x7A,
978+
(byte)0x8A, (byte)0x9A, (byte)0xAA, (byte)0xBA,
979+
(byte)0xCA, (byte)0xDA, (byte)0xEA, (byte)0xFA
980+
};
981+
String greaseString = new String(greaseBytes,
982+
StandardCharsets.ISO_8859_1);
983+
sArgs = new TestArgs(null, null, true, true, true, null);
984+
sArgs.setAlpnList(new String[] {greaseString});
985+
sArgs.setExpectedAlpn(greaseString);
986+
cArgs = new TestArgs(null, null, false, false, true, null);
987+
cArgs.setAlpnList(new String[] {greaseString});
988+
cArgs.setExpectedAlpn(greaseString);
989+
alpnClientServerRunner(sArgs, cArgs, false);
990+
970991
System.out.println("\t... passed");
971992
}
972993

src/test/com/wolfssl/test/WolfSSLSessionTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import java.util.concurrent.TimeUnit;
4545
import java.util.concurrent.CountDownLatch;
4646
import java.nio.ByteBuffer;
47+
import java.nio.charset.StandardCharsets;
4748
import java.security.Security;
4849

4950
import com.wolfssl.WolfSSL;
@@ -886,6 +887,60 @@ public void test_WolfSSLSession_useALPN()
886887
System.out.println("\t\t\t... passed");
887888
}
888889

890+
/**
891+
* Test useALPN() with non-ASCII GREASE byte values.
892+
*
893+
* GREASE (Generate Random Extensions And Sustain Extensibility) values
894+
* include high bytes (greater than 127) which would be corrupted by UTF-8
895+
* encoding. This test verifies that ALPN protocol names are encoded using
896+
* ISO-8859-1 to preserve raw byte values through the JNI layer.
897+
*/
898+
@Test
899+
public void test_WolfSSLSession_useALPN_GREASE()
900+
throws WolfSSLException, WolfSSLJNIException {
901+
902+
int ret;
903+
WolfSSLSession ssl = null;
904+
String greaseString;
905+
String[] alpnProtos;
906+
907+
/* GREASE bytes per RFC 8701. Values > 127 would be corrupted by UTF-8
908+
* encoding, so ISO-8859-1 must be used to preserve raw byte values. */
909+
byte[] greaseBytes = new byte[] {
910+
(byte)0x0A, (byte)0x1A, (byte)0x2A, (byte)0x3A,
911+
(byte)0x4A, (byte)0x5A, (byte)0x6A, (byte)0x7A,
912+
(byte)0x8A, (byte)0x9A, (byte)0xAA, (byte)0xBA,
913+
(byte)0xCA, (byte)0xDA, (byte)0xEA, (byte)0xFA
914+
};
915+
916+
System.out.print("\tuseALPN() GREASE");
917+
918+
/* Create String from bytes using ISO-8859-1 to preserve byte values */
919+
greaseString = new String(greaseBytes, StandardCharsets.ISO_8859_1);
920+
alpnProtos = new String[] { greaseString };
921+
922+
ssl = new WolfSSLSession(ctx);
923+
924+
try {
925+
ret = ssl.useALPN(alpnProtos,
926+
WolfSSL.WOLFSSL_ALPN_CONTINUE_ON_MISMATCH);
927+
928+
if (ret == WolfSSL.NOT_COMPILED_IN) {
929+
System.out.println("\t\t... skipped");
930+
return;
931+
932+
} else if (ret != WolfSSL.SSL_SUCCESS) {
933+
System.out.println("\t\t... failed");
934+
fail("Failed useALPN GREASE test, ret = " + ret);
935+
}
936+
937+
} finally {
938+
ssl.freeSSL();
939+
}
940+
941+
System.out.println("\t\t... passed");
942+
}
943+
889944
@Test
890945
public void test_WolfSSLSession_freeSSL()
891946
throws WolfSSLJNIException, WolfSSLException {

0 commit comments

Comments
 (0)