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 { }