diff --git a/build.gradle b/build.gradle index 55b31ca816214..502d0fc58f673 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,6 @@ apply from: 'gradle/ide.gradle' apply from: 'gradle/forbidden-dependencies.gradle' apply from: 'gradle/formatting.gradle' apply from: 'gradle/local-distribution.gradle' -apply from: 'gradle/fips.gradle' apply from: 'gradle/run.gradle' apply from: 'gradle/missing-javadoc.gradle' apply from: 'gradle/code-coverage.gradle' diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index b984ef3800490..a9f2351994048 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -123,6 +123,8 @@ dependencies { api 'org.jruby.joni:joni:2.2.1' api "com.fasterxml.jackson.core:jackson-databind:${props.getProperty('jackson_databind')}" api "org.ajoberstar.grgit:grgit-core:5.2.1" + api "org.bouncycastle:bc-fips:1.0.2.5" + testFixturesApi "junit:junit:${props.getProperty('junit')}" testFixturesApi "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${props.getProperty('randomizedrunner')}" diff --git a/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java b/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java index a9ccb75569002..bbd06c84b30c9 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java @@ -52,6 +52,7 @@ public class BuildParams { private static JavaVersion gradleJavaVersion; private static JavaVersion runtimeJavaVersion; private static String runtimeJavaDetails; + @Deprecated private static Boolean inFipsJvm; private static String gitRevision; private static String gitOrigin; diff --git a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java index 268de50340cbf..e8bec036bf5a9 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java @@ -1182,6 +1182,7 @@ private void createConfiguration() { baseConfig.put("indices.breaker.total.use_real_memory", "false"); // Don't wait for state, just start up quickly. This will also allow new and old nodes in the BWC case to become the master baseConfig.put("discovery.initial_state_timeout", "0s"); + baseConfig.put("fips.approved", "true"); // TODO: Remove these once https://github.com/elastic/elasticsearch/issues/46091 is fixed baseConfig.put("logger.org.opensearch.action.support.master", "DEBUG"); diff --git a/buildSrc/src/main/resources/cacerts.bcfks b/buildSrc/src/main/resources/cacerts.bcfks deleted file mode 100644 index 9c3863eda60c5..0000000000000 Binary files a/buildSrc/src/main/resources/cacerts.bcfks and /dev/null differ diff --git a/buildSrc/src/main/resources/fips_java_bcjsse_11.policy b/buildSrc/src/main/resources/fips_java_bcjsse_11.policy deleted file mode 100644 index 10193f4eb385d..0000000000000 --- a/buildSrc/src/main/resources/fips_java_bcjsse_11.policy +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -// Security Policy for JDK 11 and higher, with BouncyCastle FIPS provider and BouncyCastleJsseProvider in FIPS mode - -grant { - permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; - permission java.security.SecurityPermission "putProviderProperty.BCJSSE"; - permission java.lang.RuntimePermission "getProtectionDomain"; - permission java.util.PropertyPermission "java.runtime.name", "read"; - permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled"; - //io.netty.handler.codec.DecoderException - permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; - //java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec - permission java.lang.RuntimePermission "accessDeclaredMembers"; - permission java.util.PropertyPermission "intellij.debug.agent", "read"; - permission java.util.PropertyPermission "intellij.debug.agent", "write"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; - permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read"; -}; diff --git a/buildSrc/src/main/resources/fips_java_bcjsse_8.policy b/buildSrc/src/main/resources/fips_java_bcjsse_8.policy deleted file mode 100644 index 87dbe85c22ab5..0000000000000 --- a/buildSrc/src/main/resources/fips_java_bcjsse_8.policy +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -// Security Policy for JDK 8, with BouncyCastle FIPS provider and BouncyCastleJsseProvider in FIPS mode - -grant codeBase "file:${java.home}/lib/ext/localedata.jar" { - // Allow resource bundles to be loaded for non root locales. See - // https://github.com/elastic/elasticsearch/issues/39981 - permission java.lang.RuntimePermission "accessClassInPackage.sun.util.*"; -}; -grant { - permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; - permission java.security.SecurityPermission "putProviderProperty.BCJSSE"; - permission java.lang.RuntimePermission "getProtectionDomain"; - permission java.util.PropertyPermission "java.runtime.name", "read"; - permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled"; - //io.netty.handler.codec.DecoderException - permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; - //java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec - permission java.lang.RuntimePermission "accessDeclaredMembers"; - permission java.util.PropertyPermission "intellij.debug.agent", "read"; - permission java.util.PropertyPermission "intellij.debug.agent", "write"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; - permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read"; -}; diff --git a/buildSrc/src/main/resources/fips_java_bcjsse_8.security b/buildSrc/src/main/resources/fips_java_bcjsse_8.security deleted file mode 100644 index df21041f5191b..0000000000000 --- a/buildSrc/src/main/resources/fips_java_bcjsse_8.security +++ /dev/null @@ -1,134 +0,0 @@ -# Security Properties for JDK 8, with BouncyCastle FIPS provider and BouncyCastleJsseProvider in FIPS mode - -security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:HYBRID;ENABLE{All}; -security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS -security.provider.3=sun.security.provider.Sun -security.provider.4=sun.security.jgss.SunProvider -securerandom.source=file:/dev/urandom -securerandom.strongAlgorithms=NativePRNGBlocking:SUN -login.configuration.provider=sun.security.provider.ConfigFile -policy.provider=sun.security.provider.PolicyFile -policy.url.1=file:${java.home}/lib/security/java.policy -policy.url.2=file:${user.home}/.java.policy -policy.expandProperties=true -policy.allowSystemProperty=true -policy.ignoreIdentityScope=false -keystore.type=bcfks -keystore.type.compat=true -package.access=sun.,\ - org.GNOME.Accessibility.,\ - com.sun.xml.internal.,\ - com.sun.imageio.,\ - com.sun.istack.internal.,\ - com.sun.jmx.,\ - com.sun.media.sound.,\ - com.sun.naming.internal.,\ - com.sun.proxy.,\ - com.sun.corba.se.,\ - com.sun.org.apache.bcel.internal.,\ - com.sun.org.apache.regexp.internal.,\ - com.sun.org.apache.xerces.internal.,\ - com.sun.org.apache.xpath.internal.,\ - com.sun.org.apache.xalan.internal.extensions.,\ - com.sun.org.apache.xalan.internal.lib.,\ - com.sun.org.apache.xalan.internal.res.,\ - com.sun.org.apache.xalan.internal.templates.,\ - com.sun.org.apache.xalan.internal.utils.,\ - com.sun.org.apache.xalan.internal.xslt.,\ - com.sun.org.apache.xalan.internal.xsltc.cmdline.,\ - com.sun.org.apache.xalan.internal.xsltc.compiler.,\ - com.sun.org.apache.xalan.internal.xsltc.trax.,\ - com.sun.org.apache.xalan.internal.xsltc.util.,\ - com.sun.org.apache.xml.internal.res.,\ - com.sun.org.apache.xml.internal.resolver.helpers.,\ - com.sun.org.apache.xml.internal.resolver.readers.,\ - com.sun.org.apache.xml.internal.security.,\ - com.sun.org.apache.xml.internal.serializer.utils.,\ - com.sun.org.apache.xml.internal.utils.,\ - com.sun.org.glassfish.,\ - com.oracle.xmlns.internal.,\ - com.oracle.webservices.internal.,\ - oracle.jrockit.jfr.,\ - org.jcp.xml.dsig.internal.,\ - jdk.internal.,\ - jdk.nashorn.internal.,\ - jdk.nashorn.tools.,\ - jdk.xml.internal.,\ - com.sun.activation.registries. - -package.definition=sun.,\ - com.sun.xml.internal.,\ - com.sun.imageio.,\ - com.sun.istack.internal.,\ - com.sun.jmx.,\ - com.sun.media.sound.,\ - com.sun.naming.internal.,\ - com.sun.proxy.,\ - com.sun.corba.se.,\ - com.sun.org.apache.bcel.internal.,\ - com.sun.org.apache.regexp.internal.,\ - com.sun.org.apache.xerces.internal.,\ - com.sun.org.apache.xpath.internal.,\ - com.sun.org.apache.xalan.internal.extensions.,\ - com.sun.org.apache.xalan.internal.lib.,\ - com.sun.org.apache.xalan.internal.res.,\ - com.sun.org.apache.xalan.internal.templates.,\ - com.sun.org.apache.xalan.internal.utils.,\ - com.sun.org.apache.xalan.internal.xslt.,\ - com.sun.org.apache.xalan.internal.xsltc.cmdline.,\ - com.sun.org.apache.xalan.internal.xsltc.compiler.,\ - com.sun.org.apache.xalan.internal.xsltc.trax.,\ - com.sun.org.apache.xalan.internal.xsltc.util.,\ - com.sun.org.apache.xml.internal.res.,\ - com.sun.org.apache.xml.internal.resolver.helpers.,\ - com.sun.org.apache.xml.internal.resolver.readers.,\ - com.sun.org.apache.xml.internal.security.,\ - com.sun.org.apache.xml.internal.serializer.utils.,\ - com.sun.org.apache.xml.internal.utils.,\ - com.sun.org.glassfish.,\ - com.oracle.xmlns.internal.,\ - com.oracle.webservices.internal.,\ - oracle.jrockit.jfr.,\ - org.jcp.xml.dsig.internal.,\ - jdk.internal.,\ - jdk.nashorn.internal.,\ - jdk.nashorn.tools.,\ - jdk.xml.internal.,\ - com.sun.activation.registries. - -ssl.KeyManagerFactory.algorithm=PKIX -ssl.TrustManagerFactory.algorithm=PKIX -networkaddress.cache.negative.ttl=10 -krb5.kdc.bad.policy = tryLast -jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer, \ - RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224 - -jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySize < 1024 - - -jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 1024, \ - EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC - -jdk.tls.legacyAlgorithms= \ - K_NULL, C_NULL, M_NULL, \ - DH_anon, ECDH_anon, \ - RC4_128, RC4_40, DES_CBC, DES40_CBC, \ - 3DES_EDE_CBC -crypto.policy=unlimited - -jdk.xml.dsig.secureValidationPolicy=\ - disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\ - maxTransforms 5,\ - maxReferences 30,\ - disallowReferenceUriSchemes file http https,\ - minKeySize RSA 1024,\ - minKeySize DSA 1024,\ - minKeySize EC 224,\ - noDuplicateIds,\ - noRetrievalMethodLoops - -jceks.key.serialFilter = java.lang.Enum;java.security.KeyRep;\ - java.security.KeyRep$Type;javax.crypto.spec.SecretKeySpec;!* diff --git a/buildSrc/src/main/resources/fips_java_sunjsse.policy b/buildSrc/src/main/resources/fips_java_sunjsse.policy deleted file mode 100644 index 1d2f06e3a1314..0000000000000 --- a/buildSrc/src/main/resources/fips_java_sunjsse.policy +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -// Security Policy for JDK 8, with BouncyCastle FIPS provider and SunJSSE in FIPS mode - -grant codeBase "file:${java.home}/lib/ext/localedata.jar" { - // Allow resource bundles to be loaded for non root locales. See - // https://github.com/elastic/elasticsearch/issues/39981 - permission java.lang.RuntimePermission "accessClassInPackage.sun.util.*"; -}; -grant { - permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; - permission java.lang.RuntimePermission "getProtectionDomain"; - permission java.util.PropertyPermission "java.runtime.name", "read"; - permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled"; - //io.netty.handler.codec.DecoderException - permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; - //java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec - permission java.lang.RuntimePermission "accessDeclaredMembers"; -}; diff --git a/buildSrc/src/main/resources/fips_java_sunjsse.security b/buildSrc/src/main/resources/fips_java_sunjsse.security deleted file mode 100644 index 2959bea3b8596..0000000000000 --- a/buildSrc/src/main/resources/fips_java_sunjsse.security +++ /dev/null @@ -1,134 +0,0 @@ -# Security Properties for JDK 8, with BouncyCastle FIPS provider and SunJSSE in FIPS mode - -security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:HYBRID;ENABLE{All}; -security.provider.2=com.sun.net.ssl.internal.ssl.Provider BCFIPS -security.provider.3=sun.security.provider.Sun -security.provider.4=sun.security.jgss.SunProvider -securerandom.source=file:/dev/urandom -securerandom.strongAlgorithms=NativePRNGBlocking:SUN -login.configuration.provider=sun.security.provider.ConfigFile -policy.provider=sun.security.provider.PolicyFile -policy.url.1=file:${java.home}/lib/security/java.policy -policy.url.2=file:${user.home}/.java.policy -policy.expandProperties=true -policy.allowSystemProperty=true -policy.ignoreIdentityScope=false -keystore.type=bcfks -keystore.type.compat=true -package.access=sun.,\ - org.GNOME.Accessibility.,\ - com.sun.xml.internal.,\ - com.sun.imageio.,\ - com.sun.istack.internal.,\ - com.sun.jmx.,\ - com.sun.media.sound.,\ - com.sun.naming.internal.,\ - com.sun.proxy.,\ - com.sun.corba.se.,\ - com.sun.org.apache.bcel.internal.,\ - com.sun.org.apache.regexp.internal.,\ - com.sun.org.apache.xerces.internal.,\ - com.sun.org.apache.xpath.internal.,\ - com.sun.org.apache.xalan.internal.extensions.,\ - com.sun.org.apache.xalan.internal.lib.,\ - com.sun.org.apache.xalan.internal.res.,\ - com.sun.org.apache.xalan.internal.templates.,\ - com.sun.org.apache.xalan.internal.utils.,\ - com.sun.org.apache.xalan.internal.xslt.,\ - com.sun.org.apache.xalan.internal.xsltc.cmdline.,\ - com.sun.org.apache.xalan.internal.xsltc.compiler.,\ - com.sun.org.apache.xalan.internal.xsltc.trax.,\ - com.sun.org.apache.xalan.internal.xsltc.util.,\ - com.sun.org.apache.xml.internal.res.,\ - com.sun.org.apache.xml.internal.resolver.helpers.,\ - com.sun.org.apache.xml.internal.resolver.readers.,\ - com.sun.org.apache.xml.internal.security.,\ - com.sun.org.apache.xml.internal.serializer.utils.,\ - com.sun.org.apache.xml.internal.utils.,\ - com.sun.org.glassfish.,\ - com.oracle.xmlns.internal.,\ - com.oracle.webservices.internal.,\ - oracle.jrockit.jfr.,\ - org.jcp.xml.dsig.internal.,\ - jdk.internal.,\ - jdk.nashorn.internal.,\ - jdk.nashorn.tools.,\ - jdk.xml.internal.,\ - com.sun.activation.registries. - -package.definition=sun.,\ - com.sun.xml.internal.,\ - com.sun.imageio.,\ - com.sun.istack.internal.,\ - com.sun.jmx.,\ - com.sun.media.sound.,\ - com.sun.naming.internal.,\ - com.sun.proxy.,\ - com.sun.corba.se.,\ - com.sun.org.apache.bcel.internal.,\ - com.sun.org.apache.regexp.internal.,\ - com.sun.org.apache.xerces.internal.,\ - com.sun.org.apache.xpath.internal.,\ - com.sun.org.apache.xalan.internal.extensions.,\ - com.sun.org.apache.xalan.internal.lib.,\ - com.sun.org.apache.xalan.internal.res.,\ - com.sun.org.apache.xalan.internal.templates.,\ - com.sun.org.apache.xalan.internal.utils.,\ - com.sun.org.apache.xalan.internal.xslt.,\ - com.sun.org.apache.xalan.internal.xsltc.cmdline.,\ - com.sun.org.apache.xalan.internal.xsltc.compiler.,\ - com.sun.org.apache.xalan.internal.xsltc.trax.,\ - com.sun.org.apache.xalan.internal.xsltc.util.,\ - com.sun.org.apache.xml.internal.res.,\ - com.sun.org.apache.xml.internal.resolver.helpers.,\ - com.sun.org.apache.xml.internal.resolver.readers.,\ - com.sun.org.apache.xml.internal.security.,\ - com.sun.org.apache.xml.internal.serializer.utils.,\ - com.sun.org.apache.xml.internal.utils.,\ - com.sun.org.glassfish.,\ - com.oracle.xmlns.internal.,\ - com.oracle.webservices.internal.,\ - oracle.jrockit.jfr.,\ - org.jcp.xml.dsig.internal.,\ - jdk.internal.,\ - jdk.nashorn.internal.,\ - jdk.nashorn.tools.,\ - jdk.xml.internal.,\ - com.sun.activation.registries. - -ssl.KeyManagerFactory.algorithm=SunX509 -ssl.TrustManagerFactory.algorithm=PKIX -networkaddress.cache.negative.ttl=10 -krb5.kdc.bad.policy = tryLast -jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer, \ - RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224 - -jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySize < 1024 - - -jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 1024, \ - EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC - -jdk.tls.legacyAlgorithms= \ - K_NULL, C_NULL, M_NULL, \ - DH_anon, ECDH_anon, \ - RC4_128, RC4_40, DES_CBC, DES40_CBC, \ - 3DES_EDE_CBC -crypto.policy=unlimited - -jdk.xml.dsig.secureValidationPolicy=\ - disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\ - maxTransforms 5,\ - maxReferences 30,\ - disallowReferenceUriSchemes file http https,\ - minKeySize RSA 1024,\ - minKeySize DSA 1024,\ - minKeySize EC 224,\ - noDuplicateIds,\ - noRetrievalMethodLoops - -jceks.key.serialFilter = java.lang.Enum;java.security.KeyRep;\ - java.security.KeyRep$Type;javax.crypto.spec.SecretKeySpec;!* diff --git a/buildSrc/src/main/resources/fips_java_bcjsse_11.security b/distribution/src/config/fips_java.security similarity index 84% rename from buildSrc/src/main/resources/fips_java_bcjsse_11.security rename to distribution/src/config/fips_java.security index e6a025e7eb10d..e54e471c89e71 100644 --- a/buildSrc/src/main/resources/fips_java_bcjsse_11.security +++ b/distribution/src/config/fips_java.security @@ -12,7 +12,6 @@ policy.provider=sun.security.provider.PolicyFile policy.expandProperties=true policy.allowSystemProperty=true policy.ignoreIdentityScope=false -keystore.type=BCFKS keystore.type.compat=true package.access=sun.misc.,\ sun.reflect. @@ -23,12 +22,9 @@ ssl.KeyManagerFactory.algorithm=PKIX ssl.TrustManagerFactory.algorithm=PKIX networkaddress.cache.negative.ttl=10 krb5.kdc.bad.policy = tryLast -jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer, \ - RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224 -jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, \ - DSA keySize < 1024 -jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 1024, \ - EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC +jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1, jdkCA&usageTLSServer, RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224 +jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySize < 1024 +jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, MD5withRSA, DH keySize < 1024, EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC jdk.tls.legacyAlgorithms= \ K_NULL, C_NULL, M_NULL, \ DH_anon, ECDH_anon, \ diff --git a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/JvmOptionsParser.java b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/JvmOptionsParser.java index 533d1f7e782ba..8ab3a65090b2b 100644 --- a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/JvmOptionsParser.java +++ b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/JvmOptionsParser.java @@ -148,7 +148,7 @@ private List jvmOptions(final Path config, final String opensearchJavaOp final List substitutedJvmOptions = substitutePlaceholders(jvmOptions, Collections.unmodifiableMap(substitutions)); final List ergonomicJvmOptions = JvmErgonomics.choose(substitutedJvmOptions); - final List systemJvmOptions = SystemJvmOptions.systemJvmOptions(); + final List systemJvmOptions = SystemJvmOptions.systemJvmOptions(config); final List finalJvmOptions = new ArrayList<>( systemJvmOptions.size() + substitutedJvmOptions.size() + ergonomicJvmOptions.size() ); diff --git a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java index af7138569972a..5098d0e343f5f 100644 --- a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java +++ b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java @@ -32,6 +32,7 @@ package org.opensearch.tools.launchers; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -39,7 +40,7 @@ final class SystemJvmOptions { - static List systemJvmOptions() { + static List systemJvmOptions(final Path config) { return Collections.unmodifiableList( Arrays.asList( /* @@ -79,11 +80,17 @@ static List systemJvmOptions() { "-Dlog4j2.disable.jmx=true", // security manager allowSecurityManagerOption(), + loadJavaSecurityProperties(config), javaLocaleProviders() ) ).stream().filter(e -> e.isEmpty() == false).collect(Collectors.toList()); } + private static String loadJavaSecurityProperties(final Path config) { + var securityFile = config.resolve("fips_java.security").toFile(); + return "-Djava.security.properties=" + securityFile.getAbsolutePath(); + } + private static String allowSecurityManagerOption() { if (Runtime.version().feature() > 17) { return "-Djava.security.manager=allow"; diff --git a/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt b/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt index 1bd35a7a35c21..5c7c14696849d 100644 --- a/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt +++ b/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt @@ -1,17 +1,14 @@ -Copyright (c) 2000-2015 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2000 - 2023 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) -Permission is hereby granted, free of charge, to any person obtaining a copy of this software -and associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/gradle/fips.gradle b/gradle/fips.gradle deleted file mode 100644 index 1ce2cb89176f6..0000000000000 --- a/gradle/fips.gradle +++ /dev/null @@ -1,96 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -import org.opensearch.gradle.ExportOpenSearchBuildResourcesTask -import org.opensearch.gradle.info.BuildParams -import org.opensearch.gradle.testclusters.OpenSearchCluster - -// Common config when running with a FIPS-140 runtime JVM -if (BuildParams.inFipsJvm) { - - allprojects { - File fipsResourcesDir = new File(project.buildDir, 'fips-resources') - boolean java8 = BuildParams.runtimeJavaVersion == JavaVersion.VERSION_1_8 - boolean zulu8 = java8 && BuildParams.runtimeJavaDetails.contains("Zulu") - File fipsSecurity; - File fipsPolicy; - if (java8) { - if (zulu8) { - //Azul brings many changes from JDK 11 to their Zulu8 so we can only use BCJSSE - fipsSecurity = new File(fipsResourcesDir, "fips_java_bcjsse_8.security") - fipsPolicy = new File(fipsResourcesDir, "fips_java_bcjsse_8.policy") - } else { - fipsSecurity = new File(fipsResourcesDir, "fips_java_sunjsse.security") - fipsPolicy = new File(fipsResourcesDir, "fips_java_sunjsse.policy") - } - } else { - fipsSecurity = new File(fipsResourcesDir, "fips_java_bcjsse_11.security") - fipsPolicy = new File(fipsResourcesDir, "fips_java_bcjsse_11.policy") - } - File fipsTrustStore = new File(fipsResourcesDir, 'cacerts.bcfks') - def bcFips = dependencies.create('org.bouncycastle:bc-fips:1.0.2.1') - def bcTlsFips = dependencies.create('org.bouncycastle:bctls-fips:1.0.12.2') - - pluginManager.withPlugin('java') { - TaskProvider fipsResourcesTask = project.tasks.register('fipsResources', ExportOpenSearchBuildResourcesTask) - fipsResourcesTask.configure { - outputDir = fipsResourcesDir - copy fipsSecurity.name - copy fipsPolicy.name - copy 'cacerts.bcfks' - } - - project.afterEvaluate { - def extraFipsJars = configurations.detachedConfiguration(bcFips, bcTlsFips) - // ensure that bouncycastle is on classpath for the all of test types, must happen in evaluateAfter since the rest tests explicitly - // set the class path to help maintain pure black box testing, and here we are adding to that classpath - tasks.withType(Test).configureEach { Test test -> - test.setClasspath(test.getClasspath().plus(extraFipsJars)) - } - } - - pluginManager.withPlugin("opensearch.testclusters") { - afterEvaluate { - // This afterEvaluate hooks is required to avoid deprecated configuration resolution - // This configuration can be removed once system modules are available - def extraFipsJars = configurations.detachedConfiguration(bcFips, bcTlsFips) - testClusters.all { - extraFipsJars.files.each { - extraJarFile it - } - } - } - testClusters.all { - extraConfigFile "fips_java.security", fipsSecurity - extraConfigFile "fips_java.policy", fipsPolicy - extraConfigFile "cacerts.bcfks", fipsTrustStore - systemProperty 'java.security.properties', '=${OPENSEARCH_PATH_CONF}/fips_java.security' - systemProperty 'java.security.policy', '=${OPENSEARCH_PATH_CONF}/fips_java.policy' - systemProperty 'javax.net.ssl.trustStore', '${OPENSEARCH_PATH_CONF}/cacerts.bcfks' - systemProperty 'javax.net.ssl.trustStorePassword', 'password' - systemProperty 'javax.net.ssl.keyStorePassword', 'password' - systemProperty 'javax.net.ssl.keyStoreType', 'BCFKS' - } - } - project.tasks.withType(Test).configureEach { Test task -> - task.dependsOn('fipsResources') - task.systemProperty('javax.net.ssl.trustStorePassword', 'password') - task.systemProperty('javax.net.ssl.keyStorePassword', 'password') - task.systemProperty('javax.net.ssl.trustStoreType', 'BCFKS') - // Using the key==value format to override default JVM security settings and policy - // see also: https://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.html - task.systemProperty('java.security.properties', String.format(Locale.ROOT, "=%s", fipsSecurity)) - task.systemProperty('java.security.policy', String.format(Locale.ROOT, "=%s", fipsPolicy)) - task.systemProperty('javax.net.ssl.trustStore', fipsTrustStore) - } - } - } -} diff --git a/libs/ssl-config/build.gradle b/libs/ssl-config/build.gradle index 3226ec12ff6f7..889c278ffbfcb 100644 --- a/libs/ssl-config/build.gradle +++ b/libs/ssl-config/build.gradle @@ -35,6 +35,10 @@ apply plugin: "opensearch.publish" dependencies { api project(':libs:opensearch-common') + // bouncyCastle + implementation 'org.bouncycastle:bcpkix-fips:1.0.7' + compileOnly 'org.bouncycastle:bc-fips:1.0.2.5' + testImplementation(project(":test:framework")) { exclude group: 'org.opensearch', module: 'opensearch-ssl-config' } @@ -49,6 +53,10 @@ tasks.named('forbiddenApisMain').configure { replaceSignatureFiles 'jdk-signatures' } +tasks.named("dependencyLicenses").configure { + mapping from: /bc.*/, to: 'bouncycastle' +} + forbiddenPatterns { exclude '**/*.key' exclude '**/*.pem' diff --git a/libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 b/libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 new file mode 100644 index 0000000000000..cc29a787cee56 --- /dev/null +++ b/libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 @@ -0,0 +1 @@ +704e65f7e4fe679e5ab2aa8a840f27f8ced4c522 diff --git a/libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 b/libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 new file mode 100644 index 0000000000000..57cf4cfe10b5f --- /dev/null +++ b/libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 @@ -0,0 +1 @@ +fe07959721cfa2156be9722ba20fdfee2b5441b0 diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java index 859b74b200dc6..6d5b1b02c67ca 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java @@ -65,7 +65,7 @@ final class DefaultJdkTrustConfig implements SslTrustConfig { * Create a trust config that uses supplied {@link BiFunction} to determine the TrustStore type, and the relevant password. */ DefaultJdkTrustConfig(BiFunction systemProperties) { - this(systemProperties, isPkcs11Truststore(systemProperties) ? getSystemTrustStorePassword(systemProperties) : null); + this(systemProperties, getSystemTrustStorePassword(systemProperties)); } /** @@ -93,11 +93,16 @@ public X509ExtendedTrustManager createTrustManager() { * @return the KeyStore used as truststore for PKCS#11 initialized with the password, null otherwise */ private KeyStore getSystemTrustStore() { - if (isPkcs11Truststore(systemProperties) && trustStorePassword != null) { + if (trustStorePassword != null) { try { - KeyStore keyStore = KeyStore.getInstance("PKCS11"); - keyStore.load(null, trustStorePassword); - return keyStore; + if (isBcfksTruststore(systemProperties)) { + var path = Path.of(System.getProperty("javax.net.ssl.trustStore", "")); + KeyStoreUtil.readKeyStore(path, "BCFKS", trustStorePassword); + } else if (isPkcs11Truststore(systemProperties)) { + KeyStore keyStore = KeyStore.getInstance("PKCS11"); + keyStore.load(null, trustStorePassword); + return keyStore; + } } catch (GeneralSecurityException | IOException e) { throw new SslConfigException("failed to load the system PKCS#11 truststore", e); } @@ -105,12 +110,17 @@ private KeyStore getSystemTrustStore() { return null; } + private static boolean isBcfksTruststore(BiFunction systemProperties) { + return systemProperties.apply("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("BCFKS"); + } + private static boolean isPkcs11Truststore(BiFunction systemProperties) { return systemProperties.apply("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11"); } private static char[] getSystemTrustStorePassword(BiFunction systemProperties) { - return systemProperties.apply("javax.net.ssl.trustStorePassword", "").toCharArray(); + var password = systemProperties.apply("javax.net.ssl.trustStorePassword", ""); + return password.isEmpty() ? null : password.toCharArray(); } @Override diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java index b6b6cdd90af14..3fca84e303e27 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java @@ -71,6 +71,8 @@ static String inferKeyStoreType(Path path) { String name = path == null ? "" : path.toString().toLowerCase(Locale.ROOT); if (name.endsWith(".p12") || name.endsWith(".pfx") || name.endsWith(".pkcs12")) { return "PKCS12"; + } else if (name.endsWith(".bks")) { + return "BCFKS"; } else { return "jks"; } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java index bfc29a5801b11..1865b13d644aa 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java @@ -32,6 +32,8 @@ package org.opensearch.common.ssl; +import org.bouncycastle.pkcs.PKCSException; + import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; @@ -82,7 +84,12 @@ public X509ExtendedKeyManager createKeyManager() { private PrivateKey getPrivateKey() { try { - final PrivateKey privateKey = PemUtils.readPrivateKey(key, () -> keyPassword); + final PrivateKey privateKey = PemUtils.readPrivateKey(key, () -> { + if (keyPassword.length == 0) { + throw new SslConfigException("cannot read encrypted key [" + key.toAbsolutePath() + "] without a password"); + } + return keyPassword; + }); if (privateKey == null) { throw new SslConfigException("could not load ssl private key file [" + key + "]"); } @@ -91,7 +98,7 @@ private PrivateKey getPrivateKey() { throw new SslConfigException("the configured ssl private key file [" + key.toAbsolutePath() + "] does not exist", e); } catch (IOException e) { throw new SslConfigException("the configured ssl private key file [" + key.toAbsolutePath() + "] cannot be read", e); - } catch (GeneralSecurityException e) { + } catch (PKCSException e) { throw new SslConfigException("cannot load ssl private key file [" + key.toAbsolutePath() + "]", e); } } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java index 8a3730ee554f9..df842650d65ca 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java @@ -32,68 +32,32 @@ package org.opensearch.common.ssl; -import org.opensearch.common.CharArrays; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.pkcs.PKCSException; +import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder; -import javax.crypto.Cipher; -import javax.crypto.EncryptedPrivateKeyInfo; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; +import java.security.PrivateKey; -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; +import java.io.*; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.MessageDigest; -import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; -import java.security.interfaces.ECKey; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.DSAPrivateKeySpec; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.RSAPrivateCrtKeySpec; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Supplier; final class PemUtils { - private static final String PKCS1_HEADER = "-----BEGIN RSA PRIVATE KEY-----"; - private static final String PKCS1_FOOTER = "-----END RSA PRIVATE KEY-----"; - private static final String OPENSSL_DSA_HEADER = "-----BEGIN DSA PRIVATE KEY-----"; - private static final String OPENSSL_DSA_FOOTER = "-----END DSA PRIVATE KEY-----"; - private static final String OPENSSL_DSA_PARAMS_HEADER = "-----BEGIN DSA PARAMETERS-----"; - private static final String OPENSSL_DSA_PARAMS_FOOTER = "-----END DSA PARAMETERS-----"; - private static final String PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----"; - private static final String PKCS8_FOOTER = "-----END PRIVATE KEY-----"; - private static final String PKCS8_ENCRYPTED_HEADER = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; - private static final String PKCS8_ENCRYPTED_FOOTER = "-----END ENCRYPTED PRIVATE KEY-----"; - private static final String OPENSSL_EC_HEADER = "-----BEGIN EC PRIVATE KEY-----"; - private static final String OPENSSL_EC_FOOTER = "-----END EC PRIVATE KEY-----"; - private static final String OPENSSL_EC_PARAMS_HEADER = "-----BEGIN EC PARAMETERS-----"; - private static final String OPENSSL_EC_PARAMS_FOOTER = "-----END EC PARAMETERS-----"; - private static final String HEADER = "-----BEGIN"; - private PemUtils() { throw new IllegalStateException("Utility class should not be instantiated"); } @@ -106,555 +70,88 @@ private PemUtils() { * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key * @return a private key from the contents of the file */ - public static PrivateKey readPrivateKey(Path keyPath, Supplier passwordSupplier) throws IOException, GeneralSecurityException { - try (BufferedReader bReader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) { - String line = bReader.readLine(); - while (null != line && line.startsWith(HEADER) == false) { - line = bReader.readLine(); - } - if (null == line) { - throw new SslConfigException("Error parsing Private Key [" + keyPath.toAbsolutePath() + "], file is empty"); - } - if (PKCS8_ENCRYPTED_HEADER.equals(line.trim())) { - char[] password = passwordSupplier.get(); - if (password == null) { - throw new SslConfigException("cannot read encrypted key [" + keyPath.toAbsolutePath() + "] without a password"); - } - return parsePKCS8Encrypted(bReader, password); - } else if (PKCS8_HEADER.equals(line.trim())) { - return parsePKCS8(bReader); - } else if (PKCS1_HEADER.equals(line.trim())) { - return parsePKCS1Rsa(bReader, passwordSupplier); - } else if (OPENSSL_DSA_HEADER.equals(line.trim())) { - return parseOpenSslDsa(bReader, passwordSupplier); - } else if (OPENSSL_DSA_PARAMS_HEADER.equals(line.trim())) { - return parseOpenSslDsa(removeDsaHeaders(bReader), passwordSupplier); - } else if (OPENSSL_EC_HEADER.equals(line.trim())) { - return parseOpenSslEC(bReader, passwordSupplier); - } else if (OPENSSL_EC_PARAMS_HEADER.equals(line.trim())) { - return parseOpenSslEC(removeECHeaders(bReader), passwordSupplier); - } else { - throw new SslConfigException( - "error parsing Private Key [" + keyPath.toAbsolutePath() + "], file does not contain a supported key format" - ); - } - } catch (FileNotFoundException | NoSuchFileException e) { - throw new SslConfigException("private key file [" + keyPath.toAbsolutePath() + "] does not exist", e); - } catch (IOException | GeneralSecurityException e) { - throw new SslConfigException("private key file [" + keyPath.toAbsolutePath() + "] cannot be parsed", e); - } - } - - /** - * Removes the EC Headers that OpenSSL adds to EC private keys as the information in them - * is redundant - * - * @throws IOException if the EC Parameter footer is missing - */ - private static BufferedReader removeECHeaders(BufferedReader bReader) throws IOException { - String line = bReader.readLine(); - while (line != null) { - if (OPENSSL_EC_PARAMS_FOOTER.equals(line.trim())) { - break; - } - line = bReader.readLine(); - } - if (null == line || OPENSSL_EC_PARAMS_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, EC Parameters footer is missing"); - } - // Verify that the key starts with the correct header before passing it to parseOpenSslEC - if (OPENSSL_EC_HEADER.equals(bReader.readLine()) == false) { - throw new IOException("Malformed PEM file, EC Key header is missing"); - } - return bReader; - } - - /** - * Removes the DSA Params Headers that OpenSSL adds to DSA private keys as the information in them - * is redundant - * - * @throws IOException if the EC Parameter footer is missing - */ - private static BufferedReader removeDsaHeaders(BufferedReader bReader) throws IOException { - String line = bReader.readLine(); - while (line != null) { - if (OPENSSL_DSA_PARAMS_FOOTER.equals(line.trim())) { - break; - } - line = bReader.readLine(); - } - if (null == line || OPENSSL_DSA_PARAMS_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, DSA Parameters footer is missing"); - } - // Verify that the key starts with the correct header before passing it to parseOpenSslDsa - if (OPENSSL_DSA_HEADER.equals(bReader.readLine()) == false) { - throw new IOException("Malformed PEM file, DSA Key header is missing"); - } - return bReader; - } - - /** - * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an plaintext private key encoded in - * PKCS#8 - * - * @param bReader the {@link BufferedReader} containing the key file contents - * @return {@link PrivateKey} - * @throws IOException if the file can't be read - * @throws GeneralSecurityException if the private key can't be generated from the {@link PKCS8EncodedKeySpec} - */ - private static PrivateKey parsePKCS8(BufferedReader bReader) throws IOException, GeneralSecurityException { - StringBuilder sb = new StringBuilder(); - String line = bReader.readLine(); - while (line != null) { - if (PKCS8_FOOTER.equals(line.trim())) { - break; - } - sb.append(line.trim()); - line = bReader.readLine(); - } - if (null == line || PKCS8_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); - } - byte[] keyBytes = Base64.getDecoder().decode(sb.toString()); - String keyAlgo = getKeyAlgorithmIdentifier(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(keyAlgo); - return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); - } - - /** - * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an EC private key encoded in - * OpenSSL traditional format. - * - * @param bReader the {@link BufferedReader} containing the key file contents - * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key - * @return {@link PrivateKey} - * @throws IOException if the file can't be read - * @throws GeneralSecurityException if the private key can't be generated from the {@link ECPrivateKeySpec} - */ - private static PrivateKey parseOpenSslEC(BufferedReader bReader, Supplier passwordSupplier) throws IOException, - GeneralSecurityException { - StringBuilder sb = new StringBuilder(); - String line = bReader.readLine(); - Map pemHeaders = new HashMap<>(); - while (line != null) { - if (OPENSSL_EC_FOOTER.equals(line.trim())) { - break; - } - // Parse PEM headers according to https://www.ietf.org/rfc/rfc1421.txt - if (line.contains(":")) { - String[] header = line.split(":"); - pemHeaders.put(header[0].trim(), header[1].trim()); - } else { - sb.append(line.trim()); - } - line = bReader.readLine(); - } - if (null == line || OPENSSL_EC_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); - } - byte[] keyBytes = possiblyDecryptPKCS1Key(pemHeaders, sb.toString(), passwordSupplier); - KeyFactory keyFactory = KeyFactory.getInstance("EC"); - ECPrivateKeySpec ecSpec = parseEcDer(keyBytes); - return keyFactory.generatePrivate(ecSpec); + public static PrivateKey readPrivateKey(Path keyPath, Supplier passwordSupplier) throws IOException, PKCSException { + PrivateKeyInfo pki = loadPrivateKeyFromFile(keyPath, passwordSupplier); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + return converter.getPrivateKey(pki); } - /** - * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an RSA private key encoded in - * OpenSSL traditional format. - * - * @param bReader the {@link BufferedReader} containing the key file contents - * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key - * @return {@link PrivateKey} - * @throws IOException if the file can't be read - * @throws GeneralSecurityException if the private key can't be generated from the {@link RSAPrivateCrtKeySpec} - */ - private static PrivateKey parsePKCS1Rsa(BufferedReader bReader, Supplier passwordSupplier) throws IOException, - GeneralSecurityException { - StringBuilder sb = new StringBuilder(); - String line = bReader.readLine(); - Map pemHeaders = new HashMap<>(); - - while (line != null) { - if (PKCS1_FOOTER.equals(line.trim())) { - // Unencrypted - break; - } - // Parse PEM headers according to https://www.ietf.org/rfc/rfc1421.txt - if (line.contains(":")) { - String[] header = line.split(":"); - pemHeaders.put(header[0].trim(), header[1].trim()); - } else { - sb.append(line.trim()); + static List readCertificates(Collection certPaths) throws CertificateException, IOException { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + List certificates = new ArrayList<>(certPaths.size()); + for (Path path : certPaths) { + try (InputStream input = Files.newInputStream(path)) { + final Collection parsed = certFactory.generateCertificates(input); + if (parsed.isEmpty()) { + throw new SslConfigException("failed to parse any certificates from [" + path.toAbsolutePath() + "]"); + } + certificates.addAll(parsed); } - line = bReader.readLine(); } - if (null == line || PKCS1_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); - } - byte[] keyBytes = possiblyDecryptPKCS1Key(pemHeaders, sb.toString(), passwordSupplier); - RSAPrivateCrtKeySpec spec = parseRsaDer(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - return keyFactory.generatePrivate(spec); + return certificates; } /** - * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an DSA private key encoded in - * OpenSSL traditional format. + * Creates a {@link PrivateKey} from the private key, with or without encryption. + * When enforcing the approved-only mode in Java security settings, some functionalities might be restricted due to the limited + * set of allowed algorithms. One such restriction includes Password Based Key Derivation Functions (PBKDF) like those used by OpenSSL + * and PKCS#12 formats. Since these formats rely on PBKDF algorithms, they cannot operate correctly within the approved-only mode. + * Consequently, attempting to utilize them could result in a {@link java.security.NoSuchAlgorithmException}. * - * @param bReader the {@link BufferedReader} containing the key file contents - * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key + * @param passwordSupplier The password supplier for the encrypted (password protected) key * @return {@link PrivateKey} - * @throws IOException if the file can't be read - * @throws GeneralSecurityException if the private key can't be generated from the {@link DSAPrivateKeySpec} - */ - private static PrivateKey parseOpenSslDsa(BufferedReader bReader, Supplier passwordSupplier) throws IOException, - GeneralSecurityException { - StringBuilder sb = new StringBuilder(); - String line = bReader.readLine(); - Map pemHeaders = new HashMap<>(); - - while (line != null) { - if (OPENSSL_DSA_FOOTER.equals(line.trim())) { - // Unencrypted - break; - } - // Parse PEM headers according to https://www.ietf.org/rfc/rfc1421.txt - if (line.contains(":")) { - String[] header = line.split(":"); - pemHeaders.put(header[0].trim(), header[1].trim()); + * @throws IOException If the file can't be read + */ + private static PrivateKeyInfo loadPrivateKeyFromFile(Path keyPath, Supplier passwordSupplier) + throws IOException, PKCSException { + try (PEMParser pemParser = new PEMParser(new FileReader(keyPath.toFile()))) { + Object object = readObject(keyPath, pemParser); + + if (object instanceof PKCS8EncryptedPrivateKeyInfo) { // encrypted private key in pkcs8-format + var privateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) object; + var inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder() + .setProvider("BCFIPS") + .build(passwordSupplier.get()); + return privateKeyInfo.decryptPrivateKeyInfo(inputDecryptorProvider); + } else if (object instanceof PEMEncryptedKeyPair) { // encrypted private key + var encryptedKeyPair = (PEMEncryptedKeyPair) object; + var decryptorProvider = new JcePEMDecryptorProviderBuilder() + .setProvider("BCFIPS") + .build(passwordSupplier.get()); + var keyPair = encryptedKeyPair.decryptKeyPair(decryptorProvider); + return keyPair.getPrivateKeyInfo(); + } else if (object instanceof PEMKeyPair) { // unencrypted private key + return ((PEMKeyPair) object).getPrivateKeyInfo(); + } else if (object instanceof PrivateKeyInfo) { // unencrypted private key in pkcs8-format + return (PrivateKeyInfo) object; } else { - sb.append(line.trim()); + throw new SslConfigException(String.format( + "error parsing private key [%s], invalid encrypted private key class: [%s]", + keyPath.toAbsolutePath(), + object.getClass().getName() + )); } - line = bReader.readLine(); } - if (null == line || OPENSSL_DSA_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); - } - byte[] keyBytes = possiblyDecryptPKCS1Key(pemHeaders, sb.toString(), passwordSupplier); - DSAPrivateKeySpec spec = parseDsaDer(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance("DSA"); - return keyFactory.generatePrivate(spec); - } - - /** - * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an encrypted private key encoded in - * PKCS#8 - * - * @param bReader the {@link BufferedReader} containing the key file contents - * @param keyPassword The password for the encrypted (password protected) key - * @return {@link PrivateKey} - * @throws IOException if the file can't be read - * @throws GeneralSecurityException if the private key can't be generated from the {@link PKCS8EncodedKeySpec} - */ - private static PrivateKey parsePKCS8Encrypted(BufferedReader bReader, char[] keyPassword) throws IOException, GeneralSecurityException { - StringBuilder sb = new StringBuilder(); - String line = bReader.readLine(); - while (line != null) { - if (PKCS8_ENCRYPTED_FOOTER.equals(line.trim())) { - break; - } - sb.append(line.trim()); - line = bReader.readLine(); - } - if (null == line || PKCS8_ENCRYPTED_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); - } - byte[] keyBytes = Base64.getDecoder().decode(sb.toString()); - - EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(keyBytes); - SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); - SecretKey secretKey = secretKeyFactory.generateSecret(new PBEKeySpec(keyPassword)); - Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); - cipher.init(Cipher.DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters()); - PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher); - String keyAlgo = getKeyAlgorithmIdentifier(keySpec.getEncoded()); - KeyFactory keyFactory = KeyFactory.getInstance(keyAlgo); - return keyFactory.generatePrivate(keySpec); - } - - /** - * Decrypts the password protected contents using the algorithm and IV that is specified in the PEM Headers of the file - * - * @param pemHeaders The Proc-Type and DEK-Info PEM headers that have been extracted from the key file - * @param keyContents The key as a base64 encoded String - * @param passwordSupplier A password supplier for the encrypted (password protected) key - * @return the decrypted key bytes - * @throws GeneralSecurityException if the key can't be decrypted - * @throws IOException if the PEM headers are missing or malformed - */ - private static byte[] possiblyDecryptPKCS1Key(Map pemHeaders, String keyContents, Supplier passwordSupplier) - throws GeneralSecurityException, IOException { - byte[] keyBytes = Base64.getDecoder().decode(keyContents); - String procType = pemHeaders.get("Proc-Type"); - if ("4,ENCRYPTED".equals(procType)) { - // We only handle PEM encryption - String encryptionParameters = pemHeaders.get("DEK-Info"); - if (null == encryptionParameters) { - // malformed pem - throw new IOException("Malformed PEM File, DEK-Info header is missing"); - } - char[] password = passwordSupplier.get(); - if (password == null) { - throw new IOException("cannot read encrypted key without a password"); - } - Cipher cipher = getCipherFromParameters(encryptionParameters, password); - byte[] decryptedKeyBytes = cipher.doFinal(keyBytes); - return decryptedKeyBytes; - } - return keyBytes; - } - - /** - * Creates a {@link Cipher} from the contents of the DEK-Info header of a PEM file. RFC 1421 indicates that supported algorithms are - * defined in RFC 1423. RFC 1423 only defines DES-CBS and triple DES (EDE) in CBC mode. AES in CBC mode is also widely used though ( 3 - * different variants of 128, 192, 256 bit keys ) - * - * @param dekHeaderValue The value of the DEK-Info PEM header - * @param password The password with which the key is encrypted - * @return a cipher of the appropriate algorithm and parameters to be used for decryption - * @throws GeneralSecurityException if the algorithm is not available in the used security provider, or if the key is inappropriate - * for the cipher - * @throws IOException if the DEK-Info PEM header is invalid - */ - private static Cipher getCipherFromParameters(String dekHeaderValue, char[] password) throws GeneralSecurityException, IOException { - final String padding = "PKCS5Padding"; - final SecretKey encryptionKey; - final String[] valueTokens = dekHeaderValue.split(","); - if (valueTokens.length != 2) { - throw new IOException("Malformed PEM file, DEK-Info PEM header is invalid"); - } - final String algorithm = valueTokens[0]; - final String ivString = valueTokens[1]; - final byte[] iv; - try { - iv = hexStringToByteArray(ivString); - } catch (IllegalArgumentException e) { - throw new IOException("Malformed PEM file, DEK-Info IV is invalid", e); - } - if ("DES-CBC".equals(algorithm)) { - byte[] key = generateOpenSslKey(password, iv, 8); - encryptionKey = new SecretKeySpec(key, "DES"); - } else if ("DES-EDE3-CBC".equals(algorithm)) { - byte[] key = generateOpenSslKey(password, iv, 24); - encryptionKey = new SecretKeySpec(key, "DESede"); - } else if ("AES-128-CBC".equals(algorithm)) { - byte[] key = generateOpenSslKey(password, iv, 16); - encryptionKey = new SecretKeySpec(key, "AES"); - } else if ("AES-192-CBC".equals(algorithm)) { - byte[] key = generateOpenSslKey(password, iv, 24); - encryptionKey = new SecretKeySpec(key, "AES"); - } else if ("AES-256-CBC".equals(algorithm)) { - byte[] key = generateOpenSslKey(password, iv, 32); - encryptionKey = new SecretKeySpec(key, "AES"); - } else { - throw new GeneralSecurityException("Private Key encrypted with unsupported algorithm [" + algorithm + "]"); - } - String transformation = encryptionKey.getAlgorithm() + "/" + "CBC" + "/" + padding; - Cipher cipher = Cipher.getInstance(transformation); - cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv)); - return cipher; - } - - /** - * Performs key stretching in the same manner that OpenSSL does. This is basically a KDF - * that uses n rounds of salted MD5 (as many times as needed to get the necessary number of key bytes) - *

- * https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_PrivateKey_traditional.html - */ - private static byte[] generateOpenSslKey(char[] password, byte[] salt, int keyLength) { - byte[] passwordBytes = CharArrays.toUtf8Bytes(password); - MessageDigest md5 = SslUtil.messageDigest("md5"); - byte[] key = new byte[keyLength]; - int copied = 0; - int remaining; - while (copied < keyLength) { - remaining = keyLength - copied; - md5.update(passwordBytes, 0, passwordBytes.length); - md5.update(salt, 0, 8);// AES IV (salt) is longer but we only need 8 bytes - byte[] tempDigest = md5.digest(); - int bytesToCopy = (remaining > 16) ? 16 : remaining; // MD5 digests are 16 bytes - System.arraycopy(tempDigest, 0, key, copied, bytesToCopy); - copied += bytesToCopy; - if (remaining == 0) { - break; - } - md5.update(tempDigest, 0, 16); // use previous round digest as IV - } - Arrays.fill(passwordBytes, (byte) 0); - return key; - } - - /** - * Converts a hexadecimal string to a byte array - */ - private static byte[] hexStringToByteArray(String hexString) { - int len = hexString.length(); - if (len % 2 == 0) { - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - final int k = Character.digit(hexString.charAt(i), 16); - final int l = Character.digit(hexString.charAt(i + 1), 16); - if (k == -1 || l == -1) { - throw new IllegalStateException("String [" + hexString + "] is not hexadecimal"); - } - data[i / 2] = (byte) ((k << 4) + l); - } - return data; - } else { - throw new IllegalStateException( - "Hexadecimal string [" + hexString + "] has odd length and cannot be converted to a byte array" - ); - } - } - - /** - * Parses a DER encoded EC key to an {@link ECPrivateKeySpec} using a minimal {@link DerParser} - * - * @param keyBytes the private key raw bytes - * @return {@link ECPrivateKeySpec} - * @throws IOException if the DER encoded key can't be parsed - */ - private static ECPrivateKeySpec parseEcDer(byte[] keyBytes) throws IOException, GeneralSecurityException { - DerParser parser = new DerParser(keyBytes); - DerParser.Asn1Object sequence = parser.readAsn1Object(); - parser = sequence.getParser(); - parser.readAsn1Object().getInteger(); // version - String keyHex = parser.readAsn1Object().getString(); - BigInteger privateKeyInt = new BigInteger(keyHex, 16); - DerParser.Asn1Object choice = parser.readAsn1Object(); - parser = choice.getParser(); - String namedCurve = getEcCurveNameFromOid(parser.readAsn1Object().getOid()); - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); - AlgorithmParameterSpec algorithmParameterSpec = new ECGenParameterSpec(namedCurve); - keyPairGenerator.initialize(algorithmParameterSpec); - ECParameterSpec parameterSpec = ((ECKey) keyPairGenerator.generateKeyPair().getPrivate()).getParams(); - return new ECPrivateKeySpec(privateKeyInt, parameterSpec); - } - - /** - * Parses a DER encoded RSA key to a {@link RSAPrivateCrtKeySpec} using a minimal {@link DerParser} - * - * @param keyBytes the private key raw bytes - * @return {@link RSAPrivateCrtKeySpec} - * @throws IOException if the DER encoded key can't be parsed - */ - private static RSAPrivateCrtKeySpec parseRsaDer(byte[] keyBytes) throws IOException { - DerParser parser = new DerParser(keyBytes); - DerParser.Asn1Object sequence = parser.readAsn1Object(); - parser = sequence.getParser(); - parser.readAsn1Object().getInteger(); // (version) We don't need it but must read to get to modulus - BigInteger modulus = parser.readAsn1Object().getInteger(); - BigInteger publicExponent = parser.readAsn1Object().getInteger(); - BigInteger privateExponent = parser.readAsn1Object().getInteger(); - BigInteger prime1 = parser.readAsn1Object().getInteger(); - BigInteger prime2 = parser.readAsn1Object().getInteger(); - BigInteger exponent1 = parser.readAsn1Object().getInteger(); - BigInteger exponent2 = parser.readAsn1Object().getInteger(); - BigInteger coefficient = parser.readAsn1Object().getInteger(); - return new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, prime1, prime2, exponent1, exponent2, coefficient); - } - - /** - * Parses a DER encoded DSA key to a {@link DSAPrivateKeySpec} using a minimal {@link DerParser} - * - * @param keyBytes the private key raw bytes - * @return {@link DSAPrivateKeySpec} - * @throws IOException if the DER encoded key can't be parsed - */ - private static DSAPrivateKeySpec parseDsaDer(byte[] keyBytes) throws IOException { - DerParser parser = new DerParser(keyBytes); - DerParser.Asn1Object sequence = parser.readAsn1Object(); - parser = sequence.getParser(); - parser.readAsn1Object().getInteger(); // (version) We don't need it but must read to get to p - BigInteger p = parser.readAsn1Object().getInteger(); - BigInteger q = parser.readAsn1Object().getInteger(); - BigInteger g = parser.readAsn1Object().getInteger(); - parser.readAsn1Object().getInteger(); // we don't need x - BigInteger x = parser.readAsn1Object().getInteger(); - return new DSAPrivateKeySpec(x, p, q, g); } /** - * Parses a DER encoded private key and reads its algorithm identifier Object OID. + * Supports PEM files that includes parameters. * - * @param keyBytes the private key raw bytes - * @return A string identifier for the key algorithm (RSA, DSA, or EC) - * @throws GeneralSecurityException if the algorithm oid that is parsed from ASN.1 is unknown - * @throws IOException if the DER encoded key can't be parsed - */ - private static String getKeyAlgorithmIdentifier(byte[] keyBytes) throws IOException, GeneralSecurityException { - DerParser parser = new DerParser(keyBytes); - DerParser.Asn1Object sequence = parser.readAsn1Object(); - parser = sequence.getParser(); - parser.readAsn1Object().getInteger(); // version - DerParser.Asn1Object algSequence = parser.readAsn1Object(); - parser = algSequence.getParser(); - String oidString = parser.readAsn1Object().getOid(); - switch (oidString) { - case "1.2.840.10040.4.1": - return "DSA"; - case "1.2.840.113549.1.1.1": - return "RSA"; - case "1.2.840.10045.2.1": - return "EC"; - } - throw new GeneralSecurityException( - "Error parsing key algorithm identifier. Algorithm with OID [" + oidString + "] is not żsupported" - ); - } - - static List readCertificates(Collection certPaths) throws CertificateException, IOException { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - List certificates = new ArrayList<>(certPaths.size()); - for (Path path : certPaths) { - try (InputStream input = Files.newInputStream(path)) { - final Collection parsed = certFactory.generateCertificates(input); - if (parsed.isEmpty()) { - throw new SslConfigException("failed to parse any certificates from [" + path.toAbsolutePath() + "]"); + * @return high-level Object from the content + */ + private static Object readObject(Path keyPath, PEMParser pemParser) throws IOException { + while (pemParser.ready()) { + try { + var object = pemParser.readObject(); + if (object instanceof ASN1ObjectIdentifier) { // handles -----BEGIN EC PARAMETERS----- + continue; } - certificates.addAll(parsed); + return object; + } catch (IOException e) { // handles -----BEGIN DSA PARAMETERS----- + // ignore } } - return certificates; - } - - private static String getEcCurveNameFromOid(String oidString) throws GeneralSecurityException { - switch (oidString) { - // see https://tools.ietf.org/html/rfc5480#section-2.1.1.1 - case "1.2.840.10045.3.1": - return "secp192r1"; - case "1.3.132.0.1": - return "sect163k1"; - case "1.3.132.0.15": - return "sect163r2"; - case "1.3.132.0.33": - return "secp224r1"; - case "1.3.132.0.26": - return "sect233k1"; - case "1.3.132.0.27": - return "sect233r1"; - case "1.2.840.10045.3.1.7": - return "secp256r1"; - case "1.3.132.0.16": - return "sect283k1"; - case "1.3.132.0.17": - return "sect283r1"; - case "1.3.132.0.34": - return "secp384r1"; - case "1.3.132.0.36": - return "sect409k1"; - case "1.3.132.0.37": - return "sect409r1"; - case "1.3.132.0.35": - return "secp521r1"; - case "1.3.132.0.38": - return "sect571k1"; - case "1.3.132.0.39": - return "sect571r1"; - } - throw new GeneralSecurityException( - "Error parsing EC named curve identifier. Named curve with OID: " + oidString + " is not supported" - ); + throw new SslConfigException("Error parsing Private Key [" + keyPath.toAbsolutePath() + "], file is empty"); } } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java index 23acb0ff269e2..d6aa106039ce3 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java @@ -32,6 +32,8 @@ package org.opensearch.common.ssl; +import org.bouncycastle.crypto.CryptoServicesRegistrar; + import javax.net.ssl.SSLContext; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; @@ -155,7 +157,12 @@ public SSLContext createSslContext() { final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(); final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); try { - SSLContext sslContext = SSLContext.getInstance(contextProtocol()); + SSLContext sslContext; + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + sslContext = SSLContext.getInstance("TLS"); + } else { + sslContext = SSLContext.getInstance(contextProtocol()); + } sslContext.init(new X509ExtendedKeyManager[] { keyManager }, new X509ExtendedTrustManager[] { trustManager }, null); return sslContext; } catch (GeneralSecurityException e) { diff --git a/plugins/identity-shiro/build.gradle b/plugins/identity-shiro/build.gradle index 222443efcb214..2d0d8f27f46b0 100644 --- a/plugins/identity-shiro/build.gradle +++ b/plugins/identity-shiro/build.gradle @@ -28,7 +28,8 @@ dependencies { implementation 'org.passay:passay:1.6.3' - implementation "org.bouncycastle:bcprov-jdk18on:${versions.bouncycastle}" + // Bcrypt hash matching + implementation 'com.password4j:password4j:1.8.2' testImplementation project(path: ':modules:transport-netty4') // for http testImplementation project(path: ':plugins:transport-nio') // for http diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java index a2cb78425929e..e86430c59cb9b 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java @@ -12,7 +12,16 @@ import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.CredentialsMatcher; -import org.bouncycastle.crypto.generators.OpenBSDBCrypt; +import com.password4j.BcryptFunction; +import com.password4j.Password; +import org.opensearch.SpecialPermission; + +import java.nio.CharBuffer; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; + +import static org.opensearch.core.common.Strings.isNullOrEmpty; /** * Password matcher for BCrypt @@ -30,7 +39,35 @@ public class BCryptPasswordMatcher implements CredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { final UsernamePasswordToken userToken = (UsernamePasswordToken) token; - return OpenBSDBCrypt.checkPassword((String) info.getCredentials(), userToken.getPassword()); + return check(userToken.getPassword(), (String) info.getCredentials()); + + } + + private boolean check(char[] password, String hash) { + if (password == null || password.length == 0) { + throw new IllegalStateException("Password cannot be empty or null"); + } + if (isNullOrEmpty(hash)) { + throw new IllegalStateException("Hash cannot be empty or null"); + } + CharBuffer passwordBuffer = CharBuffer.wrap(password); + try { + SecurityManager securityManager = System.getSecurityManager(); + if (securityManager != null) { + securityManager.checkPermission(new SpecialPermission()); + } + return AccessController.doPrivileged((PrivilegedAction) () -> Password.check(passwordBuffer, hash) + .with(BcryptFunction.getInstanceFromHash(hash))); + } finally { + cleanup(passwordBuffer); + } + } + + private void cleanup(CharBuffer password) { + password.clear(); + char[] passwordOverwrite = new char[password.capacity()]; + Arrays.fill(passwordOverwrite, '\0'); + password.put(passwordOverwrite); } } diff --git a/plugins/ingest-attachment/build.gradle b/plugins/ingest-attachment/build.gradle index d631855013527..5fae4fb5f3da7 100644 --- a/plugins/ingest-attachment/build.gradle +++ b/plugins/ingest-attachment/build.gradle @@ -81,9 +81,6 @@ dependencies { api "org.apache.pdfbox:fontbox:${versions.pdfbox}" api "org.apache.pdfbox:jempbox:1.8.17" api "commons-logging:commons-logging:${versions.commonslogging}" - api "org.bouncycastle:bcmail-jdk18on:${versions.bouncycastle}" - api "org.bouncycastle:bcprov-jdk18on:${versions.bouncycastle}" - api "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}" // OpenOffice api "org.apache.poi:poi-ooxml:${versions.poi}" api "org.apache.poi:poi:${versions.poi}" diff --git a/plugins/ingest-attachment/licenses/bcmail-jdk18on-1.78.jar.sha1 b/plugins/ingest-attachment/licenses/bcmail-jdk18on-1.78.jar.sha1 deleted file mode 100644 index eb7e650306f73..0000000000000 --- a/plugins/ingest-attachment/licenses/bcmail-jdk18on-1.78.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -d26f5514b8c54f2878f8d49e0bc8e2acaab3c8bd \ No newline at end of file diff --git a/plugins/ingest-attachment/licenses/bcmail-jdk18on-LICENSE.txt b/plugins/ingest-attachment/licenses/bcmail-jdk18on-LICENSE.txt deleted file mode 100644 index dbba1dd7829c7..0000000000000 --- a/plugins/ingest-attachment/licenses/bcmail-jdk18on-LICENSE.txt +++ /dev/null @@ -1,23 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. - (http://www.bouncycastle.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/plugins/ingest-attachment/licenses/bcmail-jdk18on-NOTICE.txt b/plugins/ingest-attachment/licenses/bcmail-jdk18on-NOTICE.txt deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/plugins/ingest-attachment/licenses/bcpkix-jdk18on-1.78.jar.sha1 b/plugins/ingest-attachment/licenses/bcpkix-jdk18on-1.78.jar.sha1 deleted file mode 100644 index 385a9d930eede..0000000000000 --- a/plugins/ingest-attachment/licenses/bcpkix-jdk18on-1.78.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -dd61bcdb87678451dd42d42e267979bd4b4451a1 \ No newline at end of file diff --git a/plugins/ingest-attachment/licenses/bcpkix-jdk18on-LICENSE.txt b/plugins/ingest-attachment/licenses/bcpkix-jdk18on-LICENSE.txt deleted file mode 100644 index e1fc4a1506db5..0000000000000 --- a/plugins/ingest-attachment/licenses/bcpkix-jdk18on-LICENSE.txt +++ /dev/null @@ -1,23 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. - (http://www.bouncycastle.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/plugins/ingest-attachment/licenses/bcpkix-jdk18on-NOTICE.txt b/plugins/ingest-attachment/licenses/bcpkix-jdk18on-NOTICE.txt deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/plugins/ingest-attachment/licenses/bcprov-jdk18on-1.78.jar.sha1 b/plugins/ingest-attachment/licenses/bcprov-jdk18on-1.78.jar.sha1 deleted file mode 100644 index 47fb5fd5e5f5d..0000000000000 --- a/plugins/ingest-attachment/licenses/bcprov-jdk18on-1.78.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -619aafb92dc0b4c6cc4cf86c487ca48ee2d67a8e \ No newline at end of file diff --git a/plugins/ingest-attachment/licenses/bcprov-jdk18on-LICENSE.txt b/plugins/ingest-attachment/licenses/bcprov-jdk18on-LICENSE.txt deleted file mode 100644 index 9f27bafe96885..0000000000000 --- a/plugins/ingest-attachment/licenses/bcprov-jdk18on-LICENSE.txt +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. - (http://www.bouncycastle.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/plugins/ingest-attachment/licenses/bcprov-jdk18on-NOTICE.txt b/plugins/ingest-attachment/licenses/bcprov-jdk18on-NOTICE.txt deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/server/build.gradle b/server/build.gradle index 429af5d0ac258..9a9b7a4175688 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -125,6 +125,10 @@ dependencies { api "com.google.protobuf:protobuf-java:${versions.protobuf}" api "jakarta.annotation:jakarta.annotation-api:${versions.jakarta_annotation}" + // bouncyCastle + api 'org.bouncycastle:bc-fips:1.0.2.5' + api 'org.bouncycastle:bctls-fips:1.0.19' + testImplementation(project(":test:framework")) { // tests use the locally compiled version of server exclude group: 'org.opensearch', module: 'server' diff --git a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java index 4e167d10b99fa..1dca035bb9736 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -40,6 +40,7 @@ import org.apache.logging.log4j.core.config.Configurator; import org.apache.lucene.util.Constants; import org.apache.lucene.util.StringHelper; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.OpenSearchException; import org.opensearch.Version; import org.opensearch.cli.KeyStoreAwareCommand; @@ -51,6 +52,7 @@ import org.opensearch.common.logging.LogConfigurator; import org.opensearch.common.logging.Loggers; import org.opensearch.common.network.IfConfig; +import org.opensearch.common.settings.FipsSettings; import org.opensearch.common.settings.KeyStoreWrapper; import org.opensearch.common.settings.SecureSettings; import org.opensearch.common.settings.Settings; @@ -195,6 +197,17 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot BootstrapSettings.CTRLHANDLER_SETTING.get(settings) ); + var isFipsEnabled = FipsSettings.FIPS_ENABLED.get(settings); + var isRunningInFipsMode = CryptoServicesRegistrar.setApprovedOnlyMode(isFipsEnabled); + + if (!isRunningInFipsMode && isFipsEnabled){ + throw new BootstrapException(new RuntimeException("cannot enable FIPS mode")); + } + + if (isRunningInFipsMode) { + LogManager.getLogger(Bootstrap.class).info("running in FIPS mode"); + } + // initialize probes before the security manager is installed initializeProbes(); diff --git a/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java b/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java index 5dcf23ae52294..1faf4c0faf04e 100644 --- a/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java @@ -657,6 +657,7 @@ public void apply(Settings value, Settings current, Settings previous) { ClusterManagerTaskThrottler.THRESHOLD_SETTINGS, ClusterManagerTaskThrottler.BASE_DELAY_SETTINGS, ClusterManagerTaskThrottler.MAX_DELAY_SETTINGS, + FipsSettings.FIPS_ENABLED, // Settings related to search backpressure SearchBackpressureSettings.SETTING_MODE, diff --git a/server/src/main/java/org/opensearch/common/settings/FipsSettings.java b/server/src/main/java/org/opensearch/common/settings/FipsSettings.java new file mode 100644 index 0000000000000..958a1440ec29f --- /dev/null +++ b/server/src/main/java/org/opensearch/common/settings/FipsSettings.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.settings; + +import org.opensearch.common.settings.Setting.Property; + +/** + * Settings used for NIST FIPS 140-2 compliance + */ +public class FipsSettings { + + public static final Setting FIPS_ENABLED = Setting.boolSetting( + "fips.approved", + false, + Property.NodeScope); + +} diff --git a/server/src/main/resources/org/opensearch/bootstrap/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index 55e8db0d9c6a3..e64a7b6f05fd0 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -90,6 +90,28 @@ grant codeBase "${codebase.reactor-core}" { permission java.net.SocketPermission "*", "connect,resolve"; }; +// security +grant { + permission java.security.SecurityPermission "getProperty.jdk.tls.disabledAlgorithms"; + permission java.security.SecurityPermission "getProperty.jdk.certpath.disabledAlgorithms"; + permission java.security.SecurityPermission "getProperty.keystore.type.compat"; + permission java.security.SecurityPermission "getProperty.org.bouncycastle.*"; + permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; + permission java.security.SecurityPermission "putProviderProperty.BCJSSE"; + permission java.lang.RuntimePermission "getProtectionDomain"; + permission java.util.PropertyPermission "java.runtime.name", "read"; + permission org.bouncycastle.crypto.CryptoServicesPermission "changeToApprovedModeEnabled"; + permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled"; + //io.netty.handler.codec.DecoderException + permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; + //java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec + permission java.lang.RuntimePermission "accessDeclaredMembers"; + permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; + permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; + permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read"; + permission java.io.FilePermission "${javaHome}/lib/security/jssecacerts", "read"; +}; + //// Everything else: grant {