diff --git a/IDE/Android/app/src/main/cpp/CMakeLists.txt b/IDE/Android/app/src/main/cpp/CMakeLists.txt index 6223b1b9..127b6fc8 100644 --- a/IDE/Android/app/src/main/cpp/CMakeLists.txt +++ b/IDE/Android/app/src/main/cpp/CMakeLists.txt @@ -272,6 +272,7 @@ list(REMOVE_ITEM TLS_SOURCES ${wolfssl_DIR}/src/ssl_misc.c) list(REMOVE_ITEM TLS_SOURCES ${wolfssl_DIR}/src/ssl_p7p12.c) list(REMOVE_ITEM TLS_SOURCES ${wolfssl_DIR}/src/ssl_sess.c) list(REMOVE_ITEM TLS_SOURCES ${wolfssl_DIR}/src/ssl_sk.c) +list(REMOVE_ITEM TLS_SOURCES ${wolfssl_DIR}/src/ssl_tsp.c) list(REMOVE_ITEM TLS_SOURCES ${wolfssl_DIR}/src/x509.c) list(REMOVE_ITEM TLS_SOURCES ${wolfssl_DIR}/src/x509_str.c) @@ -282,6 +283,7 @@ if ("${WOLFSSL_PKG_TYPE}" MATCHES "normal") list(REMOVE_ITEM CRYPTO_SOURCES ${wolfssl_DIR}/wolfcrypt/src/evp_pk.c) list(REMOVE_ITEM CRYPTO_SOURCES ${wolfssl_DIR}/wolfcrypt/src/misc.c) list(REMOVE_ITEM CRYPTO_SOURCES ${wolfssl_DIR}/wolfcrypt/src/asn_orig.c) + list(REMOVE_ITEM CRYPTO_SOURCES ${wolfssl_DIR}/wolfcrypt/src/asn_tsp.c) elseif("${WOLFSSL_PKG_TYPE}" MATCHES "fipsready") # FIPS Ready needs to explicitly order files for in-core integrity check to work properly. diff --git a/README_JCE.md b/README_JCE.md index 3abde61e..211926bb 100644 --- a/README_JCE.md +++ b/README_JCE.md @@ -36,6 +36,7 @@ file for JCE provider customization: | --- | --- | --- | --- | | wolfjce.wks.iterationCount | 210,000 | Numeric | PBKDF2 iteration count (10,000 minimum) | | wolfjce.wks.maxCertChainLength | 100 | Integer | Max cert chain length | +| wolfjce.wks.maxEntrySize | 10485760 | Integer | Max encoded entry size in bytes when loading WKS (10 MB default) | | wolfjce.keystore.kekCacheEnabled | false | true | Enable KEK caching in WKS KeyStore for performance | | wolfjce.keystore.kekCacheTtlSec | 300 | Integer | KEK cache TTL in seconds (1 second minimum) | | wolfjce.mapJKStoWKS | UNSET | true | Register fake JKS KeyStore service mapped to WKS | diff --git a/docs/design/WolfSSLKeyStore.md b/docs/design/WolfSSLKeyStore.md index e90c25d6..2bfcc620 100644 --- a/docs/design/WolfSSLKeyStore.md +++ b/docs/design/WolfSSLKeyStore.md @@ -13,6 +13,7 @@ please reference the appropriate Security Policy or contact fips@wolfssl.com. | --- | --- | --- | --- | | `wolfjce.wks.iterationCount` | 210,000 | 10,000 | PBKDF2 iteration count | | `wolfjce.wks.maxCertChainLength` | 100 | N/A | Max cert chain length | +| `wolfjce.wks.maxEntrySize` | 10485760 | N/A | Max encoded entry size in bytes | | `wolfjce.keystore.kekCacheEnabled` | false | N/A | Enable KEK caching | | `wolfjce.keystore.kekCacheTtlSec` | 300 | 1 | Cache TTL in seconds | diff --git a/jni/include/com_wolfssl_provider_jce_WolfSSLKeyStore.h b/jni/include/com_wolfssl_provider_jce_WolfSSLKeyStore.h index 08092dff..6c113af9 100644 --- a/jni/include/com_wolfssl_provider_jce_WolfSSLKeyStore.h +++ b/jni/include/com_wolfssl_provider_jce_WolfSSLKeyStore.h @@ -21,6 +21,8 @@ extern "C" { #define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_HMAC_KEY_LENGTH 64L #undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_DEFAULT_MAX_CHAIN_COUNT #define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_DEFAULT_MAX_CHAIN_COUNT 100L +#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_DEFAULT_MAX_ENTRY_SIZE +#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_DEFAULT_MAX_ENTRY_SIZE 10485760L #undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_MAGIC_NUMBER #define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_MAGIC_NUMBER 7L #undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_STORE_VERSION diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java index ea5a120a..1306e972 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java @@ -1072,7 +1072,7 @@ public CertPathValidatorResult engineValidate( /* If we are in FIPS mode, verify wolfJCE is the Signature provider * to help maintain FIPS compliance */ - if (Fips.enabled && pkixParams.getSigProvider() != "wolfJCE") { + if (Fips.enabled && !"wolfJCE".equals(pkixParams.getSigProvider())) { if (pkixParams.getSigProvider() == null) { /* Preferred Signature provider not set, set to wolfJCE */ pkixParams.setSigProvider("wolfJCE"); diff --git a/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java b/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java index adbd5502..4fe9e4f3 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 @@ 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; @@ -224,6 +225,11 @@ public class WolfSSLKeyStore extends KeyStoreSpi { private static final int WKS_DEFAULT_MAX_CHAIN_COUNT = 100; private static final int WKS_MAX_CHAIN_COUNT; + /* Max encoded entry size in bytes, configurable via + * 'wolfjce.wks.maxEntrySize' Security property */ + private static final int WKS_DEFAULT_MAX_ENTRY_SIZE = 10 * 1024 * 1024; + private static final int WKS_MAX_ENTRY_SIZE; + /* WKS magic number, used when storing KeyStore to OutputStream */ private static final int WKS_MAGIC_NUMBER = 7; @@ -341,8 +347,10 @@ void wipe() { static { int iCount = WKS_PBKDF2_DEFAULT_ITERATIONS; int cLength = WKS_DEFAULT_MAX_CHAIN_COUNT; + int eLength = WKS_DEFAULT_MAX_ENTRY_SIZE; String iterations = null; String chainCount = null; + String entrySize = null; /* Set PBKDF2 iteration count, using default or one set by * user in 'wolfjce.wks.iterationCount' Security property in @@ -389,6 +397,27 @@ void wipe() { log("setting max cert chain length: " + cLength); WKS_MAX_CHAIN_COUNT = cLength; + + /* Set max encoded entry size limit, using default or one set with + * `wolfjce.wks.maxEntrySize` Security property */ + entrySize = Security.getProperty("wolfjce.wks.maxEntrySize"); + if (entrySize != null && !entrySize.isEmpty()) { + try { + eLength = Integer.parseInt(entrySize); + if (eLength <= 0) { + log("wolfjce.wks.maxEntrySize (" + eLength + + ") lower than 0, using default"); + eLength = WKS_DEFAULT_MAX_ENTRY_SIZE; + } + } catch (NumberFormatException e) { + /* Error parsing property, fall back to default */ + log("error parsing wolfjce.wks.maxEntrySize property, " + + "using default instead"); + } + } + + log("setting max entry size: " + eLength); + WKS_MAX_ENTRY_SIZE = eLength; } /** @@ -2320,6 +2349,12 @@ public synchronized void engineLoad(InputStream stream, char[] password) 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 */ @@ -2381,8 +2416,15 @@ public synchronized void engineLoad(InputStream stream, char[] password) /* encoded entry length */ encodedLen = dis.readInt(); - if (encodedLen < 0) { - throw new IOException("Invalid encoded length, negative"); + if (encodedLen <= 0) { + throw new IOException("Invalid encoded entry length: " + + encodedLen); + } + + if (encodedLen > WKS_MAX_ENTRY_SIZE) { + throw new IOException("Encoded entry length (" + + encodedLen + ") is larger than max allowed: " + + WKS_MAX_ENTRY_SIZE); } /* encoded entry */ @@ -2397,19 +2439,19 @@ public synchronized void engineLoad(InputStream stream, char[] password) 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: @@ -2444,8 +2486,10 @@ public synchronized void engineLoad(InputStream stream, char[] password) /* HMAC len and HMAC */ hmacLen = dis.readInt(); - if (hmacLen < 0) { - throw new IOException("Invalid HMAC length, negative"); + if (hmacLen != WKS_HMAC_KEY_LENGTH) { + throw new IOException( + "HMAC length (" + hmacLen + ") is different than " + + "expected (" + WKS_HMAC_KEY_LENGTH + ")"); } hmac = new byte[hmacLen]; hmacLen = dis.read(hmac); @@ -2474,6 +2518,11 @@ public synchronized void engineLoad(InputStream stream, char[] password) "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(); @@ -2773,6 +2822,10 @@ protected WKSPrivateKey(byte[] encoded) throw new IOException( "Bad encrypted key length, negative"); } + if (tmp > WKS_MAX_ENTRY_SIZE) { + throw new IOException("Encrypted key length (" + tmp + + ") is larger than max allowed: " + WKS_MAX_ENTRY_SIZE); + } this.encryptedKey = new byte[tmp]; dis.readFully(this.encryptedKey); @@ -2800,6 +2853,11 @@ protected WKSPrivateKey(byte[] encoded) throw new IOException( "Bad encoding length, negative"); } + if (tmp > WKS_MAX_ENTRY_SIZE) { + throw new IOException("Cert encoding length (" + tmp + + ") is larger than max allowed: " + + WKS_MAX_ENTRY_SIZE); + } tmpArr = new byte[tmp]; /* encoded cert */ @@ -3116,6 +3174,10 @@ protected WKSCertificate(byte[] encoded) if (tmp < 0) { throw new IOException("Bad encoding length, negative"); } + if (tmp > WKS_MAX_ENTRY_SIZE) { + throw new IOException("Cert encoding length (" + tmp + + ") is larger than max allowed: " + WKS_MAX_ENTRY_SIZE); + } tmpArr = new byte[tmp]; /* encoded cert */ @@ -3391,6 +3453,10 @@ protected WKSSecretKey(byte[] encoded) throw new IOException( "Bad encrypted key length, negative"); } + if (tmp > WKS_MAX_ENTRY_SIZE) { + throw new IOException("Encrypted key length (" + tmp + + ") is larger than max allowed: " + WKS_MAX_ENTRY_SIZE); + } this.encryptedKey = new byte[tmp]; dis.readFully(this.encryptedKey); 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 43dd3a7e..45717766 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java @@ -45,6 +45,7 @@ import java.io.FileInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; import java.io.IOException; import java.io.FileNotFoundException; import java.nio.charset.StandardCharsets; @@ -401,6 +402,311 @@ public void testXmssCertificateRoundTrip() throws Exception { } } + /** + * A crafted WKS stream with an oversized encoded entry length must be + * rejected with an IOException, not trigger an unbounded allocation. + * Uses a null password so the HMAC integrity check is skipped. + */ + @Test + public void testEngineLoadRejectsOversizedEntry() throws Exception { + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(7); /* WKS magic number */ + dos.writeInt(1); /* WKS store version */ + dos.writeInt(1); /* entry count */ + dos.writeInt(2); /* entry type: certificate */ + dos.writeUTF("evil"); /* alias */ + dos.writeInt(Integer.MAX_VALUE); /* encoded entry length */ + dos.flush(); + + KeyStore store = KeyStore.getInstance("WKS", "wolfJCE"); + try { + store.load(new ByteArrayInputStream(bos.toByteArray()), null); + fail("oversized encoded entry length should throw IOException"); + } catch (IOException e) { + /* expected, allocation must be bounded before it happens */ + } + } + + /** + * A crafted WKS stream with an HMAC length that is not the expected + * HMAC-SHA512 size must be rejected with an IOException, not trigger an + * unbounded allocation. Uses a null password so the HMAC integrity + * check is skipped. + */ + @Test + public void testEngineLoadRejectsInvalidHmacLength() throws Exception { + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(7); /* WKS magic number */ + dos.writeInt(1); /* WKS store version */ + dos.writeInt(0); /* entry count */ + dos.writeInt(16); /* salt length */ + dos.write(new byte[16]); /* salt */ + dos.writeInt(10000); /* PBKDF2 iterations */ + dos.writeInt(Integer.MAX_VALUE); /* HMAC length */ + dos.flush(); + + KeyStore store = KeyStore.getInstance("WKS", "wolfJCE"); + try { + store.load(new ByteArrayInputStream(bos.toByteArray()), null); + fail("invalid HMAC length should throw IOException"); + } catch (IOException e) { + /* expected, allocation must be bounded before it happens */ + } + } + + /** + * 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")); + } + + /** + * A crafted WKS private key entry whose encrypted key length field is + * oversized must be rejected with an IOException. Uses a null + * password so the HMAC integrity check is skipped. + */ + @Test + public void testEngineLoadRejectsOversizedPrivateKeyField() + throws Exception { + + /* Build a WKSPrivateKey entry that is valid up to the encrypted key + * length field, which claims Integer.MAX_VALUE bytes. */ + ByteArrayOutputStream entryBos = new ByteArrayOutputStream(); + DataOutputStream e = new DataOutputStream(entryBos); + e.writeLong(0L); /* creationDate */ + e.writeInt(16); /* kdfSalt length */ + e.write(new byte[16]); /* kdfSalt */ + e.writeInt(10000); /* kdfIterations */ + e.writeInt(16); /* iv length */ + e.write(new byte[16]); /* iv */ + e.writeInt(Integer.MAX_VALUE); /* encrypted key length */ + e.flush(); + byte[] entry = entryBos.toByteArray(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(7); /* WKS magic number */ + dos.writeInt(1); /* WKS store version */ + dos.writeInt(1); /* entry count */ + dos.writeInt(1); /* entry type: private key */ + dos.writeUTF("evil"); /* alias */ + dos.writeInt(entry.length); /* encoded entry length */ + dos.write(entry); /* encoded entry */ + dos.flush(); + + KeyStore store = KeyStore.getInstance("WKS", "wolfJCE"); + try { + store.load(new ByteArrayInputStream(bos.toByteArray()), null); + fail("oversized encrypted key length should throw IOException"); + } catch (IOException ex) { + /* expected, allocation must be bounded before it happens */ + } + } + + /** + * A crafted WKS secret key entry whose encrypted key length field is + * oversized must be rejected with an IOException. Uses a null + * password so the HMAC integrity check is skipped. + */ + @Test + public void testEngineLoadRejectsOversizedSecretKeyField() + throws Exception { + + /* Build a WKSSecretKey entry that is valid up to the encrypted key + * length field, which claims Integer.MAX_VALUE bytes. */ + ByteArrayOutputStream entryBos = new ByteArrayOutputStream(); + DataOutputStream e = new DataOutputStream(entryBos); + e.writeLong(0L); /* creationDate */ + e.writeUTF("AES"); /* key algorithm */ + e.writeInt(16); /* kdfSalt length */ + e.write(new byte[16]); /* kdfSalt */ + e.writeInt(10000); /* kdfIterations */ + e.writeInt(16); /* iv length */ + e.write(new byte[16]); /* iv */ + e.writeInt(Integer.MAX_VALUE); /* encrypted key length */ + e.flush(); + byte[] entry = entryBos.toByteArray(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(7); /* WKS magic number */ + dos.writeInt(1); /* WKS store version */ + dos.writeInt(1); /* entry count */ + dos.writeInt(3); /* entry type: secret key */ + dos.writeUTF("evil"); /* alias */ + dos.writeInt(entry.length); /* encoded entry length */ + dos.write(entry); /* encoded entry */ + dos.flush(); + + KeyStore store = KeyStore.getInstance("WKS", "wolfJCE"); + try { + store.load(new ByteArrayInputStream(bos.toByteArray()), null); + fail("oversized encrypted key length should throw IOException"); + } catch (IOException ex) { + /* expected, allocation must be bounded before it happens */ + } + } + + /** + * A crafted WKS private key entry whose certificate chain contains an + * oversized cert encoding length must be rejected with an IOException. + * Uses a null password so the HMAC integrity check is skipped. + */ + @Test + public void testEngineLoadRejectsOversizedChainCert() + throws Exception { + + /* Build a WKSPrivateKey entry that is valid up to the first chain + * cert encoding length, which claims Integer.MAX_VALUE bytes. */ + ByteArrayOutputStream entryBos = new ByteArrayOutputStream(); + DataOutputStream e = new DataOutputStream(entryBos); + e.writeLong(0L); /* creationDate */ + e.writeInt(16); /* kdfSalt length */ + e.write(new byte[16]); /* kdfSalt */ + e.writeInt(10000); /* kdfIterations */ + e.writeInt(16); /* iv length */ + e.write(new byte[16]); /* iv */ + e.writeInt(0); /* encrypted key length (empty) */ + e.writeInt(1); /* chain count */ + e.writeUTF("X.509"); /* chain cert type */ + e.writeInt(Integer.MAX_VALUE); /* chain cert encoding length */ + e.flush(); + byte[] entry = entryBos.toByteArray(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(7); /* WKS magic number */ + dos.writeInt(1); /* WKS store version */ + dos.writeInt(1); /* entry count */ + dos.writeInt(1); /* entry type: private key */ + dos.writeUTF("evil"); /* alias */ + dos.writeInt(entry.length); /* encoded entry length */ + dos.write(entry); /* encoded entry */ + dos.flush(); + + KeyStore store = KeyStore.getInstance("WKS", "wolfJCE"); + try { + store.load(new ByteArrayInputStream(bos.toByteArray()), null); + fail("oversized chain cert length should throw IOException"); + } catch (IOException ex) { + /* expected, allocation must be bounded before it happens */ + } + } + + /** + * A crafted WKS certificate entry whose DER encoding length is oversized + * must be rejected with an IOException. Uses a null password so the + * HMAC integrity check is skipped. + */ + @Test + public void testEngineLoadRejectsOversizedCertificate() + throws Exception { + + /* Build a WKSCertificate entry that is valid up to the encoding + * length field, which claims Integer.MAX_VALUE bytes. */ + ByteArrayOutputStream entryBos = new ByteArrayOutputStream(); + DataOutputStream e = new DataOutputStream(entryBos); + e.writeLong(0L); /* creationDate */ + e.writeUTF("X.509"); /* certificate type */ + e.writeInt(Integer.MAX_VALUE); /* cert encoding length */ + e.flush(); + byte[] entry = entryBos.toByteArray(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(7); /* WKS magic number */ + dos.writeInt(1); /* WKS store version */ + dos.writeInt(1); /* entry count */ + dos.writeInt(2); /* entry type: certificate */ + dos.writeUTF("evil"); /* alias */ + dos.writeInt(entry.length); /* encoded entry length */ + dos.write(entry); /* encoded entry */ + dos.flush(); + + KeyStore store = KeyStore.getInstance("WKS", "wolfJCE"); + try { + store.load(new ByteArrayInputStream(bos.toByteArray()), null); + fail("oversized cert encoding length should throw IOException"); + } catch (IOException ex) { + /* expected, allocation must be bounded before it happens */ + } + } + + /** + * A crafted WKS entry with a zero-length encoded body must be rejected + * with an IOException, not an unchecked IllegalArgumentException from the + * per-entry decoder. engineLoad() declares only checked exceptions, so a + * malformed entry must fail predictably. Uses a null password so the + * HMAC integrity check is skipped, matching the zero-credential scenario. + */ + @Test + public void testEngineLoadRejectsZeroLengthEntry() throws Exception { + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(7); /* WKS magic number */ + dos.writeInt(1); /* WKS store version */ + dos.writeInt(1); /* entry count */ + dos.writeInt(1); /* entry type: private key */ + dos.writeUTF("evil"); /* alias */ + dos.writeInt(0); /* encoded entry length */ + /* trailing bytes so the entry length is not at end of stream, which + * is what lets the empty entry reach the per-entry decoder */ + dos.writeInt(16); + dos.write(new byte[16]); + dos.flush(); + + KeyStore store = KeyStore.getInstance("WKS", "wolfJCE"); + try { + store.load(new ByteArrayInputStream(bos.toByteArray()), null); + fail("zero-length entry should throw IOException"); + } catch (IOException ex) { + /* expected, malformed input fails with a checked exception */ + } + } + /** * Create PrivateKey and Certificate objects based on files.