Merge pull request #259 from rlm2002/implementSniMatchers

implement SNI matcher logic
pull/269/head
Chris Conlon 2025-05-14 16:24:23 -06:00 committed by GitHub
commit 985edddf91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 225 additions and 9 deletions

View File

@ -30,6 +30,8 @@ import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SNIMatcher;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.X509TrustManager;
@ -1474,6 +1476,11 @@ public class WolfSSLEngineHelper {
* collected (ex: protocol version). */
this.session.updateStoredSessionValues();
if (!this.clientMode && !matchSNI()) {
throw new SSLHandshakeException(
"Unrecognized Server Name");
}
return ret;
}
@ -1531,6 +1538,59 @@ public class WolfSSLEngineHelper {
return "legacy".equals(dhKeySize);
}
/**
* Validates Server Name Indication (SNI) match between client request and
* server matchers.
*
* This helper method is used only on the server side during the TLS
* handshake to check if there is a server name in the list of requested
* server names that matches the SNI matcher parameter. The check will be
* ignored (return true) if no requested server name were sent by client
* or if the SNI matcher parameter has not been set.
*
* Triggers an SSLHandshakeException on server side during handshake when
* false.
*
* @return true on success or false if no match was found
*/
protected synchronized boolean matchSNI(){
List <SNIMatcher> matchers = this.params.getSNIMatchers();
if (matchers != null && !matchers.isEmpty()) {
/* Match a server name to SNI requested by Client */
List <SNIServerName> serverNames = this.session
.getRequestedServerNames();
if (serverNames != null && !serverNames.isEmpty()) {
for (SNIServerName serverName : serverNames) {
if (serverName.getType() == WolfSSL.WOLFSSL_SNI_HOST_NAME) {
/* If the SNI is of type WOLFSSL_SNI_HOST_NAME, compare
* the name to matchers list */
for (SNIMatcher matcher : matchers) {
if (matcher.matches(serverName)) {
/* If a match is found, accept the server name
* and return true value, break from loop */
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "Accepted SNI: " + serverName);
return true;
}
}
}
}
} else {
/* If server names are null or empty, ignore server name
* indication */
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "No server names found, ignoring SNI");
return true;
}
} else {
/* If matchers are null or empty, ignore server name indication */
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "No SNIMatchers set");
return true;
}
return false;
}
/**
* Unset the native verify callback and reset internal verify
* callback state.

View File

@ -21,7 +21,9 @@
package com.wolfssl.provider.jsse;
import java.util.List;
import javax.net.ssl.SNIMatcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
/**
@ -45,6 +47,7 @@ final class WolfSSLParameters {
private boolean needClientAuth = false;
private String endpointIdAlgorithm = null;
private List<WolfSSLSNIServerName> serverNames;
private List<SNIMatcher> sniMatchers;
private boolean useCipherSuiteOrder = true;
String[] applicationProtocols = new String[0];
private boolean useSessionTickets = false;
@ -72,7 +75,7 @@ final class WolfSSLParameters {
/* TODO: duplicate other properties here when WolfSSLParameters
* can handle them */
cp.setSNIMatchers(this.getSNIMatchers());
return cp;
}
@ -184,14 +187,27 @@ final class WolfSSLParameters {
}
/* TODO, create our own class for SNIMatcher, in case Java doesn't support it */
//void setSNIMatchers(Collection<SNIMatcher> matchers) {
// /* TODO */
//}
void setSNIMatchers(Collection<SNIMatcher> matchers) {
if (matchers != null && !matchers.isEmpty()) {
if (this.sniMatchers == null) {
this.sniMatchers = new ArrayList<SNIMatcher>();
}
for (SNIMatcher matcher : matchers) {
this.sniMatchers.add(matcher);
}
} else {
this.sniMatchers = new ArrayList<SNIMatcher>();
}
}
/* TODO, create our own class for SNIMatcher, in case Java doesn't support it */
//Collection<SNIMatcher> getSNIMatchers() {
// return null; /* TODO */
//}
List<SNIMatcher> getSNIMatchers() {
if (this.sniMatchers != null && !this.sniMatchers.isEmpty()) {
return Collections.unmodifiableList(new ArrayList<SNIMatcher>(sniMatchers));
} else {
return Collections.emptyList();
}
}
void setUseCipherSuitesOrder(boolean honorOrder) {
this.useCipherSuiteOrder = honorOrder;

View File

@ -39,6 +39,8 @@ public class WolfSSLParametersHelper
private static Method setApplicationProtocols = null;
private static Method getEndpointIdentificationAlgorithm = null;
private static Method setEndpointIdentificationAlgorithm = null;
private static Method getSNIMatchers = null;
private static Method setSNIMatchers = null;
private static Method getMaximumPacketSize = null;
private static Method setMaximumPacketSize = null;
@ -77,6 +79,12 @@ public class WolfSSLParametersHelper
case "setEndpointIdentificationAlgorithm":
setEndpointIdentificationAlgorithm = m;
continue;
case "getSNIMatchers":
getSNIMatchers = m;
continue;
case "setSNIMatchers":
setSNIMatchers = m;
continue;
case "getMaximumPacketSize":
getMaximumPacketSize = m;
continue;
@ -126,7 +134,7 @@ public class WolfSSLParametersHelper
* do not existing in older JDKs. Since older JDKs will not have them,
* use Java reflection to detect availability in helper class. */
if (setServerNames != null || setApplicationProtocols != null ||
setEndpointIdentificationAlgorithm != null) {
setEndpointIdentificationAlgorithm != null || setSNIMatchers != null) {
try {
/* load WolfSSLJDK8Helper at runtime, not compiled
@ -155,6 +163,10 @@ public class WolfSSLParametersHelper
mth.invoke(obj, ret,
setEndpointIdentificationAlgorithm, in);
}
if (setSNIMatchers != null) {
mth = cls.getDeclaredMethod("setSNIMatchers", paramList);
mth.invoke(obj, ret, setSNIMatchers, in);
}
} catch (Exception e) {
/* ignore, class not found */
@ -173,6 +185,14 @@ public class WolfSSLParametersHelper
/* Not available, just ignore and continue */
}
try {
if (setSNIMatchers != null) {
ret.setSNIMatchers(in.getSNIMatchers());
}
} catch (Exception e) {
/* Not available, just ignore and continue */
}
/* The following SSLParameters features are not yet supported
* by wolfJSSE (see Android API 23 note above). They are supported
* with newer versions of SSLParameters, but will need to be added
@ -222,7 +242,7 @@ public class WolfSSLParametersHelper
* do not existing in older JDKs. Since older JDKs will not have them,
* use Java reflection to detect availability in helper class. */
if (getServerNames != null || getApplicationProtocols != null ||
getEndpointIdentificationAlgorithm != null) {
getEndpointIdentificationAlgorithm != null || getSNIMatchers != null) {
try {
/* load WolfSSLJDK8Helper at runtime, not compiled on older JDKs */
Class<?> cls = Class.forName(
@ -247,6 +267,10 @@ public class WolfSSLParametersHelper
"getEndpointIdentificationAlgorithm", paramList);
mth.invoke(obj, in, out);
}
if (getSNIMatchers != null){
mth = cls.getDeclaredMethod("getSNIMatchers", paramList);
mth.invoke(obj, in, out);
}
} catch (Exception e) {
/* ignore, class not found */
@ -276,6 +300,9 @@ public class WolfSSLParametersHelper
out.setSNIMatchers(in.getSNIMatchers());
out.setUseCipherSuitesOrder(in.getUseCipherSuitesOrder());
*/
out.setSNIMatchers(in.getSNIMatchers());
}
}

View File

@ -1569,6 +1569,10 @@ public class WolfSSLSocket extends SSLSocket {
close();
throw e;
} catch (SSLHandshakeException e){
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "got SSLHandshakeException in doHandshake()");
throw e;
} catch (SSLException e) {
final int tmpErr = err;
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,

View File

@ -27,6 +27,7 @@ import static org.junit.Assert.*;
import java.util.List;
import java.util.Arrays;
import java.util.Collection;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
@ -62,6 +63,7 @@ import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIMatcher;
import javax.net.ssl.SNIServerName;
import java.security.Security;
import java.security.Provider;
@ -3395,6 +3397,113 @@ public class WolfSSLSocketTest {
}
}
@Test
public void testSNIMatchers() throws Exception {
System.out.print("\tTesting SNI Matchers");
/* create new CTX */
this.ctx = tf.createSSLContext("TLS", ctxProvider);
/* create SSLServerSocket first to get ephemeral port */
final SSLServerSocket ss = (SSLServerSocket)ctx.getServerSocketFactory()
.createServerSocket(0);
/* Configure SNI matcher for server*/
SNIMatcher matcher = SNIHostName.createSNIMatcher("www\\.example\\.com");
Collection<SNIMatcher> matchers = new ArrayList<>();
matchers.add(matcher);
SSLParameters sp = ss.getSSLParameters();
sp.setSNIMatchers(matchers);
ss.setSSLParameters(sp);
try {
/* ------------------------------------ */
/* Test matched SNI case, should pass */
/* ------------------------------------ */
SSLSocket cs = (SSLSocket)ctx.getSocketFactory().createSocket();
cs.connect(new InetSocketAddress(ss.getLocalPort()));
/* Set SNI hostname for client */
SNIHostName serverName = new SNIHostName("www.example.com");
List<SNIServerName> serverNames = new ArrayList<>();
serverNames.add(serverName);
SSLParameters cp = cs.getSSLParameters();
cp.setServerNames(serverNames);
cs.setSSLParameters(cp);
final SSLSocket serverMatched = (SSLSocket)ss.accept();
ExecutorService es = Executors.newSingleThreadExecutor();
Future<Void> serverFuture = es.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
serverMatched.startHandshake();
serverMatched.close();
} catch (SSLException e) {
System.out.println("\t... failed");
fail();
}
return null;
}
});
cs.startHandshake();
cs.close();
es.shutdown();
serverFuture.get();
/* ------------------------------------ */
/* Test unmatched SNI case, should fail */
/* ------------------------------------ */
cs = (SSLSocket)ctx.getSocketFactory().createSocket();
cs.connect(new InetSocketAddress(ss.getLocalPort()));
/* Set non-matching SNI hostname for client */
serverName = new SNIHostName("www.example.org");
serverNames = new ArrayList<>();
serverNames.add(serverName);
cp = cs.getSSLParameters();
cp.setServerNames(serverNames);
cs.setSSLParameters(cp);
final SSLSocket serverUnmatched = (SSLSocket)ss.accept();
es = Executors.newSingleThreadExecutor();
serverFuture = es.submit(() -> {
try {
serverUnmatched.startHandshake();
fail("Server handshake succeeded with non-matching SNI");
} catch (SSLHandshakeException e) {
/* Expected failure with non-matching SNI */
}
return null;
});
try {
cs.startHandshake();
} catch (SSLHandshakeException e) {
/* Expect client to close connection, wolfJSSE does not expect
* to an exception. However, SunJSSE will throw an exception */
}
es.shutdown();
serverFuture.get();
cs.close();
System.out.println("\t\t... passed");
} catch (Exception e) {
System.out.println("\t\t... failed");
fail();
} finally {
ss.close();
}
}
/**
* Inner class used to hold configuration options for
* TestServer and TestClient classes.