diff --git a/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java b/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java index 1fdb10e7..dfe0ed69 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java @@ -24,6 +24,7 @@ package com.wolfssl.provider.jce; import java.util.Date; import java.util.Enumeration; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.Map; import java.io.InputStream; import java.io.OutputStream; @@ -2348,6 +2349,12 @@ public class WolfSSLKeyStore extends KeyStoreSpi { WKSSecretKey sKeyEntry = null; WKSCertificate certEntry = null; + /* Parse entries into a local map first. They are only committed + * into the shared this.entries map after the HMAC integrity check + * passes, so a failed or tampered load never exposes unverified + * entries to engineGetKey()/engineGetCertificate() callers. */ + Map loadedEntries = new LinkedHashMap(); + log("loading KeyStore from InputStream"); /* Clear any cached KEK entries from previous keystore */ @@ -2431,19 +2438,19 @@ public class WolfSSLKeyStore extends KeyStoreSpi { case WKS_ENTRY_ID_PRIVATE_KEY: log("loading PrivateKey: " + alias); keyEntry = new WKSPrivateKey(encodedEntry); - entries.put(alias, keyEntry); + loadedEntries.put(alias, keyEntry); break; case WKS_ENTRY_ID_SECRET_KEY: log("loading SecretKey: " + alias); sKeyEntry = new WKSSecretKey(encodedEntry); - entries.put(alias, sKeyEntry); + loadedEntries.put(alias, sKeyEntry); break; case WKS_ENTRY_ID_CERTIFICATE: log("loading Certificate: " + alias); certEntry = new WKSCertificate(encodedEntry); - entries.put(alias, certEntry); + loadedEntries.put(alias, certEntry); break; default: @@ -2510,6 +2517,11 @@ public class WolfSSLKeyStore extends KeyStoreSpi { "no password provided"); } + /* Commit parsed entries into the shared map, replacing any + * previous contents */ + this.entries.clear(); + this.entries.putAll(loadedEntries); + } finally { if (dis != null) { dis.close(); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java index 2e0878a8..bdd2c9e0 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java @@ -458,6 +458,50 @@ public class WolfSSLKeyStoreTest { } } + /** + * A WKS load that fails its HMAC integrity check must not expose the + * parsed entries. A KeyStore already initialized by a prior successful + * load keeps initialized == true, so getCertificate() stays reachable + * after a failed second load. Entries from the failed load must not be + * visible. + */ + @Test + public void testEngineLoadFailureDoesNotExposeEntries() + throws Exception { + + Assume.assumeTrue("test certificate not available", + serverCertRsa != null); + + char[] pass = storePass.toCharArray(); + + /* Build a valid WKS blob, then tamper the trailing HMAC so the + * integrity check fails while the entries still parse. */ + KeyStore src = KeyStore.getInstance("WKS", "wolfJCE"); + src.load(null, pass); + src.setCertificateEntry("evil", serverCertRsa); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + src.store(bos, pass); + byte[] tampered = bos.toByteArray(); + tampered[tampered.length - 1] ^= 0x01; + + /* Initialize the victim with an empty load so it stays usable + * after the failed load below. */ + KeyStore victim = KeyStore.getInstance("WKS", "wolfJCE"); + victim.load(null, pass); + + try { + victim.load(new ByteArrayInputStream(tampered), pass); + fail("tampered WKS should fail the integrity check"); + } catch (IOException e) { + /* expected */ + } + + assertNull("failed load must not expose an entry", + victim.getCertificate("evil")); + assertFalse("failed load must not expose an alias", + victim.containsAlias("evil")); + } + /** * Create PrivateKey and Certificate objects based on files.