From 315faa5298a4963fa9df083b08813ce39168d9d4 Mon Sep 17 00:00:00 2001 From: JeremiahM37 Date: Fri, 19 Dec 2025 13:31:50 -0700 Subject: [PATCH 1/3] getPeerCertificateChain returns null fix --- .../provider/jsse/WolfSSLEngineHelper.java | 5 ++ .../jsse/WolfSSLImplementSSLSession.java | 45 +++++++++++++++-- .../provider/jsse/test/WolfSSLEngineTest.java | 50 +++++++++++++++++++ 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java b/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java index f9dbd780..6acc3123 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java @@ -1349,6 +1349,11 @@ private void initHandshakeInternal(SSLSocket socket, SSLEngine engine) else { this.session.setSessionContext(authStore.getServerContext()); this.session.setSide(WolfSSL.WOLFSSL_SERVER_END); + /* Track client auth state for getPeerCertificates() */ + boolean clientAuthRequested = + this.params.getNeedClientAuth() || + this.params.getWantClientAuth(); + this.session.setClientAuthRequested(clientAuthRequested); } if (this.sessionCreation == false && !this.session.isFromTable) { diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java b/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java index 419afe29..383cbaa3 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java @@ -73,6 +73,9 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession { byte[] pseudoSessionID = null; /* used with TLS 1.3*/ private int side = 0; + /* Track if client auth was requested, for getPeerCertificates() behavior */ + private volatile boolean clientAuthRequested = false; + /* Cache peer certificates after received. Applications assume that * SSLSocket.getSession().getPeerCertificates() will return the peer * certificate even on a resumed connection where the cert has not been @@ -260,6 +263,7 @@ public WolfSSLImplementSSLSession (WolfSSLImplementSSLSession orig) { this.pseudoSessionID = orig.pseudoSessionID.clone(); } this.side = orig.side; + this.clientAuthRequested = orig.clientAuthRequested; if (orig.peerCerts != null) { this.peerCerts = orig.peerCerts.clone(); } @@ -519,6 +523,15 @@ public synchronized Certificate[] getPeerCertificates() "SSLSocket/Engine closed"); } + /* Throw if server side with no client auth requested */ + if (this.side == WolfSSL.WOLFSSL_SERVER_END && + !this.clientAuthRequested) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "Server side, no client auth, throwing exception"); + throw new SSLPeerUnverifiedException( + "peer not authenticated (no client auth requested)"); + } + try { x509 = this.ssl.getPeerCertificate(); } catch (IllegalStateException | WolfSSLJNIException ex) { @@ -605,8 +618,8 @@ public Certificate[] getLocalCertificates() { } @Override - public synchronized javax.security.cert.X509Certificate[] getPeerCertificateChain() - throws SSLPeerUnverifiedException { + public synchronized javax.security.cert.X509Certificate[] + getPeerCertificateChain() throws SSLPeerUnverifiedException { long peerX509 = 0; WolfSSLX509X x509; @@ -615,10 +628,17 @@ public synchronized javax.security.cert.X509Certificate[] getPeerCertificateChai throw new SSLPeerUnverifiedException("handshake not done"); } + /* Throw if server side with no client auth requested */ + if (this.side == WolfSSL.WOLFSSL_SERVER_END && + !this.clientAuthRequested) { + throw new SSLPeerUnverifiedException( + "peer not authenticated (no client auth requested)"); + } + try { peerX509 = this.ssl.getPeerCertificate(); if (peerX509 == 0) { - return null; + throw new SSLPeerUnverifiedException("No peer certificate"); } /* wolfSSL starting with 5.3.0 returns a new WOLFSSL_X509 @@ -657,10 +677,17 @@ public synchronized Principal getPeerPrincipal() throw new SSLPeerUnverifiedException("handshake not done"); } + /* Throw if server side with no client auth requested */ + if (this.side == WolfSSL.WOLFSSL_SERVER_END && + !this.clientAuthRequested) { + throw new SSLPeerUnverifiedException( + "peer not authenticated (no client auth requested)"); + } + try { peerX509 = this.ssl.getPeerCertificate(); if (peerX509 == 0) { - return null; + throw new SSLPeerUnverifiedException("No peer certificate"); } /* wolfSSL starting with 5.3.0 returns a new WOLFSSL_X509 @@ -1039,6 +1066,16 @@ protected int getSide() { return this.side; } + /** + * Set whether client auth was requested. + * Used for getPeerCertificates() behavior. + * + * @param requested true if client auth was requested, false otherwise + */ + protected void setClientAuthRequested(boolean requested) { + this.clientAuthRequested = requested; + } + /** * Return the side session is on (server/client) as a String * @return "client" or "server" representing the side of this session diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineTest.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineTest.java index 5d93631b..1bf5525a 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineTest.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineTest.java @@ -54,6 +54,7 @@ import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLPeerUnverifiedException; import java.util.concurrent.TimeUnit; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; @@ -2494,5 +2495,54 @@ private ByteBuffer enlargeBuffer(ByteBuffer buffer, int size) { bb.put(buffer); return bb; } + + /** + * Verify getPeerCertificateChain() throws SSLPeerUnverifiedException + * when no client auth requested, matching SunJSSE/Netty expectations. + */ + @Test + public void testGetPeerCertificateChainNoClientAuth() throws Exception { + + System.out.print("\tgetPeerCertChain no client auth"); + + String protocol = null; + for (String p : enabledProtocols) { + if (!p.equals("TLS") && !p.contains("DTLS")) { + protocol = p; + break; + } + } + + if (protocol == null) { + pass("\t... skipped"); + return; + } + + SSLContext ctx = tf.createSSLContext(protocol, engineProvider); + + SSLEngine server = ctx.createSSLEngine(); + SSLEngine client = ctx.createSSLEngine("localhost", 11111); + + server.setUseClientMode(false); + server.setNeedClientAuth(false); + server.setWantClientAuth(false); + client.setUseClientMode(true); + + tf.testConnection(server, client, null, null, "No client auth test"); + + SSLSession serverSession = server.getSession(); + + try { + javax.security.cert.X509Certificate[] certs = + serverSession.getPeerCertificateChain(); + error("\t... failed"); + fail("Expected SSLPeerUnverifiedException, got " + + (certs == null ? "null" : "certs")); + } catch (SSLPeerUnverifiedException e) { + /* Expected */ + } + + pass("\t... passed"); + } } From 6434f177c778583bee3e4b822687d10790c8f8d9 Mon Sep 17 00:00:00 2001 From: JeremiahM37 Date: Fri, 23 Jan 2026 09:27:03 -0700 Subject: [PATCH 2/3] springboot fixes --- .../wolfssl/provider/jsse/WolfSSLEngine.java | 17 ++++- .../wolfssl/provider/jsse/WolfSSLKeyX509.java | 26 ++++++- .../provider/jsse/WolfSSLTrustManager.java | 75 ++++++++++++++++++- .../wolfssl/provider/jsse/WolfSSLUtil.java | 5 ++ 4 files changed, 117 insertions(+), 6 deletions(-) diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java b/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java index dde8ab2c..1d1dfee0 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java @@ -580,7 +580,8 @@ private synchronized int DoHandshake(boolean fromWrap) throws SSLException { } } catch (SocketTimeoutException | SocketException e) { - throw new SSLException(e); + throw new SSLHandshakeException( + "Socket error during SSL/TLS handshake: " + e.getMessage()); } return ret; @@ -1058,6 +1059,12 @@ private synchronized int RecvAppData(ByteBuffer[] out, int ofst, int length) } break; default: + /* Throw SSLHandshakeException if handshake not finished */ + if (!this.handshakeFinished) { + throw new SSLHandshakeException( + "SSL/TLS handshake error in read: " + ret + + " , err = " + err); + } throw new SSLException( "wolfSSL_read() error: " + ret + " , err = " + err); } @@ -1393,6 +1400,14 @@ else if (ret < 0 && * any more data */ this.outBoundOpen = false; } + /* Throw SSLHandshakeException if handshake not + * finished, otherwise throw SSLException for + * post-handshake errors */ + if (!this.handshakeFinished) { + throw new SSLHandshakeException( + "SSL/TLS handshake error, ret:err = " + + ret + " : " + err); + } throw new SSLException( "wolfSSL error, ret:err = " + ret + " : " + err); diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLKeyX509.java b/src/java/com/wolfssl/provider/jsse/WolfSSLKeyX509.java index 8c89b95b..fb9d0e08 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLKeyX509.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLKeyX509.java @@ -375,7 +375,18 @@ public String chooseClientAlias(String[] type, Principal[] issuers, for (i = 0; i < type.length; i++) { String[] all = getAliases(type[i], issuers); if (all != null) { - return all[0]; + /* Find first alias that has a private key, skip cert-only + * entries (trustedCertEntry) which have no private key */ + for (String alias : all) { + PrivateKey key = getPrivateKey(alias); + if (key != null) { + final String selectedAlias = alias; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "chooseClientAlias() returning alias " + + "with private key: " + selectedAlias); + return alias; + } + } } } return null; @@ -398,7 +409,18 @@ public String chooseEngineClientAlias(String[] type, Principal[] issuers, for (i = 0; i < type.length; i++) { String[] all = getAliases(type[i], issuers); if (all != null) { - return all[0]; + /* Find first alias that has a private key, skip cert-only + * entries (trustedCertEntry) which have no private key */ + for (String alias : all) { + PrivateKey key = getPrivateKey(alias); + if (key != null) { + final String selectedAlias = alias; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "chooseEngineClientAlias() returning " + + "alias with private key: " + selectedAlias); + return alias; + } + } } } return null; diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLTrustManager.java b/src/java/com/wolfssl/provider/jsse/WolfSSLTrustManager.java index 740091ed..69a7e3dd 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLTrustManager.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLTrustManager.java @@ -761,9 +761,78 @@ protected void engineInit(ManagerFactoryParameters arg0) WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "entered engineInit(ManagerFactoryParameters arg0)"); - throw new UnsupportedOperationException( - "TrustManagerFactory.init(ManagerFactoryParameters) " + - "not supported yet"); + /* Handle CertPathTrustManagerParameters (used by Tomcat, etc) */ + if (arg0 instanceof javax.net.ssl.CertPathTrustManagerParameters) { + javax.net.ssl.CertPathTrustManagerParameters certPathParams = + (javax.net.ssl.CertPathTrustManagerParameters) arg0; + java.security.cert.CertPathParameters certPathParameters = + certPathParams.getParameters(); + + if (certPathParameters instanceof + java.security.cert.PKIXParameters) { + java.security.cert.PKIXParameters pkixParams = + (java.security.cert.PKIXParameters) certPathParameters; + java.util.Set anchors = + pkixParams.getTrustAnchors(); + + try { + java.security.KeyStore ks = + java.security.KeyStore.getInstance( + java.security.KeyStore.getDefaultType()); + ks.load(null, null); + int count = 0; + for (java.security.cert.TrustAnchor anchor : anchors) { + java.security.cert.X509Certificate cert = + anchor.getTrustedCert(); + if (cert != null) { + ks.setCertificateEntry( + "trustanchor-" + count, cert); + count++; + } + } + final int finalCount = count; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "Initialized TrustManager from " + + "CertPathTrustManagerParameters with " + + finalCount + " anchors"); + engineInit(ks); + return; + } catch (Exception e) { + throw new InvalidAlgorithmParameterException( + "Failed to create KeyStore from TrustAnchors: " + + e.getMessage(), e); + } + } + } + + /* Handle KeyStoreBuilderParameters */ + if (arg0 instanceof javax.net.ssl.KeyStoreBuilderParameters) { + javax.net.ssl.KeyStoreBuilderParameters ksParams = + (javax.net.ssl.KeyStoreBuilderParameters) arg0; + java.util.List builders = + ksParams.getParameters(); + + if (builders != null && !builders.isEmpty()) { + try { + /* Use the first KeyStore builder */ + java.security.KeyStore ks = + builders.get(0).getKeyStore(); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "Initialized TrustManager from " + + "KeyStoreBuilderParameters"); + engineInit(ks); + return; + } catch (Exception e) { + throw new InvalidAlgorithmParameterException( + "Failed to get KeyStore from Builder: " + + e.getMessage(), e); + } + } + } + + throw new InvalidAlgorithmParameterException( + "Unsupported ManagerFactoryParameters type: " + + (arg0 != null ? arg0.getClass().getName() : "null")); } @Override diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java b/src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java index 87318fa4..4fc5b029 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java @@ -75,6 +75,11 @@ public WolfSSLUtil() { protected static String[] sanitizeProtocols(String[] protocols, WolfSSL.TLS_VERSION currentVersion) { + /* Return null if protocols is null, let caller handle */ + if (protocols == null) { + return null; + } + ArrayList filtered = new ArrayList(); String disabledAlgos = From a9734226f84727e95903636023e7d1467cbb88f6 Mon Sep 17 00:00:00 2001 From: JeremiahM37 Date: Mon, 26 Jan 2026 10:51:12 -0700 Subject: [PATCH 3/3] unit tests --- .../jsse/test/WolfSSLContextTest.java | 33 +++++++ .../jsse/test/WolfSSLKeyX509Test.java | 29 +++++- .../jsse/test/WolfSSLTrustX509Test.java | 92 +++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLContextTest.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLContextTest.java index 44006a0e..3585d045 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLContextTest.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLContextTest.java @@ -56,6 +56,8 @@ import com.wolfssl.WolfSSL; import com.wolfssl.provider.jsse.WolfSSLProvider; +import java.lang.reflect.Method; + public class WolfSSLContextTest { private static WolfSSLTestFactory tf; @@ -965,5 +967,36 @@ public void testWolfJSSEEnabledCipherSuites() System.out.println("\t... passed"); } + + @Test + public void testSanitizeProtocolsNullInput() { + + System.out.print("\tTesting sanitizeProtocols(null)"); + + try { + Class utilClass = Class.forName( + "com.wolfssl.provider.jsse.WolfSSLUtil"); + Method sanitizeMethod = utilClass.getDeclaredMethod( + "sanitizeProtocols", + String[].class, + WolfSSL.TLS_VERSION.class); + sanitizeMethod.setAccessible(true); + + String[] result = (String[]) sanitizeMethod.invoke( + null, (String[]) null, WolfSSL.TLS_VERSION.TLSv1_2); + + if (result != null) { + System.out.println("\t... failed"); + fail("sanitizeProtocols(null) should return null"); + return; + } + + System.out.println("\t\t... passed"); + + } catch (Exception e) { + System.out.println("\t... failed"); + fail("Exception during sanitizeProtocols test: " + e.getMessage()); + } + } } diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLKeyX509Test.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLKeyX509Test.java index bd0e329c..f463566b 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLKeyX509Test.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLKeyX509Test.java @@ -1111,6 +1111,33 @@ public void testNullKeyStoreWithCachingDisabled() pass("\t... passed"); } + /* Test that chooseAlias methods return aliases with private keys */ + @Test + public void testChooseAliasSkipsCertOnlyEntries() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + System.out.print("\tTesting chooseAlias skips cert-only"); + + KeyManager[] km = tf.createKeyManager("SunX509", tf.allJKS, provider); + X509ExtendedKeyManager x509km = (X509ExtendedKeyManager) km[0]; + String alias; + + alias = x509km.chooseClientAlias(new String[] { "RSA" }, null, null); + if (alias != null && x509km.getPrivateKey(alias) == null) { + fail("chooseClientAlias returned alias without private key"); + } + + alias = x509km.chooseEngineClientAlias( + new String[] { "RSA" }, null, null); + if (alias != null && x509km.getPrivateKey(alias) == null) { + fail("chooseEngineClientAlias returned alias without private key"); + } + + pass("\t... passed"); + } + private void pass(String msg) { WolfSSLTestFactory.pass(msg); } @@ -1118,4 +1145,4 @@ private void pass(String msg) { private void error(String msg) { WolfSSLTestFactory.fail(msg); } - } +} diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java index 73153cd7..de584f67 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java @@ -3342,6 +3342,98 @@ private void testX509ExtTrustMgrSSLSocketExtNoServerStartHandshakeSuccess() } } + /* Test TrustManagerFactory.init(CertPathTrustManagerParameters) */ + @Test + public void testInitWithCertPathTrustManagerParameters() + throws NoSuchProviderException, NoSuchAlgorithmException, + KeyStoreException, IOException, CertificateException, + java.security.InvalidAlgorithmParameterException { + + System.out.print("\tTesting init(CertPathTrustManagerParameters)"); + + /* Load CA certs and create TrustAnchors manually */ + KeyStore caStore = KeyStore.getInstance( + WolfSSLTestFactory.isAndroid() ? "BKS" : "JKS"); + InputStream stream = new FileInputStream(tf.caJKS); + caStore.load(stream, WolfSSLTestFactory.jksPass); + stream.close(); + + java.util.Set anchors = + new java.util.HashSet(); + java.util.Enumeration aliases = caStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + java.security.cert.Certificate cert = caStore.getCertificate(alias); + if (cert instanceof X509Certificate) { + anchors.add(new java.security.cert.TrustAnchor( + (X509Certificate) cert, null)); + } + } + + if (anchors.isEmpty()) { + pass("\t... skipped (no certs)"); + return; + } + + java.security.cert.PKIXParameters pkixParams = + new java.security.cert.PKIXParameters(anchors); + javax.net.ssl.CertPathTrustManagerParameters certPathParams = + new javax.net.ssl.CertPathTrustManagerParameters(pkixParams); + + TrustManagerFactory tmf = + TrustManagerFactory.getInstance("SunX509", provider); + tmf.init(certPathParams); + + TrustManager[] tms = tmf.getTrustManagers(); + if (tms == null || tms.length == 0) { + fail("TrustManagers null/empty after CertPathParams init"); + } + + X509TrustManager x509tm = (X509TrustManager) tms[0]; + if (x509tm.getAcceptedIssuers() == null || + x509tm.getAcceptedIssuers().length == 0) { + fail("No accepted issuers after CertPathParams init"); + } + + pass("\t... passed"); + } + + /* Test TrustManagerFactory.init(KeyStoreBuilderParameters) */ + @Test + public void testInitWithKeyStoreBuilderParameters() + throws NoSuchProviderException, NoSuchAlgorithmException, + KeyStoreException, IOException, CertificateException, + java.security.InvalidAlgorithmParameterException { + + System.out.print("\tTesting init(KeyStoreBuilderParameters)"); + + KeyStore.Builder ksBuilder = KeyStore.Builder.newInstance( + WolfSSLTestFactory.isAndroid() ? "BKS" : "JKS", + null, + new File(tf.caJKS), + new KeyStore.PasswordProtection(WolfSSLTestFactory.jksPass)); + + javax.net.ssl.KeyStoreBuilderParameters ksParams = + new javax.net.ssl.KeyStoreBuilderParameters(ksBuilder); + + TrustManagerFactory tmf = + TrustManagerFactory.getInstance("SunX509", provider); + tmf.init(ksParams); + + TrustManager[] tms = tmf.getTrustManagers(); + if (tms == null || tms.length == 0) { + fail("TrustManagers null/empty after KeyStoreBuilder init"); + } + + X509TrustManager x509tm = (X509TrustManager) tms[0]; + if (x509tm.getAcceptedIssuers() == null || + x509tm.getAcceptedIssuers().length == 0) { + fail("No accepted issuers after KeyStoreBuilder init"); + } + + pass("\t... passed"); + } + private void pass(String msg) { WolfSSLTestFactory.pass(msg); }