From dab6d5701114f84fb27285709d47fcd8d0b8b801 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Wed, 4 Jun 2025 17:21:07 -0600 Subject: [PATCH 1/2] JSSE: refactor WolfSSLKeyX509 (X509ExtendedKeyManager) to cache KeyStore entries instead of doing direct access for each operation --- README.md | 13 + .../provider/jsse/WolfSSLKeyManager.java | 10 +- .../wolfssl/provider/jsse/WolfSSLKeyX509.java | 403 +++++++++-- .../jsse/test/WolfSSLKeyX509Test.java | 652 ++++++++++++++++++ 4 files changed, 1011 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index c174f0e..5e70b34 100644 --- a/README.md +++ b/README.md @@ -469,6 +469,7 @@ and used by wolfSSL JNI/JSSE. | wolfjsse.enabledSignatureAlgorithms | | String | Restricts enabled signature algorithms | | wolfjsse.keystore.type.required | | String | Restricts KeyStore type | | wolfjsse.clientSessionCache.disabled | | "true" | Disables client session cache | +| wolfjsse.X509KeyManager.disableCache | "false" | "true" | Disables X509KeyManager KeyStore entry caching | **wolfssl.readWriteByteBufferPool.disabled (String)** - Can be used to disable the static per-thread ByteBuffer pool used in com.wolfssl.WolfSSLSession @@ -554,6 +555,18 @@ cache. The Java client cache is enabled by default. wolfjsse.clientSessionCache.disabled=true ``` +**wolfjsse.X509KeyManager.disableCache (String)** - Can be used to disable +KeyStore entry caching in the WolfSSLKeyX509 (X509ExtendedKeyManager) implementation. +When set to "true", the X509KeyManager will revert to the original behavior of +calling KeyStore methods directly for each operation instead of using cached +entries. This can be useful for debugging, compatibility testing, or when +KeyStore contents may change dynamically. Caching is enabled by default for +performance. This should be set to the String "true" to disable caching: + +``` +wolfjsse.X509KeyManager.disableCache=true +``` + If there are other Security properties you would like to use with wolfJSSE, please contact support@wolfssl.com. diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLKeyManager.java b/src/java/com/wolfssl/provider/jsse/WolfSSLKeyManager.java index 31e10e4..c67ee35 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLKeyManager.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLKeyManager.java @@ -222,8 +222,14 @@ public class WolfSSLKeyManager extends KeyManagerFactorySpi { "initialized before use, please call init()"); } - KeyManager[] km = {new WolfSSLKeyX509(this.store, this.pswd)}; - return km; + try { + KeyManager[] km = { new WolfSSLKeyX509(this.store, this.pswd) }; + return km; + + } catch (KeyStoreException e) { + throw new IllegalStateException( + "Failed to create WolfSSLKeyX509: " + e.getMessage(), e); + } } } diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLKeyX509.java b/src/java/com/wolfssl/provider/jsse/WolfSSLKeyX509.java index 7d9f2b7..45a818c 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLKeyX509.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLKeyX509.java @@ -26,10 +26,16 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.Principal; import java.security.PrivateKey; +import java.security.Security; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.Arrays; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedKeyManager; import com.wolfssl.WolfSSLDebug; @@ -43,71 +49,257 @@ import com.wolfssl.WolfSSLDebug; */ public class WolfSSLKeyX509 extends X509ExtendedKeyManager { - private KeyStore store; - private char[] password; + /* Security property to control caching behavior. When set to "true", + * disables KeyStore entry caching and reverts to calling KeyStore + * methods directly for each operation. */ + private static final String DISABLE_CACHE_PROPERTY = + "wolfjsse.X509KeyManager.disableCache"; + + /* Reference to original KeyStore for non-cached operations */ + private final KeyStore keyStore; + private final char[] keyStorePassword; + + /* Cache behavior determined once at construction time */ + private final boolean cacheDisabled; + + /* Cache for KeyStore entries to avoid concurrent access issues. Prior + * to the addition of these caches, WolfSSLKeyX509 called down directly + * to the underlying KeyStore for each operation. Since the KeyStore + * operations are synchronized, concurrent threads accessing this + * KeyManager can queue up in that scenario and hurt performance. + * These caches are only used if caching has not been disabled via + * Security property. */ + private final Map certificateCache; + private final Map certificateChainCache; + private final Map privateKeyCache; + private final Set aliasSet; /** * Create new WolfSSLKeyX509 object * - * @param in input KeyStore to use with this object + * @param store input KeyStore to cache entries from * @param password input KeyStore password + * @throws KeyStoreException if unable to populate cache from KeyStore */ - public WolfSSLKeyX509(KeyStore in, char[] password) { - this.store = in; - this.password = password; + public WolfSSLKeyX509(KeyStore store, char[] password) + throws KeyStoreException { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "creating new WolfSSLKeyX509 object"); + + /* Check Security property once at construction time */ + this.cacheDisabled = "true".equalsIgnoreCase( + Security.getProperty(DISABLE_CACHE_PROPERTY)); + + if (this.cacheDisabled) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "KeyStore caching disabled via " + + DISABLE_CACHE_PROPERTY + + " Security property, using direct KeyStore access"); + + this.keyStore = store; + this.keyStorePassword = + (password != null) ? password.clone() : null; + + this.certificateCache = null; + this.certificateChainCache = null; + this.privateKeyCache = null; + this.aliasSet = null; + + } else { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "KeyStore caching enabled, populating caches"); + + this.keyStore = null; + this.keyStorePassword = null; + + /* Initialize cache data structures */ + this.certificateCache = new HashMap(); + this.certificateChainCache = + new HashMap(); + this.privateKeyCache = new HashMap(); + this.aliasSet = new LinkedHashSet(); + + /* Populate caches from KeyStore */ + populateCache(store, password); + } } /** - * Return array of aliases from current KeyStore that matches provided - * type and issuers array. + * Populate internal caches with all entries from KeyStore + * + * @param store KeyStore to read entries from + * @param password KeyStore password to access private keys + * @throws KeyStoreException if unable to read from KeyStore + */ + private void populateCache(KeyStore store, char[] password) + throws KeyStoreException { + + if (store == null) { + return; + } + + Enumeration aliases = store.aliases(); + + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + aliasSet.add(alias); + + try { + /* Cache individual certificate */ + Certificate cert = store.getCertificate(alias); + if (cert instanceof X509Certificate) { + certificateCache.put(alias, (X509Certificate)cert); + } + + /* Cache certificate chain */ + Certificate[] certChain = store.getCertificateChain(alias); + if (certChain != null) { + int x509Cnt = 0; + + /* Count X509Certificate entries */ + for (int i = 0; i < certChain.length; i++) { + if (certChain[i] instanceof X509Certificate) { + x509Cnt++; + } + } + + /* Store X509Certificate chain */ + if (x509Cnt > 0) { + int idx = 0; + X509Certificate[] x509Chain = + new X509Certificate[x509Cnt]; + + for (int i = 0; i < certChain.length; i++) { + if (certChain[i] instanceof X509Certificate) { + x509Chain[idx++] = (X509Certificate)certChain[i]; + } + } + certificateChainCache.put(alias, x509Chain); + } + } + + /* Cache private key */ + PrivateKey key = (PrivateKey)store.getKey(alias, password); + if (key != null) { + privateKeyCache.put(alias, key); + } + + } catch (Exception e) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.ERROR, + () -> "Error caching entry for alias: " + alias + ", " + e); + /* Continue processing other aliases */ + } + } + + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "Cached " + aliasSet.size() + " aliases from KeyStore"); + } + + + /** + * Return array of aliases from KeyStore that matches provided + * type and issuers array (non-cached version). * * Returns: - * null - if current KeyStore is null, error getting aliases from store, - * or no alias mathes found in current KeyStore. + * null - if no alias matches found in KeyStore. * String[] - aliases, if found that match type and/or issuers */ - private String[] getAliases(String type, Principal[] issuers) { - Enumeration aliases = null; + private String[] getAliasesFromKeyStore(String type, Principal[] issuers) + throws KeyStoreException { + int i; ArrayList ret = new ArrayList(); - if (store == null) { + if (keyStore == null) { return null; } - try { - aliases = this.store.aliases(); - } catch (KeyStoreException ex) { - WolfSSLDebug.log(getClass(), WolfSSLDebug.ERROR, - () -> "Error getting aliases from current KeyStore"); - return null; - } + Enumeration aliases = keyStore.aliases(); - /* loop through each alias in KeyStore */ + /* loop through each KeyStore alias */ while (aliases.hasMoreElements()) { String current = aliases.nextElement(); - X509Certificate cert = null; - try { - cert = (X509Certificate)this.store.getCertificate(current); - } catch (KeyStoreException ex) { - WolfSSLDebug.log(getClass(), WolfSSLDebug.ERROR, - () -> "Error getting certificate from KeyStore " + - "for alias: " + current + ", continuing to next alias"); + Certificate cert = keyStore.getCertificate(current); + + if (!(cert instanceof X509Certificate)) { continue; } + X509Certificate x509cert = (X509Certificate)cert; + + if (type != null && + !x509cert.getPublicKey().getAlgorithm().equals(type)) { + /* different public key type, skip */ + continue; + } + + /* if issuers is null then it does not matter which issuer */ + if (issuers == null) { + ret.add(current); + } + else { + /* search through issuers for matching issuer name */ + for (i = 0; i < issuers.length; i++) { + String certIssuer = x509cert.getIssuerDN().getName(); + String issuerName = issuers[i].getName(); + + /* normalize spaces after commas, needed on some JDKs */ + certIssuer = certIssuer.replaceAll(", ", ","); + issuerName = issuerName.replaceAll(", ", ","); + + if (certIssuer.equals(issuerName)) { + /* matched issuer, add alias and continue on */ + ret.add(current); + break; + } + } + } + } + + if (ret.size() == 0) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "No aliases found in KeyStore that match type " + + "and/or issuer"); + return null; + } + + return ret.toArray(new String[0]); + } + + /** + * Return array of aliases that matches provided type and issuers array. + * Uses either cached entries or direct KeyStore access based on Security + * property configuration. + * + * Returns: + * null - if no alias matches found. + * String[] - aliases, if found that match type and/or issuers + */ + private String[] getAliases(String type, Principal[] issuers) { + + /* Check if caching is disabled, use direct KeyStore access */ + if (this.cacheDisabled) { + try { + return getAliasesFromKeyStore(type, issuers); + } catch (KeyStoreException e) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.ERROR, + () -> "Error accessing KeyStore directly: " + + e.getMessage()); + return null; + } + } + + /* Use cached entries */ + int i; + ArrayList ret = new ArrayList(); + + /* loop through each cached alias */ + for (String current : aliasSet) { + X509Certificate cert = certificateCache.get(current); + if (type != null && cert != null && !cert.getPublicKey().getAlgorithm().equals(type)) { - - /* free native memory early if X509Certificate is WolfSSLX509 */ - if (cert instanceof WolfSSLX509) { - ((WolfSSLX509)cert).free(); - } - cert = null; - /* different public key type, skip */ continue; } @@ -135,11 +327,11 @@ public class WolfSSLKeyX509 extends X509ExtendedKeyManager { } } } - } /* end while */ + } if (ret.size() == 0) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "No aliases found in KeyStore that match type " + + () -> "No aliases found in cache that match type " + "and/or issuer"); return null; } @@ -244,59 +436,140 @@ public class WolfSSLKeyX509 extends X509ExtendedKeyManager { @Override public X509Certificate[] getCertificateChain(String alias) { - X509Certificate[] ret = null; - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "entered getCertificateChain(), alias: " + alias); - if (store == null || alias == null) { + if (alias == null) { return null; } - try { - Certificate[] certs = this.store.getCertificateChain(alias); - if (certs != null) { - int x509Cnt = 0; + /* Check if caching is disabled, use direct KeyStore access */ + if (this.cacheDisabled) { + try { + if (keyStore == null) { + return null; + } - /* count up X509Certificate type in certs[] */ - for (int i = 0; i < certs.length; i++) { - if (certs[i] instanceof X509Certificate) { + Certificate[] certChain = keyStore.getCertificateChain(alias); + if (certChain == null) { + return null; + } + + /* Convert to X509Certificate array */ + int x509Cnt = 0; + for (int i = 0; i < certChain.length; i++) { + if (certChain[i] instanceof X509Certificate) { x509Cnt++; } } - /* store into X509Certificate array */ - ret = new X509Certificate[x509Cnt]; - for (int i = 0; i < certs.length; i++) { - if (certs[i] instanceof X509Certificate) { - ret[i] = (X509Certificate)certs[i]; + if (x509Cnt == 0) { + return null; + } + + X509Certificate[] x509Chain = new X509Certificate[x509Cnt]; + int idx = 0; + for (int i = 0; i < certChain.length; i++) { + if (certChain[i] instanceof X509Certificate) { + x509Chain[idx++] = (X509Certificate)certChain[i]; } } - } - } catch (KeyStoreException ex) { - return null; + return x509Chain; + + } catch (KeyStoreException e) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.ERROR, + () -> "Error accessing certificate chain from KeyStore: " + + e.getMessage()); + return null; + } } - return ret; + /* Return cached certificate chain */ + return certificateChainCache.get(alias); } @Override public PrivateKey getPrivateKey(String alias) { - PrivateKey key = null; - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "entered getPrivateKey(), alias: " + alias); - try { - key = (PrivateKey)store.getKey(alias, password); - } catch (Exception e) { - /* @TODO unable to get key */ - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "failed to load private key: " + e); + if (alias == null) { + return null; + } + + /* Check if caching is disabled, use direct KeyStore access */ + if (this.cacheDisabled) { + try { + if (keyStore == null || keyStorePassword == null) { + return null; + } + + return (PrivateKey)keyStore.getKey(alias, keyStorePassword); + + } catch (Exception e) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.ERROR, + () -> "Error accessing private key from KeyStore: " + + e.getMessage()); + return null; + } + } + + /* Return cached private key */ + return privateKeyCache.get(alias); + } + + /** + * Clear sensitive data when object is garbage collected + */ + @Override + protected void finalize() throws Throwable { + try { + /* Clear KeyStore password if present */ + if (keyStorePassword != null) { + Arrays.fill(keyStorePassword, (char)0); + } + + /* Clear cached private keys */ + if (privateKeyCache != null) { + privateKeyCache.clear(); + } + + /* Free WolfSSLX509 certificates if present */ + if (certificateCache != null) { + for (X509Certificate cert : certificateCache.values()) { + if (cert instanceof WolfSSLX509) { + ((WolfSSLX509)cert).free(); + } + } + certificateCache.clear(); + } + + /* Free WolfSSLX509 certificate chains if present */ + if (certificateChainCache != null) { + for (X509Certificate[] chain : certificateChainCache.values()) { + if (chain != null) { + for (X509Certificate cert : chain) { + if (cert instanceof WolfSSLX509) { + ((WolfSSLX509)cert).free(); + } + } + } + } + certificateChainCache.clear(); + } + + if (aliasSet != null) { + aliasSet.clear(); + } + + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "WolfSSLKeyX509 finalized, sensitive data cleared"); + + } finally { + super.finalize(); } - return key; } } diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLKeyX509Test.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLKeyX509Test.java index 465f33f..bd0e329 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLKeyX509Test.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLKeyX509Test.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import java.io.IOException; +import java.security.KeyStore; import java.security.Principal; import java.security.Provider; import java.security.Security; @@ -459,6 +460,657 @@ public class WolfSSLKeyX509Test { pass("\t... passed"); } + @Test + public void testConstructorWithInvalidKeyStore() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + System.out.print("\tTesting with invalid KeyStore"); + + /* Test with null KeyStore - should not throw exception */ + try { + com.wolfssl.provider.jsse.WolfSSLKeyX509 km = + new com.wolfssl.provider.jsse.WolfSSLKeyX509(null, null); + /* Should succeed with empty cache */ + } catch (Exception e) { + error("\t... failed"); + fail("Constructor should handle null KeyStore gracefully: " + e); + } + + pass("\t... passed"); + } + + @Test + public void testCacheConsistencyWithKeyStore() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + KeyManager[] list; + X509KeyManager km; + String[] aliases; + + System.out.print("\tTesting cache consistency"); + + /* Create KeyManager with cached WolfSSLKeyX509 */ + list = tf.createKeyManager("SunX509", tf.allJKS, provider); + km = (X509KeyManager) list[0]; + + /* Test that cached aliases match what we expect */ + aliases = km.getClientAliases("RSA", null); + if (aliases == null || aliases.length == 0) { + error("\t... failed"); + fail("No RSA client aliases found in cache"); + } + + /* Test certificate chain consistency */ + for (String alias : aliases) { + if (alias != null) { + X509Certificate[] chain = km.getCertificateChain(alias); + if (chain == null) { + error("\t... failed"); + fail("Certificate chain missing from cache for alias: " + alias); + } + } + } + + /* Test private key consistency */ + for (String alias : aliases) { + if (alias != null) { + /* Private key may be null for some aliases, that's expected */ + km.getPrivateKey(alias); + } + } + + pass("\t... passed"); + } + + @Test + public void testEmptyKeyStoreCache() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + System.out.print("\tTesting empty KeyStore cache"); + + try { + /* Create empty KeyStore */ + KeyStore emptyStore = KeyStore.getInstance("JKS"); + emptyStore.load(null, null); + + /* Create WolfSSLKeyX509 with empty KeyStore */ + com.wolfssl.provider.jsse.WolfSSLKeyX509 km = + new com.wolfssl.provider.jsse.WolfSSLKeyX509(emptyStore, null); + + /* Test methods with empty cache */ + String[] aliases = km.getClientAliases("RSA", null); + if (aliases != null) { + error("\t... failed"); + fail("Expected null aliases from empty KeyStore"); + } + + aliases = km.getServerAliases("RSA", null); + if (aliases != null) { + error("\t... failed"); + fail("Expected null server aliases from empty KeyStore"); + } + + String alias = km.chooseClientAlias(new String[] {"RSA"}, null, null); + if (alias != null) { + error("\t... failed"); + fail("Expected null client alias from empty KeyStore"); + } + + alias = km.chooseServerAlias("RSA", null, null); + if (alias != null) { + error("\t... failed"); + fail("Expected null server alias from empty KeyStore"); + } + + } catch (Exception e) { + error("\t... failed"); + fail("Empty KeyStore test failed: " + e); + } + + pass("\t... passed"); + } + + @Test + public void testNullKeyStoreCache() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + System.out.print("\tTesting null KeyStore cache"); + + try { + /* Create WolfSSLKeyX509 with null KeyStore */ + com.wolfssl.provider.jsse.WolfSSLKeyX509 km = + new com.wolfssl.provider.jsse.WolfSSLKeyX509(null, null); + + /* Test methods with null KeyStore */ + String[] aliases = km.getClientAliases("RSA", null); + if (aliases != null) { + error("\t... failed"); + fail("Expected null aliases from null KeyStore"); + } + + aliases = km.getServerAliases("RSA", null); + if (aliases != null) { + error("\t... failed"); + fail("Expected null server aliases from null KeyStore"); + } + + String alias = km.chooseClientAlias(new String[] {"RSA"}, null, null); + if (alias != null) { + error("\t... failed"); + fail("Expected null client alias from null KeyStore"); + } + + alias = km.chooseServerAlias("RSA", null, null); + if (alias != null) { + error("\t... failed"); + fail("Expected null server alias from null KeyStore"); + } + + X509Certificate[] chain = km.getCertificateChain("nonexistent"); + if (chain != null) { + error("\t... failed"); + fail("Expected null certificate chain from null KeyStore"); + } + + java.security.PrivateKey key = km.getPrivateKey("nonexistent"); + if (key != null) { + error("\t... failed"); + fail("Expected null private key from null KeyStore"); + } + + } catch (Exception e) { + error("\t... failed"); + fail("Null KeyStore test failed: " + e); + } + + pass("\t... passed"); + } + + @Test + public void testCertificateChainCaching() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + KeyManager[] list; + X509KeyManager km; + X509Certificate[] chain1, chain2; + + System.out.print("\tTesting cert chain caching"); + + list = tf.createKeyManager("SunX509", tf.allJKS, provider); + km = (X509KeyManager) list[0]; + + /* Get certificate chain twice to test caching */ + chain1 = km.getCertificateChain("client"); + chain2 = km.getCertificateChain("client"); + + if (chain1 == null) { + error("\t... failed"); + fail("Certificate chain should not be null for 'client' alias"); + } + + if (chain2 == null) { + error("\t... failed"); + fail("Second certificate chain retrieval should not be null"); + } + + /* Test that both retrievals return the same cached object */ + if (chain1 != chain2) { + error("\t... failed"); + fail("Certificate chain caching failed - different objects returned"); + } + + /* Test with non-existent alias */ + X509Certificate[] nullChain = km.getCertificateChain("nonexistent"); + if (nullChain != null) { + error("\t... failed"); + fail("Expected null certificate chain for non-existent alias"); + } + + /* Test with null alias */ + nullChain = km.getCertificateChain(null); + if (nullChain != null) { + error("\t... failed"); + fail("Expected null certificate chain for null alias"); + } + + pass("\t... passed"); + } + + @Test + public void testPrivateKeyCaching() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + KeyManager[] list; + X509KeyManager km; + java.security.PrivateKey key1, key2; + + System.out.print("\tTesting private key caching"); + + list = tf.createKeyManager("SunX509", tf.allJKS, provider); + km = (X509KeyManager) list[0]; + + /* Get private key twice to test caching */ + key1 = km.getPrivateKey("client"); + key2 = km.getPrivateKey("client"); + + if (key1 == null) { + error("\t... failed"); + fail("Private key should not be null for 'client' alias"); + } + + if (key2 == null) { + error("\t... failed"); + fail("Second private key retrieval should not be null"); + } + + /* Test that both retrievals return the same cached object */ + if (key1 != key2) { + error("\t... failed"); + fail("Private key caching failed - different objects returned"); + } + + /* Test with non-existent alias */ + java.security.PrivateKey nullKey = km.getPrivateKey("nonexistent"); + if (nullKey != null) { + error("\t... failed"); + fail("Expected null private key for non-existent alias"); + } + + /* Test with null alias */ + nullKey = km.getPrivateKey(null); + if (nullKey != null) { + error("\t... failed"); + fail("Expected null private key for null alias"); + } + + pass("\t... passed"); + } + + @Test + public void testCacheDisabledSecurityProperty() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + System.out.print("\tTesting cache disabled"); + + /* Save original property value */ + String originalValue = + Security.getProperty("wolfjsse.X509KeyManager.disableCache"); + + try { + /* Test with caching disabled */ + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", "true"); + + /* Create KeyManager with caching disabled */ + KeyStore ks = KeyStore.getInstance("JKS"); + java.io.FileInputStream fis = + new java.io.FileInputStream(tf.allJKS); + ks.load(fis, "wolfSSL test".toCharArray()); + fis.close(); + + com.wolfssl.provider.jsse.WolfSSLKeyX509 km = + new com.wolfssl.provider.jsse.WolfSSLKeyX509( + ks, "wolfSSL test".toCharArray()); + + /* Test that operations work with caching disabled */ + String[] aliases = km.getClientAliases("RSA", null); + if (aliases == null || aliases.length == 0) { + error("\t\t... failed"); + fail("No RSA client aliases found with caching disabled"); + } + + /* Test certificate chain retrieval */ + X509Certificate[] chain = km.getCertificateChain("client"); + if (chain == null) { + error("\t\t... failed"); + fail("Certificate chain should not be null with " + + "caching disabled"); + } + + /* Test private key retrieval */ + java.security.PrivateKey key = km.getPrivateKey("client"); + if (key == null) { + error("\t\t... failed"); + fail("Private key should not be null with caching disabled"); + } + + /* Test alias selection */ + String selectedAlias = + km.chooseClientAlias(new String[] {"RSA"}, null, null); + if (selectedAlias == null) { + error("\t\t... failed"); + fail("Should be able to choose client alias with " + + "caching disabled"); + } + + } finally { + /* Restore original property value */ + if (originalValue != null) { + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", originalValue); + } else { + /* Remove property if it wasn't set originally */ + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", ""); + } + } + + pass("\t\t... passed"); + } + + @Test + public void testCacheEnabledSecurityProperty() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + System.out.print("\tTesting cache enabled"); + + /* Save original property value */ + String originalValue = + Security.getProperty("wolfjsse.X509KeyManager.disableCache"); + + try { + /* Test with caching explicitly enabled */ + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", "false"); + + /* Create KeyManager with caching enabled */ + KeyStore ks = KeyStore.getInstance("JKS"); + java.io.FileInputStream fis = + new java.io.FileInputStream(tf.allJKS); + ks.load(fis, "wolfSSL test".toCharArray()); + fis.close(); + + com.wolfssl.provider.jsse.WolfSSLKeyX509 km = + new com.wolfssl.provider.jsse.WolfSSLKeyX509( + ks, "wolfSSL test".toCharArray()); + + /* Test that operations work with caching enabled */ + String[] aliases = km.getClientAliases("RSA", null); + if (aliases == null || aliases.length == 0) { + error("\t\t... failed"); + fail("No RSA client aliases found with caching enabled"); + } + + /* Test certificate chain retrieval and caching */ + X509Certificate[] chain1 = km.getCertificateChain("client"); + X509Certificate[] chain2 = km.getCertificateChain("client"); + if (chain1 == null || chain2 == null) { + error("\t\t... failed"); + fail("Certificate chains should not be null " + + "with caching enabled"); + } + + /* With caching enabled, should return same cached object */ + if (chain1 != chain2) { + error("\t\t... failed"); + fail("Certificate chain caching failed - different " + + "objects returned"); + } + + /* Test private key retrieval and caching */ + java.security.PrivateKey key1 = km.getPrivateKey("client"); + java.security.PrivateKey key2 = km.getPrivateKey("client"); + if (key1 == null || key2 == null) { + error("\t\t... failed"); + fail("Private keys should not be null with caching enabled"); + } + + /* With caching enabled, should return same cached object */ + if (key1 != key2) { + error("\t\t... failed"); + fail("Private key caching failed - different objects returned"); + } + + } finally { + /* Restore original property value */ + if (originalValue != null) { + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", originalValue); + } else { + /* Remove property if it wasn't set originally */ + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", ""); + } + } + + pass("\t\t... passed"); + } + + @Test + public void testDefaultCachingBehavior() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + System.out.print("\tTesting default cache behavior"); + + /* Save original property value */ + String originalValue = Security.getProperty( + "wolfjsse.X509KeyManager.disableCache"); + + try { + /* Clear property to test default behavior */ + Security.setProperty("wolfjsse.X509KeyManager.disableCache", ""); + + /* Create KeyManager with default behavior + * (should be caching enabled) */ + KeyStore ks = KeyStore.getInstance("JKS"); + java.io.FileInputStream fis = + new java.io.FileInputStream(tf.allJKS); + ks.load(fis, "wolfSSL test".toCharArray()); + fis.close(); + + com.wolfssl.provider.jsse.WolfSSLKeyX509 km = + new com.wolfssl.provider.jsse.WolfSSLKeyX509( + ks, "wolfSSL test".toCharArray()); + + /* Test that operations work with default behavior */ + String[] aliases = km.getClientAliases("RSA", null); + if (aliases == null || aliases.length == 0) { + error("\t... failed"); + fail("No RSA client aliases found with default behavior"); + } + + /* Test that caching works by default (same objects returned) */ + X509Certificate[] chain1 = km.getCertificateChain("client"); + X509Certificate[] chain2 = km.getCertificateChain("client"); + if (chain1 == null || chain2 == null) { + error("\t... failed"); + fail("Certificate chains should not be null " + + "with default behavior"); + } + + /* Default behavior should be caching enabled */ + if (chain1 != chain2) { + error("\t... failed"); + fail("Default behavior should enable caching - " + + "different objects returned"); + } + + } finally { + /* Restore original property value */ + if (originalValue != null) { + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", originalValue); + } else { + /* Remove property if it wasn't set originally */ + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", ""); + } + } + + pass("\t... passed"); + } + + @Test + public void testCaseInsensitiveSecurityProperty() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + System.out.print("\tCase insensitive cache disable"); + + /* Save original property value */ + String originalValue = Security.getProperty( + "wolfjsse.X509KeyManager.disableCache"); + + try { + /* Test different case variations of "true" */ + String[] trueVariations = {"true", "TRUE", "True", "tRuE"}; + + for (String trueValue : trueVariations) { + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", trueValue); + + KeyStore ks = KeyStore.getInstance("JKS"); + java.io.FileInputStream fis = + new java.io.FileInputStream(tf.allJKS); + ks.load(fis, "wolfSSL test".toCharArray()); + fis.close(); + + com.wolfssl.provider.jsse.WolfSSLKeyX509 km = + new com.wolfssl.provider.jsse.WolfSSLKeyX509( + ks, "wolfSSL test".toCharArray()); + + /* Should work with any case variation of "true" */ + String[] aliases = km.getClientAliases("RSA", null); + if (aliases == null || aliases.length == 0) { + error("\t... failed"); + fail("No RSA client aliases found with '" + + trueValue + "'"); + } + } + + /* Test values that should NOT disable caching */ + String[] falseVariations = {"false", "FALSE", "False", "0", + "no", "disabled", "random"}; + + for (String falseValue : falseVariations) { + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", falseValue); + + KeyStore ks = KeyStore.getInstance("JKS"); + java.io.FileInputStream fis = + new java.io.FileInputStream(tf.allJKS); + ks.load(fis, "wolfSSL test".toCharArray()); + fis.close(); + + com.wolfssl.provider.jsse.WolfSSLKeyX509 km = + new com.wolfssl.provider.jsse.WolfSSLKeyX509( + ks, "wolfSSL test".toCharArray()); + + /* Should have caching enabled (same objects returned) */ + X509Certificate[] chain1 = km.getCertificateChain("client"); + X509Certificate[] chain2 = km.getCertificateChain("client"); + if (chain1 == null || chain2 == null) { + error("\t... failed"); + fail("Certificate chains should not be null with '" + + falseValue + "'"); + } + + if (chain1 != chain2) { + error("\t... failed"); + fail("Caching should be enabled with '" + + falseValue + "' - different objects returned"); + } + } + + } finally { + /* Restore original property value */ + if (originalValue != null) { + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", originalValue); + } else { + /* Remove property if it wasn't set originally */ + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", ""); + } + } + + pass("\t... passed"); + } + + @Test + public void testNullKeyStoreWithCachingDisabled() + throws NoSuchAlgorithmException, KeyStoreException, + KeyManagementException, CertificateException, IOException, + NoSuchProviderException, UnrecoverableKeyException { + + System.out.print("\tnull KeyStore with no caching"); + + /* Save original property value */ + String originalValue = Security.getProperty( + "wolfjsse.X509KeyManager.disableCache"); + + try { + /* Test with caching disabled and null KeyStore */ + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", "true"); + + com.wolfssl.provider.jsse.WolfSSLKeyX509 km = + new com.wolfssl.provider.jsse.WolfSSLKeyX509(null, null); + + /* Test that all operations return null gracefully */ + String[] aliases = km.getClientAliases("RSA", null); + if (aliases != null) { + error("\t... failed"); + fail("Expected null aliases with null KeyStore and " + + "caching disabled"); + } + + X509Certificate[] chain = km.getCertificateChain("client"); + if (chain != null) { + error("\t... failed"); + fail("Expected null certificate chain with null " + + "KeyStore and caching disabled"); + } + + java.security.PrivateKey key = km.getPrivateKey("client"); + if (key != null) { + error("\t... failed"); + fail("Expected null private key with null KeyStore " + + "and caching disabled"); + } + + String alias = km.chooseClientAlias(new String[] {"RSA"}, + null, null); + if (alias != null) { + error("\t... failed"); + fail("Expected null alias with null KeyStore and " + + "caching disabled"); + } + + } finally { + /* Restore original property value */ + if (originalValue != null) { + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", originalValue); + } else { + /* Remove property if it wasn't set originally */ + Security.setProperty( + "wolfjsse.X509KeyManager.disableCache", ""); + } + } + + pass("\t... passed"); + } + private void pass(String msg) { WolfSSLTestFactory.pass(msg); } From e3eab4dee01a30327b0a2b71e87bed132badadf9 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Mon, 9 Jun 2025 15:06:11 -0600 Subject: [PATCH 2/2] JSSE: defer creation of List ArrayList until needed in getRequestedServerNames() --- .../com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java b/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java index d77b6de..6333d40 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java @@ -1081,7 +1081,6 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession { throws UnsupportedOperationException { byte[] sniRequestArr = null; - List sniNames = new ArrayList<>(1); if (this.ssl == null) { return Collections.emptyList(); @@ -1103,6 +1102,7 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession { } if (sniRequestArr != null) { + List sniNames = new ArrayList<>(1); SNIHostName sniName = new SNIHostName(sniRequestArr); sniNames.add(sniName);