JSSE: add support for SSLSocket/SSLEngine get/setHandshakeApplicationProtocolSelector() for ALPN select support

pull/163/head
Chris Conlon 2023-12-05 17:10:40 -07:00
parent 4cc6a45842
commit 682f1ea5fc
13 changed files with 1308 additions and 64 deletions

View File

@ -161,8 +161,8 @@
<property name="java.debuglevel" value="source,lines,vars"/>
<property name="java.deprecation" value="true"/>
<property name="java.optimize" value="false"/>
<property name="java.source" value="1.7"/>
<property name="java.target" value="1.7"/>
<property name="java.source" value="1.8"/>
<property name="java.target" value="1.8"/>
</target>
<target name="jar">

View File

@ -0,0 +1,51 @@
/* MyALPNSelectCallback.java
*
* Copyright (C) 2006-2023 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
import java.io.*;
import java.net.*;
import java.nio.*;
import com.wolfssl.*;
class MyALPNSelectCallback implements WolfSSLALPNSelectCallback
{
public int alpnSelectCallback(WolfSSLSession ssl, String[] out,
String[] in, Object arg) {
System.out.println("Entered MyALPNSelectCallback");
System.out.println("... out.length = " + out.length);
if (out.length > 0) {
System.out.println("out[0] = " + out[0]);
}
System.out.println("... in.length = " + in.length);
if (in.length > 0) {
System.out.println("in[0] = " + in[0]);
}
System.out.println("... arg = " + arg);
if (in.length > 0 && in[0].equals("h2")) {
out[0] = "h22";
return WolfSSL.SSL_TLSEXT_ERR_OK;
}
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
}

View File

@ -469,6 +469,15 @@ public class Server {
}
}
/* ALPN select callback */
MyALPNSelectCallback alpnSelectCb = new MyALPNSelectCallback();
ret = ssl.setAlpnSelectCb(alpnSelectCb, null);
if (ret != WolfSSL.SSL_SUCCESS) {
System.out.println("failed to set ALPN select callback, " +
"ret = " + ret);
System.exit(1);
}
if (useIOCallbacks || (doDTLS == 1)) {
/* register I/O callback user context */
MyIOCtx ioctx = new MyIOCtx(outstream, instream,

View File

@ -81,6 +81,8 @@ extern "C" {
#define com_wolfssl_WolfSSL_SSL_ERROR_SSL 85L
#undef com_wolfssl_WolfSSL_SSL_ERROR_SOCKET_PEER_CLOSED
#define com_wolfssl_WolfSSL_SSL_ERROR_SOCKET_PEER_CLOSED -397L
#undef com_wolfssl_WolfSSL_UNKNOWN_ALPN_PROTOCOL_NAME_E
#define com_wolfssl_WolfSSL_UNKNOWN_ALPN_PROTOCOL_NAME_E -405L
#undef com_wolfssl_WolfSSL_WOLFSSL_CRL_CHECKALL
#define com_wolfssl_WolfSSL_WOLFSSL_CRL_CHECKALL 1L
#undef com_wolfssl_WolfSSL_WOLFSSL_OCSP_URL_OVERRIDE
@ -167,6 +169,12 @@ extern "C" {
#define com_wolfssl_WolfSSL_CACHE_MATCH_ERROR -280L
#undef com_wolfssl_WolfSSL_WOLFSSL_SNI_HOST_NAME
#define com_wolfssl_WolfSSL_WOLFSSL_SNI_HOST_NAME 0L
#undef com_wolfssl_WolfSSL_SSL_TLSEXT_ERR_OK
#define com_wolfssl_WolfSSL_SSL_TLSEXT_ERR_OK 0L
#undef com_wolfssl_WolfSSL_SSL_TLSEXT_ERR_NOACK
#define com_wolfssl_WolfSSL_SSL_TLSEXT_ERR_NOACK 3L
#undef com_wolfssl_WolfSSL_SSL_TLSEXT_ERR_ALERT_FATAL
#define com_wolfssl_WolfSSL_SSL_TLSEXT_ERR_ALERT_FATAL 2L
#undef com_wolfssl_WolfSSL_MEMORY_E
#define com_wolfssl_WolfSSL_MEMORY_E -125L
#undef com_wolfssl_WolfSSL_BUFFER_E

View File

@ -45,8 +45,14 @@
#include "com_wolfssl_WolfSSL.h"
/* custom I/O native fn prototypes */
int NativeSSLIORecvCb(WOLFSSL *ssl, char *buf, int sz, void *ctx);
int NativeSSLIOSendCb(WOLFSSL *ssl, char *buf, int sz, void *ctx);
int NativeSSLIORecvCb(WOLFSSL *ssl, char *buf, int sz, void *ctx);
int NativeSSLIOSendCb(WOLFSSL *ssl, char *buf, int sz, void *ctx);
/* ALPN select native callback prototype */
int NativeALPNSelectCb(WOLFSSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg);
#ifdef HAVE_CRL
/* global object refs for CRL callback */
static jobject g_crlCbIfaceObj;
@ -4168,6 +4174,336 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_useALPN
return ret;
}
JNIEXPORT int JNICALL Java_com_wolfssl_WolfSSLSession_setALPNSelectCb
(JNIEnv* jenv, jobject jcl, jlong sslPtr)
{
#if defined(HAVE_ALPN) && (LIBWOLFSSL_VERSION_HEX >= 0x05006006)
/* wolfSSL_set_alpn_select_cb() added as of wolfSSL 5.6.6 */
WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr;
int ret = SSL_SUCCESS;
(void)jcl;
if (jenv == NULL || ssl == NULL) {
return BAD_FUNC_ARG;
}
/* set ALPN select callback */
wolfSSL_set_alpn_select_cb(ssl, NativeALPNSelectCb, NULL);
return ret;
#else
(void)jenv;
(void)jcl;
(void)sslPtr;
return NOT_COMPILED_IN;
#endif
}
#ifdef HAVE_ALPN
int NativeALPNSelectCb(WOLFSSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg)
{
JNIEnv* jenv; /* JNI environment */
jclass excClass; /* WolfSSLJNIException class */
int needsDetach = 0; /* Should we explicitly detach? */
jint vmret = 0;
jobject* g_cachedSSLObj; /* WolfSSLSession cached object */
jclass sslClass; /* WolfSSLSession class */
jmethodID alpnSelectMethodId; /* internalAlpnSelectCallback ID */
int ret = 0;
int idx = 0;
int peerProtoCount = 0;
char* peerProtos = NULL;
char* peerProtosCopy = NULL;
word16 peerProtosSz = 0;
char* curr = NULL;
char* ptr = NULL;
jobjectArray peerProtosArr = NULL;
jobjectArray outProtoArr = NULL;
int outProtoArrSz = 0;
jstring selectedProto = NULL;
const char* selectedProtoCharArr = NULL;
int selectedProtoCharArrSz = 0;
if (g_vm == NULL || ssl == NULL || out == NULL || outlen == NULL ||
in == NULL) {
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* get JavaEnv from JavaVM */
vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6));
if (vmret == JNI_EDETACHED) {
#ifdef __ANDROID__
vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL);
#else
vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL);
#endif
if (vmret) {
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
needsDetach = 1;
}
else if (vmret != JNI_OK) {
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* find exception class in case we need it */
excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException");
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* get stored WolfSSLSession object */
g_cachedSSLObj = (jobject*) wolfSSL_get_jobject(ssl);
if (!g_cachedSSLObj) {
(*jenv)->ThrowNew(jenv, excClass,
"Can't get native WolfSSLSession object reference in "
"NativeALPNSelectCb");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* lookup WolfSSLSession class from object */
sslClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj));
if (sslClass == NULL) {
(*jenv)->ThrowNew(jenv, excClass,
"Can't get native WolfSSLSession class reference in "
"NativeALPNSelectCb");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* call internal ALPN select callback */
alpnSelectMethodId = (*jenv)->GetMethodID(jenv, sslClass,
"internalAlpnSelectCallback",
"(Lcom/wolfssl/WolfSSLSession;[Ljava/lang/String;[Ljava/lang/String;)I");
if (alpnSelectMethodId == NULL) {
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
}
(*jenv)->ThrowNew(jenv, excClass,
"Error getting internalAlpnSelectCallback method from JNI");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* Use wolfSSL_ALPN_GetPeerProtocol() here to get ALPN protocols sent
* by the peer instead of directly using in/inlen, since this API
* splits/formats into a comma-separated, null-terminated list */
ret = wolfSSL_ALPN_GetPeerProtocol(ssl, &peerProtos, &peerProtosSz);
if (ret != WOLFSSL_SUCCESS) {
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
}
(*jenv)->ThrowNew(jenv, excClass,
"Error in wolfSSL_ALPN_GetPeerProtocol()");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* Make a copy of peer protos since we have to scan through it first
* to get total number of tokens */
peerProtosCopy = (char*)XMALLOC(peerProtosSz, NULL,
DYNAMIC_TYPE_TMP_BUFFER);
if (peerProtosCopy == NULL) {
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
}
(*jenv)->ThrowNew(jenv, excClass,
"Error allocating memory for peer protocols array");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
XMEMCPY(peerProtosCopy, peerProtos, peerProtosSz);
/* get count of protocols, used to create Java array of proper size */
curr = XSTRTOK(peerProtosCopy, ",", &ptr);
while (curr != NULL) {
peerProtoCount++;
curr = XSTRTOK(NULL, ",", &ptr);
}
XFREE(peerProtosCopy, NULL, DYNAMIC_TYPE_TMP_BUFFER);
peerProtosCopy = NULL;
if (peerProtoCount == 0) {
wolfSSL_ALPN_FreePeerProtocol(ssl, &peerProtos);
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
}
(*jenv)->ThrowNew(jenv, excClass,
"ALPN peer protocol list size is 0");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* create Java String[] of size peerProtoCount */
peerProtosArr = (*jenv)->NewObjectArray(jenv, peerProtoCount,
(*jenv)->FindClass(jenv, "java/lang/String"), NULL);
if (peerProtosArr == NULL) {
wolfSSL_ALPN_FreePeerProtocol(ssl, &peerProtos);
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
}
(*jenv)->ThrowNew(jenv, excClass,
"Failed to create JNI String[] for ALPN protocols");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* add each char* to String[] for call to Java callback method */
curr = XSTRTOK(peerProtos, ",", &ptr);
while (curr != NULL) {
(*jenv)->SetObjectArrayElement(jenv, peerProtosArr, idx++,
(*jenv)->NewStringUTF(jenv, curr));
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
wolfSSL_ALPN_FreePeerProtocol(ssl, &peerProtos);
(*jenv)->ThrowNew(jenv, excClass,
"Failed to add String to JNI String[] for ALPN protocols");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
curr = XSTRTOK(NULL, ",", &ptr);
}
/* free native peer protocol list, no longer needed */
wolfSSL_ALPN_FreePeerProtocol(ssl, &peerProtos);
/* create new String[1] to let Java callback put output selection into
* first array offset, ie String[0] */
outProtoArr = (*jenv)->NewObjectArray(jenv, 1,
(*jenv)->FindClass(jenv, "java/lang/String"), NULL);
if (outProtoArr == NULL) {
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
}
(*jenv)->ThrowNew(jenv, excClass,
"Failed to create JNI String[1] for output ALPN protocol");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* call Java callback */
ret = (*jenv)->CallIntMethod(jenv, (jobject)(*g_cachedSSLObj),
alpnSelectMethodId, (jobject)(*g_cachedSSLObj), outProtoArr,
peerProtosArr);
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
(*jenv)->ThrowNew(jenv, excClass,
"Exception while calling internalAlpnSelectCallback()");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
if (ret == SSL_TLSEXT_ERR_OK) {
/* convert returned String[0] into char* */
outProtoArrSz = (*jenv)->GetArrayLength(jenv, outProtoArr);
if (outProtoArrSz != 1) {
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
}
(*jenv)->ThrowNew(jenv, excClass,
"Output String[] for ALPN result not size 1");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* get jstring from String[0] */
selectedProto = (jstring)(*jenv)->GetObjectArrayElement(
jenv, outProtoArr, 0);
if (selectedProto == NULL) {
if ((*jenv)->ExceptionOccurred(jenv)) {
(*jenv)->ExceptionDescribe(jenv);
(*jenv)->ExceptionClear(jenv);
}
(*jenv)->ThrowNew(jenv, excClass,
"Selected ALPN protocol in String[] is NULL");
if (needsDetach) {
(*g_vm)->DetachCurrentThread(g_vm);
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
/* get char* from jstring */
selectedProtoCharArr = (*jenv)->GetStringUTFChars(jenv,
selectedProto, 0);
selectedProtoCharArrSz = XSTRLEN(selectedProtoCharArr);
/* see if selected ALPN protocol is in original sent list */
if (selectedProtoCharArr != NULL) {
for (idx = 0; idx < inlen; idx++) {
if (idx + selectedProtoCharArrSz > inlen) {
/* No match found, fatal error. in not long enough for
* search. Reset ret to error condition, match not set
* correctly */
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
break;
}
if (XMEMCMP(in + idx, selectedProtoCharArr,
selectedProtoCharArrSz) == 0) {
/* Match found. Format of input array is length byte of
* ALPN protocol, followed by ALPN protocol,
* ie (LEN+ALPN|LEN+ALPN|...) We set *out to ALPN selected
* protocol and *outlen to length of protocol (idx - 1) */
*out = in + idx;
*outlen = in[idx - 1];
break;
}
}
}
else {
/* Not able to get selected ALPN protocol from Java, fatal error */
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
}
}
return ret;
}
#endif /* HAVE_ALPN */
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_useSecureRenegotiation
(JNIEnv* jenv, jobject jcl, jlong ssl)
{

View File

@ -759,6 +759,14 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_WolfSSLSession_sslGet0AlpnSelected
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_useALPN
(JNIEnv *, jobject, jlong, jstring, jint);
/*
* Class: com_wolfssl_WolfSSLSession
* Method: setALPNSelectCb
* Signature: (J)I
*/
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setALPNSelectCb
(JNIEnv *, jobject, jlong);
/*
* Class: com_wolfssl_WolfSSLSession
* Method: useSecureRenegotiation

View File

@ -174,6 +174,8 @@ public class WolfSSL {
public static final int SSL_ERROR_SSL = 85;
/** Peer closed socket */
public static final int SSL_ERROR_SOCKET_PEER_CLOSED = -397;
/** Unrecognized ALPN protocol name */
public static final int UNKNOWN_ALPN_PROTOCOL_NAME_E = -405;
/* extra definitions from ssl.h */
/** CertManager: check all cert CRLs */
@ -302,6 +304,15 @@ public class WolfSSL {
/** SNI Host name type, for UseSNI() */
public static final int WOLFSSL_SNI_HOST_NAME = 0;
/** ALPN ERR OK, ALPN protocol match */
public static final int SSL_TLSEXT_ERR_OK = 0;
/** ALPN ERR NOACK, ALPN callback no match but not fatal */
public static final int SSL_TLSEXT_ERR_NOACK = 3;
/** ALPN ERR FATAL, ALPN callback no match and fatal */
public static final int SSL_TLSEXT_ERR_ALERT_FATAL = 2;
/* ---------------------- wolfCrypt codes ---------------------------- */
/** Out of memory error */

View File

@ -0,0 +1,63 @@
/* WolfSSLALPNSelectCallback.java
*
* Copyright (C) 2006-2023 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
package com.wolfssl;
/**
* wolfSSL ALPN Select Callback Interface.
* This interface specifies how applications should implement the ALPN
* select callback class to be used by wolfSSL.
* <p>
* After implementing this interface, it should be passed as a parameter
* to the {@link WolfSSLSession#setAlpnSelectCb(WolfSSLALPNSelectCallback,
* Object) WolfSSLSession.setALPNSelectCb()} method to be registered with the
* native wolfSSL session.
*
* @author wolfSSL
*/
public interface WolfSSLALPNSelectCallback {
/**
* ALPN select callback method.
* This method acts as the selection callback for Application Layer
* Negotiation Protocol (ALPN). This will be called during the handshake
* and gives the ALPN protocols proposed by the peer, allowing the server
* to select the desired protocol.
*
* @param ssl the current SSL session object from which the callback was
* initiated.
* @param out output array; the selected ALPN protocol should be placed as
* a String into the first array element, ie out[0].
* @param in input array containing the ALPN values sent by the client
* in the ClientHello message.
* @param arg Object set by user when registering callback, passed back
* to user inside callback in case needed to select ALPN.
* @return WolfSSL.SSL_TLSEXT_ERR_OK if ALPN protocol has been selected,
* WolfSSL.SSL_TLSEXT_ERR_NOACK if ALPN protocol was not selected
* but handshake should proceed without ALPN,
* WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL if no ALPN match can be found
* and a fatal alert should be sent to peer to end the
* handshake.
*/
public int alpnSelectCallback(WolfSSLSession ssl, String[] out,
String[] in, Object arg);
}

View File

@ -55,6 +55,7 @@ public class WolfSSLSession {
private Object rsaVerifyCtx;
private Object rsaEncCtx;
private Object rsaDecCtx;
private Object alpnSelectArg;
/* reference to the associated WolfSSLContext */
private WolfSSLContext ctx = null;
@ -69,6 +70,10 @@ public class WolfSSLSession {
private WolfSSLIORecvCallback internRecvSSLCb;
private WolfSSLIOSendCallback internSendSSLCb;
/* user-registered ALPN select callback, called by internal WolfSSLSession
* ALPN select callback */
private WolfSSLALPNSelectCallback internAlpnSelectCb;
/* have session tickets been enabled for this session? Default to false. */
private boolean sessionTicketsEnabled = false;
@ -164,6 +169,31 @@ public class WolfSSLSession {
return this.rsaDecCtx;
}
/* these callbacks will be registered with native wolfSSL library */
private int internalIOSSLRecvCallback(WolfSSLSession ssl, byte[] buf,
int sz)
{
int ret;
/* call user-registered recv method */
ret = internRecvSSLCb.receiveCallback(ssl, buf, sz,
ssl.getIOReadCtx());
return ret;
}
private int internalIOSSLSendCallback(WolfSSLSession ssl, byte[] buf,
int sz)
{
int ret;
/* call user-registered recv method */
ret = internSendSSLCb.sendCallback(ssl, buf, sz,
ssl.getIOWriteCtx());
return ret;
}
private long internalPskClientCallback(WolfSSLSession ssl, String hint,
StringBuffer identity, long idMaxLen, byte[] key,
long keyMaxLen)
@ -189,6 +219,18 @@ public class WolfSSLSession {
return ret;
}
private int internalAlpnSelectCallback(WolfSSLSession ssl, String[] out,
String[] in)
{
int ret;
/* call user-registered ALPN select callback */
ret = internAlpnSelectCb.alpnSelectCallback(ssl, out, in,
this.alpnSelectArg);
return ret;
}
/**
* Verifies that the current WolfSSLSession object is active.
*
@ -305,6 +347,7 @@ public class WolfSSLSession {
private native int sslSetAlpnProtos(long ssl, byte[] alpnProtos);
private native byte[] sslGet0AlpnSelected(long ssl);
private native int useALPN(long ssl, String protocols, int options);
private native int setALPNSelectCb(long ssl);
private native int useSecureRenegotiation(long ssl);
private native int rehandshake(long ssl);
private native int set1SigAlgsList(long ssl, String list);
@ -3337,6 +3380,43 @@ public class WolfSSLSession {
}
}
/**
* Registers ALPN select callback.
*
* This callback is called by native wolfSSL during the handshake
* on the server side after receiving the ALPN protocols by the client
* in the ClientHello message.
*
* @param cb callback to be registered with SSL session
* @param arg Object that will be passed back to user inside callback
* @return <code>SSL_SUCCESS</code> upon success. <code>
* NOT_COMPILED_IN</code> if wolfSSL was not compiled with
* ALPN support, and other negative value representing other
* error scenarios.
* @throws IllegalStateException WolfSSLSession has been freed
* @throws WolfSSLJNIException Internal JNI error
*/
public int setAlpnSelectCb(WolfSSLALPNSelectCallback cb, Object arg)
throws IllegalStateException, WolfSSLJNIException {
int ret = 0;
confirmObjectIsActive();
synchronized (sslLock) {
ret = setALPNSelectCb(getSessionPtr());
if (ret == WolfSSL.SSL_SUCCESS) {
/* set ALPN select callback */
internAlpnSelectCb = cb;
/* set ALPN select arg Object, returned to user in callback */
this.alpnSelectArg = arg;
}
}
return ret;
}
/**
* Enable use of secure renegotiation on this session. Calling this
* API does not initiate secure renegotiation, but enables it. If enabled,
@ -3420,31 +3500,6 @@ public class WolfSSLSession {
}
}
/* this will be registered with native wolfSSL library */
private int internalIOSSLRecvCallback(WolfSSLSession ssl, byte[] buf,
int sz)
{
int ret;
/* call user-registered recv method */
ret = internRecvSSLCb.receiveCallback(ssl, buf, sz,
ssl.getIOReadCtx());
return ret;
}
private int internalIOSSLSendCallback(WolfSSLSession ssl, byte[] buf,
int sz)
{
int ret;
/* call user-registered recv method */
ret = internSendSSLCb.sendCallback(ssl, buf, sz,
ssl.getIOWriteCtx());
return ret;
}
@SuppressWarnings("deprecation")
@Override
protected void finalize() throws Throwable

View File

@ -27,7 +27,12 @@ import com.wolfssl.WolfSSLIORecvCallback;
import com.wolfssl.WolfSSLIOSendCallback;
import com.wolfssl.WolfSSLJNIException;
import com.wolfssl.WolfSSLSession;
import com.wolfssl.WolfSSLALPNSelectCallback;
import java.nio.ByteBuffer;
import java.util.function.BiFunction;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLEngine;
@ -35,6 +40,7 @@ import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLParameters;
import java.net.SocketException;
@ -97,6 +103,9 @@ public class WolfSSLEngine extends SSLEngine {
private final Object ioLock = new Object();
private final Object toSendLock = new Object();
/* ALPN selector callback, if set */
protected BiFunction<SSLEngine, List<String>, String> alpnSelector = null;
/** Turn on extra/verbose SSLEngine debug logging */
public boolean extraDebugEnabled = false;
@ -961,8 +970,15 @@ public class WolfSSLEngine extends SSLEngine {
if (ret < 0 &&
(err != WolfSSL.SSL_ERROR_WANT_READ) &&
(err != WolfSSL.SSL_ERROR_WANT_WRITE)) {
throw new SSLException(
"wolfSSL error, ret:err = " + ret + " : " + err);
if (err == WolfSSL.UNKNOWN_ALPN_PROTOCOL_NAME_E) {
throw new SSLHandshakeException(
"Unrecognized protocol name error, ret:err = " +
ret + " : " + err);
}
else {
throw new SSLException(
"wolfSSL error, ret:err = " + ret + " : " + err);
}
}
synchronized (toSendLock) {
@ -1381,6 +1397,160 @@ public class WolfSSLEngine extends SSLEngine {
return EngineHelper.getAlpnSelectedProtocolString();
}
/**
* Returns the application protocol value negotiated on a handshake
* currently in progress.
*
* After the handshake has finished, this will return null. To get the
* ALPN protocol negotiated during the handshake, after it has completed,
* call getApplicationProtocol().
*
* Not marked at @Override since this API was added as of
* Java SE 8 Maintenance Release 3, and Java 7 SSLSocket will not
* have this.
*
* @return String representating the application protocol negotiated
*/
public synchronized String getHandshakeApplicationProtocol() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getHandshakeApplicationProtocol()");
if (!this.needInit && !this.handshakeFinished) {
return EngineHelper.getAlpnSelectedProtocolString();
}
return null;
}
/**
* Returns the callback that selects an application protocol during the
* SSL/TLS handshake.
*
* @return the callback function, or null if no callback has been set
*/
public synchronized BiFunction<SSLEngine,List<String>,String>
getHandshakeApplicationProtocolSelector() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getHandshakeApplicationProtocolSelector()");
return this.alpnSelector;
}
/**
* Registers a callback function that selects an application protocol
* value for the SSL/TLS handshake.
*
* Usage of this callback will override any values set by
* SSLParameters.setApplicationProtocols().
*
* Callback argument descriptions:
*
* SSLEngine - the current SSLEngine, allows for inspection by the
* callback if needed
* List&lt;String&gt; - List of Strings representing application protocol
* names sent by the peer
* String - Result of the callback is an application protocol
* name String, or null if none of the peer's protocols
* are acceptable. If return value is an empty String,
* ALPN will not be used.
*
* @param selector callback used to select ALPN protocol for handshake
*/
public synchronized void setHandshakeApplicationProtocolSelector(
BiFunction<SSLEngine,List<String>,String> selector) {
int ret = 0;
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered setHandshakeApplicationProtocolSelector()");
if (selector != null) {
ALPNSelectCallback alpnCb = new ALPNSelectCallback();
try {
/* Pass in SSLSocket Object for use inside callback */
ret = this.ssl.setAlpnSelectCb(alpnCb, this);
if (ret != WolfSSL.SSL_SUCCESS) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"Native setAlpnSelectCb() failed, ret = " + ret +
", not setting selector");
return;
}
/* called from within ALPNSelectCallback during the handshake */
this.alpnSelector = selector;
} catch (WolfSSLJNIException e) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"Exception while calling ssl.setAlpnSelectCb, not setting");
}
}
}
/**
* Inner class that implement the ALPN select callback which is registered
* with our com.wolfssl.WolfSSLSession when
* setHandshakeApplicationProtocolSelector() has been called.
*/
class ALPNSelectCallback implements WolfSSLALPNSelectCallback
{
public int alpnSelectCallback(WolfSSLSession ssl, String[] out,
String[] in, Object arg) {
SSLEngine engine = (SSLEngine)arg;
List<String> peerProtos = new ArrayList<String>();
String selected = null;
if (alpnSelector == null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"alpnSelector null inside ALPNSelectCallback");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
if (!(arg instanceof SSLEngine)) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"alpnSelectCallback arg not type of SSLEngine");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
if (in.length == 0) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"peer protocol list is 0 inside alpnSelectCallback");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN protos sent by peer: " + Arrays.toString(in));
for (String s: in) {
peerProtos.add(s);
}
selected = alpnSelector.apply(engine, peerProtos);
if (selected == null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN protocol string is null, no peer match");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
else {
if (selected.isEmpty()) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN not being used, selected proto empty");
return WolfSSL.SSL_TLSEXT_ERR_NOACK;
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN protocol selected by callback: " + selected);
out[0] = selected;
return WolfSSL.SSL_TLSEXT_ERR_OK;
}
}
}
/**
* Set the SSLParameters for this SSLEngine.
*

View File

@ -455,19 +455,22 @@ public class WolfSSLEngineHelper {
/**
* Get selected ALPN protocol string
*
* @return String representation of selected ALPN protocol or null
* if handshake has not finished
* @return String representation of selected ALPN protocol, null
* if protocol is not available yet, or empty String if
* ALPN will not be used for this connection.
*/
protected String getAlpnSelectedProtocolString() {
if (this.ssl.handshakeDone()) {
String proto = ssl.getAlpnSelectedString();
String proto = ssl.getAlpnSelectedString();
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"selected ALPN protocol = " + proto);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"selected ALPN protocol = " + proto);
return proto;
if (proto == null && this.ssl.handshakeDone()) {
/* ALPN not used if proto is null and handshake is done */
return "";
}
return null;
return proto;
}
/********** Calls to transfer over parameter to wolfSSL before connection */
@ -800,9 +803,9 @@ public class WolfSSLEngineHelper {
"\t" + i + ": " + applicationProtocols[i]);
}
/* continue on mismatch */
/* fail on mismatch */
this.ssl.useALPN(applicationProtocols,
WolfSSL.WOLFSSL_ALPN_CONTINUE_ON_MISMATCH);
WolfSSL.WOLFSSL_ALPN_FAILED_ON_MISMATCH);
}
if (alpnProtos == null && applicationProtocols == null) {

View File

@ -32,6 +32,8 @@ import java.net.SocketException;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.function.BiFunction;
import java.util.List;
import java.util.Arrays;
import java.nio.channels.SocketChannel;
@ -47,6 +49,7 @@ import com.wolfssl.WolfSSL;
import com.wolfssl.WolfSSLException;
import com.wolfssl.WolfSSLIOSendCallback;
import com.wolfssl.WolfSSLIORecvCallback;
import com.wolfssl.WolfSSLALPNSelectCallback;
import com.wolfssl.WolfSSLJNIException;
import com.wolfssl.WolfSSLSession;
@ -92,6 +95,9 @@ public class WolfSSLSocket extends SSLSocket {
* accessing WolfSSLSession object / WOLFSSL struct */
private final Object ioLock = new Object();
/* ALPN selector callback, if set */
protected BiFunction<SSLSocket, List<String>, String> alpnSelector = null;
/**
* Create new WolfSSLSocket object
*
@ -1018,6 +1024,159 @@ public class WolfSSLSocket extends SSLSocket {
return EngineHelper.getAlpnSelectedProtocolString();
}
/**
* Returns the application protocol value negotiated on a handshake
* currently in progress.
*
* After the handshake has finished, this will return null. To get the
* ALPN protocol negotiated during the handshake, after it has completed,
* call getApplicationProtocol().
*
* Not marked at @Override since this API was added as of
* Java SE 8 Maintenance Release 3, and Java 7 SSLSocket will not
* have this.
*
* @return String representating the application protocol negotiated
*/
public synchronized String getHandshakeApplicationProtocol() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getHandshakeApplicationProtocol()");
if (this.handshakeStarted && !this.handshakeComplete) {
return EngineHelper.getAlpnSelectedProtocolString();
}
return null;
}
/**
* Returns the callback that selects an application protocol during the
* SSL/TLS handshake.
*
* @return the callback function, or null if no callback has been set
*/
public synchronized BiFunction<SSLSocket,List<String>,String>
getHandshakeApplicationProtocolSelector() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getHandshakeApplicationProtocolSelector()");
return this.alpnSelector;
}
/**
* Registers a callback function that selects an application protocol
* value for the SSL/TLS handshake.
*
* Usage of this callback will override any values set by
* SSLParameters.setApplicationProtocols().
*
* Callback argument descriptions:
*
* SSLSocket - the current SSLSocket, allows for inspection by the
* callback if needed
* List&lt;String&gt; - List of Strings representing application protocol
* names sent by the peer
* String - Result of the callback is an application protocol
* name String, or null if none of the peer's protocols
* are acceptable. If return value is an empty String,
* ALPN will not be used.
*
* @param selector callback used to select ALPN protocol for handshake
*/
public synchronized void setHandshakeApplicationProtocolSelector(
BiFunction<SSLSocket,List<String>,String> selector) {
int ret = 0;
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered setHandshakeApplicationProtocolSelector()");
if (selector != null) {
ALPNSelectCallback alpnCb = new ALPNSelectCallback();
try {
/* Pass in SSLSocket Object for use inside callback */
ret = this.ssl.setAlpnSelectCb(alpnCb, this);
if (ret != WolfSSL.SSL_SUCCESS) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"Native setAlpnSelectCb() failed, ret = " + ret +
", not setting selector");
return;
}
/* called from within ALPNSelectCallback during the handshake */
this.alpnSelector = selector;
} catch (WolfSSLJNIException e) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"Exception while calling ssl.setAlpnSelectCb, not setting");
}
}
}
/**
* Inner class that implement the ALPN select callback which is registered
* with our com.wolfssl.WolfSSLSession when
* setHandshakeApplicationProtocolSelector() has been called.
*/
class ALPNSelectCallback implements WolfSSLALPNSelectCallback
{
public int alpnSelectCallback(WolfSSLSession ssl, String[] out,
String[] in, Object arg) {
SSLSocket sock = (SSLSocket)arg;
List<String> peerProtos = new ArrayList<String>();
String selected = null;
if (alpnSelector == null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"alpnSelector null inside ALPNSelectCallback");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
if (!(arg instanceof SSLSocket)) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"alpnSelectCallback arg not type of SSLSocket");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
if (in.length == 0) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"peer protocol list is 0 inside alpnSelectCallback");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN protos sent by peer: " + Arrays.toString(in));
for (String s: in) {
peerProtos.add(s);
}
selected = alpnSelector.apply(sock, peerProtos);
if (selected == null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN protocol string is null, no peer match");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
else {
if (selected.isEmpty()) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN not being used, selected proto empty");
return WolfSSL.SSL_TLSEXT_ERR_NOACK;
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN protocol selected by callback: " + selected);
out[0] = selected;
return WolfSSL.SSL_TLSEXT_ERR_OK;
}
}
}
/**
* Returns array of protocols supported by this SSLSocket.
*

View File

@ -27,6 +27,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.logging.Level;
@ -43,6 +44,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.ServerSocket;
import java.net.SocketAddress;
@ -65,6 +68,8 @@ import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import java.security.Security;
import java.security.Provider;
import java.security.KeyStore;
@ -79,6 +84,7 @@ import java.security.NoSuchProviderException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import com.wolfssl.provider.jsse.WolfSSLProvider;
@ -477,9 +483,11 @@ public class WolfSSLSocketTest {
ss = (SSLServerSocket)ctx.getServerSocketFactory()
.createServerSocket(0);
server = new TestServer(this, ss);
TestArgs sArgs = new TestArgs(null, null, true, true, true, null);
TestArgs cArgs = new TestArgs(null, null, false, false, true, null);
server = new TestServer(this.ctx, ss, sArgs, 1);
server.start();
client = new TestClient(this, ss.getLocalPort());
client = new TestClient(this.ctx, ss.getLocalPort(), cArgs);
client.start();
srvException = server.getException();
@ -517,9 +525,11 @@ public class WolfSSLSocketTest {
ss = (SSLServerSocket)ctx.getServerSocketFactory()
.createServerSocket(0);
server = new TestServer(this, ss);
TestArgs sArgs = new TestArgs(null, null, true, true, true, null);
TestArgs cArgs = new TestArgs(null, null, false, false, true, null);
server = new TestServer(this.ctx, ss, sArgs, 1);
server.start();
client = new TestClient(this, ss.getLocalPort());
client = new TestClient(this.ctx, ss.getLocalPort(), cArgs);
client.start();
srvException = server.getException();
@ -557,9 +567,11 @@ public class WolfSSLSocketTest {
ss = (SSLServerSocket)ctx.getServerSocketFactory()
.createServerSocket(0);
server = new TestServer(this, ss);
TestArgs sArgs = new TestArgs(null, null, true, true, true, null);
TestArgs cArgs = new TestArgs(null, null, false, false, true, null);
server = new TestServer(this.ctx, ss, sArgs, 1);
server.start();
client = new TestClient(this, ss.getLocalPort());
client = new TestClient(this.ctx, ss.getLocalPort(), cArgs);
client.start();
srvException = server.getException();
@ -597,9 +609,11 @@ public class WolfSSLSocketTest {
ss = (SSLServerSocket)ctx.getServerSocketFactory()
.createServerSocket(0);
server = new TestServer(this, ss);
TestArgs sArgs = new TestArgs(null, null, true, true, true, null);
TestArgs cArgs = new TestArgs(null, null, false, false, true, null);
server = new TestServer(this.ctx, ss, sArgs, 1);
server.start();
client = new TestClient(this, ss.getLocalPort());
client = new TestClient(this.ctx, ss.getLocalPort(), cArgs);
client.start();
srvException = server.getException();
@ -651,10 +665,13 @@ public class WolfSSLSocketTest {
SSLServerSocket ss = (SSLServerSocket)ctx.getServerSocketFactory()
.createServerSocket(0);
TestServer server = new TestServer(this, ss);
TestArgs sArgs = new TestArgs(null, null, true, true, true, null);
TestArgs cArgs = new TestArgs(null, null, false, false, true, null);
TestServer server = new TestServer(this.ctx, ss, sArgs, 1);
server.start();
TestClient client = new TestClient(this, ss.getLocalPort());
TestClient client = new TestClient(this.ctx, ss.getLocalPort(), cArgs);
client.start();
@ -680,6 +697,129 @@ public class WolfSSLSocketTest {
System.out.println("\t... passed");
}
public void alpnClientServerRunner(TestArgs sArgs, TestArgs cArgs,
boolean expectingException) throws Exception {
if (sArgs == null || cArgs == null) {
throw new Exception("client/server TestArgs can not be null");
}
this.ctx = tf.createSSLContext("TLS", ctxProvider);
/* create SSLServerSocket first to get ephemeral port */
SSLServerSocket ss = (SSLServerSocket)ctx.getServerSocketFactory()
.createServerSocket(0);
TestServer server = new TestServer(this.ctx, ss, sArgs, 1);
server.start();
TestClient client = new TestClient(this.ctx, ss.getLocalPort(), cArgs);
client.start();
try {
client.join(1000);
server.join(1000);
} catch (InterruptedException e) {
System.out.println("interrupt happened");
fail("Threaded client/server test failed");
}
Exception srvException = server.getException();
Exception cliException = client.getException();
if (srvException != null || cliException != null) {
if (!expectingException) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
if (srvException != null) {
srvException.printStackTrace(pw);
}
if (cliException != null) {
cliException.printStackTrace(pw);
}
String traceString = sw.toString();
throw new Exception(traceString);
}
}
else if (expectingException) {
throw new Exception("Expecting exception but got none");
}
}
@Test
public void testClientServerThreadedAlpnSelectCallback() throws Exception {
TestArgs sArgs = null;
TestArgs cArgs = null;
System.out.print("\tTesting ALPN select callback");
/* wolfSSL_set_alpn_select_cb() added in wolfSSL 5.6.6 */
if (WolfSSL.getLibVersionHex() < 0x05006006) {
System.out.println("\t... skipped");
return;
}
/* Successful test:
* Sanity check, no ALPN */
sArgs = new TestArgs(null, null, true, true, true, null);
sArgs.setExpectedAlpn("");
cArgs = new TestArgs(null, null, false, false, true, null);
cArgs.setExpectedAlpn("");
alpnClientServerRunner(sArgs, cArgs, false);
/* Successful test:
* ALPN callback, server selects matching protocol from client list */
sArgs = new TestArgs(null, null, true, true, true, null);
sArgs.setAlpnForCallback("h2");
sArgs.setExpectedAlpn("h2");
cArgs = new TestArgs(null, null, false, false, true, null);
cArgs.setAlpnList(new String[] {"h2", "http/1.1"});
cArgs.setExpectedAlpn("h2");
alpnClientServerRunner(sArgs, cArgs, false);
/* Successful test:
* ALPN callback, server selects matching protocol from client list */
sArgs = new TestArgs(null, null, true, true, true, null);
sArgs.setAlpnForCallback("http/1.1");
sArgs.setExpectedAlpn("http/1.1");
cArgs = new TestArgs(null, null, false, false, true, null);
cArgs.setAlpnList(new String[] {"h2", "http/1.1"});
cArgs.setExpectedAlpn("http/1.1");
alpnClientServerRunner(sArgs, cArgs, false);
/* Successful test:
* ALPN callback, client list is empty so callback not called */
sArgs = new TestArgs(null, null, true, true, true, null);
sArgs.setAlpnForCallback("h2");
sArgs.setExpectedAlpn("");
cArgs = new TestArgs(null, null, false, false, true, null);
cArgs.setAlpnList(null);
cArgs.setExpectedAlpn("");
alpnClientServerRunner(sArgs, cArgs, false);
/* Successful test:
* ALPN set on client and server without callback */
sArgs = new TestArgs(null, null, true, true, true, null);
sArgs.setAlpnList(new String[] {"h2"});
sArgs.setExpectedAlpn("h2");
cArgs = new TestArgs(null, null, false, false, true, null);
cArgs.setAlpnList(new String[] {"h2", "http/1.1"});
cArgs.setExpectedAlpn("h2");
alpnClientServerRunner(sArgs, cArgs, false);
/* Failure test:
* ALPN callback, server selects protocol not from client list */
sArgs = new TestArgs(null, null, true, true, true, null);
sArgs.setAlpnForCallback("invalid");
cArgs = new TestArgs(null, null, false, false, true, null);
cArgs.setAlpnList(new String[] {"h2", "http/1.1"});
alpnClientServerRunner(sArgs, cArgs, true);
System.out.println("\t... passed");
}
/**
* Internal multi-threaded SSLSocket-based server.
* Used when testing concurrent threaded SSLSocket client connections
@ -2432,36 +2572,219 @@ public class WolfSSLSocketTest {
System.out.println("\t... passed");
}
/**
* Inner class used to hold configuration options for
* TestServer and TestClient classes.
*/
protected class TestArgs
{
private String endpointIDAlg = null;
private String sniName = null;
private boolean wantClientAuth = true;
private boolean needClientAuth = true;
private boolean callStartHandshake = true;
private X509Certificate expectedPeerCert = null;
private String[] alpnList = null;
private String callbackAlpn = null;
private String expectedAlpn = null;
public TestArgs() { }
public TestArgs(String endpointID, String sni,
boolean wantClientAuth, boolean needClientAuth,
boolean callStartHandshake, X509Certificate expectedPeerCert) {
this.endpointIDAlg = endpointID;
this.sniName = sni;
this.wantClientAuth = wantClientAuth;
this.needClientAuth = needClientAuth;
this.callStartHandshake = callStartHandshake;
this.expectedPeerCert = expectedPeerCert;
}
public void setEndpointIdentificationAlg(String alg) {
this.endpointIDAlg = alg;
}
public String getEndpointIdentificationAlg() {
return this.endpointIDAlg;
}
public void setSNIName(String sni) {
this.sniName = sni;
}
public String getSNIName() {
return this.sniName;
}
public void setWantClientAuth(boolean want) {
this.wantClientAuth = want;
}
public boolean getWantClientAuth() {
return this.wantClientAuth;
}
public void setExpectedPeerCert(X509Certificate cert) {
this.expectedPeerCert = cert;
}
public X509Certificate getExpectedPeerCert() {
return this.expectedPeerCert;
}
public void setNeedClientAuth(boolean need) {
this.needClientAuth = need;
}
public boolean getNeedClientAuth() {
return this.needClientAuth;
}
public void setCallStartHandshake(boolean call) {
this.callStartHandshake = call;
}
public boolean getCallStartHandshake() {
return this.callStartHandshake;
}
public void setAlpnList(String[] alpns) {
this.alpnList = alpns;
}
public String[] getAlpnList() {
return this.alpnList;
}
public void setAlpnForCallback(String alpn) {
this.callbackAlpn = alpn;
}
public String getAlpnForCallback() {
return this.callbackAlpn;
}
public void setExpectedAlpn(String alpn) {
this.expectedAlpn = alpn;
}
public String getExpectedAlpn() {
return this.expectedAlpn;
}
}
protected class TestServer extends Thread
{
private SSLContext ctx;
private int port;
private Exception exception = null;
private TestArgs args = null;
private int numConnections = 1;
WolfSSLSocketTest wst;
SSLServerSocket ss = null;
public TestServer(WolfSSLSocketTest in, SSLServerSocket ss) {
this.ctx = in.ctx;
this.wst = in;
public TestServer(SSLContext ctx, SSLServerSocket ss,
TestArgs args, int numConnections) {
this.ctx = ctx;
this.ss = ss;
this.args = args;
this.numConnections = numConnections;
}
@Override
public void run() {
try {
SSLSocket sock = (SSLSocket)ss.accept();
sock.startHandshake();
int in = sock.getInputStream().read();
assertEquals(in, (int)'A');
sock.getOutputStream().write('B');
sock.close();
for (int i = 0; i < numConnections; i++) {
SSLSocket sock = (SSLSocket)ss.accept();
sock.setUseClientMode(false);
SSLParameters params = sock.getSSLParameters();
params.setWantClientAuth(this.args.getWantClientAuth());
params.setNeedClientAuth(this.args.getNeedClientAuth());
/* Set ALPN list of supported */
if (this.args.getAlpnList() != null) {
params.setApplicationProtocols(this.args.getAlpnList());
}
sock.setSSLParameters(params);
if (sock.getHandshakeApplicationProtocol() != null) {
throw new Exception(
"getHandshakeApplicationProtocol() should be " +
"null before handshake");
}
if (sock.getHandshakeApplicationProtocolSelector()
!= null) {
throw new Exception(
"getHandshakeApplicationProtocolSelector() " +
"should be null before being set");
}
/* wolfSSL_set_alpn_select_cb() added in wolfSSL 5.6.6 */
if (WolfSSL.getLibVersionHex() >= 0x05006006) {
/* Set ALPN selector callback if needed, Calls
* chooseAppProtocol during handshake to let server
* pick desired ALPN value */
if (this.args.getAlpnForCallback() != null) {
sock.setHandshakeApplicationProtocolSelector(
(serverSocket, clientProtocols) -> {
SSLSession s =
serverSocket.getHandshakeSession();
return chooseAppProtocol(
serverSocket,
clientProtocols,
s.getProtocol(),
s.getCipherSuite());
});
}
}
if (this.args.getCallStartHandshake()) {
sock.startHandshake();
}
int in = sock.getInputStream().read();
assertEquals(in, (int)'A');
sock.getOutputStream().write('B');
if (this.args.getExpectedAlpn() != null) {
if (!sock.getApplicationProtocol().equals(
this.args.getExpectedAlpn())) {
throw new Exception(
"Expected getApplicationProtocol() " +
"did not match actual\n" +
"expected: " + this.args.getExpectedAlpn() +
"\nactual: " + sock.getApplicationProtocol());
}
}
sock.close();
}
} catch (Exception e) {
this.exception = e;
}
}
public String chooseAppProtocol(SSLSocket serverSock,
List<String> clientProtocols, String protocol,
String cipherSuite) {
if (this.args.getAlpnForCallback() == null) {
/* empty string will ignore ALPN and continue handshake */
return "";
}
return this.args.getAlpnForCallback();
}
public Exception getException() {
return this.exception;
}
@ -2472,12 +2795,13 @@ public class WolfSSLSocketTest {
private SSLContext ctx;
private int srvPort;
private Exception exception = null;
private TestArgs args = null;
WolfSSLSocketTest wst;
public TestClient(WolfSSLSocketTest in, int port) {
this.ctx = in.ctx;
public TestClient(SSLContext ctx, int port, TestArgs args) {
this.ctx = ctx;
this.srvPort = port;
this.wst = in;
this.args = args;
}
@Override
@ -2486,11 +2810,58 @@ public class WolfSSLSocketTest {
try {
SSLSocket sock = (SSLSocket)ctx.getSocketFactory()
.createSocket();
sock.setUseClientMode(true);
sock.connect(new InetSocketAddress(srvPort));
sock.startHandshake();
SSLParameters params = sock.getSSLParameters();
/* Enable Endpoint Identification for hostname verification */
if (this.args.getEndpointIdentificationAlg() != null) {
params.setEndpointIdentificationAlgorithm(
this.args.getEndpointIdentificationAlg());
}
/* Set SNI, used for hostname verification of server cert */
if (this.args.getSNIName() != null) {
SNIHostName sniName = new SNIHostName(
this.args.getSNIName());
List<SNIServerName> sniNames = new ArrayList<>(1);
sniNames.add(sniName);
params.setServerNames(sniNames);
}
/* Set client ALPN list to include in ClientHello */
if (this.args.getAlpnList() != null) {
params.setApplicationProtocols(this.args.getAlpnList());
}
sock.setSSLParameters(params);
if (sock.getHandshakeApplicationProtocol() != null) {
throw new Exception(
"getHandshakeApplicationProtocol() should be " +
"null before handshake");
}
if (this.args.getCallStartHandshake()) {
sock.startHandshake();
}
sock.getOutputStream().write('A');
int in = sock.getInputStream().read();
assertEquals(in, (int)'B');
if (this.args.getExpectedAlpn() != null) {
if (!sock.getApplicationProtocol().equals(
this.args.getExpectedAlpn())) {
throw new Exception(
"Expected getApplicationProtocol() " +
"did not match actual\n" +
"expected: " + this.args.getExpectedAlpn() +
"\nactual: " + sock.getApplicationProtocol());
}
}
sock.close();
} catch (Exception e) {