diff --git a/.gitignore b/.gitignore
index 51f5360..0779f1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,7 @@ build/
lib/
# Generated Javadocs
-docs/
+docs/javadoc
# makefile, since copied from makefile.linux or makefile.macosx
makefile
@@ -31,3 +31,6 @@ infer-out/
# Maven output directory
target/
+# Generated system cacerts.wks and jssecacerts.wks
+examples/certs/systemcerts/cacerts.wks
+examples/certs/systemcerts/jssecacerts.wks
diff --git a/README_JCE.md b/README_JCE.md
index 49cf358..3dc0ecb 100644
--- a/README_JCE.md
+++ b/README_JCE.md
@@ -21,6 +21,31 @@ and Android platforms.
Pre-compiled and signed wolfCrypt JNI/JCE JAR's are included with the stable
releases of the JCE provider. See below for more details.
+### System and Security Property Support
+---------
+
+wolfJCE supports the following System and Security properties for behavior
+customization and debugging.
+
+#### Security Property Support
+
+The following Java Security properties can be set in the `java.security`
+file for JCE provider customization:
+
+| Security Property | Default | To Enable | Description |
+| --- | --- | --- | --- |
+| wolfjce.wks.iterationCount | 210,000 | Numeric | PBKDF2 iteration count (10,000 minimum) |
+| wolfjce.wks.maxCertChainLength | 100 | Integer | Max cert chain length |
+
+#### System Property Support
+
+The following Java System properties can be set on the command line or
+programatically for JCE provider customization:
+
+| System Property | Default | To Enable | Description |
+| --- | --- | --- | --- |
+| wolfjce.debug | "false" | "true" | Enable wolfJCE debug logging |
+
### Algorithm Support:
---------
@@ -87,6 +112,9 @@ The JCE provider currently supports the following algorithms:
PBKDF2WithHmacSHA3-384
PBKDF2WithHmacSHA3-512
+ KeyStore
+ WKS
+
### SecureRandom.getInstanceStrong()
When registered as the highest priority security provider, wolfJCE will provide
@@ -104,6 +132,104 @@ securerandom.strongAlgorithms=HashDRBG:wolfJCE
Note that the `securerandom.source` property in `java.security` has no affect
on the wolfJCE provider.
+### WolfSSLKeyStore (WKS) Implementation Details and Usage
+
+wolfJCE implements one custom KeyStore class named WolfSSLKeyStore, represented
+as "WKS". If wolfJCE has been installed as a Security provider, this KeyStore
+can be used with:
+
+```
+KeyStore store = KeyStore.getInstance("WKS");
+```
+
+#### Algorithm Use and FIPS 140-2 / 140-3 Compatibility
+
+The WKS KeyStore has been designed to be compatible with wolfCrypt
+FIPS 140-2 and 140-3.
+
+PrivateKey and SecretKey objects stored are protected inside the KeyStore
+using AES-CBC-256 with HMAC-SHA512 in an Encrypt-then-MAC manner. PKCS#5
+PBKDF2-HMAC-SHA512 is used to generate 96 bytes of key material which is split
+between a 32-byte AES-CBC-256 key and 64-byte HMAC-SHA512 key.
+
+PBKDF2 salt is 16 bytes, randomly generated for each key storage operation
+PBKDF2 iteration count defaults to 210,000 (current OWASP recommendation), but
+is user overridable with wolfjce.wks.iterationCount Security property in
+java.security file. User password is converted from char[] to byte[] using
+UTF-8, consistent with how SunJCE uses UTF-8 for PBKDF2 SecretKeyFactory.
+AES-CBC IV is randomly generated for each key storage operation
+
+This KeyStore uses a different format that is not directly compatible with
+existing formats (ex: JKS, PKCS12, etc). Other KeyStore types will need to be
+converted over to WKS KeyStore objects for FIPS compliant use with wolfCrypt
+FIPS 140-2/3.
+
+#### Stored Object Compatibility
+
+The WKS KeyStore supports storage of PrivateKey, Certificate, and
+SecretKey objects.
+
+#### Converting Other KeyStore Formats to WKS
+
+The Java `keytool` application can be used to convert between KeyStore formats.
+This can be easily used for example to convert a JKS KeyStore into a WKS
+format KeyStore.
+
+The following example command would convert a KeyStore in JKS format named
+`server.jks` to a KeyStore in WKS format named `server.wks`:
+
+```
+keytool -importkeystore -srckeystore server.jks -destkeystore server.wks \
+ -srcstoretype JKS -deststoretype WKS \
+ -srcstorepass "pass" -deststorepass "pass" \
+ -provider com.wolfssl.provider.jce.WolfCryptProvider \
+ --providerpath /path/to/wolfcrypt-jni.jar
+```
+
+To list entries inside a WKS keystore using the `keytool`, a command
+similar to the following can be used (with the `-list` option):
+
+```
+keytool -list -provider com.wolfssl.provider.jce.WolfCryptProvider \
+ --providerpath /path/to/wolfcrypt-jni.jar \
+ -storetype WKS -storepass "pass" -keystore server.wks
+```
+
+If running the above commands gives an error about the native wolfcryptjni
+shared library not being found, you may need to add the library location
+to `LD_LIBRARY_PATH` (Linux) or `DYLD_LIBRARY_PATH` (Mac OSX), ie:
+
+```
+export LD_LIBRARY_PATH=/path/to/libwolfcryptjni.so:$LD_LIBRARY_PATH
+```
+
+#### Converting System cacerts to WKS Format KeyStore
+
+For FIPS compatibility, users who do not want to use non-wolfSSL KeyStore
+implementations (ex: JKS) may need to convert the system cacerts or
+jssecacerts KeyStore to WKS format. This can be done using the keytool
+command as described above (default password for cacerts is 'changeit'), or
+the helper script located in this package at:
+
+```
+examples/certs/systemcerts/system-cacerts-to-wks.sh
+```
+
+This is a shell script that takes no arguments. It tries to detect the
+location of the active Java installation and converts `cacerts` and/or
+`jssecacerts` to WKS format if they are found. Converted KeyStores are placed
+under the same directory as the script, specifically:
+
+```
+examples/certs/systemcerts/cacerts.wks
+examples/certs/systemcerts/jssecacerts.wks
+```
+
+#### Design Notes
+
+More complete design documentation can be found in
+[docs/WolfSSLKeyStore.md](./docs/design/WolfSSLKeyStore.md).
+
### Example / Test Code
---------
diff --git a/build.xml b/build.xml
index d8a1ee2..5edf329 100644
--- a/build.xml
+++ b/build.xml
@@ -26,7 +26,7 @@
-
+
@@ -86,6 +86,9 @@
+
+
+
@@ -111,6 +114,7 @@
+
diff --git a/docs/design/WolfSSLKeyStore.md b/docs/design/WolfSSLKeyStore.md
new file mode 100644
index 0000000..8e2fe28
--- /dev/null
+++ b/docs/design/WolfSSLKeyStore.md
@@ -0,0 +1,256 @@
+
+# wolfSSL KeyStore (WKS) Design Notes
+
+The WKS KeyStore format was designed to be compatible with wolfCrypt FIPS
+140-2 and 140-3, meaning it utilizes FIPS validated cryptographic algorithms.
+This document includes notes on the design and algorithm choices used by WKS.
+For details on the wolfCrypt FIPS 140-2/3 cryptographic module and boundary,
+please reference the appropriate Security Policy or contact fips@wolfssl.com.
+
+## User Customizable Properties
+
+| Security Property | Default | Min | Description |
+| --- | --- | --- | --- |
+| `wolfjce.wks.iterationCount` | 210,000 | 10,000 | PBKDF2 iteration count |
+| `wolfjce.wks.maxCertChainLength` | 100 | N/A | Max cert chain length |
+
+## Notes on Algorithm and Security Properties
+
+PBKDF2-HMAC-SHA512 was chosen over PBKDF2-HMAC-SHA256 for AES and HMAC key
+generation to allow use of fewer iterations (210,000, as per current
+[OWASP recommendations](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2)) versus the recommended 600,000 for SHA-256.
+
+PBKDF2 salt size of 128-bits (16 bytes) is used to follow recommendations
+in [NIST SP 800-132, Page 6](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf).
+
+AES-CBC (AES/CBC/PKCS5Padding) was chosen over AES-GCM since AES-GCM requires
+that each {key,nonce} combination be unique. Simply generating a random nonce
+via RNG does not guarantee uniqueness, and we have no way of maintaining an
+accurate counter across KeyStore objects and store/load operations.
+
+Different keys are used for PrivateKey/SecretKey encryption and HMAC, and
+derived from one larger PBKDF2 operation (96 bytes) then split between
+encryption (32-byte key) and HMAC (64-byte key) operations. A
+random salt is generated for each PBKDF2 key generation operation.
+
+HMAC values are calculated over content but also the PBKDF2 salt length,
+salt, and iteration count, and all other key parameters (ex: IV and length) to
+also include those in the integrity check.
+
+## KeyStore Integrity
+
+### HMAC Generation During KeyStore Storage
+
+When WKS KeyStore objects are stored (`engineStore()`), the following format
+is used. This is composed of a *HEADER* section, an *ENTRIES* section, followed
+lastly by an HMAC generated over the *HEADER* and *ENTRIES*, including the
+PBKDF2 salt, salt length, and iteration count.
+
+The *HEADER* includes a magic number specific to the WKS KeyStore type (`7`), a
+WKS KeyStore version (may be incremented in the future as features are added
+or if the WKS type definition changes), and a count of the entries included in
+the store.
+
+The *ENTRIES* section is made up of one or more `WKSPrivateKey`,
+`WKSSecretKey`, or `WKSCertificate` entries. These represent the storage of
+a `PrivateKey`, `SecretKey`, and `Certificate` objects respectively.
+
+Generation of the HMAC happens during a call to
+`engineStore(OutputStream stream, char[] password)` and is generated in the
+following manner:
+
+- Input password must not be null or zero length
+- Input password is converted from `char[]` into `byte[]` using password
+conversion algorithm described below.
+- Random salt of size `WKS_PBKDF2_SALT_SIZE` (128 bits) is generated
+- HMAC-SHA512 key (64-bytes) is generated with PBKDF2-HMAC-SHA512 using:
+ + Password byte array
+ + Random 16-byte salt (`WKS_PBKDF2_SALT_SIZE`)
+ + 210,000 iterations (`WKS_PBKDF2_ITERATION_COUNT`), but can be overriden
+ by user by setting `wolfjce.wks.iterationCount` Security property.
+ Minimum iteration count is 10,000.
+- The final HMAC-SHA512 is calculated using the derived key over the bytes of
+*HEADER*, *ENTRIES*, salt length, salt, and iteration count. It is then
+written out to the OutputStream.
+
+### HMAC Verification During KeyStore Load
+
+When a WKS KeyStore is loaded with
+`engineLoad(InputStream stream, char[] password)`, the input password is
+optional. If a password is provided, the KeyStore integrity will be checked
+using the included HMAC, otherwise the integrity check will be skipped.
+This design is to maintain consistency with how the Java JKS format handles
+integrity checks upon KeyStore load, and allows for easy conversion and use
+of files such as `cacerts` to a WKS type where users do not normally provide
+the password when loading the KeyStore file.
+
+Since the HMAC is stored at the end of the KeyStore stream, `engineLoad()`
+buffers KeyStore bytes as they are read in, up to and including the PBKDF2
+salt size, salt, and PBKDF2 iteration count. Once all entries have been read,
+the HMAC is read and verified:
+- The salt length is read, sanitized against `WKS_PBKDF2_SALT_SIZE`
+- The salt is read
+- The PBKDF2 iteration count is read, and checked against min size of
+`WKS_PBKDF2_MIN_ITERATIONS`
+- Caching of data is paused while the HMAC is read in next
+- The original HMAC length is read
+- An HMAC-SHA512 is regenerated over the buffered header and entry bytes
+ + Password is converted from char[] to byte[] as explained below
+ + An HMAC-SHA512 key (64-bytes) is calculated as explained above, using
+ salt that was read from input KeyStore stream
+ + The generated HMAC value is calculated using this key
+- The generated HMAC is compared in both size and contents against the stored
+HMAC. If these are different, an IOException is thrown.
+
+### Stored WKS format:
+
+```
+ * HEADER:
+ * magicNumber (int / 7)
+ * keystoreVersion (int)
+ * entryCount (int)
+ * ENTRIES (can be any of below, depending on type)
+ * [WKSPrivateKey]
+ * entryId (int / 1)
+ * alias (UTF String)
+ * creationDate.getTime() (long)
+ * kdfSalt.length (int)
+ * kdfSalt (byte[])
+ * kdfIterations (int)
+ * iv.length (int)
+ * iv (byte[])
+ * encryptedKey.length (int)
+ * encryptedKey (byte[])
+ * chain.length (int)
+ * FOR EACH CERT:
+ * chain[i].getType() (UTF String)
+ * chain[i].getEncoded().length (int)
+ * chain[i].getEncoced() (byte[])
+ * hmac.length (int)
+ * hmac (HMAC-SHA512) (byte[])
+ * [WKSSecretKey]
+ * entryId (int / 3)
+ * alias (UTF String)
+ * creationDate.getTime() (long)
+ * key.getAlgorithm() (UTF String)
+ * kdfSalt.length (int)
+ * kdfIterations (int)
+ * kdfSalt (byte[])
+ * iv.length (int)
+ * iv (byte[])
+ * encryptedKey.length (int)
+ * encryptedKey (byte[])
+ * hmac.length (int)
+ * hmac (HMAC-SHA512) (byte[])
+ * [WKSCertificate]
+ * entryId (int / 2)
+ * alias (UTF String)
+ * creationDate.getTime() (long)
+ * cert.getType() (UTF String)
+ * cert.getEncoded().length (int)
+ * cert.getEncoced() (byte[])
+ * HMAC PBKDF2 salt length int
+ * HMAC PBKDF2 salt (byte[])
+ * HMAC PBKDF2 iterations int
+ * HMAC length int
+ * HMAC (HMAC-SHA512) (byte[])
+```
+
+## PrivateKey Protection
+
+A PrivateKey entry is stored into the KeyStore with the `engineSetKeyEntry()`
+method, exposed publicly through `KeyStore` as `setKeyEntry()`, when
+passing in a `Key` of type `PrivateKey`. The password argument is not allowed
+to be null, otherwise a KeyStoreException is thrown.
+
+```
+void setKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
+```
+
+Process of storing a PrivateKey is as follows:
+- Sanity check the certificate chain:
+ + Chain is not null or zero length chain
+ + Chain is made up of X509Certificate objects
+ + Chain cert signatures are correct as we walk up the chain. The cert
+ chain should be ordered from leaf cert (entity) to top-most intermedate
+ certificate. The last cert is loaded as a trusted root, and used to
+ verify the rest of the chain, since we don't have the root CA cert
+ available at this point.
+- Verify private key (`Key key`) matches the leaf certificate (`chain[0]`)
+- Encrypt private key before storing into KeyStore map:
+ + Generate random PBKDF2 salt, of size `WKS_PBKDF2_SALT_SIZE` bytes
+ + Generate random IV, of size `WKS_ENC_IV_LENGTH` bytes
+ + Convert password from char[] into byte[] using password conversion
+ algorithm described below.
+ + Encryption key is derived using PBKDF2-SHA256 using byte array, random
+ salt, and `WKS_PBKDF2_ITERATION_COUNT` (or customized) iteration count.
+ - 96-byte key is generated with PBKDF2 in total, split between 32-byte
+ AES-CBC-256 and 64-byte HMAC-SHA512 keys.
+ + Encrypt key bytes using AES-CBC-256
+ + Generate HMAC-SHA512 over encrypted key and other WKSPrivateKey
+ object members
+ + Zeroize KEK and HMAC keys (generated from PBKDF2)
+
+When importing a PrivateKey from a KeyStore stream, the process is reversed.
+Initially during `engineLoad()`, parameters are read in as well as the encrypted
+key:
+- Read PBKDF2 salt length, sanity check against `WKS_PBKDF2_SALT_SIZE`
+- Read PBKDF2 salt
+- Read PBKDF2 iterations, sanity check against `WKS_PBKDF2_MIN_ITERATIONS`
+- Read encryption IV, santiy check against `WKS_ENC_IV_LENGTH`
+- Read encrypted key
+- Read certificate chain if present, check length against `WKS_MAX_CHAIN_COUNT`
+- Read HMAC value into object variable, will be checked when user gets key out
+
+The PrivateKey is stored encrypted internal to the WolfSSLKeyStore until
+a caller retrieves it with `getKey()`. At that point, WolfSSLKeyStore:
+- Derives the decryption key using PBKDF2-SHA256
+ + Converts password from `char[]` to `byte[]` using algorithm below
+ + Uses salt and iteration count stored internally from encryption
+ process or read from KeyStore stream after loading
+ + Derives decryption key and HMAC key with PBKDF2-HMAC-SHA512
+ + Regenerate and verify HMAC against stored value
+ + Decrypts key using AES-CBC-256
+ + Zeroizes KEK and HMAC keys (generated from PBKDF2)
+
+## SecretKey Protection
+
+A SecretKey entry is stored into the KeyStore with the `engineSetKeyEntry()`
+method, exposed publicly through `KeyStore` as `setKeyEntry()`, when
+passing in a `Key` of type `SecretKey`. The password argument is not allowed
+to be null, otherwise a KeyStoreException is thrown.
+
+```
+void setKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
+```
+
+Process of storing a SecretKey is the same as PrivateKey above, except
+there is no certificate so no certifiate or private key sanity checks are done.
+The same encrypt/decrypt process is shared between PrivateKey and SecretKey
+protection.
+
+## Certificate Protection
+
+A Certificate entry is stored into the KeyStore with the
+`engineSetCertificateEntry()` method. Certificate entries are not protected
+and are stored directly into the KeyStore.
+
+They are integrity protected by the KeyStore HMAC when a KeyStore is written
+out to a stream with `engineStore()`, but otherwise have no internal
+encryption or integrity protection since no password is provided when storing
+certificates.
+
+## Password Conversion Algorithm
+
+The Java KeyStore class specifies that passwords are provided by the user as a
+Java character array (`char[]`). Before using a password as input to PBKDF2,
+wolfJCE is converts it into a byte array. In Java, one character (`char`) is
+composed of two bytes (`byte`). RFC 2898 (PBKDF2) considers a password to be an
+octet string and recommends for interop ASCII or UTF-8 encoding be used. SunJCE
+uses UTF-8 for PBKDF2 SecretKeyFactory, so we do the same in WolfSSLKeyStore
+using `WolfCryptSecretKeyFactory.passwordToByteArray(char[])`.
+
+# Support
+
+Please email support@wolfssl.com with any questions.
+
diff --git a/examples/certs/README.md b/examples/certs/README.md
index 6fea89e..0d96f8a 100644
--- a/examples/certs/README.md
+++ b/examples/certs/README.md
@@ -26,18 +26,28 @@ the example Java KeyStore files, see the next section.
## Updating Example Java KeyStore Files
-To update the example Java KeyStore files, use the provided `update-jks.sh`
+To update the example Java KeyStore files, use the provided `update-jks-wks.sh`
bash script. This script requires one argument on the command line which is
the location of the wolfSSL proper certs directory.
+This script will create new KeyStore files from original certificates. It will
+first create JKS KeyStore files, then convert those to WKS (WolfSSLKeyStore)
+format.
+
```
$ cd wolfcryptjni/examples/certs
-$ ./update-jks.sh /path/to/wolfssl/certs
+$ ./update-jks-wks.sh /path/to/wolfssl/certs
```
-This script only updates the example .jks files and not the individual
+This script only updates the example .jks and .wks files and not the individual
.pem or .der files in this directory. For that, please see the above section.
+## Testing that Java keytool can read/parse WKS files
+
+To confirm that Java keytool can parse WolfSSLKeyStore (WKS) format stores OK,
+the `keytool-print-wks.sh` script can be used. This will call `keytool -list`
+on each WKS KeyStore which is expected to pass successfully.
+
## Support
Please contact the wolfSSL support team at support@wolfssl.com with any
diff --git a/examples/certs/ca-client.jks b/examples/certs/ca-client.jks
index e014ee3..3ab5459 100644
Binary files a/examples/certs/ca-client.jks and b/examples/certs/ca-client.jks differ
diff --git a/examples/certs/ca-client.wks b/examples/certs/ca-client.wks
new file mode 100644
index 0000000..7ac30cd
Binary files /dev/null and b/examples/certs/ca-client.wks differ
diff --git a/examples/certs/ca-server-ecc-256.jks b/examples/certs/ca-server-ecc-256.jks
index 480cc29..7ef145f 100644
Binary files a/examples/certs/ca-server-ecc-256.jks and b/examples/certs/ca-server-ecc-256.jks differ
diff --git a/examples/certs/ca-server-ecc-256.wks b/examples/certs/ca-server-ecc-256.wks
new file mode 100644
index 0000000..7b583fe
Binary files /dev/null and b/examples/certs/ca-server-ecc-256.wks differ
diff --git a/examples/certs/ca-server-rsa-2048.jks b/examples/certs/ca-server-rsa-2048.jks
index 9d7205b..675dec6 100644
Binary files a/examples/certs/ca-server-rsa-2048.jks and b/examples/certs/ca-server-rsa-2048.jks differ
diff --git a/examples/certs/ca-server-rsa-2048.wks b/examples/certs/ca-server-rsa-2048.wks
new file mode 100644
index 0000000..2b376cc
Binary files /dev/null and b/examples/certs/ca-server-rsa-2048.wks differ
diff --git a/examples/certs/ca-server.jks b/examples/certs/ca-server.jks
index 31b28e2..04aaaa7 100644
Binary files a/examples/certs/ca-server.jks and b/examples/certs/ca-server.jks differ
diff --git a/examples/certs/ca-server.wks b/examples/certs/ca-server.wks
new file mode 100644
index 0000000..9913222
Binary files /dev/null and b/examples/certs/ca-server.wks differ
diff --git a/examples/certs/cacerts.jks b/examples/certs/cacerts.jks
index a1f72ba..85ad0ba 100644
Binary files a/examples/certs/cacerts.jks and b/examples/certs/cacerts.jks differ
diff --git a/examples/certs/cacerts.wks b/examples/certs/cacerts.wks
new file mode 100644
index 0000000..caf0bbb
Binary files /dev/null and b/examples/certs/cacerts.wks differ
diff --git a/examples/certs/client-ecc.jks b/examples/certs/client-ecc.jks
index 7326d8a..bbb83b8 100644
Binary files a/examples/certs/client-ecc.jks and b/examples/certs/client-ecc.jks differ
diff --git a/examples/certs/client-ecc.wks b/examples/certs/client-ecc.wks
new file mode 100644
index 0000000..22c8dcd
Binary files /dev/null and b/examples/certs/client-ecc.wks differ
diff --git a/examples/certs/client-rsa-1024.jks b/examples/certs/client-rsa-1024.jks
index 884078b..f4d1c7f 100644
Binary files a/examples/certs/client-rsa-1024.jks and b/examples/certs/client-rsa-1024.jks differ
diff --git a/examples/certs/client-rsa-1024.wks b/examples/certs/client-rsa-1024.wks
new file mode 100644
index 0000000..5c7c6b3
Binary files /dev/null and b/examples/certs/client-rsa-1024.wks differ
diff --git a/examples/certs/client-rsa.jks b/examples/certs/client-rsa.jks
index cf3162f..b0a7f57 100644
Binary files a/examples/certs/client-rsa.jks and b/examples/certs/client-rsa.jks differ
diff --git a/examples/certs/client-rsa.wks b/examples/certs/client-rsa.wks
new file mode 100644
index 0000000..381c708
Binary files /dev/null and b/examples/certs/client-rsa.wks differ
diff --git a/examples/certs/client.jks b/examples/certs/client.jks
index 46803c0..d9ab90d 100644
Binary files a/examples/certs/client.jks and b/examples/certs/client.jks differ
diff --git a/examples/certs/client.wks b/examples/certs/client.wks
new file mode 100644
index 0000000..db0befb
Binary files /dev/null and b/examples/certs/client.wks differ
diff --git a/examples/certs/crl/cliCrl.pem b/examples/certs/crl/cliCrl.pem
index 00c4853..e20203e 100644
--- a/examples/certs/crl/cliCrl.pem
+++ b/examples/certs/crl/cliCrl.pem
@@ -2,41 +2,41 @@ Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, ST = Montana, L = Bozeman, O = wolfSSL_2048, OU = Programming-2048, CN = www.wolfssl.com, emailAddress = info@wolfssl.com
- Last Update: Dec 13 22:19:33 2023 GMT
- Next Update: Sep 8 22:19:33 2026 GMT
+ Last Update: Jan 9 00:34:30 2024 GMT
+ Next Update: Oct 5 00:34:30 2026 GMT
CRL extensions:
X509v3 CRL Number:
8
Revoked Certificates:
Serial Number: 02
- Revocation Date: Dec 13 22:19:33 2023 GMT
+ Revocation Date: Jan 9 00:34:30 2024 GMT
Signature Algorithm: sha256WithRSAEncryption
- 74:17:9b:40:81:d2:a0:f3:26:68:44:5b:f8:a2:6c:3f:7e:71:
- 75:a2:7f:c6:e6:71:cb:f9:08:57:42:cd:3e:3f:ab:cd:0c:85:
- 36:45:58:8b:59:28:81:d9:b0:6b:10:4a:d0:7d:59:ad:cf:53:
- 05:cb:13:c7:c1:ec:65:64:6b:4d:e6:87:0b:ae:06:60:ab:8a:
- 3c:ae:c1:7d:ed:8f:ee:09:02:7a:3a:f2:21:bf:89:ef:cd:14:
- b1:03:64:2d:b2:b6:45:15:da:2d:ee:2d:c0:15:3b:a8:01:a8:
- 4f:30:61:ae:99:b9:16:07:b5:8b:71:8f:38:ac:69:82:39:90:
- 92:ff:d6:41:33:3b:92:5b:f2:dd:56:5a:8f:82:d1:1f:76:ee:
- ca:01:a2:ac:c0:22:41:dd:6e:e1:ce:06:b0:6f:bc:e2:da:91:
- 11:c1:a0:41:16:7d:ba:7e:a1:53:13:14:4b:54:3b:b9:44:cf:
- 4f:1c:ef:ce:a8:bd:e8:ab:ba:de:97:f7:b7:7d:4f:ab:7a:e7:
- 73:65:97:a1:d9:a3:f3:92:f1:95:06:6d:52:7b:6e:fd:26:56:
- 55:83:c7:71:f7:a4:8f:9a:2c:52:04:dd:9f:85:ab:9c:88:e1:
- 30:c6:4a:88:7d:20:1b:c6:47:8b:82:cc:9d:0f:51:69:b1:90:
- b2:8a:9c:74
+ 52:11:97:57:04:d7:e2:14:1f:c4:7f:a2:d8:cf:4c:b7:5b:0c:
+ d3:ac:ca:29:10:74:09:2f:3d:fb:4d:75:3e:32:21:5a:0f:41:
+ 5f:cc:e7:98:f8:ea:8e:e2:c9:57:60:b6:a3:b0:70:10:18:b9:
+ 86:a3:65:1e:3a:88:13:df:44:18:15:51:00:f6:33:d6:ab:90:
+ 18:93:df:ac:7d:15:5c:6a:63:55:d1:4d:41:37:03:89:86:65:
+ fa:fb:d7:b1:73:db:c3:43:08:ff:89:94:89:b1:b4:ad:96:78:
+ 52:92:50:8c:0a:5d:ca:29:8b:e0:bc:ca:88:c0:7a:52:48:d3:
+ cf:09:03:08:5f:a1:b9:16:b0:55:5e:11:60:7f:73:9a:98:05:
+ 54:97:bf:eb:0e:04:61:4f:b4:40:23:61:9a:07:69:78:fc:16:
+ de:f4:54:04:cf:f0:2b:07:8d:51:9e:6b:b5:77:c4:13:2c:a3:
+ 40:99:ed:fa:f4:00:4a:45:36:da:52:9d:dc:88:66:3e:03:f0:
+ 20:ce:54:a4:56:58:a8:9e:30:78:e8:42:2d:a8:0f:9b:c4:a9:
+ ab:13:c2:4e:ec:be:2e:99:16:56:2f:22:86:96:27:1d:30:80:
+ 7d:a5:f8:45:ef:93:b4:63:13:96:4f:6a:df:a0:11:3b:52:be:
+ 93:03:7a:81
-----BEGIN X509 CRL-----
MIICDjCB9wIBATANBgkqhkiG9w0BAQsFADCBnjELMAkGA1UEBhMCVVMxEDAOBgNV
BAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xFTATBgNVBAoMDHdvbGZTU0xf
MjA0ODEZMBcGA1UECwwQUHJvZ3JhbW1pbmctMjA0ODEYMBYGA1UEAwwPd3d3Lndv
-bGZzc2wuY29tMR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29tFw0yMzEy
-MTMyMjE5MzNaFw0yNjA5MDgyMjE5MzNaMBQwEgIBAhcNMjMxMjEzMjIxOTMzWqAO
-MAwwCgYDVR0UBAMCAQgwDQYJKoZIhvcNAQELBQADggEBAHQXm0CB0qDzJmhEW/ii
-bD9+cXWif8bmccv5CFdCzT4/q80MhTZFWItZKIHZsGsQStB9Wa3PUwXLE8fB7GVk
-a03mhwuuBmCrijyuwX3tj+4JAno68iG/ie/NFLEDZC2ytkUV2i3uLcAVO6gBqE8w
-Ya6ZuRYHtYtxjzisaYI5kJL/1kEzO5Jb8t1WWo+C0R927soBoqzAIkHdbuHOBrBv
-vOLakRHBoEEWfbp+oVMTFEtUO7lEz08c786oveirut6X97d9T6t653Nll6HZo/OS
-8ZUGbVJ7bv0mVlWDx3H3pI+aLFIE3Z+Fq5yI4TDGSoh9IBvGR4uCzJ0PUWmxkLKK
-nHQ=
+bGZzc2wuY29tMR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29tFw0yNDAx
+MDkwMDM0MzBaFw0yNjEwMDUwMDM0MzBaMBQwEgIBAhcNMjQwMTA5MDAzNDMwWqAO
+MAwwCgYDVR0UBAMCAQgwDQYJKoZIhvcNAQELBQADggEBAFIRl1cE1+IUH8R/otjP
+TLdbDNOsyikQdAkvPftNdT4yIVoPQV/M55j46o7iyVdgtqOwcBAYuYajZR46iBPf
+RBgVUQD2M9arkBiT36x9FVxqY1XRTUE3A4mGZfr717Fz28NDCP+JlImxtK2WeFKS
+UIwKXcopi+C8yojAelJI088JAwhfobkWsFVeEWB/c5qYBVSXv+sOBGFPtEAjYZoH
+aXj8Ft70VATP8CsHjVGea7V3xBMso0CZ7fr0AEpFNtpSndyIZj4D8CDOVKRWWKie
+MHjoQi2oD5vEqasTwk7svi6ZFlYvIoaWJx0wgH2l+EXvk7RjE5ZPat+gETtSvpMD
+eoE=
-----END X509 CRL-----
diff --git a/examples/certs/crl/crl.der b/examples/certs/crl/crl.der
index c6ec65c..7ce490c 100644
Binary files a/examples/certs/crl/crl.der and b/examples/certs/crl/crl.der differ
diff --git a/examples/certs/crl/crl.pem b/examples/certs/crl/crl.pem
index a4a09f0..5831287 100644
--- a/examples/certs/crl/crl.pem
+++ b/examples/certs/crl/crl.pem
@@ -2,40 +2,40 @@ Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = info@wolfssl.com
- Last Update: Dec 13 22:19:33 2023 GMT
- Next Update: Sep 8 22:19:33 2026 GMT
+ Last Update: Jan 9 00:34:30 2024 GMT
+ Next Update: Oct 5 00:34:30 2026 GMT
CRL extensions:
X509v3 CRL Number:
2
Revoked Certificates:
Serial Number: 02
- Revocation Date: Dec 13 22:19:33 2023 GMT
+ Revocation Date: Jan 9 00:34:30 2024 GMT
Signature Algorithm: sha256WithRSAEncryption
- 48:36:98:18:42:9c:0c:81:51:19:75:4b:26:9a:e0:07:18:89:
- a2:a1:bd:b6:4e:91:f2:44:93:1a:50:a1:8f:72:1f:c4:ae:99:
- 81:c5:00:3a:94:03:de:00:24:98:d4:2c:17:e5:ba:f2:29:3a:
- 43:c8:23:ba:73:6a:5c:99:5d:ba:80:dd:bd:4f:cd:53:a6:cf:
- 33:11:31:30:27:e2:d2:31:06:65:b8:3e:cf:fe:00:21:ff:0d:
- 18:4f:fc:fd:d5:80:75:72:7c:2e:44:c1:a1:26:a6:8a:88:c8:
- c0:66:1a:d4:99:36:ca:8f:67:42:8f:7c:f2:1a:e7:1b:d0:90:
- 05:22:0d:29:d3:35:57:23:8c:bb:d2:53:c1:a8:00:3c:d4:b3:
- 97:23:8a:4f:1d:8b:c9:73:6a:96:40:b0:a4:b1:c7:de:06:4d:
- a3:5d:6a:d2:f5:5c:1e:f0:21:0f:d1:fd:21:89:e2:9e:3d:c1:
- b2:f0:0f:5e:79:1e:47:48:92:bf:eb:96:28:ad:0b:89:5e:3b:
- ed:97:29:bb:8d:24:c2:e6:26:e5:33:ef:88:17:c1:1a:97:fa:
- 51:44:a2:cc:b2:64:e5:5c:94:54:ed:3b:7d:8f:34:4a:4b:d3:
- ca:62:f9:20:00:86:26:ea:1b:a9:b4:df:8f:f4:4d:d8:3e:95:
- aa:3b:43:1c
+ b3:6f:ed:72:d2:73:6a:77:bf:3a:55:bc:54:18:6a:71:bc:6a:
+ cc:cd:5d:90:f5:64:8d:1b:f0:e0:48:7b:f2:7b:06:86:53:63:
+ 9b:d8:24:15:10:b1:19:96:9b:d2:75:a8:25:a2:35:a9:14:d6:
+ d5:5e:53:e3:34:9d:f2:8b:07:19:9b:1f:f1:02:0f:04:46:e8:
+ b8:b6:f2:8d:c7:c0:15:3e:3e:8e:96:73:15:1e:62:f6:4e:2a:
+ f7:aa:a0:91:80:12:7f:81:0c:65:cc:38:be:58:6c:14:a5:21:
+ a1:8d:f7:8a:b9:24:f4:2d:ca:c0:67:43:0b:c8:1c:b4:7d:12:
+ 7f:a2:1b:19:0e:94:cf:7b:9f:75:a0:08:9a:67:3f:87:89:3e:
+ f8:58:a5:8a:1b:2d:da:9b:d0:1b:18:92:c3:d2:6a:d7:1c:fc:
+ 45:69:77:c3:57:65:75:99:9e:47:2a:20:25:ef:90:f2:5f:3b:
+ 7d:9c:7d:00:ea:92:54:eb:0b:e7:17:af:24:1a:f9:7c:83:50:
+ 68:1d:dc:5b:60:12:a7:52:78:d9:a9:b0:1f:59:48:36:c7:a6:
+ 97:34:c7:87:3f:ae:fd:a9:56:5d:48:cc:89:7a:79:60:8f:9b:
+ 2b:63:3c:b3:04:1d:5f:f7:20:d2:fd:f2:51:b1:96:93:13:5b:
+ ab:74:82:8b
-----BEGIN X509 CRL-----
MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBlDELMAkGA1UEBhMCVVMxEDAOBgNV
BAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoMCFNhd3Rvb3Ro
MRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20x
-HzAdBgkqhkiG9w0BCQEWEGluZm9Ad29sZnNzbC5jb20XDTIzMTIxMzIyMTkzM1oX
-DTI2MDkwODIyMTkzM1owFDASAgECFw0yMzEyMTMyMjE5MzNaoA4wDDAKBgNVHRQE
-AwIBAjANBgkqhkiG9w0BAQsFAAOCAQEASDaYGEKcDIFRGXVLJprgBxiJoqG9tk6R
-8kSTGlChj3IfxK6ZgcUAOpQD3gAkmNQsF+W68ik6Q8gjunNqXJlduoDdvU/NU6bP
-MxExMCfi0jEGZbg+z/4AIf8NGE/8/dWAdXJ8LkTBoSamiojIwGYa1Jk2yo9nQo98
-8hrnG9CQBSINKdM1VyOMu9JTwagAPNSzlyOKTx2LyXNqlkCwpLHH3gZNo11q0vVc
-HvAhD9H9IYninj3BsvAPXnkeR0iSv+uWKK0LiV477Zcpu40kwuYm5TPviBfBGpf6
-UUSizLJk5VyUVO07fY80SkvTymL5IACGJuobqbTfj/RN2D6VqjtDHA==
+HzAdBgkqhkiG9w0BCQEWEGluZm9Ad29sZnNzbC5jb20XDTI0MDEwOTAwMzQzMFoX
+DTI2MTAwNTAwMzQzMFowFDASAgECFw0yNDAxMDkwMDM0MzBaoA4wDDAKBgNVHRQE
+AwIBAjANBgkqhkiG9w0BAQsFAAOCAQEAs2/tctJzane/OlW8VBhqcbxqzM1dkPVk
+jRvw4Eh78nsGhlNjm9gkFRCxGZab0nWoJaI1qRTW1V5T4zSd8osHGZsf8QIPBEbo
+uLbyjcfAFT4+jpZzFR5i9k4q96qgkYASf4EMZcw4vlhsFKUhoY33irkk9C3KwGdD
+C8gctH0Sf6IbGQ6Uz3ufdaAImmc/h4k++Filihst2pvQGxiSw9Jq1xz8RWl3w1dl
+dZmeRyogJe+Q8l87fZx9AOqSVOsL5xevJBr5fINQaB3cW2ASp1J42amwH1lINsem
+lzTHhz+u/alWXUjMiXp5YI+bK2M8swQdX/cg0v3yUbGWkxNbq3SCiw==
-----END X509 CRL-----
diff --git a/examples/certs/crl/crl.revoked b/examples/certs/crl/crl.revoked
index 7325261..9dfc67f 100644
--- a/examples/certs/crl/crl.revoked
+++ b/examples/certs/crl/crl.revoked
@@ -2,43 +2,43 @@ Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = info@wolfssl.com
- Last Update: Dec 13 22:19:33 2023 GMT
- Next Update: Sep 8 22:19:33 2026 GMT
+ Last Update: Jan 9 00:34:30 2024 GMT
+ Next Update: Oct 5 00:34:30 2026 GMT
CRL extensions:
X509v3 CRL Number:
3
Revoked Certificates:
Serial Number: 01
- Revocation Date: Dec 13 22:19:33 2023 GMT
+ Revocation Date: Jan 9 00:34:30 2024 GMT
Serial Number: 02
- Revocation Date: Dec 13 22:19:33 2023 GMT
+ Revocation Date: Jan 9 00:34:30 2024 GMT
Signature Algorithm: sha256WithRSAEncryption
- 72:6e:a4:64:36:6b:e8:e0:c5:1d:98:ef:ab:7e:7a:14:f2:8d:
- 99:d0:57:4b:76:ac:f4:89:60:cd:89:23:9d:01:34:f3:83:e5:
- 82:21:b3:48:c4:42:25:7f:ea:9f:74:5f:e8:b8:d6:71:bb:a2:
- 39:d8:ef:46:a8:13:ba:7d:44:ab:d6:13:65:18:de:b5:03:85:
- a7:c6:4f:0a:a0:6a:78:ba:7b:f7:ce:6e:ba:1c:ef:6f:b1:04:
- a8:ac:c6:de:3b:76:77:3e:3d:8b:ae:8b:2b:7e:c9:4f:77:31:
- 7f:1f:f5:04:2c:e9:cf:a1:56:c2:59:e9:be:49:9f:e8:67:a3:
- 42:66:05:21:02:64:82:b2:74:a7:4b:89:89:7d:43:1a:41:fd:
- 53:8c:d6:4f:27:04:2a:48:6b:9e:62:fa:4a:42:83:22:53:3f:
- 53:07:4f:bc:cd:8d:8d:cc:15:c6:ff:3c:af:7d:db:ab:dd:fa:
- 8f:65:86:86:2a:89:5e:3f:d5:4b:39:80:78:3f:6e:38:3b:6d:
- a5:5e:2c:9e:1d:2f:9c:62:12:b1:34:f2:95:64:37:dc:4b:20:
- dc:27:f3:de:81:67:b2:04:b0:14:b9:47:e3:65:e3:2f:35:27:
- c2:fc:22:db:24:bd:04:58:88:17:e3:42:3c:a5:ef:53:39:15:
- 54:52:ac:a1
+ 35:50:96:da:71:71:90:d5:b7:37:5a:a6:b9:09:07:2f:af:c9:
+ e0:02:32:6a:43:6e:20:ec:20:a4:ac:d0:39:a9:19:35:d0:d2:
+ 6f:bb:d1:cd:46:10:a7:cb:8a:be:0a:02:a2:91:f5:29:74:ee:
+ 34:83:a3:8c:a0:ca:39:af:94:4a:23:d7:56:57:6b:cc:c6:eb:
+ b0:ce:9f:0a:e1:b0:a8:12:6b:6a:8b:21:73:22:6f:49:41:cd:
+ fd:85:44:d1:fa:52:6b:2f:b2:2b:02:e7:43:0e:f1:92:bc:15:
+ 8f:22:28:49:25:69:93:d8:50:10:2f:93:e2:f5:b0:31:5c:eb:
+ 1a:35:e2:40:83:25:87:55:4d:c0:85:06:37:9e:23:44:80:a1:
+ f9:e2:eb:9c:90:28:7a:71:d8:55:a2:8b:70:32:31:33:26:70:
+ fe:1d:11:d5:4b:c1:04:47:19:59:44:8f:0b:0a:ec:d6:62:40:
+ 8a:6f:67:2e:6a:50:38:54:35:c9:f8:d5:ec:e8:ae:93:88:3d:
+ a0:40:81:2c:e0:fe:f7:c8:68:24:8e:41:04:88:af:94:82:97:
+ 75:e5:69:4c:22:1d:f9:67:53:a3:4c:a3:db:bf:55:08:e7:3a:
+ 07:67:a2:28:25:63:af:f8:0e:c7:d3:c1:77:ef:20:20:20:63:
+ 9e:5c:22:81
-----BEGIN X509 CRL-----
MIICGTCCAQECAQEwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVTMRAwDgYD
VQQIDAdNb250YW5hMRAwDgYDVQQHDAdCb3plbWFuMREwDwYDVQQKDAhTYXd0b290
aDETMBEGA1UECwwKQ29uc3VsdGluZzEYMBYGA1UEAwwPd3d3LndvbGZzc2wuY29t
-MR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29tFw0yMzEyMTMyMjE5MzNa
-Fw0yNjA5MDgyMjE5MzNaMCgwEgIBARcNMjMxMjEzMjIxOTMzWjASAgECFw0yMzEy
-MTMyMjE5MzNaoA4wDDAKBgNVHRQEAwIBAzANBgkqhkiG9w0BAQsFAAOCAQEAcm6k
-ZDZr6ODFHZjvq356FPKNmdBXS3as9IlgzYkjnQE084PlgiGzSMRCJX/qn3Rf6LjW
-cbuiOdjvRqgTun1Eq9YTZRjetQOFp8ZPCqBqeLp7985uuhzvb7EEqKzG3jt2dz49
-i66LK37JT3cxfx/1BCzpz6FWwlnpvkmf6GejQmYFIQJkgrJ0p0uJiX1DGkH9U4zW
-TycEKkhrnmL6SkKDIlM/UwdPvM2NjcwVxv88r33bq936j2WGhiqJXj/VSzmAeD9u
-ODttpV4snh0vnGISsTTylWQ33Esg3Cfz3oFnsgSwFLlH42XjLzUnwvwi2yS9BFiI
-F+NCPKXvUzkVVFKsoQ==
+MR8wHQYJKoZIhvcNAQkBFhBpbmZvQHdvbGZzc2wuY29tFw0yNDAxMDkwMDM0MzBa
+Fw0yNjEwMDUwMDM0MzBaMCgwEgIBARcNMjQwMTA5MDAzNDMwWjASAgECFw0yNDAx
+MDkwMDM0MzBaoA4wDDAKBgNVHRQEAwIBAzANBgkqhkiG9w0BAQsFAAOCAQEANVCW
+2nFxkNW3N1qmuQkHL6/J4AIyakNuIOwgpKzQOakZNdDSb7vRzUYQp8uKvgoCopH1
+KXTuNIOjjKDKOa+USiPXVldrzMbrsM6fCuGwqBJraoshcyJvSUHN/YVE0fpSay+y
+KwLnQw7xkrwVjyIoSSVpk9hQEC+T4vWwMVzrGjXiQIMlh1VNwIUGN54jRICh+eLr
+nJAoenHYVaKLcDIxMyZw/h0R1UvBBEcZWUSPCwrs1mJAim9nLmpQOFQ1yfjV7Oiu
+k4g9oECBLOD+98hoJI5BBIivlIKXdeVpTCId+WdTo0yj279VCOc6B2eiKCVjr/gO
+x9PBd+8gICBjnlwigQ==
-----END X509 CRL-----
diff --git a/examples/certs/crl/eccCliCRL.pem b/examples/certs/crl/eccCliCRL.pem
index 86fa84a..f7a8c17 100644
--- a/examples/certs/crl/eccCliCRL.pem
+++ b/examples/certs/crl/eccCliCRL.pem
@@ -2,25 +2,25 @@ Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = US, ST = Oregon, L = Salem, O = Client ECC, OU = Fast, CN = www.wolfssl.com, emailAddress = info@wolfssl.com
- Last Update: Dec 13 22:19:33 2023 GMT
- Next Update: Sep 8 22:19:33 2026 GMT
+ Last Update: Jan 9 00:34:30 2024 GMT
+ Next Update: Oct 5 00:34:30 2026 GMT
CRL extensions:
X509v3 CRL Number:
9
Revoked Certificates:
Serial Number: 02
- Revocation Date: Dec 13 22:19:33 2023 GMT
+ Revocation Date: Jan 9 00:34:30 2024 GMT
Signature Algorithm: ecdsa-with-SHA256
- 30:45:02:21:00:dc:a7:bf:34:1b:68:b6:54:0c:38:8d:46:41:
- 84:bf:fa:f0:96:00:89:a6:81:4a:0f:15:12:ef:15:98:f7:51:
- 95:02:20:08:57:33:0d:c1:a5:c6:83:63:49:96:8c:71:41:7b:
- 40:92:67:80:d6:23:62:2a:c2:f2:43:5a:92:9b:9b:d6:83
+ 30:45:02:20:3b:07:f1:6c:fb:19:62:f2:56:2a:5c:21:a3:7d:
+ bf:06:33:3e:b4:53:01:f3:f5:0e:e6:ca:f5:b9:26:7e:4d:ca:
+ 02:21:00:dd:04:d6:b1:18:01:b7:d6:ca:d9:7b:29:53:cf:9e:
+ ad:38:ef:fa:70:2c:41:74:ba:ce:e6:77:1f:22:86:f0:e3
-----BEGIN X509 CRL-----
MIIBPDCB4wIBATAKBggqhkjOPQQDAjCBjTELMAkGA1UEBhMCVVMxDzANBgNVBAgM
Bk9yZWdvbjEOMAwGA1UEBwwFU2FsZW0xEzARBgNVBAoMCkNsaWVudCBFQ0MxDTAL
BgNVBAsMBEZhc3QxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTEfMB0GCSqGSIb3
-DQEJARYQaW5mb0B3b2xmc3NsLmNvbRcNMjMxMjEzMjIxOTMzWhcNMjYwOTA4MjIx
-OTMzWjAUMBICAQIXDTIzMTIxMzIyMTkzM1qgDjAMMAoGA1UdFAQDAgEJMAoGCCqG
-SM49BAMCA0gAMEUCIQDcp780G2i2VAw4jUZBhL/68JYAiaaBSg8VEu8VmPdRlQIg
-CFczDcGlxoNjSZaMcUF7QJJngNYjYirC8kNakpub1oM=
+DQEJARYQaW5mb0B3b2xmc3NsLmNvbRcNMjQwMTA5MDAzNDMwWhcNMjYxMDA1MDAz
+NDMwWjAUMBICAQIXDTI0MDEwOTAwMzQzMFqgDjAMMAoGA1UdFAQDAgEJMAoGCCqG
+SM49BAMCA0gAMEUCIDsH8Wz7GWLyVipcIaN9vwYzPrRTAfP1DubK9bkmfk3KAiEA
+3QTWsRgBt9bK2XspU8+erTjv+nAsQXS6zuZ3HyKG8OM=
-----END X509 CRL-----
diff --git a/examples/certs/crl/eccSrvCRL.pem b/examples/certs/crl/eccSrvCRL.pem
index 8cd5091..2b6bc71 100644
--- a/examples/certs/crl/eccSrvCRL.pem
+++ b/examples/certs/crl/eccSrvCRL.pem
@@ -2,25 +2,25 @@ Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = US, ST = Washington, L = Seattle, O = Elliptic, OU = ECC, CN = www.wolfssl.com, emailAddress = info@wolfssl.com
- Last Update: Dec 13 22:19:33 2023 GMT
- Next Update: Sep 8 22:19:33 2026 GMT
+ Last Update: Jan 9 00:34:30 2024 GMT
+ Next Update: Oct 5 00:34:30 2026 GMT
CRL extensions:
X509v3 CRL Number:
10
Revoked Certificates:
Serial Number: 02
- Revocation Date: Dec 13 22:19:33 2023 GMT
+ Revocation Date: Jan 9 00:34:30 2024 GMT
Signature Algorithm: ecdsa-with-SHA256
- 30:45:02:21:00:a9:26:ab:1a:4a:be:5c:92:da:9d:17:0a:b5:
- f6:40:ea:84:93:ce:57:b8:af:68:75:e8:e9:de:a7:27:e7:79:
- 48:02:20:11:d4:03:97:19:2a:28:04:70:28:bb:5e:6a:b7:f6:
- 32:90:f1:92:ff:48:7c:cf:e7:94:0f:ce:63:de:f8:fc:6c
+ 30:45:02:20:4e:83:3e:21:ee:69:a6:f2:7e:87:45:10:5c:60:
+ ad:24:49:1e:0f:9e:1f:81:03:00:43:a9:e6:1b:63:27:3f:6b:
+ 02:21:00:b2:7f:bd:3d:af:c4:f5:ff:82:3f:b7:6a:56:25:7c:
+ 07:85:54:d9:19:44:42:60:b4:8a:e3:55:f4:a4:96:c7:d1
-----BEGIN X509 CRL-----
MIIBPzCB5gIBATAKBggqhkjOPQQDAjCBkDELMAkGA1UEBhMCVVMxEzARBgNVBAgM
Cldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxETAPBgNVBAoMCEVsbGlwdGlj
MQwwCgYDVQQLDANFQ0MxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTEfMB0GCSqG
-SIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbRcNMjMxMjEzMjIxOTMzWhcNMjYwOTA4
-MjIxOTMzWjAUMBICAQIXDTIzMTIxMzIyMTkzM1qgDjAMMAoGA1UdFAQDAgEKMAoG
-CCqGSM49BAMCA0gAMEUCIQCpJqsaSr5cktqdFwq19kDqhJPOV7ivaHXo6d6nJ+d5
-SAIgEdQDlxkqKARwKLtearf2MpDxkv9IfM/nlA/OY974/Gw=
+SIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbRcNMjQwMTA5MDAzNDMwWhcNMjYxMDA1
+MDAzNDMwWjAUMBICAQIXDTI0MDEwOTAwMzQzMFqgDjAMMAoGA1UdFAQDAgEKMAoG
+CCqGSM49BAMCA0gAMEUCIE6DPiHuaabyfodFEFxgrSRJHg+eH4EDAEOp5htjJz9r
+AiEAsn+9Pa/E9f+CP7dqViV8B4VU2RlEQmC0iuNV9KSWx9E=
-----END X509 CRL-----
diff --git a/examples/certs/ecc-keyPkcs8.der b/examples/certs/ecc-keyPkcs8.der
new file mode 100644
index 0000000..71034c5
Binary files /dev/null and b/examples/certs/ecc-keyPkcs8.der differ
diff --git a/examples/certs/keytool-print-wks.sh b/examples/certs/keytool-print-wks.sh
new file mode 100755
index 0000000..a72188f
--- /dev/null
+++ b/examples/certs/keytool-print-wks.sh
@@ -0,0 +1,36 @@
+
+# Script to print out WKS keystores using keytool -list
+#
+# Primarily used as a sanity check that keytool can successfully process
+# WKS KeyStore files using the -list command
+#
+# Export library paths for Linux and Mac to find shared JNI library
+export LD_LIBRARY_PATH=../../lib:$LD_LIBRARY_PATH
+export DYLD_LIBRARY_PATH=../../lib:$DYLD_LIBRARY_PATH
+
+# ARGS:
+print_wks() {
+ printf "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
+ printf "KEYSTORE: $1\n"
+ keytool -list -provider com.wolfssl.provider.jce.WolfCryptProvider --providerpath ../../lib/wolfcrypt-jni.jar -storetype WKS -storepass "$2" -keystore ${1}
+ if [ $? -ne 0 ]; then
+ printf "fail"
+ exit 1
+ fi
+}
+
+print_wks "client.wks" "wolfSSL test"
+print_wks "client-rsa-1024.wks" "wolfSSL test"
+print_wks "client-rsa.wks" "wolfSSL test"
+print_wks "client-ecc.wks" "wolfSSL test"
+print_wks "server.wks" "wolfSSL test"
+print_wks "server-rsa-1024.wks" "wolfSSL test"
+print_wks "server-rsa.wks" "wolfSSL test"
+print_wks "server-ecc.wks" "wolfSSL test"
+print_wks "cacerts.wks" "wolfSSL test"
+print_wks "ca-client.wks" "wolfSSL test"
+print_wks "ca-server.wks" "wolfSSL test"
+print_wks "ca-server-rsa-2048.wks" "wolfSSL test"
+print_wks "ca-server-ecc-256.wks" "wolfSSL test"
+
+printf "\nSUCCESS printing all KeyStore files\n"
diff --git a/examples/certs/server-ecc.jks b/examples/certs/server-ecc.jks
index f89d4f6..e2bab3d 100644
Binary files a/examples/certs/server-ecc.jks and b/examples/certs/server-ecc.jks differ
diff --git a/examples/certs/server-ecc.wks b/examples/certs/server-ecc.wks
new file mode 100644
index 0000000..e4cc50b
Binary files /dev/null and b/examples/certs/server-ecc.wks differ
diff --git a/examples/certs/server-key.der b/examples/certs/server-key.der
new file mode 100644
index 0000000..868f054
Binary files /dev/null and b/examples/certs/server-key.der differ
diff --git a/examples/certs/server-keyPkcs8.der b/examples/certs/server-keyPkcs8.der
new file mode 100644
index 0000000..5a58735
Binary files /dev/null and b/examples/certs/server-keyPkcs8.der differ
diff --git a/examples/certs/server-rsa-1024.jks b/examples/certs/server-rsa-1024.jks
index 8c28689..89e0c2d 100644
Binary files a/examples/certs/server-rsa-1024.jks and b/examples/certs/server-rsa-1024.jks differ
diff --git a/examples/certs/server-rsa-1024.wks b/examples/certs/server-rsa-1024.wks
new file mode 100644
index 0000000..f2d21c6
Binary files /dev/null and b/examples/certs/server-rsa-1024.wks differ
diff --git a/examples/certs/server-rsa.jks b/examples/certs/server-rsa.jks
index 59aaea4..bf77ef7 100644
Binary files a/examples/certs/server-rsa.jks and b/examples/certs/server-rsa.jks differ
diff --git a/examples/certs/server-rsa.wks b/examples/certs/server-rsa.wks
new file mode 100644
index 0000000..148021b
Binary files /dev/null and b/examples/certs/server-rsa.wks differ
diff --git a/examples/certs/server.jks b/examples/certs/server.jks
index 2043957..faa1d02 100644
Binary files a/examples/certs/server.jks and b/examples/certs/server.jks differ
diff --git a/examples/certs/server.wks b/examples/certs/server.wks
new file mode 100644
index 0000000..fa14a38
Binary files /dev/null and b/examples/certs/server.wks differ
diff --git a/examples/certs/systemcerts/system-cacerts-to-wks.sh b/examples/certs/systemcerts/system-cacerts-to-wks.sh
new file mode 100755
index 0000000..f420c2e
--- /dev/null
+++ b/examples/certs/systemcerts/system-cacerts-to-wks.sh
@@ -0,0 +1,133 @@
+#!/bin/sh
+#
+# Script to convert system CA certs KeyStore file from JKS to WKS format
+#
+# This script tries to detect OS variant and Java version to find correct
+# CA certificate KeyStore for this system.
+#
+# The following search order is used for trying to find either cacerts,
+# jssecacerts, or both:
+#
+# cacerts
+# 1. $JAVA_HOME/lib/security/cacerts (JDK 9+)
+# 2. $JAVA_HOME/jre/lib/security/cacerts (JDK <= 8)
+#
+# jssecacerts:
+#
+# 1. $JAVA_HOME/lib/security/jssecacerts (JDK 9+)
+# 2. $JAVA_HOME/jre/lib/security/jssecacerts (JDK <= 8)
+#
+
+# Export library paths for Linux and Mac to find shared JNI library
+export LD_LIBRARY_PATH=../../../lib:$LD_LIBRARY_PATH
+export DYLD_LIBRARY_PATH=../../../lib:$DYLD_LIBRARY_PATH
+
+OUTDIR=`pwd`
+
+# ARGS:
+jks_to_wks() {
+ keytool -importkeystore -srckeystore ${1} -destkeystore ${2}.wks -srcstoretype JKS -deststoretype WKS -srcstorepass "$3" -deststorepass "$3" -provider com.wolfssl.provider.jce.WolfCryptProvider --providerpath ../../../lib/wolfcrypt-jni.jar &> /dev/null
+ if [ $? -ne 0 ]; then
+ printf "Failed to convert JKS to WKS!"
+ exit 1
+ fi
+
+}
+
+OS=`uname`
+ARCH=`uname -a`
+
+CACERTS_JDK9="lib/security/cacerts"
+CACERTS_JDK8="jre/lib/security/cacerts"
+JSSECACERTS_JDK9="lib/security/jssecacerts"
+JSSECACERTS_JDK8="jre/lib/security/jssecacerts"
+
+echo "-----------------------------------------------------------------------"
+echo "System CA KeyStore to WKS Conversion Script"
+echo "-----------------------------------------------------------------------"
+
+if [ -z "$JAVA_HOME" ]; then
+ echo "JAVA_HOME empty, trying to detect"
+else
+ echo "JAVA_HOME already set = $JAVA_HOME"
+ javaHome="$JAVA_HOME"
+fi
+
+# Set up Java include and library paths for OS X and Linux
+# NOTE: you may need to modify these if your platform uses different locations
+if [ "$OS" == "Darwin" ]; then
+ echo "Detected Darwin/OSX host OS"
+ if [ -z $javaHome ]; then
+ # this is broken since Big Sur, set JAVA_HOME environment var instead
+ # OSX JAVA_HOME is typically similar to:
+ # /Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home
+ javaHome=`/usr/libexec/java_home`
+ fi
+elif [ "$OS" == "Linux" ] ; then
+ echo "Detected Linux host OS"
+ if [ -z $javaHome ]; then
+ javaHome=`echo $(dirname $(dirname $(readlink -f $(which java))))`
+ fi
+ if [ ! -d "$javaHome/include" ]
+ then
+ javaHome=`echo $(dirname $javaHome)`
+ fi
+else
+ echo 'Unknown host OS!'
+ exit
+fi
+echo " $OS $ARCH"
+echo "Java Home = $javaHome"
+echo ""
+
+if [ ! -d $OUTDIR ]; then
+ mkdir $OUTDIR
+fi
+
+if [ -f "$javaHome/$CACERTS_JDK9" ]; then
+ echo "System cacerts found, converting from JKS to WKS:"
+ echo " FROM: $javaHome/$CACERTS_JDK9"
+ echo " TO: $OUTDIR/cacerts.wks"
+ echo " PASS (default): changeit"
+ if [ -f $OUTDIR/cacerts.wks ]; then
+ rm $OUTDIR/cacerts.wks
+ fi
+ jks_to_wks "$javaHome/$CACERTS_JDK9" "$OUTDIR/cacerts" "changeit"
+fi
+
+if [ -f "$javaHome/$CACERTS_JDK8" ]; then
+ echo "System cacerts found, converting from JKS to WKS:"
+ echo " FROM: $javaHome/$CACERTS_JDK8"
+ echo " TO: $OUTDIR/cacerts.wks"
+ echo " PASS (default): changeit"
+ if [ -f $OUTDIR/cacerts.wks ]; then
+ rm $OUTDIR/cacerts.wks
+ fi
+ jks_to_wks "$javaHome/$CACERTS_JDK8" "$OUTDIR/cacerts" "changeit"
+fi
+
+if [ -f "$javaHome/$JSSECERTS_JDK9" ]; then
+ echo "System jssecacerts found, converting from JKS to WKS:"
+ echo " FROM: $javaHome/$JSSECACERTS_JDK9"
+ echo " TO: $OUTDIR/jssecacerts.wks"
+ echo " PASS (default): changeit"
+ if [ -f $OUTDIR/jssecacerts.wks ]; then
+ rm $OUTDIR/jssecacerts.wks
+ fi
+ jks_to_wks "$javaHome/$JSSECACERTS_JDK9" "$OUTDIR/jssecacerts" "changeit"
+fi
+
+if [ -f "$javaHome/$JSSECERTS_JDK8" ]; then
+ echo "System jssecacerts found, converting from JKS to WKS:"
+ echo " FROM: $javaHome/$JSSECACERTS_JDK8"
+ echo " TO: $OUTDIR/jssecacerts.wks"
+ echo " PASS (default): changeit"
+ if [ -f $OUTDIR/jssecacerts.wks ]; then
+ rm $OUTDIR/jssecacerts.wks
+ fi
+ jks_to_wks "$javaHome/$JSSECACERTS_JDK8" "$OUTDIR/jssecacerts" "changeit"
+fi
+
+echo ""
+echo "Successfully converted JKS to WKS"
+
diff --git a/examples/certs/update-certs.sh b/examples/certs/update-certs.sh
index 022576b..98b4949 100755
--- a/examples/certs/update-certs.sh
+++ b/examples/certs/update-certs.sh
@@ -44,11 +44,14 @@ certList=(
"ecc-client-key.der"
"ecc-client-key.pem"
"ecc-key.pem"
+ "ecc-keyPkcs8.der"
"server-cert.pem"
"server-cert.der"
"server-ecc.pem"
"server-ecc.der"
"server-key.pem"
+ "server-key.der"
+ "server-keyPkcs8.der"
"crl/cliCrl.pem"
"crl/crl.pem"
"crl/crl.der"
diff --git a/examples/certs/update-jks.sh b/examples/certs/update-jks-wks.sh
similarity index 74%
rename from examples/certs/update-jks.sh
rename to examples/certs/update-jks-wks.sh
index 266b74d..ea40ba6 100755
--- a/examples/certs/update-jks.sh
+++ b/examples/certs/update-jks-wks.sh
@@ -45,7 +45,11 @@ if [ -z "$1" ]; then
fi
CERT_LOCATION=$1
-# keystore-name , cert file , alias , password
+# Export library paths for Linux and Mac to find shared JNI library
+export LD_LIBRARY_PATH=../../lib:$LD_LIBRARY_PATH
+export DYLD_LIBRARY_PATH=../../lib:$DYLD_LIBRARY_PATH
+
+# ARGS:
add_cert() {
keytool -import -keystore "$1" -file "$CERT_LOCATION/$2" -alias "$3" -noprompt -trustcacerts -deststoretype JKS -storepass "$4" &> /dev/null
if [ $? -ne 0 ]; then
@@ -54,7 +58,7 @@ add_cert() {
fi
}
-# keystore-name , cert file , key file , alias , password
+# ARGS:
add_cert_key() {
openssl pkcs12 -export -in "$CERT_LOCATION/$2" -inkey "$CERT_LOCATION/$3" -out tmp.p12 -passin pass:"$5" -passout pass:"$5" -name "$4" &> /dev/null
keytool -importkeystore -deststorepass "$5" -destkeystore "$1" -deststoretype JKS -srckeystore tmp.p12 -srcstoretype PKCS12 -srcstorepass "$5" -alias "$4" &> /dev/null
@@ -65,6 +69,16 @@ add_cert_key() {
rm tmp.p12
}
+# ARGS:
+jks_to_wks() {
+ keytool -importkeystore -srckeystore ${1}.jks -destkeystore ${1}.wks -srcstoretype JKS -deststoretype WKS -srcstorepass "$2" -deststorepass "$2" -provider com.wolfssl.provider.jce.WolfCryptProvider --providerpath ../../lib/wolfcrypt-jni.jar &> /dev/null
+ if [ $? -ne 0 ]; then
+ printf "fail"
+ exit 1
+ fi
+
+}
+
#################### CLIENT KEYSTORES ####################
# Client cert: both RSA 2048-bit and ECC
@@ -166,3 +180,72 @@ rm ca-server-ecc-256.jks &> /dev/null
add_cert "ca-server-ecc-256.jks" "/ca-ecc-cert.pem" "ca-ecc" "wolfSSL test"
printf "done\n"
+################### CONVERT JKS TO WKS ###################
+
+printf "\nConverting keystores from JKS to WKS ...\n"
+
+printf "\tCreating client.wks ..."
+rm client.wks &> /dev/null
+jks_to_wks "client" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating client-rsa-1024.wks ..."
+rm client-rsa-1024.wks &> /dev/null
+jks_to_wks "client-rsa-1024" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating client-rsa.wks ..."
+rm client-rsa.wks &> /dev/null
+jks_to_wks "client-rsa" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating client-ecc.wks ..."
+rm client-ecc.wks &> /dev/null
+jks_to_wks "client-ecc" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating server.wks ..."
+rm server.wks &> /dev/null
+jks_to_wks "server" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating server-rsa-1024.wks ..."
+rm server-rsa-1024.wks &> /dev/null
+jks_to_wks "server-rsa-1024" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating server-rsa.wks ..."
+rm server-rsa.wks &> /dev/null
+jks_to_wks "server-rsa" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating server-ecc.wks ..."
+rm server-ecc.wks &> /dev/null
+jks_to_wks "server-ecc" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating cacerts.wks ..."
+rm cacerts.wks &> /dev/null
+jks_to_wks "cacerts" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating ca-client.wks ..."
+rm ca-client.wks &> /dev/null
+jks_to_wks "ca-client" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating ca-server.wks ..."
+rm ca-server.wks &> /dev/null
+jks_to_wks "ca-server" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating ca-server-rsa-2048.wks ..."
+rm ca-server-rsa-2048.wks &> /dev/null
+jks_to_wks "ca-server-rsa-2048" "wolfSSL test"
+printf "done\n"
+
+printf "\tCreating ca-server-ecc-256.wks ..."
+rm ca-server-ecc-256.wks &> /dev/null
+jks_to_wks "ca-server-ecc-256" "wolfSSL test"
+printf "done\n"
+
diff --git a/examples/provider/WolfSSLKeyStoreExample.java b/examples/provider/WolfSSLKeyStoreExample.java
new file mode 100644
index 0000000..1435d21
--- /dev/null
+++ b/examples/provider/WolfSSLKeyStoreExample.java
@@ -0,0 +1,276 @@
+/* WolfSSLKeyStoreExample.java
+ *
+ * Copyright (C) 2006-2024 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.nio.file.Files;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.SecureRandom;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.KeyFactory;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import com.wolfssl.provider.jce.WolfCryptProvider;
+
+public class WolfSSLKeyStoreExample {
+
+ /* KeyStore password */
+ static String storePass = "wolfSSL test";
+
+ /* KeyStore output file */
+ static String wksFile = "wolfssl.wks";
+
+ /* KeyStore type */
+ static String storeType = "WKS";
+
+ /* RSA server cert and private key */
+ static String serverCertRsaDer = "../../certs/server-cert.der";
+ static String serverRsaPkcs8Der = "../../certs/server-keyPkcs8.der";
+
+ /* ECC server cert and private key */
+ static String serverCertEccDer = "../../certs/server-ecc.der";
+ static String serverEccPkcs8Der = "../../certs/ecc-keyPkcs8.der";
+
+ /* RSA server cert chain */
+ static String intRsaServerCertDer =
+ "../../certs/intermediate/server-int-cert.pem";
+ static String intRsaInt1CertDer =
+ "../../certs/intermediate/ca-int-cert.pem";
+ static String intRsaInt2CertDer =
+ "../../certs/intermediate/ca-int2-cert.pem";
+
+ /* ECC server cert chain */
+ static String intEccServerCertDer =
+ "../../certs/intermediate/server-int-ecc-cert.der";
+ static String intEccInt1CertDer =
+ "../../certs/intermediate/ca-int-ecc-cert.der";
+ static String intEccInt2CertDer =
+ "../../certs/intermediate/ca-int2-ecc-cert.der";
+
+ /**
+ * Create and return PrivateKey object from file path to DER-encoded
+ * private key file.
+ *
+ * @param derFilePath file path to DER-encoded PKCS#8 private key file
+ * @param alg algorithm for KeyFactory instance (ex: "RSA", "EC")
+ *
+ * @return PrivateKey object created from file path given
+ *
+ * @throws IllegalArgumentException on bad argument or processing of arg
+ * @throws IOException on error converting File to Path
+ * @throws NoSuchAlgorithmException on bad "alg" when getting KeyFactory
+ * @throws InvalidKeySpecException on error generating PrivateKey object
+ * @throws Exception on other error
+ */
+ private static PrivateKey DerFileToPrivateKey(String derFilePath,
+ String alg) throws IllegalArgumentException, IOException,
+ NoSuchAlgorithmException, InvalidKeySpecException,
+ InvalidKeySpecException {
+
+ byte[] fileBytes = null;
+ PKCS8EncodedKeySpec spec = null;
+ KeyFactory kf = null;
+ PrivateKey key = null;
+
+ if (derFilePath == null || derFilePath.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Input DER file path is null or empty");
+ }
+
+ fileBytes = Files.readAllBytes(new File(derFilePath).toPath());
+ if (fileBytes == null || fileBytes.length == 0) {
+ throw new IllegalArgumentException(
+ "Bytes read from DER file is null or empty, bad file path?");
+ }
+
+ spec = new PKCS8EncodedKeySpec(fileBytes);
+ if (spec == null) {
+ throw new InvalidKeySpecException(
+ "Unable to create PKCS8EncodedKeySpec");
+ }
+
+ kf = KeyFactory.getInstance(alg);
+ key = kf.generatePrivate(spec);
+
+ return key;
+ }
+
+ /**
+ * Read in and convert certificate file to Certificate object.
+ *
+ * @param certPath path to DER-encoded certificate file
+ *
+ * @return new Certificate object representing certPath file
+ *
+ * @throws FileNotFoundException on error reading certPath file
+ * @throws CertificateException on error geting CertificateFactory or
+ * generating Certificate object
+ */
+ private static Certificate CertFileToCertificate(String certPath)
+ throws FileNotFoundException, CertificateException {
+
+ CertificateFactory cf = null;
+ Certificate cert = null;
+
+ cf = CertificateFactory.getInstance("X.509");
+ cert = cf.generateCertificate(new FileInputStream(certPath));
+
+ return cert;
+ }
+
+ public static void InsertKeyStoreEntries(KeyStore store)
+ throws FileNotFoundException, KeyStoreException, IOException,
+ CertificateException, NoSuchAlgorithmException,
+ InvalidKeySpecException {
+
+ byte[] fileBytes = null;
+ PrivateKey privKey = null;
+ Certificate cert = null;
+ Certificate[] chain = null;
+ KeyGenerator kg = null;
+ SecretKey aesKey = null;
+
+ /* INSERT [1]: RSA cert only */
+ cert = CertFileToCertificate(serverCertRsaDer);
+ store.setCertificateEntry("serverRsa", cert);
+
+ /* INSERT [2]: RSA priv key + single cert */
+ privKey = DerFileToPrivateKey(serverRsaPkcs8Der, "RSA");
+ store.setKeyEntry("rsaCert", privKey,
+ storePass.toCharArray(), new Certificate[] { cert });
+
+ /* INSERT [5]: RSA priv key + cert chain */
+ chain = new Certificate[3];
+ cert = CertFileToCertificate(intRsaServerCertDer);
+ chain[0] = cert;
+ cert = CertFileToCertificate(intRsaInt2CertDer);
+ chain[1] = cert;
+ cert = CertFileToCertificate(intRsaInt1CertDer);
+ chain[2] = cert;
+ store.setKeyEntry("rsaChain", privKey, storePass.toCharArray(), chain);
+
+ /* INSERT [3]: ECC cert only */
+ cert = CertFileToCertificate(serverCertEccDer);
+ store.setCertificateEntry("serverEcc", cert);
+
+ /* INSERT [4]: ECC priv key + single cert */
+ privKey = DerFileToPrivateKey(serverEccPkcs8Der, "EC");
+ store.setKeyEntry("eccCert", privKey,
+ storePass.toCharArray(), new Certificate[] { cert });
+
+ /* INSERT [6]: ECC priv key + cert chain */
+ chain = new Certificate[3];
+ cert = CertFileToCertificate(intEccServerCertDer);
+ chain[0] = cert;
+ cert = CertFileToCertificate(intEccInt2CertDer);
+ chain[1] = cert;
+ cert = CertFileToCertificate(intEccInt1CertDer);
+ chain[2] = cert;
+ store.setKeyEntry("eccChain", privKey, storePass.toCharArray(), chain);
+
+ /* INSERT [7]: AES SecretKey */
+ /* If running this example with JKS type, JKS cannot import
+ * non-private keys. Only do for WKS type. */
+ if (storeType.equals("WKS")) {
+ kg = KeyGenerator.getInstance("AES");
+ kg.init(256, new SecureRandom());
+ aesKey = kg.generateKey();
+ store.setKeyEntry("aesKey", aesKey, storePass.toCharArray(), null);
+ }
+ }
+
+ public static void WriteKeyStoreToFile(KeyStore store)
+ throws FileNotFoundException, KeyStoreException, IOException,
+ NoSuchAlgorithmException, CertificateException {
+
+ FileOutputStream fos = new FileOutputStream(wksFile);
+ store.store(fos, storePass.toCharArray());
+ fos.close();
+ }
+
+ public static KeyStore ReadKeyStoreFromFile(String fileName)
+ throws KeyStoreException, FileNotFoundException, IOException,
+ NoSuchAlgorithmException, CertificateException {
+
+ KeyStore store = null;
+
+ store = KeyStore.getInstance(storeType);
+ store.load(new FileInputStream(fileName), storePass.toCharArray());
+
+ return store;
+ }
+
+ public static void main(String args [])
+ {
+ KeyStore store = null;
+ Provider p = null;
+
+ System.out.println("WolfSSLKeyStore (WKS) Example App\n");
+
+ /* Install wolfJCE */
+ Security.insertProviderAt(new WolfCryptProvider(), 1);
+
+ try {
+ store = KeyStore.getInstance(storeType);
+ store.load(null, storePass.toCharArray());
+
+ p = store.getProvider();
+ System.out.println("KeyStore('" + storeType + "') provider = " + p);
+
+ /* Insert variety of entry types */
+ System.out.println("\n-------------------------------------------");
+ System.out.println("Inserting entries into KeyStore");
+ System.out.println("-------------------------------------------");
+ InsertKeyStoreEntries(store);
+
+ /* Store KeyStore to file (wolfssl.wks) */
+ System.out.println("\n-------------------------------------------");
+ System.out.println("Writing KeyStore to file: " + wksFile);
+ System.out.println("-------------------------------------------");
+ WriteKeyStoreToFile(store);
+
+ /* Read KeyStore back in from file */
+ System.out.println("\n-------------------------------------------");
+ System.out.println("Reading KeyStore in from file: " + wksFile);
+ System.out.println("-------------------------------------------");
+ store = ReadKeyStoreFromFile(wksFile);
+
+ System.out.println("\nExample Finished Successfully");
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
+
diff --git a/examples/provider/WolfSSLKeyStoreExample.sh b/examples/provider/WolfSSLKeyStoreExample.sh
new file mode 100755
index 0000000..1941476
--- /dev/null
+++ b/examples/provider/WolfSSLKeyStoreExample.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+cd ./examples/build/provider
+export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../../lib/:/usr/local/lib
+java -classpath ../../../lib/wolfcrypt-jni.jar:./ -Dsun.boot.library.path=../../../lib/ -Dwolfjce.debug=true WolfSSLKeyStoreExample $@
+
diff --git a/jni/include/com_wolfssl_provider_jce_WolfSSLKeyStore.h b/jni/include/com_wolfssl_provider_jce_WolfSSLKeyStore.h
new file mode 100644
index 0000000..5bea1da
--- /dev/null
+++ b/jni/include/com_wolfssl_provider_jce_WolfSSLKeyStore.h
@@ -0,0 +1,45 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class com_wolfssl_provider_jce_WolfSSLKeyStore */
+
+#ifndef _Included_com_wolfssl_provider_jce_WolfSSLKeyStore
+#define _Included_com_wolfssl_provider_jce_WolfSSLKeyStore
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_PBKDF2_SALT_SIZE
+#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_PBKDF2_SALT_SIZE 16L
+#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_PBKDF2_MIN_ITERATIONS
+#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_PBKDF2_MIN_ITERATIONS 10000L
+#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_PBKDF2_DEFAULT_ITERATIONS
+#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_PBKDF2_DEFAULT_ITERATIONS 210000L
+#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_ENC_IV_LENGTH
+#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_ENC_IV_LENGTH 16L
+#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_ENC_KEY_LENGTH
+#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_ENC_KEY_LENGTH 32L
+#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_HMAC_KEY_LENGTH
+#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_MAGIC_NUMBER
+#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_MAGIC_NUMBER 7L
+#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_STORE_VERSION
+#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_STORE_VERSION 1L
+#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_ENTRY_ID_PRIVATE_KEY
+#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_ENTRY_ID_PRIVATE_KEY 1L
+#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_ENTRY_ID_CERTIFICATE
+#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_ENTRY_ID_CERTIFICATE 2L
+#undef com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_ENTRY_ID_SECRET_KEY
+#define com_wolfssl_provider_jce_WolfSSLKeyStore_WKS_ENTRY_ID_SECRET_KEY 3L
+/*
+ * Class: com_wolfssl_provider_jce_WolfSSLKeyStore
+ * Method: X509CheckPrivateKey
+ * Signature: ([B[B)Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_provider_jce_WolfSSLKeyStore_X509CheckPrivateKey
+ (JNIEnv *, jobject, jbyteArray, jbyteArray);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/jni/include/com_wolfssl_wolfcrypt_Asn.h b/jni/include/com_wolfssl_wolfcrypt_Asn.h
index 0c478d5..17060cd 100644
--- a/jni/include/com_wolfssl_wolfcrypt_Asn.h
+++ b/jni/include/com_wolfssl_wolfcrypt_Asn.h
@@ -9,6 +9,16 @@ extern "C" {
#endif
#undef com_wolfssl_wolfcrypt_Asn_MAX_ENCODED_SIG_SIZE
#define com_wolfssl_wolfcrypt_Asn_MAX_ENCODED_SIG_SIZE 512L
+#undef com_wolfssl_wolfcrypt_Asn_DSAk
+#define com_wolfssl_wolfcrypt_Asn_DSAk 515L
+#undef com_wolfssl_wolfcrypt_Asn_RSAk
+#define com_wolfssl_wolfcrypt_Asn_RSAk 645L
+#undef com_wolfssl_wolfcrypt_Asn_RSAPSSk
+#define com_wolfssl_wolfcrypt_Asn_RSAPSSk 654L
+#undef com_wolfssl_wolfcrypt_Asn_RSAESOAEPk
+#define com_wolfssl_wolfcrypt_Asn_RSAESOAEPk 651L
+#undef com_wolfssl_wolfcrypt_Asn_ECDSAk
+#define com_wolfssl_wolfcrypt_Asn_ECDSAk 518L
/*
* Class: com_wolfssl_wolfcrypt_Asn
* Method: encodeSignature
@@ -33,6 +43,14 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_Asn_encodeSignature___3B_3BJI
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getCTC_1HashOID
(JNIEnv *, jclass, jint);
+/*
+ * Class: com_wolfssl_wolfcrypt_Asn
+ * Method: getPkcs8AlgoID
+ * Signature: ([B)I
+ */
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getPkcs8AlgoID
+ (JNIEnv *, jclass, jbyteArray);
+
#ifdef __cplusplus
}
#endif
diff --git a/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h b/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h
index 35e4449..b881f53 100644
--- a/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h
+++ b/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h
@@ -191,6 +191,14 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_HmacSha3_138
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_HmacSha3_1512Enabled
(JNIEnv *, jclass);
+/*
+ * Class: com_wolfssl_wolfcrypt_FeatureDetect
+ * Method: Pbkdf1Enabled
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_Pbkdf1Enabled
+ (JNIEnv *, jclass);
+
/*
* Class: com_wolfssl_wolfcrypt_FeatureDetect
* Method: Pbkdf2Enabled
@@ -199,6 +207,14 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_HmacSha3_151
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_Pbkdf2Enabled
(JNIEnv *, jclass);
+/*
+ * Class: com_wolfssl_wolfcrypt_FeatureDetect
+ * Method: Pkcs12PbkdfEnabled
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_Pkcs12PbkdfEnabled
+ (JNIEnv *, jclass);
+
/*
* Class: com_wolfssl_wolfcrypt_FeatureDetect
* Method: RsaEnabled
diff --git a/jni/jni_asn.c b/jni/jni_asn.c
index 4ccfd9f..916bbca 100644
--- a/jni/jni_asn.c
+++ b/jni/jni_asn.c
@@ -24,6 +24,7 @@
#elif !defined(__ANDROID__)
#include
#endif
+#include
#include
#include
@@ -76,3 +77,65 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getCTC_1HashOID(
{
return wc_GetCTC_HashOID(type);
}
+
+JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Asn_getPkcs8AlgoID
+ (JNIEnv* env, jclass class, jbyteArray pkcs8Der)
+{
+#if !defined(NO_ASN) && !defined(NO_PWDBASED) && defined(HAVE_PKCS8)
+ int ret = 0;
+ word32 algoId = 0;
+ byte* p8 = NULL;
+ byte* p8Copy = NULL;
+ word32 p8Len = 0;
+
+ if (pkcs8Der != NULL) {
+ p8 = (byte*)(*env)->GetByteArrayElements(env, pkcs8Der, NULL);
+ p8Len = (*env)->GetArrayLength(env, pkcs8Der);
+ }
+
+ if (p8 == NULL || p8Len == 0) {
+ ret = BAD_FUNC_ARG;
+ }
+
+ if (ret == 0) {
+ p8Copy = (byte*)XMALLOC(p8Len, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ if (p8Copy == NULL) {
+ ret = MEMORY_E;
+ }
+ }
+
+ if (ret == 0) {
+ /* Copy array since ToTraditional modifies source buffer */
+ XMEMSET(p8Copy, 0, p8Len);
+ XMEMCPY(p8Copy, p8, p8Len);
+
+ ret = ToTraditional_ex(p8Copy, p8Len, &algoId);
+ if (ret > 0) {
+ /* returns length of header, but not needed here */
+ ret = 0;
+ }
+ }
+
+ if (p8Copy != NULL) {
+ XMEMSET(p8Copy, 0, p8Len);
+ XFREE(p8Copy, NULL, DYNAMIC_TYPE_TMP_BUFFER);
+ }
+
+ if (pkcs8Der != NULL) {
+ (*env)->ReleaseByteArrayElements(env, pkcs8Der, (jbyte*)p8, JNI_ABORT);
+ }
+
+ if (ret == 0) {
+ ret = (int)algoId;
+ }
+
+ return (jint)ret;
+
+#else
+ (void)env;
+ (void)class;
+ (void)pkcs8Der;
+ return (jint)NOT_COMPILED_IN;
+#endif
+}
+
diff --git a/jni/jni_feature_detect.c b/jni/jni_feature_detect.c
index 54cc6f9..d356511 100644
--- a/jni/jni_feature_detect.c
+++ b/jni/jni_feature_detect.c
@@ -304,6 +304,18 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_HmacSha3_151
#endif
}
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_Pbkdf1Enabled
+ (JNIEnv* env, jclass jcl)
+{
+ (void)env;
+ (void)jcl;
+#if !defined(NO_PWDBASED) && defined(HAVE_PBKDF1)
+ return JNI_TRUE;
+#else
+ return JNI_FALSE;
+#endif
+}
+
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_Pbkdf2Enabled
(JNIEnv* env, jclass jcl)
{
@@ -316,6 +328,18 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_Pbkdf2Enable
#endif
}
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_Pkcs12PbkdfEnabled
+ (JNIEnv* env, jclass jcl)
+{
+ (void)env;
+ (void)jcl;
+#if !defined(NO_PWDBASED) && defined(HAVE_PKCS12)
+ return JNI_TRUE;
+#else
+ return JNI_FALSE;
+#endif
+}
+
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RsaEnabled
(JNIEnv* env, jclass jcl)
{
diff --git a/jni/jni_jce_wolfsslkeystore.c b/jni/jni_jce_wolfsslkeystore.c
new file mode 100644
index 0000000..a70c733
--- /dev/null
+++ b/jni/jni_jce_wolfsslkeystore.c
@@ -0,0 +1,131 @@
+/* jni_jce_wolfsslkeystore.c
+ *
+ * Copyright (C) 2006-2024 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+#ifdef WOLFSSL_USER_SETTINGS
+ #include
+#elif !defined(__ANDROID__)
+ #include
+#endif
+
+#include
+#include
+#include
+
+/* #define WOLFCRYPT_JNI_DEBUG_ON */
+#include
+
+JNIEXPORT jboolean JNICALL Java_com_wolfssl_provider_jce_WolfSSLKeyStore_X509CheckPrivateKey
+ (JNIEnv* env, jobject class, jbyteArray certDerArr, jbyteArray pkcs8KeyDerArr)
+{
+#if !defined(WOLFCRYPT_ONLY) && !defined(NO_CERTS) && defined(OPENSSL_EXTRA)
+
+ int ret = WOLFSSL_SUCCESS;
+ int certDerSz = 0;
+ int keyDerSz = 0;
+ byte* certDer = NULL;
+ byte* keyDer = NULL;
+ WOLFSSL_X509* x509 = NULL;
+ WOLFSSL_EVP_PKEY* key = NULL;
+ WOLFSSL_PKCS8_PRIV_KEY_INFO* keyInfo = NULL;
+ (void)class;
+
+ if (env == NULL || certDerArr == NULL || pkcs8KeyDerArr == NULL) {
+ throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG);
+ return JNI_FALSE;
+ }
+
+ /* Get byte* and sizes from jbyteArrays */
+ certDer = (byte*)(*env)->GetByteArrayElements(env, certDerArr, NULL);
+ certDerSz = (*env)->GetArrayLength(env, certDerArr);
+
+ keyDer = (byte*)(*env)->GetByteArrayElements(env, pkcs8KeyDerArr, NULL);
+ keyDerSz = (*env)->GetArrayLength(env, pkcs8KeyDerArr);
+
+ if (certDer == NULL || certDerSz <= 0 || keyDer == NULL || keyDerSz <= 0) {
+ fprintf(stderr, "Native X509CheckPrivateKey() bad args");
+ ret = BAD_FUNC_ARG;
+ }
+
+ if (ret == WOLFSSL_SUCCESS) {
+ x509 = wolfSSL_X509_load_certificate_buffer(certDer, certDerSz,
+ WOLFSSL_FILETYPE_ASN1);
+ if (x509 == NULL) {
+ fprintf(stderr,
+ "Native wolfSSL_X509_load_certificate_buffer() failed");
+ ret = WOLFSSL_FAILURE;
+ }
+ }
+
+ if (ret == WOLFSSL_SUCCESS) {
+ keyInfo = wolfSSL_d2i_PKCS8_PKEY(NULL, (const byte**)&keyDer, keyDerSz);
+ if (keyInfo == NULL) {
+ fprintf(stderr, "Native wolfSSL_d2i_PKCS8_PKEY() failed");
+ ret = WOLFSSL_FAILURE;
+ }
+ }
+
+ if (ret == WOLFSSL_SUCCESS) {
+ key = wolfSSL_EVP_PKCS82PKEY(keyInfo);
+ if (key == NULL) {
+ fprintf(stderr, "Native wolfSSL_EVP_PKCS82PKEY() failed");
+ ret = WOLFSSL_FAILURE;
+ }
+ }
+
+ if (ret == WOLFSSL_SUCCESS) {
+ ret = wolfSSL_X509_check_private_key(x509, key);
+ if (ret != WOLFSSL_SUCCESS) {
+ fprintf(stderr, "Native wolfSSL_X509_check_private_key() failed");
+ }
+ }
+
+ if (key != NULL) {
+ wolfSSL_EVP_PKEY_free(key);
+ }
+ if (x509 != NULL) {
+ wolfSSL_X509_free(x509);
+ }
+ if (certDer != NULL) {
+ (*env)->ReleaseByteArrayElements(env, certDerArr,
+ (jbyte*)certDer, JNI_ABORT);
+ }
+ if (keyDer != NULL) {
+ (*env)->ReleaseByteArrayElements(env, pkcs8KeyDerArr,
+ (jbyte*)keyDer, JNI_ABORT);
+ }
+
+ if (ret == WOLFSSL_SUCCESS) {
+ return JNI_TRUE;
+ }
+ else {
+ return JNI_FALSE;
+ }
+
+#else
+ (void)env;
+ (void)class;
+ (void)certDer;
+ (void)pkcs8Der;
+ throwWolfCryptExceptionFromError(env, NOT_COMPILED_IN);
+ return JNI_FALSE;
+#endif
+}
+
diff --git a/jni/jni_pwdbased.c b/jni/jni_pwdbased.c
index 190588a..ecd464f 100644
--- a/jni/jni_pwdbased.c
+++ b/jni/jni_pwdbased.c
@@ -132,6 +132,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Pwdbased_wc_1PBKDF2
if ((passBuf != NULL) && (passBufLen > 0)) {
pass = (byte*)(*env)->GetByteArrayElements(env, passBuf, NULL);
}
+
salt = (byte*)(*env)->GetByteArrayElements(env, saltBuf, NULL);
ret = wc_PBKDF2(outKey, pass, passBufLen, salt, sBufLen,
@@ -155,6 +156,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Pwdbased_wc_1PBKDF2
if (pass != NULL) {
(*env)->ReleaseByteArrayElements(env, passBuf, (jbyte*)pass, JNI_ABORT);
}
+
(*env)->ReleaseByteArrayElements(env, saltBuf, (jbyte*)salt, JNI_ABORT);
if (ret != 0) {
diff --git a/jni/jni_wolfcrypt.c b/jni/jni_wolfcrypt.c
index c57c597..e1913ea 100644
--- a/jni/jni_wolfcrypt.c
+++ b/jni/jni_wolfcrypt.c
@@ -27,6 +27,7 @@
#include
#endif
+#include
#include
#include
diff --git a/makefile.linux b/makefile.linux
index 0b4fe24..1e881ee 100644
--- a/makefile.linux
+++ b/makefile.linux
@@ -31,7 +31,7 @@ OBJ_LIST = jni_fips.o jni_native_struct.o jni_pwdbased.o jni_aes.o \
jni_rng.o jni_rsa.o jni_dh.o jni_ecc.o jni_ed25519.o \
jni_curve25519.o jni_chacha.o jni_error.o jni_asn.o jni_logging.o \
jni_feature_detect.o jni_wolfobject.o jni_wolfcrypt.o \
- jni_wolfssl_cert_manager.o
+ jni_wolfssl_cert_manager.o jni_jce_wolfsslkeystore.o
OBJS = $(patsubst %,$(OUT_PATH)/%,$(OBJ_LIST))
TARGET = $(OUT_PATH)/libwolfcryptjni.so
diff --git a/makefile.macosx b/makefile.macosx
index 58c1091..bf53e02 100644
--- a/makefile.macosx
+++ b/makefile.macosx
@@ -25,7 +25,7 @@ OBJ_LIST = jni_fips.o jni_native_struct.o jni_pwdbased.o jni_aes.o \
jni_rsa.o jni_dh.o jni_ecc.o jni_ed25519.o jni_curve25519.o \
jni_chacha.o jni_error.o jni_asn.o jni_logging.o \
jni_feature_detect.o jni_wolfobject.o jni_wolfcrypt.o \
- jni_wolfssl_cert_manager.o
+ jni_wolfssl_cert_manager.o jni_jce_wolfsslkeystore.o
OBJS = $(patsubst %,$(OUT_PATH)/%,$(OBJ_LIST))
TARGET = $(OUT_PATH)/libwolfcryptjni.dylib
diff --git a/scripts/infer.sh b/scripts/infer.sh
index 0a0ac36..561b540 100755
--- a/scripts/infer.sh
+++ b/scripts/infer.sh
@@ -78,6 +78,7 @@ infer --fail-on-issue run -- javac \
src/main/java/com/wolfssl/provider/jce/WolfCryptRandom.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptSecretKeyFactory.java \
src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java
+ src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java
RETVAL=$?
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java
index 142b009..fac55d6 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptProvider.java
@@ -172,10 +172,6 @@ public final class WolfCryptProvider extends Provider {
put("Alg.Alias.KeyPairGenerator.DiffieHellman", "DH");
}
- /* KeyStore */
- put("KeyStore.WKS",
- "com.wolfssl.provider.jce.WolfSSLKeyStore$WolfSSLKeyStoreWKS");
-
/* CertPathValidator */
put("CertPathValidator.PKIX",
"com.wolfssl.provider.jce.WolfCryptPKIXCertPathValidator");
@@ -220,6 +216,10 @@ public final class WolfCryptProvider extends Provider {
}
}
+ /* KeyStore */
+ put("KeyStore.WKS",
+ "com.wolfssl.provider.jce.WolfSSLKeyStore");
+
/* If using a FIPS version of wolfCrypt, allow private key to be
* exported for use. Only applicable to FIPS 140-3 */
if (Fips.enabled) {
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSecretKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSecretKeyFactory.java
index bcf5b70..e9e8d20 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptSecretKeyFactory.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSecretKeyFactory.java
@@ -251,7 +251,7 @@ public class WolfCryptSecretKeyFactory extends SecretKeyFactorySpi {
* @return password as UTF-8 encoded byte array, or null if input password
* is null or zero length
*/
- private static byte[] passwordToByteArray(char[] pass) {
+ protected static byte[] passwordToByteArray(char[] pass) {
byte[] passBytes = null;
CharBuffer passBuf = null;
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java
index 60561ab..40b678f 100644
--- a/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java
+++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptSignature.java
@@ -301,11 +301,11 @@ public class WolfCryptSignature extends SignatureSpi {
if (this.keyType == KeyType.WC_RSA &&
!(publicKey instanceof RSAPublicKey)) {
- throw new InvalidKeyException("Key is not of type RSAPrivateKey");
+ throw new InvalidKeyException("Key is not of type RSAPublicKey");
} else if (this.keyType == KeyType.WC_ECDSA &&
!(publicKey instanceof ECPublicKey)) {
- throw new InvalidKeyException("Key is not of type ECPrivateKey");
+ throw new InvalidKeyException("Key is not of type ECPublicKey");
}
/* get encoded key, returns PKCS#8 formatted private key */
diff --git a/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java b/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java
new file mode 100644
index 0000000..74af11a
--- /dev/null
+++ b/src/main/java/com/wolfssl/provider/jce/WolfSSLKeyStore.java
@@ -0,0 +1,2866 @@
+/* WolfSSLKeyStore.java
+ *
+ * Copyright (C) 2006-2024 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce;
+
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Key;
+import java.security.KeyStoreSpi;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.KeyFactory;
+import java.security.Security;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.UnrecoverableEntryException;
+import java.security.KeyStoreException;
+import java.security.NoSuchProviderException;
+import java.security.InvalidKeyException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateEncodingException;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.spec.SecretKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.security.auth.DestroyFailedException;
+
+import com.wolfssl.wolfcrypt.Asn;
+import com.wolfssl.wolfcrypt.Aes;
+import com.wolfssl.wolfcrypt.Pwdbased;
+import com.wolfssl.wolfcrypt.WolfCrypt;
+import com.wolfssl.wolfcrypt.WolfSSLCertManager;
+import com.wolfssl.wolfcrypt.WolfCryptException;
+import com.wolfssl.provider.jce.WolfCryptDebug;
+
+/**
+ * wolfSSL KeyStore implementation (WKS).
+ *
+ * This KeyStore has been designed to be compatible with wolfCrypt
+ * FIPS 140-2 and 140-3, using algorithms and modes inside the wolfCrypt FIPS
+ * module boundary.
+ *
+ * Private keys are protected inside this KeyStore implementation using
+ * PKCS#5 PBKDF2 and AES-CBC with HMAC-SHA512, specifically:
+ *
+ * 1. PKCS#5 PBKDF2 derives an encryption key from provided user password
+ * + Password is converted from char[] to byte[] using UTF-8
+ * + Salt size = 16 bytes, Iteration count = 210,000
+ * + Iterations can be customized using wolfjce.wks.iterationCount
+ * Security property in a java.security file
+ *
+ * 2. AES-CBC encrypts the private key using derived password
+ * + IV length = 16 bytes
+ * + Key length = 32 bytes (256 bits)
+ *
+ * 3. HMAC-SHA512 is calculated over the encrypted key and associated
+ * parameters (Encrypt-then-MAC).
+ *
+ * When this KeyStore is stored (engineStore()), the following format is used.
+ * There is an HMAC-SHA512 stored at the end which is calculated over the
+ * entire HEADER + ENTRIES + PBKDF2 SALT LEN/SALT + PBKDF2 iterations, which
+ * is used to check the KeyStore integrity when loaded back in (engineLoad())
+ * to detect corrupt or tampered KeyStores.
+ *
+ * HEADER:
+ * magicNumber (int / 7)
+ * keystoreVersion (int)
+ * entryCount (int)
+ * ENTRIES (can be any of below, depending on type)
+ * [WKSPrivateKey]
+ * entryId (int / 1)
+ * alias (UTF String)
+ * creationDate.getTime() (long)
+ * kdfSalt.length (int)
+ * kdfSalt (byte[])
+ * kdfIterations (int)
+ * iv.length (int)
+ * iv (byte[])
+ * encryptedKey.length (int)
+ * encryptedKey (byte[])
+ * chain.length (int)
+ * FOR EACH CERT:
+ * chain[i].getType() (UTF String)
+ * chain[i].getEncoded().length (int)
+ * chain[i].getEncoced() (byte[])
+ * hmac.length (int)
+ * hmac (HMAC-SHA512) (byte[])
+ * [WKSSecretKey]
+ * entryId (int / 3)
+ * alias (UTF String)
+ * creationDate.getTime() (long)
+ * key.getAlgorithm() (UTF String)
+ * kdfSalt.length (int)
+ * kdfIterations (int)
+ * kdfSalt (byte[])
+ * iv.length (int)
+ * iv (byte[])
+ * encryptedKey.length (int)
+ * encryptedKey (byte[])
+ * hmac.length (int)
+ * hmac (HMAC-SHA512) (byte[])
+ * [WKSCertificate]
+ * entryId (int / 2)
+ * alias (UTF String)
+ * creationDate.getTime() (long)
+ * cert.getType() (UTF String)
+ * cert.getEncoded().length (int)
+ * cert.getEncoced() (byte[])
+ * HMAC PBKDF2 salt length int
+ * HMAC PBKDF2 salt (byte[])
+ * HMAC PBKDF2 iterations int
+ * HMAC length int
+ * HMAC (HMAC-SHA512) (byte[])
+ *
+ * When loading a KeyStore (engineLoad()), the password is optional. If a
+ * password is provided, we recalculate the HMAC over the input KeyStore and
+ * check against the HMAC encoded in the KeyStore bytes to detect if the
+ * stored KeyStore has been tampered with. If a password is not provided,
+ * the integrity check will be skipped. This is consistent with existing
+ * (ie: JKS) KeyStore implementation behavior and allows for consistent use of
+ * system KeyStores (ex: cacerts) where users do not normally have/use the
+ * password when loading the KeyStore.
+ *
+ * Each PrivateKey and SecretKey entry includes a separate HMAC-SHA512.
+ * That HMAC is loaded together with the entry and verified against the
+ * provided password when the entry is retrieved by the user. This is
+ * independent of the entire KeyStore integrity HMAC verification.
+ */
+public class WolfSSLKeyStore extends KeyStoreSpi {
+
+ private static WolfCryptDebug debug;
+
+ /* RNG used for generating random IVs and salts */
+ private SecureRandom rand = null;
+ private static final Object randLock = new Object();
+
+ /* PBKDF2 parameters:
+ * [salt]: NIST SP 800-132 recommends salts should be at least 128 bits
+ * [iterations]: OWASP PBKDF2 guidance recommends 210,000 iterations
+ * of PBKDF2-HMAC-SHA512. HMAC-SHA512 was chosen since significantly
+ * fewer iterations are required as compared to HMAC-SHA256 (requires
+ * 600,000 iterations to match OWASP recommendations). Iterations
+ * can be customized using Java Security property
+ * 'wolfjce.wks.iterationCount' in java.security. Minimum iterations
+ * allowed is 10,000.
+ * [type]: SHA-512 (WolfCrypt.WC_HASH_TYPE_SHA512) */
+ private static final int WKS_PBKDF2_SALT_SIZE = 16;
+ private static final int WKS_PBKDF2_MIN_ITERATIONS = 10000;
+ private static final int WKS_PBKDF2_DEFAULT_ITERATIONS = 210000;
+ private static final int WKS_PBKDF2_ITERATION_COUNT;
+ private static final int WKS_PBKDF2_TYPE = WolfCrypt.WC_HASH_TYPE_SHA512;
+
+ /* AES-CBC parameters (bytes) */
+ private static final int WKS_ENC_IV_LENGTH = 16;
+ private static final int WKS_ENC_KEY_LENGTH = Aes.KEY_SIZE_256;
+
+ /* HMAC parameters:
+ * 64-bytes (512-bit) to match usage with HMAC-SHA512 */
+ private static final int WKS_HMAC_KEY_LENGTH = 64;
+
+ /* Max cert chain length, used in sanity check when loading a KeyStore.
+ * Can be customized via 'wolfjce.wks.maxCertChainLength' Java Security
+ * property in java.security file */
+ private static final int WKS_DEFAULT_MAX_CHAIN_COUNT = 100;
+ private static final int WKS_MAX_CHAIN_COUNT;
+
+ /* WKS magic number, used when storing KeyStore to OutputStream */
+ private static final int WKS_MAGIC_NUMBER = 7;
+
+ /* WKS KeyStore version (may increment in future if behavior changes) */
+ private static final int WKS_STORE_VERSION = 1;
+
+ /* WKS entry IDs, used when storing/loading KeyStore */
+ private static final int WKS_ENTRY_ID_PRIVATE_KEY = 1;
+ private static final int WKS_ENTRY_ID_CERTIFICATE = 2;
+ private static final int WKS_ENTRY_ID_SECRET_KEY = 3;
+
+ /**
+ * KeyStore entries as ConcurrentHashMap.
+ * Entry values are objects of one of the following types:
+ * WKSPrivateKey, WKSCertificate, WKSSecretKey. Keys are Strings which
+ * represent an alias name.
+ */
+ private ConcurrentHashMap entries =
+ new ConcurrentHashMap<>();
+
+ private enum EntryType {
+ PRIVATE_KEY, /* WKSPrivateKey */
+ CERTIFICATE, /* WKSCertificate */
+ SECRET_KEY /* WKSSecretKey */
+ };
+
+ static {
+ int iCount = WKS_PBKDF2_DEFAULT_ITERATIONS;
+ int cLength = WKS_DEFAULT_MAX_CHAIN_COUNT;
+ String iterations = null;
+ String chainCount = null;
+
+ /* Set PBKDF2 iteration count, using default or one set by
+ * user in 'wolfjce.wks.iterationCount' Security property in
+ * java.security file */
+ iterations = Security.getProperty("wolfjce.wks.iterationCount");
+ if (iterations != null && !iterations.isEmpty()) {
+ try {
+ iCount = Integer.parseInt(iterations);
+ if (iCount < WKS_PBKDF2_MIN_ITERATIONS) {
+ log("wolfjce.wks.iterationCount (" + iCount + ") lower " +
+ "than min allowed (" + WKS_PBKDF2_MIN_ITERATIONS +
+ ")");
+ iCount = WKS_PBKDF2_DEFAULT_ITERATIONS;
+ }
+
+ } catch (NumberFormatException e) {
+ /* Error parsing property, fall back to default */
+ log("error parsing wolfjce.wks.iterationCount property, " +
+ "using default instead");
+ }
+ }
+
+ log("setting PBKDF2 iterations: " + iCount);
+ WKS_PBKDF2_ITERATION_COUNT = iCount;
+
+ /* Set max certificate chain length limitation, using default or one
+ * set with `wolfjce.wks.maxCertChainLength` Security property in
+ * java.security file */
+ chainCount = Security.getProperty("wolfjce.wks.maxCertChainLength");
+ if (chainCount != null && !chainCount.isEmpty()) {
+ try {
+ cLength = Integer.parseInt(chainCount);
+ if (cLength <= 0) {
+ log("wolfjce.wks.maxCertChainLength (" + cLength +
+ ") lower than 0, using default");
+ cLength = WKS_DEFAULT_MAX_CHAIN_COUNT;
+ }
+ } catch (NumberFormatException e) {
+ /* Error parsing property, fall back to default */
+ log("error parsing wolfjce.wks.maxCertChainLength property, " +
+ "using default instead");
+ }
+ }
+
+ log("setting max cert chain length: " + cLength);
+ WKS_MAX_CHAIN_COUNT = cLength;
+ }
+
+ /**
+ * Create new WolfSSLKeyStore object
+ */
+ public WolfSSLKeyStore() {
+ log("created new KeyStore: type WKS (version: " +
+ WKS_STORE_VERSION + ")");
+ }
+
+ /**
+ * Native JNI method that calls wolfSSL_X509_check_private_key()
+ * to confirm that the provided X.509 certificate matches the given
+ * private key.
+ *
+ * @param derCert X.509 certificate encoded as DER byte array
+ * @param pkcs8PrivKey Private key encoded as PKCS#8 byte array
+ *
+ * @return true if matches, otherwise false if no match
+ *
+ * @throws WolfCryptException on native wolfSSL error
+ */
+ private native boolean X509CheckPrivateKey(
+ byte[] derCert, byte[] pkcs8PrivKey) throws WolfCryptException;
+
+ /**
+ * Return entry from internal map that matches alias and type.
+ *
+ * @param alias Alias for entry to retrieve
+ * @param type type of entry that should be returned, either
+ * EntryType.PRIVATE_KEY, EntryType.CERTIFICATE, or
+ * EntryType.SECRET_KEY
+ *
+ * @return entry Object if found, otherwise null if not found or entry
+ * for given alias does not match type requested
+ */
+ private Object getEntryFromAlias(String alias, EntryType type) {
+
+ Object entry = null;
+
+ if (alias == null || alias.isEmpty()) {
+ return null;
+ }
+
+ entry = entries.get(alias);
+ if (entry == null) {
+ return null;
+ }
+
+ switch (type) {
+ case PRIVATE_KEY:
+ if (entry instanceof WKSPrivateKey) {
+ return entry;
+ }
+ break;
+ case CERTIFICATE:
+ if (entry instanceof WKSCertificate) {
+ return entry;
+ }
+ break;
+ case SECRET_KEY:
+ if (entry instanceof WKSSecretKey) {
+ return entry;
+ }
+ default:
+ break;
+ }
+
+ return null;
+ }
+
+ /**
+ * Derive encryption and authentication keys from password using PBKDF2.
+ *
+ * @param pass password to use for key protection
+ * @param salt salt for PBKDF2 derivation
+ * @param iterations iterations for PBKDF2 derivation
+ * @param kLen key length to generate
+ *
+ * @return byte array continaing derived key of specified length
+ *
+ * @throws KeyStoreException on error deriving key
+ */
+ private static byte[] deriveKeyFromPassword(char[] pass,
+ byte[] salt, int iterations, int kLen) throws KeyStoreException {
+
+ byte[] kek = null;
+
+ if (pass == null || pass.length == 0 || salt == null ||
+ salt.length == 0 || iterations <= 0 || kLen <= 0) {
+ throw new KeyStoreException(
+ "Invalid arguments when deriving key from password");
+ }
+
+ try {
+ kek = Pwdbased.PBKDF2(
+ WolfCryptSecretKeyFactory.passwordToByteArray(pass),
+ salt, iterations, kLen, WKS_PBKDF2_TYPE);
+
+ if (kek == null) {
+ throw new KeyStoreException(
+ "Error deriving key encryption key with PBKDF2");
+ }
+
+ } catch (WolfCryptException e) {
+ if (kek != null) {
+ Arrays.fill(kek, (byte)0);
+ }
+ throw new KeyStoreException(e);
+ }
+
+ return kek;
+ }
+
+ /**
+ * Generate HMAC over data using provided key.
+ *
+ * @param key HMAC key to be used
+ * @param data data to be used as input for HMAC
+ *
+ * @return generated HMAC value on success, null on error
+ *
+ * @throws KeyStoreException on error generating HMAC
+ */
+ private static byte[] generateHmac(byte[] key, byte[] data)
+ throws KeyStoreException {
+
+ byte[] hmac = null;
+ SecretKeySpec spec = null;
+ Mac mac = null;
+
+ if (key == null || key.length == 0 ||
+ data == null || data.length == 0) {
+ throw new KeyStoreException(
+ "HMAC key or data null or zero length when generating");
+ }
+
+ try {
+ mac = Mac.getInstance("HmacSHA512", "wolfJCE");
+ spec = new SecretKeySpec(key, "SHA512");
+
+ /* Generate HMAC-SHA512 */
+ mac.init(spec);
+ mac.update(data);
+ hmac = mac.doFinal();
+
+ } catch (NoSuchAlgorithmException e) {
+ throw new KeyStoreException(
+ "HmacSHA512 not available in wolfJCE Mac service", e);
+
+ } catch (NoSuchProviderException e) {
+ throw new KeyStoreException(
+ "WolfSSLKeyStore must currently use wolfJCE for " +
+ "HmacSHA512", e);
+
+ } catch (IllegalStateException e) {
+ throw new KeyStoreException(
+ "Error initializing Mac object", e);
+
+ } catch (InvalidKeyException e) {
+ throw new KeyStoreException(
+ "Invalid SecretKeySpec passed to Mac.init()");
+
+ } finally {
+ if (spec != null) {
+ try {
+ spec.destroy();
+ } catch (DestroyFailedException e) {
+ log("SecretKeySpec.destroy() failed in generateHmac()");
+ }
+ }
+ }
+
+ return hmac;
+ }
+
+ /**
+ * Encrypt plaintext key using AES-CBC.
+ *
+ * AES-CBC encryption uses Cipher.AES/CBC/PKCS5Padding mode.
+ *
+ * @param plainKey plaintext key to be encrypted/protected
+ * @param kek key encryption key, used to encrypt plaintext key
+ * @param pass password to use for key protection
+ * @param iv initialization vector (IV) for encryption operation
+ *
+ * @return byte array containing encrypted/protected key
+ *
+ * @throws KeyStoreException on error encrypting key
+ */
+ private static byte[] encryptKey(byte[] plainKey, byte[] kek,
+ byte[] iv) throws KeyStoreException {
+
+ Cipher enc = null;
+ SecretKeySpec keySpec = null;
+ IvParameterSpec ivSpec = null;
+ byte[] encrypted = null;
+
+ if (plainKey == null || plainKey.length == 0 || kek == null ||
+ kek.length != WKS_ENC_KEY_LENGTH || iv == null ||
+ iv.length != Aes.BLOCK_SIZE) {
+ throw new KeyStoreException(
+ "Invalid arguments not allowed when encrypting key");
+ }
+
+ try {
+ try {
+ enc = Cipher.getInstance("AES/CBC/PKCS5Padding", "wolfJCE");
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new KeyStoreException(
+ "AES/CBC/PKCS5Padding not available in wolfJCE Cipher", e);
+
+ } catch (NoSuchProviderException e) {
+ throw new KeyStoreException(
+ "WolfSSLKeyStore must currently use wolfJCE for AES", e);
+ }
+
+ keySpec = new SecretKeySpec(kek, "AES");
+ ivSpec = new IvParameterSpec(iv);
+
+ try {
+ enc.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
+ } catch (InvalidKeyException e) {
+ throw new KeyStoreException(
+ "Invalid AES key used for private key encryption", e);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new KeyStoreException(
+ "Invalid params used for private key encryption", e);
+ }
+
+ try {
+ encrypted = enc.doFinal(plainKey);
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
+ throw new KeyStoreException(
+ "Error encrypting private key with AES-CBC", e);
+ }
+
+ } finally {
+ if (keySpec != null) {
+ try {
+ keySpec.destroy();
+ } catch (DestroyFailedException e) {
+ log("SecretKeySpec.destroy() failed in encryptKey()");
+ }
+ }
+ }
+
+ return encrypted;
+ }
+
+ /**
+ * Decrypt protected key using AES-CBC, return original plaintext
+ * key as byte array.
+ *
+ * @param encKey encrypted/protected key as byte array
+ * @param kek key encryption key to decrypt with
+ * @param iv initialization vector (IV) for decryption operation
+ *
+ * @return unprotected plaintext key as byte array
+ *
+ * @throws KeyStoreException on error unprotecting/decrypting key
+ */
+ private static byte[] decryptKey(byte[] encKey, byte[] kek, byte[] iv)
+ throws KeyStoreException {
+
+ Cipher dec = null;
+ SecretKeySpec keySpec = null;
+ IvParameterSpec ivSpec = null;
+ byte[] plain = null;
+
+ if (encKey == null || encKey.length == 0 || kek == null ||
+ kek.length == 0 || iv == null || iv.length == 0 ||
+ iv.length != Aes.BLOCK_SIZE) {
+ throw new KeyStoreException(
+ "Invalid arguments not allowed when decrypting key");
+ }
+
+ try {
+ /* Decrypt protected key with AES-CBC and KEK */
+ try {
+ dec = Cipher.getInstance("AES/CBC/PKCS5Padding", "wolfJCE");
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new KeyStoreException(
+ "AES/CBC/PKCS5Padding not available in wolfJCE Cipher", e);
+ } catch (NoSuchProviderException e) {
+ throw new KeyStoreException(
+ "WolfSSLKeyStore must currently use wolfJCE for AES", e);
+ }
+
+ keySpec = new SecretKeySpec(kek, "AES");
+ ivSpec = new IvParameterSpec(iv);
+
+ try {
+ dec.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
+ } catch (InvalidKeyException e) {
+ throw new KeyStoreException(
+ "Invalid AES key used for private key decryption");
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new KeyStoreException(
+ "Invalid AES-CBC parameters for private key decryption", e);
+ }
+
+ try {
+ /* Strips off padding internally, return is only plaintext */
+ plain = dec.doFinal(encKey);
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
+ if (plain != null) {
+ Arrays.fill(plain, (byte)0);
+ }
+ throw new KeyStoreException(
+ "Error decrypting private key with AES-CBC", e);
+ }
+
+ } finally {
+ if (keySpec != null) {
+ try {
+ keySpec.destroy();
+ } catch (DestroyFailedException e) {
+ log("SecretKeySpec.destroy() failed in decryptKey()");
+ }
+ }
+ }
+
+ return plain;
+ }
+
+ /**
+ * Return the key associated with the given alias, using the provided
+ * password to decrypt it.
+ *
+ * In order for a key to be returned it must have been associated with
+ * the alias through a call to setKeyEntry() with a PrivateKey or
+ * SecretKey object.
+ *
+ * @param alias alias for which to return the associated key
+ * @param password password used to decrypt key
+ *
+ * @return the requested Key, or null if the alias does not exist or does
+ * not match a key entry.
+ *
+ * @throws NoSuchAlgorithmException if the algorithm for recovering the
+ * key cannot be found
+ * @throws UnrecoverableKeyException if the key cannot be recovered
+ */
+ @Override
+ public synchronized Key engineGetKey(String alias, char[] password)
+ throws NoSuchAlgorithmException, UnrecoverableKeyException {
+
+ int algoId = 0;
+ byte[] plainKey = null;
+ Object entry = null;
+
+ PrivateKey pKey = null;
+ PKCS8EncodedKeySpec p8Spec = null;
+ KeyFactory keyFact = null;
+
+ SecretKey sKey = null;
+ SecretKeySpec skSpec = null;
+
+ log("returning Key entry for alias: " + alias);
+
+ entry = getEntryFromAlias(alias, EntryType.PRIVATE_KEY);
+ if (entry == null) {
+ entry = getEntryFromAlias(alias, EntryType.SECRET_KEY);
+ if (entry == null) {
+ return null;
+ }
+ }
+
+ if (password == null || password.length == 0) {
+ throw new UnrecoverableKeyException("Password cannot be null");
+ }
+
+ try {
+ if (entry instanceof WKSPrivateKey) {
+ plainKey = ((WKSPrivateKey)entry).getDecryptedKey(password);
+
+ p8Spec = new PKCS8EncodedKeySpec(plainKey);
+
+ algoId = Asn.getPkcs8AlgoID(plainKey);
+ if (algoId == 0) {
+ throw new UnrecoverableKeyException(
+ "Unable to parse PKCS#8 algorithm ID from " +
+ "unprotected key");
+ }
+
+ switch (algoId) {
+ case Asn.RSAk:
+ keyFact = KeyFactory.getInstance("RSA");
+ break;
+ case Asn.ECDSAk:
+ keyFact = KeyFactory.getInstance("EC");
+ break;
+ default:
+ throw new NoSuchAlgorithmException(
+ "Only RSA and EC private key encoding supported");
+ }
+
+ try {
+ pKey = keyFact.generatePrivate(p8Spec);
+ if (pKey == null) {
+ throw new UnrecoverableKeyException(
+ "Error generating PrivateKey from " +
+ "PKCS8EncodedKeySpec");
+ }
+ } catch (InvalidKeySpecException e) {
+ throw new UnrecoverableKeyException(
+ "Invalid key spec for KeyFactory");
+ }
+ }
+ else if (entry instanceof WKSSecretKey) {
+ WKSSecretKey sk = (WKSSecretKey)entry;
+
+ plainKey = sk.getDecryptedKey(password);
+
+ sKey = new SecretKeySpec(plainKey, sk.keyAlgo);
+ }
+
+ } finally {
+ if (plainKey != null) {
+ Arrays.fill(plainKey, (byte)0);
+ }
+ }
+
+ if (entry instanceof WKSPrivateKey) {
+ return (Key)pKey;
+ }
+ else if (entry instanceof WKSSecretKey) {
+ return (Key)sKey;
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the certificate chain associated with the provided alias.
+ *
+ * The certificate chain returned must have been associated with the
+ * alias through a call to setKeyEntry() with a PrivateKey object.
+ *
+ * @param alias the alias for which to return the matching cert chain
+ *
+ * @return the certificate chain, ordered with the user/peer certificate
+ * first then going up to the root CA last. null if the alias
+ * does not exist or does not contain a certificate chain.
+ */
+ @Override
+ public synchronized Certificate[] engineGetCertificateChain(String alias) {
+
+ Object entry = null;
+
+ log("returning Certificate[] for alias: " + alias);
+
+ entry = entries.get(alias);
+ if ((entry != null) && (entry instanceof WKSPrivateKey)) {
+ return ((WKSPrivateKey)entry).chain.clone();
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the certificate associated with the provided alias.
+ *
+ * If the stored certificate was associated with the alias using a call
+ * to setCertificateEntry() then the trusted certificate contained in
+ * the entry is returned.
+ *
+ * If the given alias contains a private key entry which was created
+ * with a call to setKeyEntry(), the first certificate in the chain
+ * used to create that key entry is returned (if the chain exists).
+ *
+ * @param alias the alias for which to return the matching certificate
+ *
+ * @return the certificate, or null if the alias does not exist or
+ * does not match any entries.
+ */
+ @Override
+ public synchronized Certificate engineGetCertificate(String alias) {
+
+ Object entry = null;
+
+ log("returning Certificate for alias: " + alias);
+
+ entry = entries.get(alias);
+ if (entry != null) {
+ if (entry instanceof WKSCertificate) {
+ return ((WKSCertificate)entry).cert;
+ }
+ else if (entry instanceof WKSPrivateKey) {
+ WKSPrivateKey key = (WKSPrivateKey)entry;
+ if (key.chain != null && key.chain.length > 0) {
+ return key.chain[0];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the creation date of the entry matching the provided alias.
+ *
+ * @param alias the alias used to find matching entry
+ *
+ * @return the creation date of the entry matching alias, or null if the
+ * alias does not exist.
+ */
+ @Override
+ public synchronized Date engineGetCreationDate(String alias) {
+
+ Object entry = null;
+
+ log("returning creation date for entry at alias: " + alias);
+
+ entry = entries.get(alias);
+ if (entry != null) {
+ if (entry instanceof WKSCertificate) {
+ return ((WKSCertificate)entry).creationDate;
+ }
+ else if (entry instanceof WKSPrivateKey) {
+ return ((WKSPrivateKey)entry).creationDate;
+ }
+ else if (entry instanceof WKSSecretKey) {
+ return ((WKSSecretKey)entry).creationDate;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Internal method to check if a Key object is supported by this KeyStore
+ * for storing into an alias.
+ *
+ * 1. Key must be PrivateKey or SecretKey
+ * 2. If PrivateKey object:
+ * a. Must be of format "PKCS#8"
+ * b. Must support encoding (.getEncoded())
+ * 3. If SecretKey object:
+ * a. Must by of format "RAW"
+ * b. Must support encoding (.getEncoded())
+ *
+ * @param key Key object to check if supported
+ *
+ * @throws KeyStoreException if Key object is not supported
+ */
+ private void checkKeyIsSupported(Key key) throws KeyStoreException {
+
+ if (key == null) {
+ throw new KeyStoreException("Input key is null");
+ }
+
+ if (key instanceof PrivateKey) {
+ if (!key.getFormat().equals("PKCS#8")) {
+ throw new KeyStoreException("Only PKCS#8 format PrivateKeys " +
+ "are supported");
+ }
+ if (key.getEncoded() == null) {
+ throw new KeyStoreException("Key does not support encoding");
+ }
+ }
+ else if (key instanceof SecretKey) {
+ if (!key.getFormat().equals("RAW")) {
+ /* SecretKey should always be of format "RAW", double check */
+ throw new KeyStoreException("Only RAW format SecretKeys " +
+ "are supported");
+ }
+ if (key.getEncoded() == null) {
+ throw new KeyStoreException("Key does not support encoding");
+ }
+ }
+ else {
+ throw new KeyStoreException("Key must be of type PrivateKey " +
+ "or SecretKey, unsupported type");
+ }
+ }
+
+ /**
+ * Internal method to check that this is a supported Certificate chain.
+ *
+ * Current checks include:
+ * 1. Chain is not null or a zero length array
+ * 2. Chain is made up of X509Certificate objects
+ * 3. Chain cert signatures are correct as we walk up the chain
+ *
+ * The certificate chain should be ordered from leaf cert (entity) to
+ * top-most intermedate certificate.
+ *
+ * @param chain Certificate chain to check
+ *
+ * @throws KeyStoreException if Certificate array is not supported
+ */
+ private void checkCertificateChain(Certificate[] chain)
+ throws KeyStoreException {
+
+ int i = 0;
+ byte[] encodedCert = null;
+
+ if (chain == null || chain.length == 0) {
+ throw new KeyStoreException("Certificate chain must not " +
+ "be null or empty when storing PrivateKey");
+ }
+
+ for (Certificate cert : chain) {
+ if (!(cert instanceof X509Certificate)) {
+ throw new KeyStoreException("Certificate chain objects must " +
+ "be of type X509Certificate");
+ }
+ }
+
+ if (chain.length > 1) {
+ /* Use wolfSSL CertManager to verify chain cert signatures match */
+ WolfSSLCertManager cm = new WolfSSLCertManager();
+
+ /* Load first chain cert as trusted (we don't have the
+ * root CA available to verify full chain at this point */
+ try {
+ encodedCert = chain[chain.length-1].getEncoded();
+ cm.CertManagerLoadCABuffer(encodedCert, encodedCert.length,
+ WolfCrypt.SSL_FILETYPE_ASN1);
+ } catch (WolfCryptException | CertificateEncodingException e) {
+ cm.free();
+ throw new KeyStoreException(
+ "Error checking cert chain integrity, loading " +
+ "chain[" + chain.length + "]");
+ }
+
+ try {
+ for (i = chain.length-2; i > 0; i--) {
+ encodedCert = chain[i].getEncoded();
+ /* Verify chain cert first against loaded CAs */
+ cm.CertManagerVerifyBuffer(encodedCert, encodedCert.length,
+ WolfCrypt.SSL_FILETYPE_ASN1);
+
+ if (i > 0) {
+ /* If verification passes, load as trusted */
+ cm.CertManagerLoadCABuffer(encodedCert,
+ encodedCert.length,
+ WolfCrypt.SSL_FILETYPE_ASN1);
+ }
+ }
+ } catch (WolfCryptException | CertificateEncodingException e) {
+ cm.free();
+ throw new KeyStoreException(
+ "Certificate chain invalid", e);
+ }
+
+ cm.free();
+ }
+ }
+
+ /**
+ * Internal method to check that an X509Certificate matches the provided
+ * private key.
+ *
+ * @param cert X.509 certificate to check, which should match PrivateKey
+ * @param key PrivateKey to check against certificate
+ *
+ * @throws KeyStoreException if leaf cert does not match private key
+ */
+ private void checkCertificateChainMatchesPrivateKey(
+ X509Certificate cert, PrivateKey key) throws KeyStoreException {
+
+ boolean match = false;
+ byte[] derCert = null;
+ byte[] pkcs8Key = null;
+
+ if (cert == null || key == null) {
+ throw new KeyStoreException("Certificate or PrivateKey is null");
+ }
+
+ try {
+ derCert = cert.getEncoded();
+ if (derCert == null || derCert.length == 0) {
+ throw new KeyStoreException("Bad X509Certificate DER encoding");
+ }
+ } catch (CertificateEncodingException e) {
+ throw new KeyStoreException(e);
+ }
+
+ if (!key.getFormat().equals("PKCS#8") &&
+ !key.getFormat().equals("PKCS8")) {
+ throw new KeyStoreException("PrivateKey encoding not type PKCS#8");
+ }
+
+ pkcs8Key = key.getEncoded();
+ if (pkcs8Key == null || pkcs8Key.length == 0) {
+ throw new KeyStoreException("Bad PrivateKey PKCS#8 encoding");
+ }
+
+ match = X509CheckPrivateKey(derCert, pkcs8Key);
+ if (!match) {
+ throw new KeyStoreException("X509Certificate does not match " +
+ "provided private key");
+ }
+ }
+
+ /**
+ * Assign the given key to an alias and protects it using the
+ * provided password.
+ *
+ * If the key is of type java.security.PrivateKey, it must be accompanied
+ * by a certificate chain which includes the corresponding public key.
+ *
+ * If the key is of type javax.crypto.SecretKey, no certificate chain
+ * should be provided.
+ *
+ * If the alias already exists, the existing entry is overwritten
+ * with the provided key (and cert chain if applicable).
+ *
+ * @param alias the alias name to associate and store
+ * @param key the key to be associated with alias
+ * @param password the password used to protect the key. Password cannot
+ * be null, but can be empty array. If wolfCrypt FIPS is used,
+ * this will cause an error since the minimum HMAC key length is
+ * 14, meaning passwords must be at least 14 characters for use
+ * with this KeyStore and wolfCrypt FIPS.
+ * @param chain the cert chain for the corresponding public key - only
+ * required if the key is of type java.security.PrivateKey
+ *
+ * @throws KeyStoreException if the key cannot be protected or the
+ * operation fails.
+ */
+ @Override
+ public synchronized void engineSetKeyEntry(String alias, Key key,
+ char[] password, Certificate[] chain) throws KeyStoreException {
+
+ byte[] encodedKey = null;
+ WKSPrivateKey privKey = null;
+ WKSSecretKey secretKey = null;
+
+ if (alias == null) {
+ throw new KeyStoreException("Alias cannot be null");
+ }
+
+ if (key == null) {
+ throw new KeyStoreException("Key cannot be null");
+ }
+
+ if (password == null) {
+ throw new KeyStoreException("Password cannot be null");
+ }
+
+ checkKeyIsSupported(key);
+
+ /* PKCS#8 private key (PrivateKey) or raw key bytes (SecretKey) */
+ encodedKey = key.getEncoded();
+ if (encodedKey == null || encodedKey.length == 0) {
+ throw new KeyStoreException(
+ "Error getting encoded key bytes from Key");
+ }
+
+ try {
+ if (key instanceof PrivateKey) {
+ log("inserting PrivateKey at alias: " + alias);
+
+ /* Sanity check on cert chain, chain is required */
+ checkCertificateChain(chain);
+
+ /* Verify private key matches leaf cert */
+ checkCertificateChainMatchesPrivateKey(
+ (X509Certificate)chain[0], (PrivateKey)key);
+
+ /* Protect key and store inside new WKSPrivateKey object,
+ * throws KeyStoreException on error */
+ privKey = new WKSPrivateKey(encodedKey, password,
+ chain, this.rand);
+
+ /* Store entry into map */
+ entries.put(alias, privKey);
+ }
+ else if (key instanceof SecretKey) {
+ log("inserting SecretKey at alias: " + alias);
+
+ /* Protect secret key inside WKSSecretKey object */
+ secretKey = new WKSSecretKey(encodedKey, password,
+ key.getAlgorithm(), this.rand);
+
+ /* Store entry into map */
+ entries.put(alias, secretKey);
+ }
+
+ } finally {
+ /* Zero out encoded key array */
+ Arrays.fill(encodedKey, (byte)0);
+ }
+
+ return;
+ }
+
+ /**
+ * Assign the given key to the provided alias, where the key has already
+ * been protected.
+ *
+ * This method is not supported by this KeyStore implementation since
+ * key protection method would not normally be known/used by external
+ * parties without using this KeyStore.
+ *
+ * @param alias the alias name to associate and store
+ * @param key the key to be associated with the alias, already in
+ * protected format.
+ * @param chain the cert chain for the corresponding public key - only
+ * required if the key is of type java.security.PrivateKey
+ *
+ * @throws KeyStoreException if the operation fails
+ */
+ @Override
+ public synchronized void engineSetKeyEntry(String alias, byte[] key,
+ Certificate[] chain) throws KeyStoreException {
+
+ throw new UnsupportedOperationException(
+ "WolfSSLKeyStore does not support storing already protected keys");
+ }
+
+ /**
+ * Assign a certificate to the provided alias.
+ *
+ * If the alias already holds an existing entry created by
+ * setCertificateEntry() that trusted certificate is overwritten.
+ *
+ * If the alias already holds an existing entry which is a private key,
+ * a KeyStoreException will be thrown since this method cannot overwrite
+ * a private key entry.
+ *
+ * @param alias the alias name to map and store this certificate into
+ * @param cert the certificate to store and associate with alias
+ *
+ * @throws KeyStoreException if the alias alreday exists and does not
+ * identify an entry containing a trusted certificate, or this
+ * method fails.
+ */
+ @Override
+ public synchronized void engineSetCertificateEntry(String alias,
+ Certificate cert) throws KeyStoreException {
+
+ Object entry = entries.get(alias);
+ if (entry instanceof WKSPrivateKey) {
+ throw new KeyStoreException("Cannot overwrite private key entry");
+ }
+
+ log("inserting Certificate at alias: " + alias);
+
+ WKSCertificate obj = new WKSCertificate();
+ obj.cert = cert;
+ obj.creationDate = new Date();
+
+ entries.put(alias, obj);
+ }
+
+ /**
+ * Delete the entry associated with the provided alias.
+ *
+ * @param alias the alias used to delete matching entry
+ *
+ * @throws KeyStoreException if the operation fails
+ */
+ @Override
+ public synchronized void engineDeleteEntry(String alias)
+ throws KeyStoreException {
+
+ log("deleting entry at alias: " + alias);
+
+ entries.remove(alias);
+ }
+
+ /**
+ * Return enumeration of all alias names in this KeyStore.
+ *
+ * @return enumeration of all aliases
+ */
+ @Override
+ public synchronized Enumeration engineAliases() {
+
+ log("returning all alias names in KeyStore");
+
+ return entries.keys();
+ }
+
+ /**
+ * Check if an alias is in this KeyStore.
+ *
+ * @param alias the alias name to check
+ *
+ * @return true if alias is in KeyStore, otherwise false
+ */
+ @Override
+ public synchronized boolean engineContainsAlias(String alias) {
+
+ log("checking if KeyStore contains alias: " + alias);
+
+ return entries.containsKey(alias);
+ }
+
+ /**
+ * Return the total number of entries in this KeyStore.
+ *
+ * @return number of entries
+ */
+ @Override
+ public synchronized int engineSize() {
+
+ log("returning size of KeyStore: " + entries.size());
+
+ return entries.size();
+ }
+
+ /**
+ * Check if entry associated with alias is a private key entry.
+ *
+ * Checks if the alias was created by a call to setKeyEntry() with
+ * the key object of either PrivateKey or SecretKey.
+ *
+ * @param alias the alias to check
+ *
+ * @return true if entry is a key, otherwise false if not a
+ * private key entry or alias does not exist
+ */
+ @Override
+ public synchronized boolean engineIsKeyEntry(String alias) {
+
+ Object entry;
+ boolean isKey = false;
+
+ entry = entries.get(alias);
+ if ((entry != null) &&
+ (entry instanceof WKSPrivateKey ||
+ entry instanceof WKSSecretKey)) {
+ isKey = true;
+ }
+ else {
+ isKey = false;
+ }
+
+ log("checking if alias (" + alias + ") is key: " + isKey);
+
+ return isKey;
+ }
+
+ /**
+ * Check if entry associated with alias is a certificate entry.
+ *
+ * Checks if the alias was created by a call to setCertificateEntry().
+ *
+ * @param alias the alias to check
+ *
+ * @return true if entry is a certificate, otherwise false if not a
+ * certificate entry or alias does not exist
+ */
+ @Override
+ public synchronized boolean engineIsCertificateEntry(String alias) {
+
+ Object entry = null;
+ boolean isCert = false;
+
+ entry = entries.get(alias);
+ if ((entry != null) && (entry instanceof WKSCertificate)) {
+ isCert = true;
+ }
+ else {
+ isCert = false;
+ }
+
+ log("checking if alias (" + alias + ") is certificate: " + isCert);
+
+ return isCert;
+ }
+
+ /**
+ * Return the alias name of the first KeyStore entry that matches the
+ * given certificate.
+ *
+ * If a KeyStore entry was created with setCertificateEntry(), the provided
+ * certificate is compared to that entry's certificate.
+ *
+ * If a KeyStore entry was created with setKeyEntry(), then the certificate
+ * provided is compared to the first element of the certificate chain
+ * in the key entry's chain.
+ *
+ * @param cert the certificate to use for matching
+ *
+ * @return the alias name of the first entry that matches the provided
+ * certificate, or null if no entry is found.
+ */
+ @Override
+ public synchronized String engineGetCertificateAlias(Certificate cert) {
+
+ Certificate tmp = null;
+
+ if (cert == null) {
+ return null;
+ }
+
+ for (Map.Entry entry : entries.entrySet()) {
+ if (entry.getValue() instanceof WKSCertificate) {
+ tmp = ((WKSCertificate)entry.getValue()).cert;
+ }
+ else if ((entry.getValue() instanceof WKSPrivateKey) &&
+ (((WKSPrivateKey)entry.getValue()).chain != null)) {
+ tmp = ((WKSPrivateKey)entry.getValue()).chain[0];
+ }
+
+ if ((tmp != null) && tmp.equals(cert)) {
+ return entry.getKey();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Store this KeyStore into the provided OutputStream, protecting the
+ * KeyStore integrity with the given password.
+ *
+ * KeyStore integrity is protected with PBKDF2-HMAC-SHA512 and HMAC-SHA512.
+ *
+ * @param stream OutputStream to write this KeyStore to
+ * @param password password used to generate the keystore integrity check
+ *
+ * @throws IOException on I/O problem
+ * @throws NoSuchAlgorithmException if integrity algorithm can't be
+ * found
+ * @throws CertificateException if any of the certificates in this
+ * KeyStore could not be stored
+ */
+ @Override
+ public synchronized void engineStore(OutputStream stream, char[] password)
+ throws IOException, NoSuchAlgorithmException, CertificateException {
+
+ byte[] encoded = null;
+ byte[] derivedKey = null;
+ byte[] hmac = null;
+ byte[] encodedEntry = null;
+ byte[] salt = new byte[WKS_PBKDF2_SALT_SIZE];
+ Mac mac = null;
+ SecretKeySpec keySpec = null;
+ ByteArrayOutputStream bos = null;
+ DataOutputStream dos = null;
+ WKSPrivateKey keyEntry = null;
+ WKSSecretKey sKeyEntry = null;
+ WKSCertificate certEntry = null;
+
+ if (stream == null || password == null || password.length == 0) {
+ throw new IllegalArgumentException(
+ "OutputStream and password cannot be null");
+ }
+
+ log("storing KeyStore to OutputStream");
+
+ try {
+ bos = new ByteArrayOutputStream();
+ dos = new DataOutputStream(bos);
+
+ /* magic number */
+ dos.writeInt(WKS_MAGIC_NUMBER);
+
+ /* keystore version */
+ log("KeyStore version: " + WKS_STORE_VERSION);
+ dos.writeInt(WKS_STORE_VERSION);
+
+ /* entry count */
+ log("KeyStore entry count: " + entries.size());
+ dos.writeInt(entries.size());
+
+ /* write out entries */
+ for (Map.Entry entry : entries.entrySet()) {
+ if (entry.getValue() instanceof WKSPrivateKey) {
+ keyEntry = (WKSPrivateKey)entry.getValue();
+
+ log("storing PrivateKey: " + entry.getKey());
+
+ /* entry ID */
+ dos.writeInt(WKS_ENTRY_ID_PRIVATE_KEY);
+
+ /* alias */
+ dos.writeUTF(entry.getKey());
+
+ /* encoded WKSPrivateKey length and bytes */
+ encodedEntry = keyEntry.getEncoded(true);
+ dos.writeInt(encodedEntry.length);
+ dos.write(encodedEntry);
+ Arrays.fill(encodedEntry, (byte)0);
+ }
+ else if (entry.getValue() instanceof WKSCertificate) {
+ certEntry = (WKSCertificate)entry.getValue();
+
+ log("storing Certificate: " + entry.getKey());
+
+ /* entry ID */
+ dos.writeInt(WKS_ENTRY_ID_CERTIFICATE);
+
+ /* alias */
+ dos.writeUTF(entry.getKey());
+
+ /* encoded WKSCertificate length and bytes */
+ encodedEntry = certEntry.getEncoded();
+ dos.writeInt(encodedEntry.length);
+ dos.write(encodedEntry);
+ Arrays.fill(encodedEntry, (byte)0);
+ }
+ else if (entry.getValue() instanceof WKSSecretKey) {
+ sKeyEntry = (WKSSecretKey)entry.getValue();
+
+ log("storing SecretKey: " + entry.getKey());
+
+ /* entry ID */
+ dos.writeInt(WKS_ENTRY_ID_SECRET_KEY);
+
+ /* alias */
+ dos.writeUTF(entry.getKey());
+
+ /* encoded WKSSecretKey length and bytes */
+ encodedEntry = sKeyEntry.getEncoded(true);
+ dos.writeInt(encodedEntry.length);
+ dos.write(encodedEntry);
+ Arrays.fill(encodedEntry, (byte)0);
+ }
+ else {
+ throw new IOException(
+ "Encountered unsupported entry type when " +
+ "storing KeyStore");
+ }
+ }
+
+ /* Generate random PBKDF2 salt */
+ synchronized (randLock) {
+ if (this.rand == null) {
+ this.rand = new SecureRandom();
+ }
+ rand.nextBytes(salt);
+ }
+
+ /* Write salt length and salt */
+ dos.writeInt(salt.length);
+ dos.write(salt);
+
+ /* Write PBKDF2 iterations */
+ dos.writeInt(WKS_PBKDF2_ITERATION_COUNT);
+
+ /* Get encoded bytes up to this point */
+ dos.flush();
+ encoded = bos.toByteArray();
+
+ /* Derive HMAC key from password with PBKDF2 */
+ log("deriving HMAC-SHA512 key with PKCS#5 PBKDF2-HMAC-SHA512");
+ derivedKey = Pwdbased.PBKDF2(
+ WolfCryptSecretKeyFactory.passwordToByteArray(password),
+ salt, WKS_PBKDF2_ITERATION_COUNT, WKS_HMAC_KEY_LENGTH,
+ WKS_PBKDF2_TYPE);
+ if (derivedKey == null) {
+ throw new IOException("Error deriving key with PBKDF2");
+ }
+
+ /* Calculate HMAC-SHA512 of output array, hard coding use of
+ * wolfJCE provider to guarantee use when using wolfCrypt FIPS */
+ log("calculating HMAC-SHA512 for KeyStore integrity");
+ try {
+ keySpec = new SecretKeySpec(derivedKey, "SHA512");
+
+ mac = Mac.getInstance("HmacSHA512", "wolfJCE");
+ mac.init(keySpec);
+ mac.update(encoded);
+ hmac = mac.doFinal();
+
+ } catch (NoSuchProviderException e) {
+ throw new IOException("No Mac.HmacSHA512 found for wolfJCE");
+ } catch (InvalidKeyException e) {
+ throw new IOException("Invalid HmacSHA512 key");
+ }
+
+ /* Write HMAC to end of encoded store */
+ dos.writeInt(hmac.length);
+ dos.write(hmac);
+
+ dos.flush();
+
+ /* Write final array to provided OutputStream */
+ stream.write(bos.toByteArray());
+
+ } finally {
+ dos.close();
+ if (encoded != null) {
+ Arrays.fill(encoded, (byte)0);
+ }
+ if (derivedKey != null) {
+ Arrays.fill(derivedKey, (byte)0);
+ }
+ if (hmac != null) {
+ Arrays.fill(hmac, (byte)0);
+ }
+ }
+
+ log("KeyStore successfully stored to OutputStream");
+
+ return;
+ }
+
+ /**
+ * Internal InputStream class used to buffer input data and generate
+ * an HMAC-SHA512 integrity check over that data.
+ *
+ * All data passing though this InputStream will be cached internally
+ * for use in HMAC computation, unless data caching is disabled by
+ * calling enableCaching(false). If caching is disabled, no future
+ * data will be stored until caching is re-enabled.
+ */
+ private class BufferedPbkdf2HmacInputStream extends InputStream {
+
+ /* InputStream from which data will be read */
+ private InputStream is = null;
+
+ /* Internal OutputStream where all bytes read will be written
+ * to be cached for later HMAC operation */
+ private ByteArrayOutputStream bos = null;
+
+ /* Used to pause caching of data if needed, otherwise all bytes
+ * read will be copied and stored into ByteArrayOutputStream */
+ private boolean cacheData = true;
+
+ public BufferedPbkdf2HmacInputStream(InputStream stream) {
+
+ if (stream == null) {
+ throw new IllegalArgumentException(
+ "InputStream and password cannot be null");
+ }
+
+ this.is = stream;
+ this.bos = new ByteArrayOutputStream();
+
+ }
+
+ @Override
+ public synchronized int read() throws IOException {
+
+ int rByte = this.is.read();
+
+ if (this.cacheData && rByte != -1) {
+ bos.write(rByte);
+ }
+ return rByte;
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+
+ if (this.bos != null) {
+ this.bos.reset();
+ this.bos.close();
+ }
+
+ super.close();
+ }
+
+ /**
+ * Enable or disable caching of data inside this InputStream.
+ *
+ * Caching is enabled by default, unless explicitly disabled.
+ *
+ * @param enabled boolean value to enable or disable input caching
+ */
+ public synchronized void enableCaching(boolean enabled) {
+ this.cacheData = enabled;
+ }
+
+ /**
+ * Generate HMAC-SHA512 over cached data, deriving HMAC key from
+ * provided password using PBKDF2-HMAC-SHA512.
+ *
+ * @param password password to use for HMAC key generation
+ * @param salt salt to use for PBKDF2 key derivation, cannot be null
+ * @param iterations iterations to use for PBKDF2
+ *
+ * @return HMAC-SHA512 of data cached by this InputStream so far
+ *
+ * @throws IOException on error getting cached data internally
+ */
+ public synchronized byte[] generatePbkdf2Hmac(char[] password,
+ byte[] salt, int iterations) throws IOException {
+
+ Mac mac = null;
+ SecretKeySpec keySpec = null;
+ byte[] derivedKey = null;
+ byte[] buffered = null;
+ byte[] hmac = null;
+
+ if (password == null || password.length == 0 ||
+ salt == null || salt.length == 0 ||
+ iterations < WKS_PBKDF2_MIN_ITERATIONS) {
+ throw new IOException("Invalid password, salt, or iterations");
+ }
+
+ /* Derive HMAC key from password using PBKDF2 */
+ derivedKey = Pwdbased.PBKDF2(
+ WolfCryptSecretKeyFactory.passwordToByteArray(password),
+ salt, iterations, WKS_HMAC_KEY_LENGTH, WKS_PBKDF2_TYPE);
+ if (derivedKey == null) {
+ throw new IOException("Error deriving key with PBKDF2");
+ }
+
+ /* Get full byte array to generate HMAC over */
+ buffered = bos.toByteArray();
+
+ /* Calculate HMAC-SHA512 of output array, hard coding use of
+ * wolfJCE provider here to guarantee use when using FIPS */
+ try {
+ keySpec = new SecretKeySpec(derivedKey, "SHA512");
+
+ mac = Mac.getInstance("HmacSHA512", "wolfJCE");
+ mac.init(keySpec);
+ mac.update(buffered);
+ hmac = mac.doFinal();
+
+ } catch (NoSuchProviderException e) {
+ throw new IOException("No Mac.HmacSHA512 found for wolfJCE");
+
+ } catch (NoSuchAlgorithmException e) {
+ throw new IOException("No Mac.HmacSHA512 found in wolfJCE");
+
+ } catch (InvalidKeyException e) {
+ throw new IOException("Invalid HmacSHA512 key");
+
+ } finally {
+ if (buffered != null) {
+ Arrays.fill(buffered, (byte)0);
+ }
+ }
+
+ return hmac;
+ }
+ }
+
+ /**
+ * Load the KeyStore from the provided InputStream.
+ *
+ * @param stream InputStream from which to load KeyStore
+ * @param password password used to check KeyStore integrity, must not
+ * be null
+ *
+ * @throws IOException on I/O problem or issue with the
+ * KeyStore data format
+ * @throws NoSuchAlgorithmException if algorithm used to check the
+ * KeyStore integrity cannot be found
+ * @throws CertificateException if any of the certificates in the
+ * KeyStore could not be loaded
+ */
+ @Override
+ public synchronized void engineLoad(InputStream stream, char[] password)
+ throws IOException, NoSuchAlgorithmException, CertificateException {
+
+ int i;
+ int tmp = 0;
+ int entryCount = 0;
+ int entryType = 0;
+ int encodedLen = 0;
+ int bytesRead = 0;
+ int saltLen = 0;
+ int hmacLen = 0;
+ int iterations = 0;
+ byte[] streamBytes = null;
+ byte[] encodedEntry = null;
+ byte[] salt = null;
+ byte[] hmac = null;
+ byte[] genHmac = null;
+ boolean havePass = true;
+
+ String alias = null;
+ BufferedPbkdf2HmacInputStream his = null;
+ DataInputStream dis = null;
+
+ WKSPrivateKey keyEntry = null;
+ WKSSecretKey sKeyEntry = null;
+ WKSCertificate certEntry = null;
+
+ log("loading KeyStore from InputStream");
+
+ if (password == null || password.length == 0) {
+ havePass = false;
+ log("KeyStore password not provided, HMAC integrity check " +
+ "will be skipped");
+ }
+
+ if (stream == null) {
+ return;
+ }
+
+ try {
+ if (havePass) {
+ his = new BufferedPbkdf2HmacInputStream(stream);
+ dis = new DataInputStream(his);
+ }
+ else {
+ dis = new DataInputStream(stream);
+ }
+
+ /* magic number */
+ tmp = dis.readInt();
+ if (tmp != WKS_MAGIC_NUMBER) {
+ throw new IOException(
+ "Invalid magic number ( " + tmp + "), " +
+ "KeyStore not of type WKS");
+
+ }
+
+ /* store version */
+ tmp = dis.readInt();
+ if (tmp != WKS_STORE_VERSION) {
+ throw new IOException(
+ "Invalid WKS KeyStore version: " + tmp);
+ }
+ log("KeyStore version: " + tmp);
+
+ /* total entry count */
+ entryCount = dis.readInt();
+ if (entryCount < 0) {
+ throw new IOException("Invalid entry count, negative");
+ }
+ log("KeyStore entry count: " + entryCount);
+
+ for (i = 0; i < entryCount; i++) {
+ /* entry type */
+ entryType = dis.readInt();
+ if (entryType < 0) {
+ throw new IOException("Invalid entry type, negative");
+ }
+
+ /* alias */
+ alias = dis.readUTF();
+
+ /* encoded entry length */
+ encodedLen = dis.readInt();
+ if (encodedLen < 0) {
+ throw new IOException("Invalid encoded length, negative");
+ }
+
+ /* encoded entry */
+ encodedEntry = new byte[encodedLen];
+ bytesRead = dis.read(encodedEntry);
+ if (bytesRead != encodedLen) {
+ throw new IOException(
+ "Unable to read total encoded entry byte array");
+ }
+
+ switch (entryType) {
+ case WKS_ENTRY_ID_PRIVATE_KEY:
+ log("loading PrivateKey: " + alias);
+ keyEntry = new WKSPrivateKey(encodedEntry);
+ entries.put(alias, keyEntry);
+ break;
+
+ case WKS_ENTRY_ID_SECRET_KEY:
+ log("loading SecretKey: " + alias);
+ sKeyEntry = new WKSSecretKey(encodedEntry);
+ entries.put(alias, sKeyEntry);
+ break;
+
+ case WKS_ENTRY_ID_CERTIFICATE:
+ log("loading Certificate: " + alias);
+ certEntry = new WKSCertificate(encodedEntry);
+ entries.put(alias, certEntry);
+ break;
+
+ default:
+ throw new IOException(
+ "Invalid entry type found: " + entryType);
+ }
+ }
+
+ /* PBKDF2 salt len and salt */
+ saltLen = dis.readInt();
+ if (saltLen != WKS_PBKDF2_SALT_SIZE) {
+ throw new IOException("Invalid salt size: " + saltLen);
+ }
+
+ salt = new byte[saltLen];
+ saltLen = dis.read(salt);
+ if (saltLen != WKS_PBKDF2_SALT_SIZE) {
+ throw new IOException("Failed to read entire salt from WKS");
+ }
+
+ /* PBKDF2 iterations */
+ iterations = dis.readInt();
+ if (iterations < WKS_PBKDF2_MIN_ITERATIONS) {
+ throw new IOException(
+ "PBKDF2 iterations too small: " + iterations);
+ }
+
+ /* Pause caching of input data, HMAC itself not included in HMAC */
+ if (havePass) {
+ his.enableCaching(false);
+ }
+
+ /* HMAC len and HMAC */
+ hmacLen = dis.readInt();
+ if (hmacLen < 0) {
+ throw new IOException("Invalid HMAC length, negative");
+ }
+ hmac = new byte[hmacLen];
+ hmacLen = dis.read(hmac);
+ if (hmacLen != hmac.length) {
+ throw new IOException(
+ "Failed to read entire HMAC from WKS stream");
+ }
+
+ /* Regenerate HMAC-SHA512 over bytes read so far */
+ if (havePass) {
+ genHmac = his.generatePbkdf2Hmac(password, salt, iterations);
+ if (genHmac == null || genHmac.length == 0) {
+ throw new IOException(
+ "Unable to generate HMAC-SHA512 over input WKS stream");
+ }
+
+ if (!WolfCrypt.ConstantCompare(hmac, genHmac)) {
+ throw new IOException("Integrity check failed on WKS, " +
+ "KeyStore has been tampered with!");
+ }
+
+ log("HMAC-SHA512 integrity verification successful");
+ }
+ else {
+ log("HMAC-SHA512 integrity verification skipped, " +
+ "no password provided");
+ }
+
+ } finally {
+ if (dis != null) {
+ dis.close();
+ }
+ }
+
+ log("KeyStore successfully loaded from InputStream");
+
+ return;
+ }
+
+ /**
+ * Internal method for logging output.
+ *
+ * @param msg message to be logged
+ */
+ private static synchronized void log(String msg) {
+ if (debug.DEBUG) {
+ debug.print("[WolfSSLKeyStore] " + msg);
+ }
+ }
+
+ /**
+ * Inner class representing a private key entry.
+ *
+ * When encoded to a byte[] for storage (getEncoded()), the following
+ * format is used. Items are written out through DataOutputStream
+ * backed by ByteArrayOutputStream, thus long values are stored
+ * to 8 bytes big endian, int is 4 bytes big endian, etc.
+ *
+ * The HMAC-SHA512 at the end is calculated over all items above it along
+ * with their lengths.
+ *
+ * creationDate.getTime() (long | 8 bytes)
+ * kdfSalt.length (int | 4 bytes)
+ * kdfSalt (byte[])
+ * kdfIterations (int | 4 bytes)
+ * iv.length (int | 4 bytes)
+ * iv (byte[])
+ * encryptedKey.length (int | 4 bytes)
+ * encryptedKey (byte[])
+ * chain.length (int | 4 bytes)
+ * FOR EACH CERT:
+ * chain[i].getType() (UTF String)
+ * chain[i].getEncoded().length (int | 4 bytes)
+ * chain[i].getEncoced() (byte[])
+ * hmac.length (int | 4 bytes)
+ * hmac (byte[])
+ */
+ private static class WKSPrivateKey {
+
+ byte[] encryptedKey; /* protected/encrypted key */
+ byte[] iv; /* AES-GCM IV */
+ byte[] kdfSalt; /* PBKDF2 salt */
+ int kdfIterations; /* PBKDF2 iterations */
+ Certificate[] chain; /* cert chain matching this private key */
+ Date creationDate; /* creation date for this object */
+ byte[] hmacSha512; /* HMAC calculated over members and lengths */
+
+ protected WKSPrivateKey() {
+ }
+
+ /**
+ * Create new WKSPrivateKey from plaintext key and certificate chain,
+ * encrypt/protect plaintext key using provided password.
+ *
+ * @param plainKey unencrypted private key to protect/encrypt inside
+ * this object
+ * @param password password to be used for key protection
+ * @param chain Certificate array containing cert chain matching key
+ *
+ * @throws IllegalArgumentException if input arguments are null
+ * @throws KeyStoreException if encrypting/protecting private key fails
+ */
+ protected WKSPrivateKey(byte[] plainKey, char[] password,
+ Certificate[] chain, SecureRandom rand)
+ throws IllegalArgumentException, KeyStoreException {
+
+ byte[] derivedKey = null;
+ byte[] protectedKey = null;
+ byte[] hmac = null;
+ byte[] kek = new byte[WKS_ENC_KEY_LENGTH];
+ byte[] hmacKey = new byte[WKS_HMAC_KEY_LENGTH];
+ byte[] encoded = null;
+ SecureRandom rng = rand;
+
+ if (plainKey == null || plainKey.length == 0 ||
+ password == null || password.length == 0 ||
+ chain == null || chain.length == 0) {
+ throw new IllegalArgumentException(
+ "Invalid null arguments when creating WKSPrivateKey");
+ }
+
+ /* Generate random salt and IV */
+ this.kdfSalt = new byte[WKS_PBKDF2_SALT_SIZE];
+ this.iv = new byte[WKS_ENC_IV_LENGTH];
+
+ synchronized (randLock) {
+ if (rng == null) {
+ rng = new SecureRandom();
+ }
+ rng.nextBytes(this.kdfSalt);
+ rng.nextBytes(this.iv);
+ }
+
+ try {
+ /* Derive key encryption key from password using
+ * PBKDF2-HMAC-SHA512. Generate a 96 byte key in total, split
+ * between 32-byte AES-CBC-256 and 64-byte HMAC-SHA512 keys */
+ derivedKey = deriveKeyFromPassword(password, this.kdfSalt,
+ WKS_PBKDF2_ITERATION_COUNT,
+ WKS_ENC_KEY_LENGTH + WKS_HMAC_KEY_LENGTH);
+
+ if (derivedKey == null) {
+ throw new KeyStoreException(
+ "Error deriving key encryption key, got null key");
+ }
+ if (derivedKey.length !=
+ (WKS_ENC_KEY_LENGTH + WKS_HMAC_KEY_LENGTH)) {
+ throw new KeyStoreException(
+ "Error deriving key encryption key, wrong length. " +
+ " actual: " + derivedKey.length + ", needed: " +
+ (WKS_ENC_KEY_LENGTH + WKS_HMAC_KEY_LENGTH));
+ }
+
+ /* Split key into encrypt + HMAC keys, erase derivedKey */
+ System.arraycopy(derivedKey, 0, kek, 0, kek.length);
+ System.arraycopy(derivedKey, kek.length, hmacKey, 0,
+ hmacKey.length);
+ Arrays.fill(derivedKey, (byte)0);
+
+ /* Encrypt plain key with KEK */
+ protectedKey = encryptKey(plainKey, kek, this.iv);
+ if (protectedKey == null) {
+ throw new KeyStoreException(
+ "Failed to encrypt plaintext key");
+ }
+
+ this.encryptedKey = protectedKey;
+ this.kdfIterations = WKS_PBKDF2_ITERATION_COUNT;
+ this.chain = chain.clone();
+ this.creationDate = new Date();
+
+ /* Get encoded byte[] of object class variables without HMAC */
+ encoded = getEncoded(false);
+ if (encoded == null) {
+ throw new KeyStoreException(
+ "Failed to get encoded WKSPrivateKey for HMAC gen");
+ }
+
+ /* Generate internal HMAC over object contents */
+ hmac = generateHmac(hmacKey, encoded);
+ if (hmac == null) {
+ throw new KeyStoreException(
+ "Failed to generate HMAC over WKSPrivateKey");
+ }
+
+ this.hmacSha512 = hmac;
+
+ } catch (KeyStoreException | IOException |
+ CertificateEncodingException e) {
+ /* Clear protected key and HMAC in case of error */
+ if (protectedKey != null) {
+ Arrays.fill(protectedKey, (byte)0);
+ }
+ if (hmac != null) {
+ Arrays.fill(hmac, (byte)0);
+ }
+ throw new KeyStoreException(e);
+
+ } finally {
+ /* Clear local KEK, HMAC key, and encoded content */
+ Arrays.fill(kek, (byte)0);
+ Arrays.fill(hmacKey, (byte)0);
+ if (encoded != null) {
+ Arrays.fill(encoded, (byte)0);
+ }
+ }
+ }
+
+ /**
+ * Create new WKSPrivateKey object from encoded byte array.
+ *
+ * @param encoded encoded byte array which was created by
+ * calling WKSPrivateKey getEncoded() method.
+ *
+ * @throws IOException on error reading/parsing encoded array
+ */
+ protected WKSPrivateKey(byte[] encoded)
+ throws IOException, CertificateException {
+
+ int i;
+ int tmp = 0;
+ byte[] tmpArr = null;
+ String tmpStr = null;
+ ByteArrayInputStream bis = null;
+ ByteArrayInputStream certStream = null;
+ DataInputStream dis = null;
+ CertificateFactory cf = null;
+ Certificate tmpCert = null;
+
+ if (encoded == null || encoded.length == 0) {
+ throw new IllegalArgumentException(
+ "Input byte array cannot be null");
+ }
+
+ try {
+ bis = new ByteArrayInputStream(encoded);
+ dis = new DataInputStream(bis);
+
+ /* creationDate */
+ this.creationDate = new Date(dis.readLong());
+
+ /* kdfSalt */
+ tmp = dis.readInt();
+ if (tmp != WKS_PBKDF2_SALT_SIZE) {
+ throw new IOException(
+ "Invalid PBKDF2 salt size: " + tmp);
+ }
+ this.kdfSalt = new byte[tmp];
+ dis.read(this.kdfSalt);
+
+ /* kdfIterations */
+ tmp = dis.readInt();
+ if (tmp < WKS_PBKDF2_MIN_ITERATIONS) {
+ throw new IOException(
+ "PBKDF2 iterations too small: " + tmp);
+ }
+ this.kdfIterations = tmp;
+
+ /* iv */
+ tmp = dis.readInt();
+ if (tmp != WKS_ENC_IV_LENGTH) {
+ throw new IOException(
+ "Invalid IV size: " + tmp);
+ }
+ this.iv = new byte[tmp];
+ dis.read(this.iv);
+
+ /* encrypted key */
+ tmp = dis.readInt();
+ if (tmp < 0) {
+ throw new IOException(
+ "Bad encrypted key length, negative");
+ }
+ this.encryptedKey = new byte[tmp];
+ dis.read(this.encryptedKey);
+
+ /* chain */
+ tmp = dis.readInt();
+ if (tmp > WKS_MAX_CHAIN_COUNT) {
+ throw new IOException(
+ "Cert chain count (" + tmp + ") is larger than max " +
+ "allowed (" + WKS_MAX_CHAIN_COUNT + ")");
+ }
+ this.chain = new Certificate[tmp];
+
+ /* chain certs */
+ for (i = 0; i < chain.length; i++) {
+
+ /* type, get CertificateFactory */
+ tmpStr = dis.readUTF();
+ if ((cf == null) ||
+ ((cf != null) && !cf.getType().equals(tmpStr))) {
+ cf = CertificateFactory.getInstance(tmpStr);
+ }
+
+ /* encoding length */
+ tmp = dis.readInt();
+ if (tmp < 0) {
+ throw new IOException(
+ "Bad encoding length, negative");
+ }
+ tmpArr = new byte[tmp];
+
+ /* encoded cert */
+ dis.read(tmpArr);
+ certStream = new ByteArrayInputStream(tmpArr);
+ tmpCert = cf.generateCertificate(certStream);
+ certStream.close();
+
+ /* add to chain */
+ this.chain[i] = tmpCert;
+ }
+
+ /* HMAC-SHA512, not verifying since we don't have password */
+ tmp = dis.readInt();
+ if (tmp != WKS_HMAC_KEY_LENGTH) {
+ throw new IOException(
+ "HMAC length (" + tmp + ") is different than " +
+ "expected (" + WKS_HMAC_KEY_LENGTH + ")");
+ }
+ this.hmacSha512 = new byte[tmp];
+ dis.read(this.hmacSha512);
+
+ } catch (Exception e) {
+
+ if (this.encryptedKey != null) {
+ Arrays.fill(this.encryptedKey, (byte)0);
+ this.encryptedKey = null;
+ }
+ if (this.iv != null) {
+ Arrays.fill(this.iv, (byte)0);
+ this.iv = null;
+ }
+ if (this.kdfSalt != null) {
+ Arrays.fill(this.kdfSalt, (byte)0);
+ this.kdfSalt = null;
+ }
+ if (this.hmacSha512 != null) {
+ Arrays.fill(this.hmacSha512, (byte)0);
+ this.hmacSha512 = null;
+ }
+ this.chain = null;
+ this.creationDate = null;
+ this.kdfIterations = 0;
+
+ throw e;
+
+ } finally {
+ if (dis != null) {
+ dis.close();
+ }
+ }
+ }
+
+ /**
+ * Get encoded byte array representation of this object, optionally
+ * including HMAC over contents appended to end.
+ *
+ * @param withHMAC include HMAC on end of encoded array when true,
+ * otherwise return encoded object without HMAC. HMAC
+ * is included when writing out / storing, but not included
+ * when we are just using this as a helper function to
+ * check the HMAC value inside this object in memory.
+ *
+ * @return byte array representing this object or null on error
+ * @throws IOException on error writing to output stream
+ * @throws CertificateEncodingException on error getting
+ * Certificate encoding
+ */
+ protected synchronized byte[] getEncoded(boolean withHMAC)
+ throws IOException, CertificateEncodingException {
+
+ int i;
+ byte[] out = null;
+ ByteArrayOutputStream bos = null;
+ DataOutputStream dos = null;
+
+ if (this.creationDate == null || this.kdfSalt == null ||
+ this.kdfSalt.length == 0 || this.kdfIterations <= 0 ||
+ this.iv == null || this.iv.length == 0 ||
+ this.encryptedKey == null || this.encryptedKey.length == 0 ||
+ this.chain == null) {
+ log("invalid WKSPrivateKey class variables, returning null " +
+ "from getEncoded()");
+ return null;
+ }
+
+ if (withHMAC && (this.hmacSha512 == null ||
+ this.hmacSha512.length == 0)) {
+ log("WKSPrivateKey HMAC null or zero length, returning null " +
+ "from getEncoded()");
+ return null;
+ }
+
+ try {
+ bos = new ByteArrayOutputStream();
+ dos = new DataOutputStream(bos);
+
+ dos.writeLong(this.creationDate.getTime());
+ dos.writeInt(this.kdfSalt.length);
+ dos.write(this.kdfSalt, 0, this.kdfSalt.length);
+ dos.writeInt(this.kdfIterations);
+ dos.writeInt(this.iv.length);
+ dos.write(this.iv, 0, this.iv.length);
+ dos.writeInt(this.encryptedKey.length);
+ dos.write(this.encryptedKey, 0, this.encryptedKey.length);
+ dos.writeInt(this.chain.length);
+ if (this.chain.length > 0) {
+ for (i = 0; i < this.chain.length; i++) {
+ dos.writeUTF(this.chain[i].getType());
+ dos.writeInt(this.chain[i].getEncoded().length);
+ dos.write(this.chain[i].getEncoded(), 0,
+ this.chain[i].getEncoded().length);
+ }
+ }
+ if (withHMAC) {
+ dos.writeInt(this.hmacSha512.length);
+ dos.write(this.hmacSha512);
+ }
+
+ dos.flush();
+ out = bos.toByteArray();
+
+ } finally {
+ if (dos != null) {
+ dos.close();
+ }
+ }
+
+ return out;
+ }
+
+ /**
+ * Decrypt and return plaintext key encoded in this object,
+ * using provided password.
+ *
+ * Verifies internally-generated HMAC over WKSPrivateKey object
+ * contents using password before decrypting key.
+ *
+ * Other than password, all other information needed should already
+ * be stored in this object.
+ *
+ * @param password password to use for decryption
+ */
+ protected synchronized byte[] getDecryptedKey(char[] password)
+ throws UnrecoverableKeyException {
+
+ byte[] plain = null;
+ byte[] derivedKey = null;
+ byte[] hmac = null;
+ byte[] kek = new byte[WKS_ENC_KEY_LENGTH];
+ byte[] hmacKey = new byte[WKS_HMAC_KEY_LENGTH];
+ byte[] encoded = null;
+
+ if (password == null || password.length == 0) {
+ throw new UnrecoverableKeyException(
+ "Unable to decrypt key with null password");
+ }
+
+ try {
+ /* Derive key encryption key from password using
+ * PBKDF2-HMAC-SHA512. Generate a 96 byte key in total, to
+ * split between 32-byte AES-CBC-256 key and 64-byte
+ * HMAC-SHA512 key. */
+ derivedKey = deriveKeyFromPassword(password, this.kdfSalt,
+ WKS_PBKDF2_ITERATION_COUNT,
+ WKS_ENC_KEY_LENGTH + WKS_HMAC_KEY_LENGTH);
+
+ if (derivedKey == null) {
+ throw new KeyStoreException(
+ "Error deriving key decryption key, got null key");
+ }
+
+ /* Split key into decrypt + HMAC keys, erase derivedKey */
+ System.arraycopy(derivedKey, 0, kek, 0, kek.length);
+ System.arraycopy(derivedKey, kek.length, hmacKey, 0,
+ hmacKey.length);
+ Arrays.fill(derivedKey, (byte)0);
+
+ /* Get encoded byte[] of object class variables without HMAC */
+ encoded = getEncoded(false);
+ if (encoded == null) {
+ throw new KeyStoreException(
+ "Failed to get encoded WKSPrivateKey for HMAC");
+ }
+
+ /* Re-generate internal HMAC over object contents */
+ hmac = generateHmac(hmacKey, encoded);
+ if (hmac == null) {
+ throw new KeyStoreException(
+ "Failed to regenerate HMAC over WKSPrivateKey");
+ }
+
+ /* Verify HMAC first before decrypting key */
+ if(!WolfCrypt.ConstantCompare(hmac, this.hmacSha512)) {
+ throw new KeyStoreException(
+ "HMAC verification failed on WKSPrivateKey, entry " +
+ "corrupted");
+ } else {
+ log("HMAC verification successful on WKSPrivateKey");
+ }
+
+ /* Decrypt encrypted key with KEK */
+ plain = decryptKey(this.encryptedKey, kek, this.iv);
+ if (plain == null) {
+ throw new UnrecoverableKeyException(
+ "Unable to decrypt protected key");
+ }
+
+ } catch (KeyStoreException | IOException |
+ CertificateEncodingException e) {
+ if (plain != null) {
+ Arrays.fill(plain, (byte)0);
+ }
+ throw new UnrecoverableKeyException(e.getMessage());
+
+ } finally {
+ Arrays.fill(kek, (byte)0);
+ Arrays.fill(hmacKey, (byte)0);
+ if (hmac != null) {
+ Arrays.fill(hmac, (byte)0);
+ }
+ if (encoded != null) {
+ Arrays.fill(encoded, (byte)0);
+ }
+ }
+
+ return plain;
+ }
+ }
+
+ /**
+ * Inner class representing a single certificate-only entry.
+ *
+ * When encoded to a byte[] for storage (getEncoded()), the following
+ * format is used:
+ *
+ * creationDate.getTime() (long)
+ * cert.getType() (UTF String)
+ * cert.getEncoded().length (int)
+ * cert.getEncoced() (byte[])
+ */
+ private static class WKSCertificate {
+
+ Certificate cert;
+ Date creationDate;
+
+ protected WKSCertificate() {
+ }
+
+ /**
+ * Create new WKSCertificate object from encoded byte array.
+ *
+ * @param encoded encoded byte array obtained by calling WKSCertificate
+ * getEncoded() method.
+ *
+ * @throws IOException on error reading/parsing encoded array
+ */
+ protected WKSCertificate(byte[] encoded)
+ throws IOException, CertificateException {
+
+ int i;
+ int tmp = 0;
+ byte[] tmpArr = null;
+ String tmpStr = null;
+ ByteArrayInputStream bis = null;
+ ByteArrayInputStream certStream = null;
+ DataInputStream dis = null;
+ CertificateFactory cf = null;
+ Certificate tmpCert = null;
+
+ if (encoded == null || encoded.length == 0) {
+ throw new IllegalArgumentException(
+ "Input byte array cannot be null");
+ }
+
+ try {
+ bis = new ByteArrayInputStream(encoded);
+ dis = new DataInputStream(bis);
+
+ /* creationDate */
+ this.creationDate = new Date(dis.readLong());
+
+ /* type, get CertificateFactory */
+ tmpStr = dis.readUTF();
+ if ((cf == null) ||
+ ((cf != null) && !cf.getType().equals(tmpStr))) {
+ cf = CertificateFactory.getInstance(tmpStr);
+ }
+
+ /* encoding length */
+ tmp = dis.readInt();
+ if (tmp < 0) {
+ throw new IOException("Bad encoding length, negative");
+ }
+ tmpArr = new byte[tmp];
+
+ /* encoded cert */
+ dis.read(tmpArr);
+ certStream = new ByteArrayInputStream(tmpArr);
+ this.cert = cf.generateCertificate(certStream);
+ certStream.close();
+
+ } catch (Exception e) {
+ this.cert = null;
+ this.creationDate = null;
+ throw e;
+
+ } finally {
+ if (tmpArr != null) {
+ Arrays.fill(tmpArr, (byte)0);
+ tmpArr = null;
+ }
+
+ if (dis != null) {
+ dis.close();
+ }
+ }
+ }
+
+ /**
+ * Get encoded byte array representation of this object.
+ *
+ * @return byte array representing this object or null on error
+ * @throws IOException on error writing to output stream
+ * @throws CertificateEncodingException on error getting
+ * Certificate encoding
+ */
+ protected synchronized byte[] getEncoded()
+ throws IOException, CertificateEncodingException {
+
+ int i;
+ byte[] out = null;
+ ByteArrayOutputStream bos = null;
+ DataOutputStream dos = null;
+
+ if (this.cert == null || this.creationDate == null ||
+ this.cert.getEncoded() == null) {
+ return null;
+ }
+
+ try {
+ bos = new ByteArrayOutputStream();
+ dos = new DataOutputStream(bos);
+
+ dos.writeLong(this.creationDate.getTime());
+ dos.writeUTF(this.cert.getType());
+ dos.writeInt(this.cert.getEncoded().length);
+ dos.write(this.cert.getEncoded(), 0,
+ this.cert.getEncoded().length);
+
+ dos.flush();
+ out = bos.toByteArray();
+
+ } finally {
+ if (dos != null) {
+ dos.close();
+ }
+ }
+
+ return out;
+ }
+ }
+
+ /**
+ * Inner class representing a SecretKey entry.
+ *
+ * When encoded to a byte[] for storage (getEncoded()), the following
+ * format is used. Items are written out through DataOutputStream
+ * backed by ByteArrayOutputStream, thus long values are stored
+ * to 8 bytes big endian, int is 4 bytes big endian, etc.
+ *
+ * The HMAC-SHA512 at the end is calculated over all items above it along
+ * with their lengths.
+ *
+ * creationDate.getTime() (long | 8 bytes)
+ * key.getAlgorithm() (UTF String)
+ * kdfSalt.length (int | 4 bytes)
+ * kdfSalt (byte[])
+ * kdfIterations (int | 4 bytes)
+ * iv.length (int | 4 bytes)
+ * iv (byte[])
+ * encryptedKey.length (int | 4 bytes)
+ * encryptedKey (byte[])
+ * hmac.length (int | 4 bytes)
+ * hmac (byte[])
+ */
+ private static class WKSSecretKey {
+
+ byte[] encryptedKey = null; /* protected/encrypted key */
+ byte[] iv = null; /* AES IV */
+ byte[] kdfSalt = null; /* PBKDF2 salt */
+ int kdfIterations = 0; /* PBKDF2 iterations */
+ String keyAlgo = null; /* SecretKey.getAlgorithm() */
+ Date creationDate = null; /* creation date for this object */
+ byte[] hmacSha512 = null; /* HMAC over members and lengths */
+
+ protected WKSSecretKey() {
+ }
+
+ /**
+ * Create new WKSSecretKey from plaintext key, encrypt/protect using
+ * provided password.
+ *
+ * @param plainKey unencrypted private key to protect/encrypt inside
+ * this object
+ * @param password password to be used for key protection
+ *
+ * @throws IllegalArgumentException if input arguments are null
+ * @throws KeyStoreException if encrypting/protecting key fails
+ */
+ protected WKSSecretKey(byte[] plainKey, char[] password,
+ String keyAlgo, SecureRandom rand) throws IllegalArgumentException,
+ KeyStoreException {
+
+ byte[] derivedKey = null;
+ byte[] protectedKey = null;
+ byte[] hmac = null;
+ byte[] kek = new byte[WKS_ENC_KEY_LENGTH];
+ byte[] hmacKey = new byte[WKS_HMAC_KEY_LENGTH];
+ byte[] encoded = null;
+ SecureRandom rng = rand;
+
+ if (plainKey == null || plainKey.length == 0 ||
+ password == null || password.length == 0 ||
+ keyAlgo == null || keyAlgo.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Invalid null arguments when creating WKSSecretKey");
+ }
+
+ /* Generate random salt and IV */
+ this.kdfSalt = new byte[WKS_PBKDF2_SALT_SIZE];
+ this.iv = new byte[WKS_ENC_IV_LENGTH];
+
+ synchronized (randLock) {
+ if (rng == null) {
+ rng = new SecureRandom();
+ }
+ rng.nextBytes(this.kdfSalt);
+ rng.nextBytes(this.iv);
+ }
+
+ try {
+ /* Derive key encryption key from password using
+ * PBKDF2-HMAC-SHA512. Generate a 96 byte key in total, split
+ * between 32-byte AES-CBC-256 and 64-byte HMAC-SHA512 keys */
+ derivedKey = deriveKeyFromPassword(password, this.kdfSalt,
+ WKS_PBKDF2_ITERATION_COUNT,
+ WKS_ENC_KEY_LENGTH + WKS_HMAC_KEY_LENGTH);
+ if (derivedKey == null) {
+ throw new KeyStoreException(
+ "Error deriving key encryption key, got null key");
+ }
+
+ /* Split key into encrypt + HMAC keys, erase derivedKey */
+ System.arraycopy(derivedKey, 0, kek, 0, kek.length);
+ System.arraycopy(derivedKey, kek.length, hmacKey, 0,
+ hmacKey.length);
+ Arrays.fill(derivedKey, (byte)0);
+
+ /* Encrypt plain key */
+ protectedKey = encryptKey(plainKey, kek, this.iv);
+ if (protectedKey == null) {
+ throw new KeyStoreException(
+ "Failed to encrypt plaintext key");
+ }
+
+ this.encryptedKey = protectedKey;
+ this.kdfIterations = WKS_PBKDF2_ITERATION_COUNT;
+ this.keyAlgo = keyAlgo;
+ this.creationDate = new Date();
+
+ /* Get encoded byte[] of object class variables without HMAC */
+ encoded = getEncoded(false);
+ if (encoded == null) {
+ throw new KeyStoreException(
+ "Failed to get encoded WKSSecretKey for HMAC");
+ }
+
+ /* Generate internal HMAC over object contents */
+ hmac = generateHmac(hmacKey, encoded);
+ if (hmac == null) {
+ throw new KeyStoreException(
+ "Failed to generate HMAC over WKSSecretKey");
+ }
+
+ this.hmacSha512 = hmac;
+
+ } catch (KeyStoreException | IOException |
+ CertificateEncodingException e) {
+ /* Clear protected key and HMAC in case of error */
+ if (protectedKey != null) {
+ Arrays.fill(protectedKey, (byte)0);
+ }
+ if (hmac != null) {
+ Arrays.fill(hmac, (byte)0);
+ }
+ throw new KeyStoreException(e);
+
+ } finally {
+ /* Clear local KEK, HMAC key, and encoded content */
+ Arrays.fill(kek, (byte)0);
+ Arrays.fill(hmacKey, (byte)0);
+ if (encoded != null) {
+ Arrays.fill(encoded, (byte)0);
+ }
+ }
+ }
+
+ /**
+ * Create new WKSSecretKey object from encoded byte array.
+ *
+ * @param encoded encoded byte array obtained by calling WKSPrivateKey
+ * getEncoded() method.
+ *
+ * @throws IOException on error reading/parsing encoded array
+ */
+ protected WKSSecretKey(byte[] encoded)
+ throws IOException, CertificateException {
+
+ int i = 0;
+ int tmp = 0;
+ byte[] tmpArr = null;
+ String tmpStr = null;
+ ByteArrayInputStream bis = null;
+ ByteArrayInputStream certStream = null;
+ DataInputStream dis = null;
+
+ if (encoded == null || encoded.length == 0) {
+ throw new IllegalArgumentException(
+ "Input byte array cannot be null");
+ }
+
+ try {
+ bis = new ByteArrayInputStream(encoded);
+ dis = new DataInputStream(bis);
+
+ /* creationDate */
+ this.creationDate = new Date(dis.readLong());
+
+ /* SecretKey algorithm */
+ this.keyAlgo = dis.readUTF();
+
+ /* kdfSalt */
+ tmp = dis.readInt();
+ if (tmp != WKS_PBKDF2_SALT_SIZE) {
+ throw new IOException(
+ "Invalid PBKDF2 salt size: " + tmp);
+ }
+ this.kdfSalt = new byte[tmp];
+ dis.read(this.kdfSalt);
+
+ /* kdfIterations */
+ tmp = dis.readInt();
+ if (tmp < WKS_PBKDF2_MIN_ITERATIONS) {
+ throw new IOException(
+ "PBKDF2 iterations too small ( " + tmp +
+ "), min size: " + WKS_PBKDF2_MIN_ITERATIONS);
+ }
+ this.kdfIterations = tmp;
+
+ /* iv */
+ tmp = dis.readInt();
+ if (tmp != WKS_ENC_IV_LENGTH) {
+ throw new IOException(
+ "Invalid IV size: " + tmp);
+ }
+ this.iv = new byte[tmp];
+ dis.read(this.iv);
+
+ /* encrypted key */
+ tmp = dis.readInt();
+ if (tmp < 0) {
+ throw new IOException(
+ "Bad encrypted key length, negative");
+ }
+ this.encryptedKey = new byte[tmp];
+ dis.read(this.encryptedKey);
+
+ /* HMAC-SHA512 */
+ tmp = dis.readInt();
+ if (tmp != WKS_HMAC_KEY_LENGTH) {
+ throw new IOException(
+ "HMAC length (" + tmp + ") different length than " +
+ "expected (" + WKS_HMAC_KEY_LENGTH + ")");
+ }
+ this.hmacSha512 = new byte[tmp];
+ dis.read(this.hmacSha512);
+
+ } catch (Exception e) {
+
+ if (this.encryptedKey != null) {
+ Arrays.fill(this.encryptedKey, (byte)0);
+ this.encryptedKey = null;
+ }
+ if (this.iv != null) {
+ Arrays.fill(this.iv, (byte)0);
+ this.iv = null;
+ }
+ if (this.kdfSalt != null) {
+ Arrays.fill(this.kdfSalt, (byte)0);
+ this.kdfSalt = null;
+ }
+ if (this.hmacSha512 != null) {
+ Arrays.fill(this.hmacSha512, (byte)0);
+ this.hmacSha512 = null;
+ }
+ this.creationDate = null;
+ this.kdfIterations = 0;
+
+ throw e;
+
+ } finally {
+ if (dis != null) {
+ dis.close();
+ }
+ }
+ }
+
+ /**
+ * Get encoded byte array representation of this object.
+ *
+ * @param withHMAC include HMAC on end of encoded array when true,
+ * otherwise return encoded object without HMAC. HMAC is
+ * included when writing out / storing, but not included
+ * when we are just using this as a helper method to check
+ * the HMAC value inside this object in memory.
+ *
+ * @return byte array representing this object or null on error
+ * @throws IOException on error writing to output stream
+ * @throws CertificateEncodingException on error getting
+ * Certificate encoding
+ */
+ protected synchronized byte[] getEncoded(boolean withHMAC)
+ throws IOException, CertificateEncodingException {
+
+ int i;
+ byte[] out = null;
+ ByteArrayOutputStream bos = null;
+ DataOutputStream dos = null;
+
+ if (this.creationDate == null || this.keyAlgo == null ||
+ this.kdfSalt == null || this.kdfSalt.length == 0 ||
+ this.kdfIterations <= 0 || this.iv == null ||
+ this.iv.length == 0 || this.encryptedKey == null ||
+ this.encryptedKey.length == 0) {
+ log("invalid WKSSecretKey class variables, returning null " +
+ "from getEncoded()");
+ return null;
+ }
+
+ if (withHMAC && (this.hmacSha512 == null ||
+ this.hmacSha512.length == 0)) {
+ log("WKSSecretKey HMAC null or zero length, returning null " +
+ "from getEncoded()");
+ return null;
+ }
+
+ try {
+ bos = new ByteArrayOutputStream();
+ dos = new DataOutputStream(bos);
+
+ dos.writeLong(this.creationDate.getTime());
+ dos.writeUTF(this.keyAlgo);
+ dos.writeInt(this.kdfSalt.length);
+ dos.write(this.kdfSalt, 0, this.kdfSalt.length);
+ dos.writeInt(this.kdfIterations);
+ dos.writeInt(this.iv.length);
+ dos.write(this.iv, 0, this.iv.length);
+ dos.writeInt(this.encryptedKey.length);
+ dos.write(this.encryptedKey, 0, this.encryptedKey.length);
+ if (withHMAC) {
+ dos.writeInt(this.hmacSha512.length);
+ dos.write(this.hmacSha512);
+ }
+
+ dos.flush();
+ out = bos.toByteArray();
+
+ } finally {
+ if (dos != null) {
+ dos.close();
+ }
+ }
+
+ return out;
+ }
+
+ /**
+ * Decrypt and return plaintext key using provided password.
+ *
+ * Verifies internally-generated HMAC over WKSSecretKey object
+ * contents using password before decrypting key.
+ *
+ * Other than password, all other information needed should already
+ * be stored in this object.
+ *
+ * @param password password to use for decryption
+ */
+ protected synchronized byte[] getDecryptedKey(char[] password)
+ throws UnrecoverableKeyException {
+
+ byte[] plain = null;
+ byte[] derivedKey = null;
+ byte[] hmac = null;
+ byte[] kek = new byte[WKS_ENC_KEY_LENGTH];
+ byte[] hmacKey = new byte[WKS_HMAC_KEY_LENGTH];
+ byte[] encoded = null;
+
+ if (password == null || password.length == 0) {
+ throw new UnrecoverableKeyException(
+ "Unable to decrypt key with null password");
+ }
+
+ try {
+ /* Derive key encryption key from password using
+ * PBKDF2-HMAC-SHA512. Generate a 96 byte key in total, split
+ * between 32-byte AES-CBC-256 and 64-byte HMAC-SHA512 key */
+ derivedKey = deriveKeyFromPassword(password, this.kdfSalt,
+ WKS_PBKDF2_ITERATION_COUNT,
+ WKS_ENC_KEY_LENGTH + WKS_HMAC_KEY_LENGTH);
+
+ if (derivedKey == null) {
+ throw new KeyStoreException(
+ "Error deriving key decryption key, got null key");
+ }
+
+ /* Split key into decrypt + HMAC keys, erase derivedKey */
+ System.arraycopy(derivedKey, 0, kek, 0, kek.length);
+ System.arraycopy(derivedKey, kek.length, hmacKey, 0,
+ hmacKey.length);
+ Arrays.fill(derivedKey, (byte)0);
+
+ /* Get encoded byte[] of object class variables without HMAC */
+ encoded = getEncoded(false);
+ if (encoded == null) {
+ throw new KeyStoreException(
+ "Failed to get encoded WKSSecretKey for HMAC");
+ }
+
+ /* Re-generate internal HMAC over object contents */
+ hmac = generateHmac(hmacKey, encoded);
+ if (hmac == null) {
+ throw new KeyStoreException(
+ "Failed to regenerate HMAC over WKSSecretKey");
+ }
+
+ /* Verify HMAC first before decrypting key */
+ if (!WolfCrypt.ConstantCompare(hmac, this.hmacSha512)) {
+ throw new KeyStoreException(
+ "HMAC verification failed on WKSSecretKey, entry " +
+ "corrupted");
+ } else {
+ log("HMAC verification successful on WKSSecretKey");
+ }
+
+ /* Decrypt encrypted key with KEK */
+ plain = decryptKey(this.encryptedKey, kek, this.iv);
+ if (plain == null) {
+ throw new UnrecoverableKeyException(
+ "Unable to decrypt protected key");
+ }
+
+ } catch (KeyStoreException | IOException |
+ CertificateEncodingException e) {
+ if (plain != null) {
+ Arrays.fill(plain, (byte)0);
+ }
+ throw new UnrecoverableKeyException(e.getMessage());
+
+ } finally {
+ Arrays.fill(kek, (byte)0);
+ Arrays.fill(hmacKey, (byte)0);
+ if (hmac != null) {
+ Arrays.fill(hmac, (byte)0);
+ }
+ if (encoded != null) {
+ Arrays.fill(encoded, (byte)0);
+ }
+ }
+
+ return plain;
+ }
+ }
+}
+
diff --git a/src/main/java/com/wolfssl/wolfcrypt/Asn.java b/src/main/java/com/wolfssl/wolfcrypt/Asn.java
index 35f957c..c963921 100644
--- a/src/main/java/com/wolfssl/wolfcrypt/Asn.java
+++ b/src/main/java/com/wolfssl/wolfcrypt/Asn.java
@@ -33,6 +33,19 @@ public class Asn extends WolfObject {
/** Maximum encoded signature size */
public static final int MAX_ENCODED_SIG_SIZE = 512;
+ /* Key Sum values, from asn.h Key_Sum enum */
+
+ /** DSA key value, from asn.h Key_Sum enum */
+ public static final int DSAk = 515;
+ /** RSA key value, from asn.h Key_Sum enum */
+ public static final int RSAk = 645;
+ /** RSA-PSS key value, from asn.h Key_Sum enum */
+ public static final int RSAPSSk = 654;
+ /** RSA-OAEP key value, from asn.h Key_Sum enum */
+ public static final int RSAESOAEPk = 651;
+ /** ECDSA key value, from asn.h Key_Sum enum */
+ public static final int ECDSAk = 518;
+
/** Default Asn constructor */
public Asn() { }
@@ -67,5 +80,17 @@ public class Asn extends WolfObject {
* @return hash algorithm OID, for use with encodeSignature()
*/
public static native int getCTC_HashOID(int type);
+
+ /**
+ * Get the Algorithm Identifier from inside DER-encoded PKCS#8 key.
+ *
+ * @param pkcs8Der DER-encoded PKCS#8 private key
+ *
+ * @return Algorithm Identifier on success, will match one of the values
+ * for key sums (ie: Asn.RSAk, Asn.ECDSAk, etc)
+ *
+ * @throws WolfCryptException upon native error
+ */
+ public static native int getPkcs8AlgoID(byte[] pkcs8Der);
}
diff --git a/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java b/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java
index bbe045c..659529d 100644
--- a/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java
+++ b/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java
@@ -195,6 +195,14 @@ public class FeatureDetect {
*/
public static native boolean HmacSha3_512Enabled();
+ /**
+ * Tests if PKCS#5 PBKDF1 is compiled into the native wolfSSL library.
+ *
+ * @return true if PBKDF1 is enabled (HAVE_PBKDF1, !NO_PWDBASED),
+ * otherwise false.
+ */
+ public static native boolean Pbkdf1Enabled();
+
/**
* Tests if PKCS#5 v2.1 PBKDF2 is compiled into the native wolfSSL library.
*
@@ -203,6 +211,14 @@ public class FeatureDetect {
*/
public static native boolean Pbkdf2Enabled();
+ /**
+ * Tests if PKCS#12 PBKDF is compiled into the native wolfSSL library.
+ *
+ * @return true if PKCS#12 PBKDF is enabled (HAVE_PKCS12, !NO_PWDBASED),
+ * otherwise false.
+ */
+ public static native boolean Pkcs12PbkdfEnabled();
+
/**
* Tests if RSA is compiled into the native wolfSSL library.
*
diff --git a/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java b/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java
index abf3f0c..a6b96fe 100644
--- a/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java
+++ b/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java
@@ -150,6 +150,34 @@ public class WolfCrypt extends WolfObject {
*/
public static native boolean CrlEnabled();
+ /**
+ * Constant time byte array comparison.
+ *
+ * If arrays are of different lengths, return false right away. Apart
+ * from length check, this matches native wolfSSL ConstantCompare()
+ * logic in misc.c.
+ *
+ * @param a first byte array for comparison
+ * @param b second byte array for comparison
+ *
+ * @return true if equal, otherwise false
+ */
+ public static boolean ConstantCompare(byte[] a, byte[] b) {
+
+ int i;
+ int compareSum = 0;
+
+ if (a.length != b.length) {
+ return false;
+ }
+
+ for (i = 0; i < a.length; i++) {
+ compareSum |= a[i] ^ b[i];
+ }
+
+ return (compareSum == 0);
+ }
+
private WolfCrypt() {
}
}
diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java b/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java
index 929381a..55e5fc8 100644
--- a/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java
+++ b/src/test/java/com/wolfssl/provider/jce/test/WolfJCETestSuite.java
@@ -39,7 +39,8 @@ import org.junit.runners.Suite.SuiteClasses;
WolfCryptCipherTest.class,
WolfCryptKeyAgreementTest.class,
WolfCryptKeyPairGeneratorTest.class,
- WolfCryptPKIXCertPathValidatorTest.class
+ WolfCryptPKIXCertPathValidatorTest.class,
+ WolfSSLKeyStoreTest.class
})
public class WolfJCETestSuite { }
diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java
new file mode 100644
index 0000000..deb6df4
--- /dev/null
+++ b/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java
@@ -0,0 +1,1658 @@
+/* wolfSSLKeyStoreTest.java
+ *
+ * Copyright (C) 2006-2024 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+package com.wolfssl.provider.jce.test;
+
+import static org.junit.Assert.*;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.BeforeClass;
+import org.junit.AfterClass;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.nio.file.Files;
+import java.security.Security;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.KeyFactory;
+import java.security.KeyStoreException;
+import java.security.NoSuchProviderException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import com.wolfssl.provider.jce.WolfCryptProvider;
+
+public class WolfSSLKeyStoreTest {
+
+ private final String storeType = "WKS";
+ private final String jksExt = ".wks";
+ private static final String storeProvider = "wolfJCE";
+ protected static String storePass = "wolfSSL test";
+
+ /*
+ * Example Certificate and Key file paths:
+ * serverCertDer = server-cert.der
+ * serverEccDer = server-ecc.der
+ * clientCertDer = client-cert.der
+ * clientEccCertDer = client-ecc-cert.der
+ * caCertDer = ca-cert.der
+ * caEccCertDer = ca-ecc-cert.der
+ */
+ protected static String serverCertDer = null;
+ protected static String serverEccDer = null;
+ protected static String clientCertDer = null;
+ protected static String clientEccCertDer = null;
+ protected static String caCertDer = null;
+ protected static String caEccCertDer = null;
+
+ /*
+ * Example private key files:
+ * server-keyPkcs8.der, matches to server-cert.der
+ * ecc-keyPkcs8.der, matches to server-ecc.der
+ */
+ protected static String serverPkcs8Der = null;
+ protected static String eccPkcs8Der = null;
+
+ /* RSA-based cert chain with intermediates:
+ * server/peer: server-int-cert.der
+ * intermediate CA 2: ca-int2-cert.der
+ * intermediate CA 1: ca-int-cert.der
+ * root CA: ca-cert.pem */
+ protected static String intRsaServerCertDer = null;
+ protected static String intRsaInt2CertDer = null;
+ protected static String intRsaInt1CertDer = null;
+
+ /* ECC-based cert chain with intermediates:
+ * server/peer: server-int-ecc-cert.der
+ * intermediate CA 2: ca-in2-ecc-cert.der
+ * intermediate CA 1: ca-int-ecc-cert.der
+ * root CA: ca-ecc-cert.pem */
+ protected static String intEccServerCertDer = null;
+ protected static String intEccInt2CertDer = null;
+ protected static String intEccInt1CertDer = null;
+
+ /* Java PrivateKey / Certificate objects containing example key/certs */
+ private static PrivateKey serverKeyRsa = null; /* server-keyPkcs8.der */
+ private static PrivateKey serverKeyEcc = null; /* ecc-keyPkcs8.der */
+ private static Certificate serverCertRsa = null; /* server-cert.der */
+ private static Certificate serverCertEcc = null; /* server-ecc.der */
+ private static Certificate clientCertRsa = null; /* client-cert.der */
+ private static Certificate clientCertEcc = null; /* client-ecc-cert.der */
+ private static Certificate[] rsaServerChain = null; /* RSA chain */
+ private static Certificate[] eccServerChain = null; /* ECC chain */
+ private static Certificate[] invalidChain = null;
+
+ /* Example .wks KeyStore file paths */
+ private static String clientWKS = null; /* client.wks */
+ private static String clientRsa1024WKS = null; /* client-rsa-1024.wks */
+ private static String clientRsaWKS = null; /* client-rsa.wks */
+ private static String clientEccWKS = null; /* client-ecc.wks */
+ private static String serverWKS = null; /* server.wks */
+ private static String serverRsa1024WKS = null; /* server-rsa-1024.wks */
+ private static String serverRsaWKS = null; /* server-rsa.wks */
+ private static String serverEccWKS = null; /* server-ecc.wks */
+ private static String caCertsWKS = null; /* cacerts.wks */
+ private static String caClientWKS = null; /* ca-client.wks */
+ private static String caServerWKS = null; /* ca-server.wks */
+ private static String caServerRsa2048WKS = null; /* ca-server-rsa-2048.wks */
+ private static String caServerEcc256WKS = null; /* ca-server-ecc-256.wks */
+
+ /* Class wide SecureRandom for use, only initialize once */
+ private SecureRandom rand = new SecureRandom();
+
+ /* Used to store/reset Java Security property for PBKDF2 iteration
+ * count. Default 210,000 PBKDF2 iterations makes this test run very
+ * slow. We set down to 10,000 for test duration. */
+ private static boolean iterationCountPropSet = false;
+ private static String iterationCountProp = null;
+
+ /**
+ * Test if this environment is Android.
+ * @return true if Android, otherwise false
+ */
+ private static boolean isAndroid() {
+ if (System.getProperty("java.runtime.name").contains("Android")) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Read in and convert DER private key into PrivateKey object.
+ *
+ * @param derFilePath file path to DER-encoded private key
+ * @param alg algorithm type: "RSA", "EC"
+ *
+ * @return new PrivateKey object representing DER key file passed in
+ *
+ * @throws IllegalArgumentException on bad argument or processing of arg
+ * @throws IOException on error converting File to Path
+ * @throws NoSuchAlgorithmException on bad "alg" when getting KeyFactory
+ * @throws InvalidKeySpecException on error generating PrivateKey object
+ * @throws Exception on other error
+ */
+ private static PrivateKey derFileToPrivateKey(String derFilePath,
+ String alg) throws IllegalArgumentException, IOException,
+ NoSuchAlgorithmException, InvalidKeySpecException,
+ InvalidKeySpecException {
+
+ File file = null;
+ byte[] fileBytes = null;
+ PKCS8EncodedKeySpec spec = null;
+ KeyFactory kf = null;
+ PrivateKey key = null;
+
+ if (derFilePath == null || derFilePath.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Input DER file path is null or empty");
+ }
+
+ file = new File(derFilePath);
+ fileBytes = Files.readAllBytes(file.toPath());
+
+ if (fileBytes == null || fileBytes.length == 0) {
+ throw new IllegalArgumentException(
+ "Bytes read from DER file is null or empty, bad file path?");
+ }
+
+ spec = new PKCS8EncodedKeySpec(fileBytes);
+ if (spec == null) {
+ throw new InvalidKeySpecException(
+ "Unable to create PKCS8EncodedKeySpec");
+ }
+
+ kf = KeyFactory.getInstance(alg);
+ key = kf.generatePrivate(spec);
+
+ return key;
+ }
+
+ /**
+ * Read in and convert certificate file to Certificate object.
+ *
+ * @param certPath path to certificate file
+ *
+ * @return new Certificate object representing certPath file
+ *
+ * @throws FileNotFoundException on error reading certPath file
+ * @throws CertificateException on error geting CertificateFactory or
+ * generating Certificate object
+ */
+ private static Certificate certFileToCertificate(String certPath)
+ throws FileNotFoundException, CertificateException {
+
+ FileInputStream fis = null;
+ CertificateFactory cf = null;
+ Certificate cert = null;
+
+ fis = new FileInputStream(certPath);
+ cf = CertificateFactory.getInstance("X.509");
+ cert = cf.generateCertificate(fis);
+
+ return cert;
+ }
+
+
+ /**
+ * Create PrivateKey and Certificate objects based on files.
+ * Assumes paths have already been set prior in
+ * testSetupAndProviderInstallation().
+ */
+ private static void createTestObjects()
+ throws IOException, FileNotFoundException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException {
+
+ Certificate tmpCert = null;
+
+ /* Create PrivateKey from server RSA private key DER */
+ serverKeyRsa = derFileToPrivateKey(serverPkcs8Der, "RSA");
+ assertNotNull(serverKeyRsa);
+
+ /* Create PrivateKey from server ECC private key DER */
+ serverKeyEcc = derFileToPrivateKey(eccPkcs8Der, "EC");
+ assertNotNull(serverKeyEcc);
+
+ /* Create Certificate from server RSA cert */
+ serverCertRsa = certFileToCertificate(serverCertDer);
+ assertNotNull(serverCertRsa);
+
+ /* Create Certificate from server ECC cert */
+ serverCertEcc = certFileToCertificate(serverEccDer);
+ assertNotNull(serverCertEcc);
+
+ /* Create Certificate from client RSA cert */
+ clientCertRsa = certFileToCertificate(clientCertDer);
+ assertNotNull(clientCertRsa);
+
+ /* Create Certificate from client ECC cert */
+ clientCertEcc = certFileToCertificate(clientEccCertDer);
+ assertNotNull(clientCertEcc);
+
+ /* Create RSA cert chain */
+ rsaServerChain = new Certificate[3];
+ tmpCert = certFileToCertificate(intRsaServerCertDer);
+ rsaServerChain[0] = tmpCert;
+ tmpCert = certFileToCertificate(intRsaInt2CertDer);
+ rsaServerChain[1] = tmpCert;
+ tmpCert = certFileToCertificate(intRsaInt1CertDer);
+ rsaServerChain[2] = tmpCert;
+
+ /* Create ECC cert chain */
+ eccServerChain = new Certificate[3];
+ tmpCert = certFileToCertificate(intEccServerCertDer);
+ eccServerChain[0] = tmpCert;
+ tmpCert = certFileToCertificate(intEccInt2CertDer);
+ eccServerChain[1] = tmpCert;
+ tmpCert = certFileToCertificate(intEccInt1CertDer);
+ eccServerChain[2] = tmpCert;
+
+ /* Create invalid cert chain */
+ invalidChain = new Certificate[3];
+ tmpCert = certFileToCertificate(intRsaServerCertDer);
+ invalidChain[0] = tmpCert;
+ tmpCert = certFileToCertificate(intEccInt2CertDer);
+ invalidChain[1] = tmpCert;
+ tmpCert = certFileToCertificate(intRsaInt1CertDer);
+ invalidChain[2] = tmpCert;
+ }
+
+ @BeforeClass
+ public static void testSetupAndProviderInstallation()
+ throws Exception, NoSuchProviderException {
+
+ String certPre = "";
+
+ /* Install wolfJCE provider at runtime */
+ Security.insertProviderAt(new WolfCryptProvider(), 1);
+
+ Provider p = Security.getProvider(storeProvider);
+ assertNotNull(p);
+
+ if (isAndroid()) {
+ /* On Android, example certs/keys/KeyStores are on SD card */
+ certPre = "/sdcard/";
+ }
+
+ /* Set paths to example certs/keys */
+ serverCertDer =
+ certPre.concat("examples/certs/server-cert.der");
+ serverEccDer =
+ certPre.concat("examples/certs/server-ecc.der");
+ caCertDer =
+ certPre.concat("examples/certs/ca-cert.der");
+
+ clientCertDer =
+ certPre.concat("examples/certs/client-cert.der");
+ clientEccCertDer =
+ certPre.concat("examples/certs/client-ecc-cert.der");
+ caEccCertDer =
+ certPre.concat("examples/certs/ca-ecc-cert.der");
+
+ serverPkcs8Der =
+ certPre.concat("examples/certs/server-keyPkcs8.der");
+ eccPkcs8Der =
+ certPre.concat("examples/certs/ecc-keyPkcs8.der");
+
+ intRsaServerCertDer =
+ certPre.concat("examples/certs/intermediate/server-int-cert.pem");
+ intRsaInt1CertDer =
+ certPre.concat("examples/certs/intermediate/ca-int-cert.pem");
+ intRsaInt2CertDer =
+ certPre.concat("examples/certs/intermediate/ca-int2-cert.pem");
+
+ intEccServerCertDer =
+ certPre.concat("examples/certs/intermediate/server-int-ecc-cert.der");
+ intEccInt1CertDer =
+ certPre.concat("examples/certs/intermediate/ca-int-ecc-cert.der");
+ intEccInt2CertDer =
+ certPre.concat("examples/certs/intermediate/ca-int2-ecc-cert.der");
+
+ /* Set paths to example WKS KeyStore files */
+ clientWKS =
+ certPre.concat("examples/certs/client.wks");
+ clientRsa1024WKS =
+ certPre.concat("examples/certs/client-rsa-1024.wks");
+ clientRsaWKS =
+ certPre.concat("examples/certs/client-rsa.wks");
+ clientEccWKS =
+ certPre.concat("examples/certs/client-ecc.wks");
+ serverWKS =
+ certPre.concat("examples/certs/server.wks");
+ serverRsa1024WKS =
+ certPre.concat("examples/certs/server-rsa-1024.wks");
+ serverRsaWKS =
+ certPre.concat("examples/certs/server-rsa.wks");
+ serverEccWKS =
+ certPre.concat("examples/certs/server-ecc.wks");
+ caCertsWKS =
+ certPre.concat("examples/certs/cacerts.wks");
+ caClientWKS =
+ certPre.concat("examples/certs/ca-client.wks");
+ caServerWKS =
+ certPre.concat("examples/certs/ca-server.wks");
+ caServerRsa2048WKS =
+ certPre.concat("examples/certs/ca-server-rsa-2048.wks");
+ caServerEcc256WKS =
+ certPre.concat("examples/certs/ca-server-ecc-256.wks");
+
+ /* Test if file exists, if not might be running on Android */
+ File f = new File(serverCertDer);
+ if (!f.exists()) {
+ /* No known file paths, throw exception */
+ System.out.println("Could not find example cert file " +
+ f.getAbsolutePath());
+ throw new Exception("Unable to find example cert files for test");
+ }
+
+ /* Create PrivateKey / Certificate objects from files */
+ createTestObjects();
+
+ /* Save existing PBKDF2 iteration count, set lower for test */
+ String iCount = Security.getProperty("wolfjce.wks.iterationCount");
+ iterationCountProp = iCount;
+ Security.setProperty("wolfjce.wks.iterationCount", "10000");
+ iterationCountPropSet = true;
+ }
+
+ @AfterClass
+ public static void resetSecurityProperties()
+ throws Exception, NoSuchProviderException {
+
+ if (iterationCountPropSet && (iterationCountProp != null)) {
+ Security.setProperty("wolfjce.wks.iterationCount",
+ iterationCountProp);
+ }
+ }
+
+ @Test
+ public void testGetKeyStoreFromProvider()
+ throws NoSuchProviderException, KeyStoreException {
+
+ KeyStore store = null;
+
+ /* Getting WKS after wolfJCE is installed should work w/o exception */
+ store = KeyStore.getInstance(storeType);
+
+ /* Getting WKS type from wolfJCE should work without exception */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ assertNotNull(store);
+
+ try {
+ store = KeyStore.getInstance("NotValid", storeProvider);
+ } catch (KeyStoreException e) {
+ /* expected */
+ }
+ }
+
+ @Test
+ public void testStoreSingleKeyAndCert()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ PrivateKey keyOut = null;
+ Certificate certOut = null;
+
+ /* Storing single RSA key and matching cert should succeed */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setKeyEntry("serverCert", serverKeyRsa, storePass.toCharArray(),
+ new Certificate[] { serverCertRsa });
+ assertEquals(1, store.size());
+
+ keyOut = (PrivateKey)store.getKey("serverCert",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ if (!serverKeyRsa.equals(keyOut)) {
+ fail("Key get/set does not match each other");
+ }
+ certOut = store.getCertificate("serverCert");
+ assertNotNull(certOut);
+ assertEquals(serverCertRsa, certOut);
+
+ /* Storing single ECC key and matching cert should succeed */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setKeyEntry("serverCert", serverKeyEcc, storePass.toCharArray(),
+ new Certificate[] { serverCertEcc });
+ assertEquals(1, store.size());
+
+ keyOut = (PrivateKey)store.getKey("serverCert",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ if (!serverKeyEcc.equals(keyOut)) {
+ fail("Key get/set does not match each other");
+ }
+ certOut = store.getCertificate("serverCert");
+ assertNotNull(certOut);
+ assertEquals(serverCertEcc, certOut);
+
+ /* Storing RSA key with non-matching cert should fail */
+ /* SUN JKS seems to allow loading invalid key/cert matches */
+ if (!storeProvider.equals("SUN")) {
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ try {
+ store.setKeyEntry("invalidKey", serverKeyRsa,
+ storePass.toCharArray(),
+ new Certificate[] { serverCertEcc });
+ fail("setKeyEntry() should fail with mismatched key/cert");
+ } catch (KeyStoreException e) {
+ /* expected */
+ }
+ assertEquals(0, store.size());
+
+ /* Storing ECC key with non-matching cert should fail */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ try {
+ store.setKeyEntry("invalidKey", serverKeyEcc,
+ storePass.toCharArray(),
+ new Certificate[] { serverCertRsa });
+ fail("setKeyEntry() should fail with mismatched key/cert");
+ } catch (KeyStoreException e) {
+ /* expected */
+ }
+ assertEquals(0, store.size());
+ }
+ }
+
+ @Test
+ public void testStoreMultipleKeyAndCertPairs()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ PrivateKey keyOut = null;
+ Certificate certOut = null;
+
+ /* Storing multiple matching key/cert pairs should succeed */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setKeyEntry("serverCertRsa", serverKeyRsa,
+ storePass.toCharArray(), new Certificate[] { serverCertRsa });
+ assertEquals(1, store.size());
+ store.setKeyEntry("serverCertEcc", serverKeyEcc,
+ storePass.toCharArray(), new Certificate[] { serverCertEcc });
+ assertEquals(2, store.size());
+
+ keyOut = (PrivateKey)store.getKey("serverCertRsa",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ if (!serverKeyRsa.equals(keyOut)) {
+ fail("RSA Key get/set does not match each other");
+ }
+ certOut = store.getCertificate("serverCertRsa");
+ assertNotNull(certOut);
+ assertEquals(serverCertRsa, certOut);
+
+ keyOut = (PrivateKey)store.getKey("serverCertEcc",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ if (!serverKeyEcc.equals(keyOut)) {
+ fail("ECC Key get/set does not match each other");
+ }
+ certOut = store.getCertificate("serverCertEcc");
+ assertNotNull(certOut);
+ assertEquals(serverCertEcc, certOut);
+ }
+
+ @Test
+ public void testStoreSingleKeyAndCertChain()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ PrivateKey keyOut = null;
+ Certificate[] chainOut = null;
+
+ /* Storing single RSA key/cert chain should succeed */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setKeyEntry("serverRsa", serverKeyRsa,
+ storePass.toCharArray(), rsaServerChain);
+ assertEquals(1, store.size());
+
+ keyOut = (PrivateKey)store.getKey("serverRsa",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ if (!serverKeyRsa.equals(keyOut)) {
+ fail("RSA get/set Key does not match each other");
+ }
+ chainOut = store.getCertificateChain("serverRsa");
+ assertNotNull(chainOut);
+ if (!Arrays.equals(rsaServerChain, chainOut)) {
+ fail("RSA get/set chain does not match");
+ }
+
+ /* Storing single ECC key/cert chain should succeed */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setKeyEntry("serverEcc", serverKeyEcc,
+ storePass.toCharArray(), eccServerChain);
+ assertEquals(1, store.size());
+
+ keyOut = (PrivateKey)store.getKey("serverEcc",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ if (!serverKeyEcc.equals(keyOut)) {
+ fail("ECC get/set Key does not match each other");
+ }
+ chainOut = store.getCertificateChain("serverEcc");
+ assertNotNull(chainOut);
+ if (!Arrays.equals(eccServerChain, chainOut)) {
+ fail("ECC get/set chain does not match");
+ }
+
+ /* Storing invalid chain should fail */
+ /* SUN JKS seems to allow loading invalid cert chains, but we don't */
+ if (!storeProvider.equals("SUN")) {
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ try {
+ store.setKeyEntry("serverRsa", serverKeyRsa,
+ storePass.toCharArray(), invalidChain);
+ fail("setKeyEntry() with invalid chain should fail");
+ } catch (KeyStoreException e) {
+ /* expected */
+ }
+ assertEquals(0, store.size());
+ }
+ }
+
+ @Test
+ public void testStoreMultipleKeyAndCertChains()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ PrivateKey keyOut = null;
+ Certificate[] chainOut = null;
+
+ /* Storing multiple valid key/cert chain should succeed */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setKeyEntry("serverRsa", serverKeyRsa,
+ storePass.toCharArray(), rsaServerChain);
+ assertEquals(1, store.size());
+ store.setKeyEntry("serverEcc", serverKeyEcc,
+ storePass.toCharArray(), eccServerChain);
+ assertEquals(2, store.size());
+
+ keyOut = (PrivateKey)store.getKey("serverRsa",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ if (!serverKeyRsa.equals(keyOut)) {
+ fail("RSA get/set Key does not match");
+ }
+ chainOut = store.getCertificateChain("serverRsa");
+ assertNotNull(chainOut);
+ if (!Arrays.equals(rsaServerChain, chainOut)) {
+ fail("RSA get/set chain does not match");
+ }
+
+ keyOut = (PrivateKey)store.getKey("serverEcc",
+ storePass.toCharArray());
+ if (!serverKeyEcc.equals(keyOut)) {
+ fail("ECC get/set Key does not match each other");
+ }
+ chainOut = store.getCertificateChain("serverEcc");
+ assertNotNull(chainOut);
+ if (!Arrays.equals(eccServerChain, chainOut)) {
+ fail("ECC get/set chain does not match");
+ }
+
+ /* Storing invalid chain should fail */
+ /* SUN JKS seems to allow loading invalid cert chains, but we don't */
+ if (!storeProvider.equals("SUN")) {
+ try {
+ store.setKeyEntry("serverRsa", serverKeyRsa,
+ storePass.toCharArray(), invalidChain);
+ fail("setKeyEntry() with invalid chain should fail");
+ } catch (KeyStoreException e) {
+ /* expected */
+ }
+ /* Verify size of KeyStore has not changed on failure storing entry */
+ assertEquals(2, store.size());
+ }
+ }
+
+ @Test
+ public void testStoreSingleCertOnly()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ Certificate certOut = null;
+ String alias = null;
+
+ /* Storing single RSA cert should succeed */
+ alias = "serverRsa";
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setCertificateEntry(alias, serverCertRsa);
+ assertEquals(1, store.size());
+ assertTrue(store.isCertificateEntry(alias));
+
+ certOut = store.getCertificate(alias);
+ assertNotNull(certOut);
+ assertEquals(serverCertRsa, certOut);
+ if (storeProvider.equals("SUN")) {
+ /* SUN JKS seems to lowercase all aliases, but we don't */
+ assertEquals(alias.toLowerCase(),
+ store.getCertificateAlias(serverCertRsa));
+ }
+ else {
+ assertEquals(alias, store.getCertificateAlias(serverCertRsa));
+ }
+
+ /* Storing single ECC cert should succeed */
+ alias = "serverEcc";
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setCertificateEntry(alias, serverCertEcc);
+ assertEquals(1, store.size());
+ assertTrue(store.isCertificateEntry(alias));
+
+ certOut = store.getCertificate(alias);
+ assertNotNull(certOut);
+ assertEquals(serverCertEcc, certOut);
+ if (storeProvider.equals("SUN")) {
+ /* SUN JKS seems to lowercase all aliases, but we don't */
+ assertEquals(alias.toLowerCase(),
+ store.getCertificateAlias(serverCertEcc));
+ }
+ else {
+ assertEquals(alias, store.getCertificateAlias(serverCertEcc));
+ }
+
+ /* Storing null cert should still pass (matching SUN behavior) */
+ alias = "serverRsa";
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setCertificateEntry(alias, null);
+ assertEquals(1, store.size());
+ assertTrue(store.isCertificateEntry(alias));
+
+ certOut = store.getCertificate(alias);
+ assertNull(certOut);
+ assertNull(store.getCertificateAlias(serverCertRsa));
+ }
+
+ @Test
+ public void testStoreSecretKeysOnly()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ KeyGenerator kg = null;
+ SecretKey hmacKey = null;
+ Key keyOut = null;
+ SecretKey aesKey = null;
+
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+
+ /* Generate HMAC key (256-bit) */
+ kg = KeyGenerator.getInstance("HmacSHA256");
+ assertNotNull(kg);
+ kg.init(256, rand);
+ hmacKey = kg.generateKey();
+ assertNotNull(hmacKey);
+ assertTrue(hmacKey.getEncoded().length > 0);
+
+ /* Generate AES key (256-bit) */
+ kg = KeyGenerator.getInstance("AES");
+ assertNotNull(kg);
+ kg.init(256, rand);
+ aesKey = kg.generateKey();
+ assertNotNull(aesKey);
+ assertTrue(aesKey.getEncoded().length > 0);
+
+ /* Store HMAC and AES key */
+ store.setKeyEntry("hmacKey", hmacKey, storePass.toCharArray(), null);
+ assertEquals(1, store.size());
+ assertTrue(store.isKeyEntry("hmacKey"));
+
+ store.setKeyEntry("aesKey", aesKey, storePass.toCharArray(), null);
+ assertEquals(2, store.size());
+ assertTrue(store.isKeyEntry("aesKey"));
+
+ /* Read keys back out, compare against original */
+ keyOut = store.getKey("hmacKey", storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertTrue(keyOut instanceof SecretKey);
+ assertEquals(hmacKey, keyOut);
+ assertTrue(Arrays.equals(hmacKey.getEncoded(), keyOut.getEncoded()));
+
+ keyOut = store.getKey("aesKey", storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertTrue(keyOut instanceof SecretKey);
+ assertEquals(aesKey, keyOut);
+ assertTrue(Arrays.equals(aesKey.getEncoded(), keyOut.getEncoded()));
+ }
+
+ @Test
+ public void testStoreMultipleCertsKeysChains()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ PrivateKey keyOut = null;
+ Certificate certOut = null;
+ Certificate[] chainOut = null;
+ KeyGenerator kg = null;
+ SecretKey aesKey = null;
+ SecretKey sKeyOut = null;
+
+ /* Storing multiple certs/keys/chains should succeed */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+
+ /** ----- INSERT entries ----- */
+
+ /* INSERT [1]: RSA cert only */
+ store.setCertificateEntry("serverRsa", serverCertRsa);
+ assertEquals(1, store.size());
+ assertTrue(store.isCertificateEntry("serverRsa"));
+
+ /* INSERT [2]: ECC cert only */
+ store.setCertificateEntry("serverEcc", serverCertEcc);
+ assertEquals(2, store.size());
+ assertTrue(store.isCertificateEntry("serverEcc"));
+
+ /* INSERT [3]: RSA priv key + cert */
+ store.setKeyEntry("rsaCert", serverKeyRsa,
+ storePass.toCharArray(),
+ new Certificate[] { serverCertRsa });
+ assertEquals(3, store.size());
+ assertTrue(store.isKeyEntry("rsaCert"));
+
+ /* INSERT [4]: ECC priv key + cert */
+ store.setKeyEntry("eccCert", serverKeyEcc,
+ storePass.toCharArray(),
+ new Certificate[] { serverCertEcc });
+ assertEquals(4, store.size());
+ assertTrue(store.isKeyEntry("eccCert"));
+
+ /* INSERT [5]: RSA priv key + chain */
+ store.setKeyEntry("rsaChain", serverKeyRsa,
+ storePass.toCharArray(), rsaServerChain);
+ assertEquals(5, store.size());
+ assertTrue(store.isKeyEntry("rsaChain"));
+
+ /* INSERT [6]: ECC priv key + chain */
+ store.setKeyEntry("eccChain", serverKeyEcc,
+ storePass.toCharArray(), eccServerChain);
+ assertEquals(6, store.size());
+ assertTrue(store.isKeyEntry("eccChain"));
+
+ /* INSERT [7]: AES SecretKey */
+ kg = KeyGenerator.getInstance("AES");
+ assertNotNull(kg);
+ kg.init(256, rand);
+ aesKey = kg.generateKey();
+ assertNotNull(aesKey);
+ assertTrue(aesKey.getEncoded().length > 0);
+ store.setKeyEntry("aesKey", aesKey, storePass.toCharArray(), null);
+
+ /** ----- GET/VERIFY entries ----- */
+
+ /* GET/VERIFY [1] */
+ certOut = store.getCertificate("serverRsa");
+ assertNotNull(certOut);
+ assertEquals(serverCertRsa, certOut);
+
+ /* GET/VERIFY [2] */
+ certOut = store.getCertificate("serverEcc");
+ assertNotNull(certOut);
+ assertEquals(serverCertEcc, certOut);
+
+ /* GET/VERIFY [3] */
+ keyOut = (PrivateKey)store.getKey("rsaCert", storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyRsa, keyOut);
+ certOut = store.getCertificate("rsaCert");
+ assertNotNull(certOut);
+ assertEquals(serverCertRsa, certOut);
+
+ /* GET/VERIFY [4] */
+ keyOut = (PrivateKey)store.getKey("eccCert", storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyEcc, keyOut);
+ certOut = store.getCertificate("eccCert");
+ assertNotNull(certOut);
+ assertEquals(serverCertEcc, certOut);
+
+ /* GET/VERIFY [5] */
+ keyOut = (PrivateKey)store.getKey("rsaChain", storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyRsa, keyOut);
+ chainOut = store.getCertificateChain("rsaChain");
+ assertNotNull(chainOut);
+ assertTrue(Arrays.equals(rsaServerChain, chainOut));
+
+ /* GET/VERIFY [6] */
+ keyOut = (PrivateKey)store.getKey("eccChain", storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyEcc, keyOut);
+ chainOut = store.getCertificateChain("eccChain");
+ assertNotNull(chainOut);
+ assertTrue(Arrays.equals(eccServerChain, chainOut));
+
+ /* GET/VERIFY [7] */
+ sKeyOut = (SecretKey)store.getKey("aesKey", storePass.toCharArray());
+ assertNotNull(sKeyOut);
+ assertEquals(aesKey, sKeyOut);
+ assertTrue(Arrays.equals(aesKey.getEncoded(), sKeyOut.getEncoded()));
+ }
+
+ @Test
+ public void testDeleteEntry()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ PrivateKey keyOut = null;
+ Certificate certOut = null;
+ Certificate[] chainOut = null;
+ KeyGenerator kg = null;
+ SecretKey aesKey = null;
+ SecretKey sKeyOut = null;
+
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+
+ /** ----- INSERT entries ----- */
+
+ /* INSERT [1]: RSA cert only */
+ store.setCertificateEntry("serverRsa", serverCertRsa);
+ assertEquals(1, store.size());
+ assertTrue(store.isCertificateEntry("serverRsa"));
+ assertTrue(store.containsAlias("serverRsa"));
+
+ /* INSERT [2]: ECC cert only */
+ store.setCertificateEntry("serverEcc", serverCertEcc);
+ assertEquals(2, store.size());
+ assertTrue(store.isCertificateEntry("serverEcc"));
+ assertTrue(store.containsAlias("serverEcc"));
+
+ /* INSERT [3]: RSA priv key + cert */
+ store.setKeyEntry("rsaCert", serverKeyRsa,
+ storePass.toCharArray(),
+ new Certificate[] { serverCertRsa });
+ assertEquals(3, store.size());
+ assertTrue(store.isKeyEntry("rsaCert"));
+ assertTrue(store.containsAlias("rsaCert"));
+
+ /* INSERT [4]: ECC priv key + cert */
+ store.setKeyEntry("eccCert", serverKeyEcc,
+ storePass.toCharArray(),
+ new Certificate[] { serverCertEcc });
+ assertEquals(4, store.size());
+ assertTrue(store.isKeyEntry("eccCert"));
+ assertTrue(store.containsAlias("eccCert"));
+
+ /* INSERT [5]: RSA priv key + chain */
+ store.setKeyEntry("rsaChain", serverKeyRsa,
+ storePass.toCharArray(), rsaServerChain);
+ assertEquals(5, store.size());
+ assertTrue(store.isKeyEntry("rsaChain"));
+ assertTrue(store.containsAlias("rsaChain"));
+
+ /* INSERT [6]: ECC priv key + chain */
+ store.setKeyEntry("eccChain", serverKeyEcc,
+ storePass.toCharArray(), eccServerChain);
+ assertEquals(6, store.size());
+ assertTrue(store.isKeyEntry("eccChain"));
+ assertTrue(store.containsAlias("eccChain"));
+
+ /* INSERT [7]: AES SecretKey */
+ kg = KeyGenerator.getInstance("AES");
+ assertNotNull(kg);
+ kg.init(256, rand);
+ aesKey = kg.generateKey();
+ assertNotNull(aesKey);
+ assertTrue(aesKey.getEncoded().length > 0);
+ store.setKeyEntry("aesKey", aesKey, storePass.toCharArray(), null);
+ assertEquals(7, store.size());
+ assertTrue(store.isKeyEntry("aesKey"));
+ assertTrue(store.containsAlias("aesKey"));
+
+ /** ----- REMOVE entries ----- */
+
+ store.deleteEntry("serverRsa");
+ assertFalse(store.containsAlias("serverRsa"));
+ assertEquals(6, store.size());
+
+ store.deleteEntry("serverEcc");
+ assertFalse(store.containsAlias("serverEcc"));
+ assertEquals(5, store.size());
+
+ store.deleteEntry("rsaCert");
+ assertFalse(store.containsAlias("rsaCert"));
+ assertEquals(4, store.size());
+
+ store.deleteEntry("eccCert");
+ assertFalse(store.containsAlias("eccCert"));
+ assertEquals(3, store.size());
+
+ store.deleteEntry("rsaChain");
+ assertFalse(store.containsAlias("rsaChain"));
+ assertEquals(2, store.size());
+
+ store.deleteEntry("eccChain");
+ assertFalse(store.containsAlias("eccChain"));
+ assertEquals(1, store.size());
+
+ store.deleteEntry("aesKey");
+ assertFalse(store.containsAlias("aesKey"));
+ assertEquals(0, store.size());
+ }
+
+ @Test
+ public void testAliases()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ Enumeration aliases = null;
+ List aliasList = null;
+
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setKeyEntry("serverRsa", serverKeyRsa,
+ storePass.toCharArray(), rsaServerChain);
+ store.setKeyEntry("serverEcc", serverKeyEcc,
+ storePass.toCharArray(), eccServerChain);
+
+ aliases = store.aliases();
+ aliasList = Collections.list(aliases);
+ assertEquals(2, aliasList.size());
+ if (storeProvider.equals("SUN")) {
+ /* SUN JKS lower cases all aliases, but we don't */
+ assertTrue(aliasList.contains("serverrsa"));
+ assertTrue(aliasList.contains("serverecc"));
+ }
+ else {
+ assertTrue(aliasList.contains("serverRsa"));
+ assertTrue(aliasList.contains("serverEcc"));
+ }
+ }
+
+ @Test
+ public void testGetType()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+
+ assertEquals("WKS", store.getType());
+ }
+
+ @Test
+ public void testStoreAndLoadEmptyKeyStore()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ ByteArrayOutputStream bos = null;
+ byte[] storeOut = null;
+
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+
+ /* Store KeyStore with no entries */
+ bos = new ByteArrayOutputStream();
+ store.store(bos, storePass.toCharArray());
+ storeOut = bos.toByteArray();
+ bos.close();
+
+ assertNotNull(storeOut);
+ assertTrue(storeOut.length > 0);
+
+ /* Load back in empty stored KeyStore */
+
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new ByteArrayInputStream(storeOut), storePass.toCharArray());
+ }
+
+ @Test
+ public void testLoadFailsWithBadTampers()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ ByteArrayOutputStream bos = null;
+ byte tmp = 0;
+ byte[] storeOut = null;
+
+ /* Create and load single entry so not empty, RSA cert only */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+ store.setCertificateEntry("serverRsa", serverCertRsa);
+
+ /* Store to byte array */
+ bos = new ByteArrayOutputStream();
+ store.store(bos, storePass.toCharArray());
+ storeOut = bos.toByteArray();
+ bos.close();
+
+ assertNotNull(storeOut);
+ assertTrue(storeOut.length > 0);
+
+ /* Bad magic number should fail to load */
+ tmp = storeOut[0];
+ storeOut[0] = 9;
+ store = KeyStore.getInstance(storeType, storeProvider);
+ try {
+ store.load(new ByteArrayInputStream(storeOut),
+ storePass.toCharArray());
+ } catch (IOException e) {
+ /* expected */
+ }
+ storeOut[0] = tmp;
+
+ /* Bad KeyStore version should fail to load */
+ tmp = storeOut[1];
+ storeOut[1] = 9;
+ store = KeyStore.getInstance(storeType, storeProvider);
+ try {
+ store.load(new ByteArrayInputStream(storeOut),
+ storePass.toCharArray());
+ } catch (IOException e) {
+ /* expected */
+ }
+ storeOut[1] = tmp;
+
+ /* Sanity check that store loads successfully with no changes */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new ByteArrayInputStream(storeOut), storePass.toCharArray());
+ }
+
+ @Test
+ public void testStoreAndLoadIncludingTamper()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ PrivateKey keyOut = null;
+ Certificate certOut = null;
+ Certificate[] chainOut = null;
+ KeyGenerator kg = null;
+ SecretKey aesKey = null;
+ SecretKey sKeyOut = null;
+
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+
+ /** ----- INSERT entries ----- */
+
+ /* INSERT [1]: RSA cert only */
+ store.setCertificateEntry("serverRsa", serverCertRsa);
+ assertEquals(1, store.size());
+ assertTrue(store.isCertificateEntry("serverRsa"));
+
+ /* INSERT [2]: ECC cert only */
+ store.setCertificateEntry("serverEcc", serverCertEcc);
+ assertEquals(2, store.size());
+ assertTrue(store.isCertificateEntry("serverEcc"));
+
+ /* INSERT [3]: RSA priv key + cert */
+ store.setKeyEntry("rsaCert", serverKeyRsa,
+ storePass.toCharArray(),
+ new Certificate[] { serverCertRsa });
+ assertEquals(3, store.size());
+ assertTrue(store.isKeyEntry("rsaCert"));
+
+ /* INSERT [4]: ECC priv key + cert */
+ store.setKeyEntry("eccCert", serverKeyEcc,
+ storePass.toCharArray(),
+ new Certificate[] { serverCertEcc });
+ assertEquals(4, store.size());
+ assertTrue(store.isKeyEntry("eccCert"));
+
+ /* INSERT [5]: RSA priv key + chain */
+ store.setKeyEntry("rsaChain", serverKeyRsa,
+ storePass.toCharArray(), rsaServerChain);
+ assertEquals(5, store.size());
+ assertTrue(store.isKeyEntry("rsaChain"));
+
+ /* INSERT [6]: ECC priv key + chain */
+ store.setKeyEntry("eccChain", serverKeyEcc,
+ storePass.toCharArray(), eccServerChain);
+ assertEquals(6, store.size());
+ assertTrue(store.isKeyEntry("eccChain"));
+
+ /* INSERT [7]: AES SecretKey */
+ kg = KeyGenerator.getInstance("AES");
+ assertNotNull(kg);
+ kg.init(256, rand);
+ aesKey = kg.generateKey();
+ assertNotNull(aesKey);
+ assertTrue(aesKey.getEncoded().length > 0);
+ store.setKeyEntry("aesKey", aesKey, storePass.toCharArray(), null);
+ assertEquals(7, store.size());
+ assertTrue(store.isKeyEntry("aesKey"));
+
+ /** ----- WRITE OUT to byte array ----- */
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ store.store(bos, storePass.toCharArray());
+ byte[] storeOut = bos.toByteArray();
+ bos.close();
+
+ assertNotNull(storeOut);
+ assertTrue(storeOut.length > 0);
+
+ /** ----- READ IN from tampered byte array, should fail ----- */
+
+ /* Offset 18 gets us past the header and into the alias string */
+ byte storeOut18 = storeOut[18];
+ storeOut[18] = 'x';
+ store = KeyStore.getInstance(storeType, storeProvider);
+ try {
+ store.load(new ByteArrayInputStream(storeOut),
+ storePass.toCharArray());
+ } catch (IOException e) {
+ /* expected */
+ }
+
+ /** ----- READ IN from byte array ----- */
+
+ storeOut[18] = storeOut18;
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new ByteArrayInputStream(storeOut), storePass.toCharArray());
+
+ /** ----- GET/VERIFY entries ----- */
+
+ /* GET/VERIFY [1] */
+ certOut = store.getCertificate("serverRsa");
+ assertNotNull(certOut);
+ assertEquals(serverCertRsa, certOut);
+
+ /* GET/VERIFY [2] */
+ certOut = store.getCertificate("serverEcc");
+ assertNotNull(certOut);
+ assertEquals(serverCertEcc, certOut);
+
+ /* GET/VERIFY [3] */
+ keyOut = (PrivateKey)store.getKey("rsaCert", storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyRsa, keyOut);
+ certOut = store.getCertificate("rsaCert");
+ assertNotNull(certOut);
+ assertEquals(serverCertRsa, certOut);
+
+ /* GET/VERIFY [4] */
+ keyOut = (PrivateKey)store.getKey("eccCert", storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyEcc, keyOut);
+ certOut = store.getCertificate("eccCert");
+ assertNotNull(certOut);
+ assertEquals(serverCertEcc, certOut);
+
+ /* GET/VERIFY [5] */
+ keyOut = (PrivateKey)store.getKey("rsaChain", storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyRsa, keyOut);
+ chainOut = store.getCertificateChain("rsaChain");
+ assertNotNull(chainOut);
+ assertTrue(Arrays.equals(rsaServerChain, chainOut));
+
+ /* GET/VERIFY [6] */
+ keyOut = (PrivateKey)store.getKey("eccChain", storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyEcc, keyOut);
+ chainOut = store.getCertificateChain("eccChain");
+ assertNotNull(chainOut);
+ assertTrue(Arrays.equals(eccServerChain, chainOut));
+
+ /* GET/VERIFY [7] */
+ sKeyOut = (SecretKey)store.getKey("aesKey", storePass.toCharArray());
+ assertNotNull(sKeyOut);
+ assertEquals(aesKey, sKeyOut);
+ assertTrue(Arrays.equals(aesKey.getEncoded(), sKeyOut.getEncoded()));
+ }
+
+ @Test
+ public void testStorePreProtectedKeyIsUnsupported()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+ byte[] tmpArr = new byte[] { 0x00, 0x01, 0x02 };
+
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+
+ try {
+ store.setKeyEntry("myAlias", tmpArr, null);
+ } catch (UnsupportedOperationException e) {
+ /* expected, no supported */
+ }
+ }
+
+ @Test
+ public void testLoadWKSFromFile()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+
+ /* client.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(clientWKS),
+ storePass.toCharArray());
+ assertEquals(2, store.size());
+
+ /* client-rsa-1024.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(clientRsa1024WKS),
+ storePass.toCharArray());
+ assertEquals(1, store.size());
+
+ /* client-rsa.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(clientRsaWKS),
+ storePass.toCharArray());
+ assertEquals(1, store.size());
+
+ /* client-ecc.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(clientEccWKS),
+ storePass.toCharArray());
+ assertEquals(1, store.size());
+
+ /* server.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(serverWKS),
+ storePass.toCharArray());
+ assertEquals(2, store.size());
+
+ /* server-rsa-1024.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(serverRsa1024WKS),
+ storePass.toCharArray());
+ assertEquals(1, store.size());
+
+ /* server-rsa.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(serverRsaWKS),
+ storePass.toCharArray());
+ assertEquals(1, store.size());
+
+ /* server-ecc.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(serverEccWKS),
+ storePass.toCharArray());
+ assertEquals(1, store.size());
+
+ /* cacerts.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(caCertsWKS),
+ storePass.toCharArray());
+ assertEquals(6, store.size());
+
+ /* ca-client.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(caClientWKS),
+ storePass.toCharArray());
+ assertEquals(2, store.size());
+
+ /* ca-server.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(caServerWKS),
+ storePass.toCharArray());
+ assertEquals(2, store.size());
+
+ /* ca-server-rsa-2048.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(caServerRsa2048WKS),
+ storePass.toCharArray());
+ assertEquals(1, store.size());
+
+ /* ca-server-ecc-256.wks */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(caServerEcc256WKS),
+ storePass.toCharArray());
+ assertEquals(1, store.size());
+ }
+
+ @Test
+ public void testLoadSystemCAKeyStore()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException, InterruptedException {
+
+ int exitVal = -1;
+ String userDir = System.getProperty("user.dir");
+ String scriptDir = "/examples/certs/systemcerts/";
+ String scriptName = "system-cacerts-to-wks.sh";
+ String cacertsWKS = "cacerts.wks";
+ String jssecacertsWKS = "jssecacerts.wks";
+ String cmd = "cd " + userDir + scriptDir + " && /bin/sh " + scriptName;
+ KeyStore store = null;
+ String cacertsPass = "changeit";
+ File cacertFile = null;
+
+ /* Skip running this test on Android, since directory structure
+ * and cacert gen script won't be there. */
+ Assume.assumeTrue(!isAndroid());
+
+ assertNotNull(userDir);
+
+ /* Call system-cacerts-to-wks.sh script, converts system cacerts
+ * KeyStore from JKS to WKS type placing output cacerts.wks at
+ * /examples/certs/systemcerts/cacerts.wks */
+ Process ps = Runtime.getRuntime().exec
+ (new String[] {"sh", "-c", cmd});
+ ps.waitFor();
+
+ exitVal = ps.exitValue();
+ assertEquals(0, exitVal);
+
+ /* Try to load newly-generated cacerts.wks into WolfSSLKeyStore */
+ cacertFile = new File(userDir + scriptDir + cacertsWKS);
+ if (cacertFile.exists() && !cacertFile.isDirectory()) {
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(userDir + scriptDir + cacertsWKS),
+ cacertsPass.toCharArray());
+ }
+
+ /* Try to load newly-generated jssecacerts.wks if exists */
+ cacertFile = new File(userDir + scriptDir + jssecacertsWKS);
+ if (cacertFile.exists() && !cacertFile.isDirectory()) {
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(
+ userDir + scriptDir + jssecacertsWKS),
+ cacertsPass.toCharArray());
+ }
+ }
+
+ @Test
+ public void testLoadNullArgs()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException, InterruptedException {
+
+ KeyStore store = null;
+
+ /* load(null, null) should work */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, null);
+ }
+
+ @Test
+ public void testLoadWKSWithoutPassword()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException {
+
+ KeyStore store = null;
+
+ /* Test loading client.wks not specifying password. This should
+ * succeed and just skip integrity check. */
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new FileInputStream(clientWKS), null);
+ assertEquals(2, store.size());
+ }
+
+ @Test
+ public void testStoreToByteArrayThreaded()
+ throws KeyStoreException, IOException, FileNotFoundException,
+ NoSuchProviderException, NoSuchAlgorithmException,
+ CertificateException, InvalidKeySpecException,
+ UnrecoverableKeyException, InterruptedException {
+
+ int numThreads = 15;
+ ExecutorService service = Executors.newFixedThreadPool(numThreads);
+ final CountDownLatch latch = new CountDownLatch(numThreads);
+ final LinkedBlockingQueue results =
+ new LinkedBlockingQueue<>();
+
+ /* Insert/store/load/verify from numThreads parallel threads */
+ for (int i = 0; i < numThreads; i++) {
+ service.submit(new Runnable() {
+ @Override public void run() {
+
+ int ret = 0;
+ KeyStore store = null;
+ PrivateKey keyOut = null;
+ Certificate certOut = null;
+ Certificate[] chainOut = null;
+ KeyGenerator kg = null;
+ SecretKey aesKey = null;
+ SecretKey sKeyOut = null;
+
+ try {
+
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(null, storePass.toCharArray());
+
+ /** ----- INSERT entries ----- */
+
+ /* INSERT [1]: RSA cert only */
+ store.setCertificateEntry("serverRsa", serverCertRsa);
+ assertEquals(1, store.size());
+ assertTrue(store.isCertificateEntry("serverRsa"));
+
+ /* INSERT [2]: ECC cert only */
+ store.setCertificateEntry("serverEcc", serverCertEcc);
+ assertEquals(2, store.size());
+ assertTrue(store.isCertificateEntry("serverEcc"));
+
+ /* INSERT [3]: RSA priv key + cert */
+ store.setKeyEntry("rsaCert", serverKeyRsa,
+ storePass.toCharArray(),
+ new Certificate[] { serverCertRsa });
+ assertEquals(3, store.size());
+ assertTrue(store.isKeyEntry("rsaCert"));
+
+ /* INSERT [4]: ECC priv key + cert */
+ store.setKeyEntry("eccCert", serverKeyEcc,
+ storePass.toCharArray(),
+ new Certificate[] { serverCertEcc });
+ assertEquals(4, store.size());
+ assertTrue(store.isKeyEntry("eccCert"));
+
+ /* INSERT [5]: RSA priv key + chain */
+ store.setKeyEntry("rsaChain", serverKeyRsa,
+ storePass.toCharArray(), rsaServerChain);
+ assertEquals(5, store.size());
+ assertTrue(store.isKeyEntry("rsaChain"));
+
+ /* INSERT [6]: ECC priv key + chain */
+ store.setKeyEntry("eccChain", serverKeyEcc,
+ storePass.toCharArray(), eccServerChain);
+ assertEquals(6, store.size());
+ assertTrue(store.isKeyEntry("eccChain"));
+
+ /* INSERT [7]: AES SecretKey */
+ kg = KeyGenerator.getInstance("AES");
+ assertNotNull(kg);
+ kg.init(256, rand);
+ aesKey = kg.generateKey();
+ assertNotNull(aesKey);
+ assertTrue(aesKey.getEncoded().length > 0);
+ store.setKeyEntry("aesKey", aesKey,
+ storePass.toCharArray(), null);
+ assertEquals(7, store.size());
+ assertTrue(store.isKeyEntry("aesKey"));
+
+ /** ----- WRITE OUT to byte array ----- */
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ store.store(bos, storePass.toCharArray());
+ byte[] storeOut = bos.toByteArray();
+ bos.close();
+
+ assertNotNull(storeOut);
+ assertTrue(storeOut.length > 0);
+
+ /** ----- READ IN from byte array ----- */
+
+ store = KeyStore.getInstance(storeType, storeProvider);
+ store.load(new ByteArrayInputStream(storeOut),
+ storePass.toCharArray());
+
+ /** ----- GET/VERIFY entries ----- */
+
+ /* GET/VERIFY [1] */
+ certOut = store.getCertificate("serverRsa");
+ assertNotNull(certOut);
+ assertEquals(serverCertRsa, certOut);
+
+ /* GET/VERIFY [2] */
+ certOut = store.getCertificate("serverEcc");
+ assertNotNull(certOut);
+ assertEquals(serverCertEcc, certOut);
+
+ /* GET/VERIFY [3] */
+ keyOut = (PrivateKey)store.getKey("rsaCert",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyRsa, keyOut);
+ certOut = store.getCertificate("rsaCert");
+ assertNotNull(certOut);
+ assertEquals(serverCertRsa, certOut);
+
+ /* GET/VERIFY [4] */
+ keyOut = (PrivateKey)store.getKey("eccCert",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyEcc, keyOut);
+ certOut = store.getCertificate("eccCert");
+ assertNotNull(certOut);
+ assertEquals(serverCertEcc, certOut);
+
+ /* GET/VERIFY [5] */
+ keyOut = (PrivateKey)store.getKey("rsaChain",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyRsa, keyOut);
+ chainOut = store.getCertificateChain("rsaChain");
+ assertNotNull(chainOut);
+ assertTrue(Arrays.equals(rsaServerChain, chainOut));
+
+ /* GET/VERIFY [6] */
+ keyOut = (PrivateKey)store.getKey("eccChain",
+ storePass.toCharArray());
+ assertNotNull(keyOut);
+ assertEquals(serverKeyEcc, keyOut);
+ chainOut = store.getCertificateChain("eccChain");
+ assertNotNull(chainOut);
+ assertTrue(Arrays.equals(eccServerChain, chainOut));
+
+ /* GET/VERIFY [7] */
+ sKeyOut = (SecretKey)store.getKey("aesKey",
+ storePass.toCharArray());
+ assertNotNull(sKeyOut);
+ assertEquals(aesKey, sKeyOut);
+ assertTrue(Arrays.equals(aesKey.getEncoded(),
+ sKeyOut.getEncoded()));
+
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ results.add(1);
+
+ } finally {
+ latch.countDown();
+ }
+ }
+ });
+ }
+
+ /* wait for all threads to complete */
+ latch.await();
+
+ /* compare all digests, all should be the same across threads */
+ Iterator listIterator = results.iterator();
+ while (listIterator.hasNext()) {
+ Integer cur = listIterator.next();
+ if (cur == 1) {
+ fail("Threading error in KeyStore threaded test");
+ }
+ }
+ }
+}
+
diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java b/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java
index 552806c..2fdaae6 100644
--- a/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java
+++ b/src/test/java/com/wolfssl/wolfcrypt/test/WolfCryptTestSuite.java
@@ -27,21 +27,21 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
- AesTest.class,
- AesGcmTest.class,
- Des3Test.class,
- ChachaTest.class,
- Md5Test.class,
- ShaTest.class,
- Sha256Test.class,
- Sha384Test.class,
- Sha512Test.class,
- HmacTest.class,
- RngTest.class,
- RsaTest.class,
- DhTest.class,
- EccTest.class
- })
+ AesTest.class,
+ AesGcmTest.class,
+ Des3Test.class,
+ ChachaTest.class,
+ Md5Test.class,
+ ShaTest.class,
+ Sha256Test.class,
+ Sha384Test.class,
+ Sha512Test.class,
+ HmacTest.class,
+ RngTest.class,
+ RsaTest.class,
+ DhTest.class,
+ EccTest.class
+})
public class WolfCryptTestSuite {
}