F-4356: commit WKS entries only after HMAC verification in engineLoad

pull/238/head
Chris Conlon 2026-07-08 11:56:51 -06:00
parent 865c769427
commit 801e0eed1a
2 changed files with 59 additions and 3 deletions

View File

@ -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<String, Object> loadedEntries = new LinkedHashMap<String, Object>();
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();

View File

@ -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.