From 911dc34c6ad818742765da525fe77ccb9820c815 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Fri, 15 Mar 2024 11:26:27 -0600 Subject: [PATCH] JSSE: add wolfjsse.keystore.type.required Security property support to enforce KeyStore type used in KeyManager and TrustManager objects --- README.md | 100 ++++++++++++ .../provider/jsse/WolfSSLKeyManager.java | 40 ++++- .../provider/jsse/WolfSSLTrustManager.java | 142 +++++++++++++++--- .../wolfssl/provider/jsse/WolfSSLUtil.java | 51 +++++++ 4 files changed, 305 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 60b1b4bd..8b723fa6 100644 --- a/README.md +++ b/README.md @@ -327,6 +327,106 @@ $ ./configure --enable-secure-renegotiation Or by defining `-DHAVE_SECURE_RENEGOTIATION`. +### Security Property Support + +wolfJSSE allows for some customization through the `java.security` file +and use of Security properties. + +Support is included for the following pre-existing Java Security properties. + +**keystore.type (String)** - Specifies the default KeyStore type. This defaults +to JKS, but could be set to something else if desired. + +**jdk.tls.disabledAlgorithms (String)** - Can be used to disable algorithms, +TLS protocol versions, and key lengths, among other things. This should be a +comma-delimited String. wolfJSSE includes partial support for this property, +with supported items including disabling SSL/TLS protocol versions and setting +minimum RSA/ECC/DH key sizes. An example of potential use: + +``` +jdk.tls.disabledAlgorithms=SSLv3, TLSv1.1, DH keySize < 1024, EC keySize < 224, RSA keySize < 1024 +``` + +The following custom wolfJSSE-specific Security property settings are supported. +These can be placed into the `java.security` file and will be parsed and used +by wolfJSSE. + +**wolfjsse.enabledCipherSuites (String)** - Allows restriction of the enabled +cipher suiets to those listed in this Security property. When set, applications +wil not be able to override or add additional suites at runtime without +changing this property. This should be a comma-delimited String. Example use: + +``` +wolfjsse.enabledCipherSuites=TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 +``` + +**wolfjsse.enabledSupportedCurves (String)** - Allows setting of specific ECC +curves to be enabled for SSL/TLS connections. This propogates down to the native +wolfSSL API `wolfSSL_UseSupportedCurve()`. If invalid/bad values are found +when processing this property, connection establishment will fail with an +SSLException. This should be a comma-delimited String. Example use: + +``` +wolfjsse.enabledSupportedCurves=secp256r1, secp521r1 +``` + +**wolfjsse.enabledSignatureAlgorithms (String)** - Allows restriction of the +signature algorithms sent in the TLS ClientHello Signature Algorithms +Extension. By using/setting this property, native wolfSSL will not populate +the extension with default values, which are based on what algorithms have been +compiled into the native wolfSSL library. This should be a comma-delimited +String of signature algorithm + MAC combinations. Example use: + +``` +wolfjsse.enabledSignatureAlgorithms=RSA+SHA256:ECDSA+SHA256 +``` + +**wolfjsse.keystore.type.required (String)** - Can be used to specify a KeyStore +type that is required to be used. If this is set, wolfJSSE will not allow use +of any KeyStore instances that are not of this type. One use of this option +is when using wolfCrypt FIPS 140-2/3 with wolfJCE registered as a JCE provider. +This option can be used to restrict use of the wolfJCE "WKS" KeyStore type +to help ensure conformance to using FIPS-validated cryptography. Other +non-wolfJCE KeyStore implementations may not use/consume FIPS validated crypto. + +If there are other Security properties you would like to use with wolfJSSE, +please contact support@wolfssl.com. + +### System Property Support + +wolfJSSE allows some customization through the use of System properties. Since +these are **System** properties and not **Security** properties, they will not +get picked up if placed in the `java.security` file. That file is only used +with/for Security properties (see section above). + +**javax.net.ssl.keyStore (String)** - Can be used to specify the KeyStore file +to use for KeyManager objects. An alternative to passing in the KeyStore file +programatically at runtime. + +**javax.net.ssl.keyStoreType (String)** - Can be used to specify the KeyStore +type to use when getting KeyStore instances inside KeyManager objects. + +**javax.net.ssl.keyStorePassword (String)** - Can be used to specify the +KeyStore password to use for initializing KeyManager instances. + +**javax.net.ssl.trustStore (String)** - Can be used to specify the KeyStore +file to use with TrustManager objects. An alternative to passing in the +KeyStore file programatically at runtime. + +**javax.net.ssl.trustStoreType (String)** - Can be used to specify the KeyStore +type to use when loading KeyStore inside TrustManager objects. + +**javax.net.ssl.trustStorePassword (String)** - Can be used to specify the +KeyStore password to use when loading KeyStore inside TrustManager objects. + +**jdk.tls.client.enableSessionTicketExtension (boolean)** - Session tickets +are enabled in different ways depending on the JDK implementation. For +Oracle/OpenJDK and variants, this System property enables session tickets and +was added in Java 13. Should be set to "true" to enable. + +If there are other System properties you would like to use with wolfJSSE, +please contact support@wolfssl.com. + ## Release Notes Release notes can be found in [ChangeLog.md](./ChangeLog.md). diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLKeyManager.java b/src/java/com/wolfssl/provider/jsse/WolfSSLKeyManager.java index dc1bdad2..b9f28015 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLKeyManager.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLKeyManager.java @@ -58,13 +58,17 @@ public WolfSSLKeyManager() { } * 3. Using BKS type if on Android * 4. Using JKS type if above all fail * + * @param requiredType KeyStore type required by user through + * java.security if wolfjsse.keystore.type.required property + * has been set. + * * @return new KeyStore object that has been created and loaded using * details specified in System properties. * * @throws KeyStoreException if javax.net.ssl.keyStore property is * set but KeyStore fails to load */ - private KeyStore LoadKeyStoreFromSystemProperties() + private KeyStore LoadKeyStoreFromSystemProperties(String requiredType) throws KeyStoreException { KeyStore sysStore = null; @@ -100,19 +104,28 @@ private KeyStore LoadKeyStoreFromSystemProperties() if (type != null && !type.trim().isEmpty()) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "javax.net.ssl.keyStoreType set: " + type); + + if (requiredType != null && !requiredType.equals(type)) { + throw new KeyStoreException( + "javax.net.ssl.keyStoreType conflicts with required " + + "KeyStore type from wolfjsse.keystore.type.required"); + } + sysStore = WolfSSLUtil.LoadKeyStoreFileByType( file, this.pswd, type); } else { /* Try with wolfJCE WKS type first, in case wolfCrypt * FIPS is being used */ - if (wksAvailable) { + if (wksAvailable && + (requiredType == null || requiredType.equals("WKS"))) { sysStore = WolfSSLUtil.LoadKeyStoreFileByType( file, this.pswd, "WKS"); } /* Try with BKS, if we're running on Android */ - if ((sysStore == null) && WolfSSLUtil.isAndroid()) { + if ((sysStore == null) && WolfSSLUtil.isAndroid() && + (requiredType == null || requiredType.equals("BKS"))) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "Detected Android VM, trying BKS KeyStore type"); sysStore = WolfSSLUtil.LoadKeyStoreFileByType( @@ -120,7 +133,8 @@ private KeyStore LoadKeyStoreFromSystemProperties() } /* Try falling back to JKS */ - if (sysStore == null) { + if (sysStore == null && + (requiredType == null || requiredType.equals("JKS"))) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "javax.net.ssl.keyStoreType system property not set, " + "trying type: JKS"); @@ -150,10 +164,19 @@ protected void engineInit(KeyStore store, char[] password) this.pswd = password; KeyStore certs = store; + String requiredType = null; WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "entering engineInit(KeyStore store, char[] password)"); + requiredType = WolfSSLUtil.getRequiredKeyStoreType(); + if (requiredType != null) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "java.security has restricted KeyStore type"); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "wolfjsse.keystore.type.required = " + requiredType); + } + /* If no KeyStore passed in, try to load from system property values * if they have been set */ if (store == null) { @@ -162,13 +185,20 @@ protected void engineInit(KeyStore store, char[] password) "input KeyStore null, trying to load KeyStore from " + "system properties"); - certs = LoadKeyStoreFromSystemProperties(); + certs = LoadKeyStoreFromSystemProperties(requiredType); } else { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "input KeyStore provided, using inside KeyManager"); } + /* Verify KeyStore we got matches our requirements, for example + * type may be restricted by users trying to conform to FIPS + * requirements */ + if (certs != null) { + WolfSSLUtil.checkKeyStoreRequirements(certs); + } + this.store = certs; this.initialized = true; } diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLTrustManager.java b/src/java/com/wolfssl/provider/jsse/WolfSSLTrustManager.java index f20f631d..d817587f 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLTrustManager.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLTrustManager.java @@ -73,6 +73,9 @@ public WolfSSLTrustManager() { } * @param tsFile javax.net.ssl.trustStore system property value, or null * @param tsType javax.net.ssl.trustStoreType system property value, * or null + * @param requiredType KeyStore type required by user through + * java.security if wolfjsse.keystore.type.required property + * has been set. * * @return new KeyStore object that has been created and loaded using * details specified in System properties. @@ -81,7 +84,7 @@ public WolfSSLTrustManager() { } * set but KeyStore fails to load */ private KeyStore LoadKeyStoreFromSystemProperties(boolean wksAvailable, - String tsPass, String tsFile, String tsType) + String tsPass, String tsFile, String tsType, String requiredType) throws KeyStoreException { char[] passArr = null; @@ -107,19 +110,29 @@ private KeyStore LoadKeyStoreFromSystemProperties(boolean wksAvailable, if (tsType != null && !tsType.trim().isEmpty()) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "javax.net.ssl.trustStoreType set: " + tsType); + + if (requiredType != null && !requiredType.equals(tsType)) { + throw new KeyStoreException( + "javax.net.ssl.trustStoreType conflicts with " + + "required KeyStore type from " + + "wolfjsse.keystore.type.required"); + } + sysStore = WolfSSLUtil.LoadKeyStoreFileByType( tsFile, passArr, tsType); } else { /* Try with wolfJCE WKS type first, in case wolfCrypt * FIPS is being used */ - if (wksAvailable) { + if (wksAvailable && + (requiredType == null || requiredType.equals("WKS"))) { sysStore = WolfSSLUtil.LoadKeyStoreFileByType( tsFile, passArr, "WKS"); } /* Try with BKS, if we're running on Android */ - if (sysStore == null && WolfSSLUtil.isAndroid()) { + if (sysStore == null && WolfSSLUtil.isAndroid() && + (requiredType == null || requiredType.equals("BKS"))) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "Detected Android VM, trying BKS KeyStore type"); sysStore = WolfSSLUtil.LoadKeyStoreFileByType( @@ -127,7 +140,8 @@ private KeyStore LoadKeyStoreFromSystemProperties(boolean wksAvailable, } /* Try falling back to JKS */ - if (sysStore == null) { + if (sysStore == null && + (requiredType == null || requiredType.equals("JKS"))) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "javax.net.ssl.trustStoreType system property not set, " + "trying type: JKS"); @@ -169,12 +183,15 @@ private KeyStore LoadKeyStoreFromSystemProperties(boolean wksAvailable, * @param tsPass javax.net.ssl.trustStorePassword, or null if not set * @param certBundleName Name of system certificate bundle, either * "jssecacerts" or "cacerts" + * @param requiredType KeyStore type required by user through + * java.security if wolfjsse.keystore.type.required property + * has been set. * * @return KeyStore object loaded with CA certs from jssecacerts, or * null if not able to find KeyStore or load certs */ private KeyStore LoadJavaSystemCerts(String jh, boolean wksAvailable, - String tsPass, String certBundleName) { + String tsPass, String certBundleName, String requiredType) { char[] passArr = null; KeyStore sysStore = null; @@ -217,6 +234,15 @@ private KeyStore LoadJavaSystemCerts(String jh, boolean wksAvailable, if (f.exists()) { + if (requiredType != null && !requiredType.equals(storeType)) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "Skipping loading of system KeyStore, required type " + + "does not match wolfjsse.keystore.type.required"); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "Skipped loading: " + f.getAbsolutePath()); + return null; + } + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "Loading certs from " + f.getAbsolutePath()); @@ -276,15 +302,17 @@ private KeyStore LoadJavaSystemCerts(String jh, boolean wksAvailable, } private KeyStore LoadSystemJsseCaCerts(String jh, boolean wksAvailable, - String tsPass) { + String tsPass, String requiredType) { - return LoadJavaSystemCerts(jh, wksAvailable, tsPass, "jssecacerts"); + return LoadJavaSystemCerts(jh, wksAvailable, tsPass, "jssecacerts", + requiredType); } private KeyStore LoadSystemCaCerts(String jh, boolean wksAvailable, - String tsPass) { + String tsPass, String requiredType) { - return LoadJavaSystemCerts(jh, wksAvailable, tsPass, "cacerts"); + return LoadJavaSystemCerts(jh, wksAvailable, tsPass, "cacerts", + requiredType); } /** @@ -293,18 +321,42 @@ private KeyStore LoadSystemCaCerts(String jh, boolean wksAvailable, * Currently includes: * 1. /etc/ssl/certs/java/cacerts * + * @param wksAvailable Boolean if wolfJCE WKS KeyStore typs is available + * @param tsPass javax.net.ssl.trustStorePassword, or null if not set + * @param requiredType KeyStore type required by user through + * java.security if wolfjsse.keystore.type.required property + * has been set. + * */ private KeyStore LoadCommonSystemCerts(boolean wksAvailable, - String tsPass) { + String tsPass, String requiredType) { char[] passArr = null; File f = null; FileInputStream stream = null; KeyStore sysStore = null; + /* Get default KeyStore type, set in java.security and normally JKS */ + String storeType = Security.getProperty("keystore.type"); + if (storeType != null) { + storeType = storeType.toUpperCase(); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "keystore.type Security property set: " + storeType); + } + f = new File("/etc/ssl/certs/java/cacerts"); if (f.exists()) { + + if (requiredType != null && !requiredType.equals(storeType)) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "Skipping loading of system KeyStore, required type " + + "does not match wolfjsse.keystore.type.required"); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "Skipped loading: " + f.getAbsolutePath()); + return null; + } + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "Loading certs from " + f.getAbsolutePath()); @@ -321,13 +373,15 @@ private KeyStore LoadCommonSystemCerts(boolean wksAvailable, } try { + sysStore = KeyStore.getInstance(storeType); sysStore.load(stream, passArr); } catch (IOException | NoSuchAlgorithmException | - CertificateException e) { + CertificateException | KeyStoreException e) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - "Not able to load KeyStore with file stream: " + + "Not able to get or load KeyStore with file stream: " + f.getAbsolutePath()); + sysStore = null; } finally { try { @@ -353,13 +407,24 @@ private KeyStore LoadCommonSystemCerts(boolean wksAvailable, * certs. We try to load this first before going on to load root certs * manually, since it's already pre-imported and set up. * + * @param requiredType KeyStore type required by user through + * java.security if wolfjsse.keystore.type.required property + * has been set. + * * @return KeyStore object referencing AndroidCAStore, or null if not * found or not able to be loaded */ - private KeyStore LoadAndroidCAStore() { + private KeyStore LoadAndroidCAStore(String requiredType) { KeyStore sysStore = null; + if (requiredType != null && !requiredType.equals("AndroidCAStore")) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "Skipping loading of AndroidCAStore, required type " + + "does not match wolfjsse.keystore.type.required"); + return null; + } + try { sysStore = KeyStore.getInstance("AndroidCAStore"); @@ -390,10 +455,14 @@ private KeyStore LoadAndroidCAStore() { * Try to load Android system root certificates manually by reading * all PEM certificates in [android_root]/etc/security/cacerts directory. * + * @param requiredType KeyStore type required by user through + * java.security if wolfjsse.keystore.type.required property + * has been set. + * * @return KeyStore object containing Android system CA certificates, or * null if none found or error loading any certs */ - private KeyStore LoadAndroidSystemCertsManually() { + private KeyStore LoadAndroidSystemCertsManually(String requiredType) { int aliasCnt = 0; byte[] derArray = null; @@ -401,17 +470,25 @@ private KeyStore LoadAndroidSystemCertsManually() { CertificateFactory cfactory = null; ByteArrayInputStream bis = null; Certificate tmpCert = null; + String storeType = null; String androidRoot = System.getenv("ANDROID_ROOT"); if (androidRoot != null) { /* Android default KeyStore type is BKS */ + if (requiredType != null) { + storeType = requiredType; + } else { + storeType = "BKS"; + } + try { - sysStore = KeyStore.getInstance("BKS"); + sysStore = KeyStore.getInstance(storeType); } catch (KeyStoreException e) { - /* Unable to get or load empty BKS KeyStore type */ + /* Unable to get or load empty KeyStore type */ WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - "Unable to get or load BKS KeyStore instance"); + "Unable to get or load KeyStore instance, type: " + + storeType); return null; } @@ -568,10 +645,19 @@ protected void engineInit(KeyStore in) throws KeyStoreException { String pass = System.getProperty("javax.net.ssl.trustStorePassword"); String file = System.getProperty("javax.net.ssl.trustStore"); String type = System.getProperty("javax.net.ssl.trustStoreType"); + String requiredType = null; WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "entered engineInit(KeyStore in)"); + requiredType = WolfSSLUtil.getRequiredKeyStoreType(); + if (requiredType != null) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "java.security has restricted KeyStore type"); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "wolfjsse.keystore.type.required = " + requiredType); + } + /* [1] Just use KeyStore passed in by user if available */ if (in == null) { @@ -586,7 +672,7 @@ protected void engineInit(KeyStore in) throws KeyStoreException { /* [2] Try to load from system property details */ certs = LoadKeyStoreFromSystemProperties( - wksAvailable, pass, file, type); + wksAvailable, pass, file, type, requiredType); /* Get JAVA_HOME for trying to load system certs next */ if (certs == null) { @@ -603,27 +689,30 @@ protected void engineInit(KeyStore in) throws KeyStoreException { /* [3] Try to load system jssecacerts */ if ((certs == null) && (javaHome != null)) { - certs = LoadSystemJsseCaCerts(javaHome, wksAvailable, pass); + certs = LoadSystemJsseCaCerts(javaHome, wksAvailable, pass, + requiredType); } /* [4] Try to load system cacerts */ if ((certs == null) && (javaHome != null)) { - certs = LoadSystemCaCerts(javaHome, wksAvailable, pass); + certs = LoadSystemCaCerts(javaHome, wksAvailable, pass, + requiredType); } /* [5] Try to load common CA cert locations */ if (certs == null) { - certs = LoadCommonSystemCerts(wksAvailable, pass); + certs = LoadCommonSystemCerts(wksAvailable, pass, + requiredType); } /* [6] Try to load system certs if on Android */ if ((certs == null) && WolfSSLUtil.isAndroid()) { - certs = LoadAndroidCAStore(); + certs = LoadAndroidCAStore(requiredType); } /* [7] Try to load Android system root certs manually */ if ((certs == null) && WolfSSLUtil.isAndroid()) { - certs = LoadAndroidSystemCertsManually(); + certs = LoadAndroidSystemCertsManually(requiredType); } } else { @@ -631,6 +720,13 @@ protected void engineInit(KeyStore in) throws KeyStoreException { "input KeyStore provided, using for trusted certs"); } + /* Verify KeyStore we got matches our requirements, for example + * type may be restricted by users trying to conform to FIPS + * requirements */ + if (certs != null) { + WolfSSLUtil.checkKeyStoreRequirements(certs); + } + this.store = certs; this.initialized = true; } diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java b/src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java index 63a289b9..cea09075 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java @@ -219,6 +219,57 @@ protected static String[] getSupportedCurves() { return curves.split(","); } + /** + * Return KeyStore type restriction if set in java.security + * with 'wolfjsse.keystore.type.required' Security property. + * + * @return String with required KeyStore type, or null if no + * requirement set + */ + protected static String getRequiredKeyStoreType() { + + String requiredType = + Security.getProperty("wolfjsse.keystore.type.required"); + + if (requiredType == null || requiredType.isEmpty()) { + return null; + } + else { + requiredType = requiredType.toUpperCase(); + } + + return requiredType; + } + + /** + * Check given KeyStore against any pre-defind requirements for + * KeyStore use, including the following. + * + * Restricted KeyStore type: wolfjsse.keystore.type.required + * + * @param store Input KeyStore to check against requirements + * + * @throws KeyStoreException if KeyStore given does not meet wolfJSSE + * requirements + */ + protected static void checkKeyStoreRequirements( + KeyStore store) throws KeyStoreException { + + String requiredType = null; + + if (store == null) { + return; + } + + requiredType = getRequiredKeyStoreType(); + if ((requiredType != null) && + (!store.getType().equals(requiredType))) { + throw new KeyStoreException( + "KeyStore does not match required type, got " + + store.getType() + ", required " + requiredType); + } + } + /** * Return maximum key size allowed if minimum is set in * jdk.tls.disabledAlgorithms security property for specified algorithm.