From 74bf9743541794221ebe097b2132ca6b4d0f0d76 Mon Sep 17 00:00:00 2001 From: Ruby Martin Date: Wed, 30 Apr 2025 08:33:26 -0600 Subject: [PATCH 1/2] JSSE: implement SNIMatcher logic for wolfSSLSockets add thread safety --- .../provider/jsse/WolfSSLEngineHelper.java | 60 +++++++++++++++++++ .../provider/jsse/WolfSSLParameters.java | 30 +++++++--- .../jsse/WolfSSLParametersHelper.java | 31 +++++++++- .../wolfssl/provider/jsse/WolfSSLSocket.java | 4 ++ 4 files changed, 116 insertions(+), 9 deletions(-) diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java b/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java index 4e4d02f..ecacba7 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java @@ -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 matchers = this.params.getSNIMatchers(); + if (matchers != null && !matchers.isEmpty()) { + /* Match a server name to SNI requested by Client */ + List 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. diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLParameters.java b/src/java/com/wolfssl/provider/jsse/WolfSSLParameters.java index 8adc437..0f06d47 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLParameters.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLParameters.java @@ -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 serverNames; + private List 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 matchers) { - // /* TODO */ - //} + void setSNIMatchers(Collection matchers) { + if (matchers != null && !matchers.isEmpty()) { + if (this.sniMatchers == null) { + this.sniMatchers = new ArrayList(); + } + for (SNIMatcher matcher : matchers) { + this.sniMatchers.add(matcher); + } + } else { + this.sniMatchers = new ArrayList(); + } + } /* TODO, create our own class for SNIMatcher, in case Java doesn't support it */ - //Collection getSNIMatchers() { - // return null; /* TODO */ - //} + List getSNIMatchers() { + if (this.sniMatchers != null && !this.sniMatchers.isEmpty()) { + return Collections.unmodifiableList(new ArrayList(sniMatchers)); + } else { + return Collections.emptyList(); + } + } void setUseCipherSuitesOrder(boolean honorOrder) { this.useCipherSuiteOrder = honorOrder; diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLParametersHelper.java b/src/java/com/wolfssl/provider/jsse/WolfSSLParametersHelper.java index a2b3474..2a39368 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLParametersHelper.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLParametersHelper.java @@ -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()); + } } diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java b/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java index 492cdbf..f59bb79 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java @@ -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, From 62bdb9e60990a44936efe00f8ddc1fb1eefba4b6 Mon Sep 17 00:00:00 2001 From: Ruby Martin Date: Tue, 13 May 2025 17:10:17 -0600 Subject: [PATCH 2/2] add SNI matcher test --- .../provider/jsse/test/WolfSSLSocketTest.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java index d99f5e6..0457257 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java @@ -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 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 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 serverFuture = es.submit(new Callable() { + @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.