diff --git a/CHANGELOG.md b/CHANGELOG.md index bbd8d2c427b29..50769f634f579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Changed - Migrate BC libs to their FIPS counterparts ([#14912](https://github.com/opensearch-project/OpenSearch/pull/14912)) +### Changed +- Migrate BC libs to their FIPS counterparts ([#3420](https://github.com/opensearch-project/OpenSearch/pull/14912)) + ### Dependencies - Bump `com.nimbusds:nimbus-jose-jwt` from 9.41.1 to 10.0.2 ([#17607](https://github.com/opensearch-project/OpenSearch/pull/17607)) - Bump `com.google.api:api-common` from 1.8.1 to 2.46.1 ([#17604](https://github.com/opensearch-project/OpenSearch/pull/17604)) diff --git a/build.gradle b/build.gradle index 187574da9e62a..bdfd66560fd4f 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' @@ -472,8 +471,8 @@ gradle.projectsEvaluated { } } -// test retry configuration subprojects { + // test retry configuration tasks.withType(Test).configureEach { develocity.testRetry { if (BuildParams.isCi()) { @@ -559,6 +558,22 @@ subprojects { } } } + + // test with FIPS-140-3 enabled + plugins.withType(JavaPlugin).configureEach { + tasks.withType(Test).configureEach { testTask -> + if (BuildParams.inFipsJvm) { + testTask.jvmArgs += "-Dorg.bouncycastle.fips.approved_only=true" + } + } + } + plugins.withId('opensearch.testclusters') { + testClusters.configureEach { + if (BuildParams.inFipsJvm) { + keystorePassword 'notarealpasswordphrase' + } + } + } } // eclipse configuration diff --git a/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java index d79dfb1124757..cf2cad686f3ac 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java @@ -164,7 +164,7 @@ public void execute(Task t) { test.systemProperty("tests.seed", BuildParams.getTestSeed()); } - var securityFile = "java.security"; + var securityFile = BuildParams.isInFipsJvm() ? "fips_java.security" : "java.security"; test.systemProperty( "java.security.properties", project.getRootProject().getLayout().getProjectDirectory() + "/distribution/src/config/" + securityFile diff --git a/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java b/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java index 54c544a299b84..75f02007a5221 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java @@ -32,6 +32,8 @@ package org.opensearch.gradle.http; +import de.thetaphi.forbiddenapis.SuppressForbidden; + import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; @@ -216,7 +218,7 @@ KeyStore buildTrustStore() throws GeneralSecurityException, IOException { } private KeyStore buildTrustStoreFromFile() throws GeneralSecurityException, IOException { - KeyStore keyStore = KeyStore.getInstance(trustStoreFile.getName().endsWith(".jks") ? "JKS" : "PKCS12"); + var keyStore = getKeyStoreInstance(trustStoreFile.getName().endsWith(".jks") ? "JKS" : "PKCS12"); try (InputStream input = new FileInputStream(trustStoreFile)) { keyStore.load(input, trustStorePassword == null ? null : trustStorePassword.toCharArray()); } @@ -224,7 +226,7 @@ private KeyStore buildTrustStoreFromFile() throws GeneralSecurityException, IOEx } private KeyStore buildTrustStoreFromCA() throws GeneralSecurityException, IOException { - final KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType()); + var store = getKeyStoreInstance(KeyStore.getDefaultType()); store.load(null, null); final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); int counter = 0; @@ -239,6 +241,11 @@ private KeyStore buildTrustStoreFromCA() throws GeneralSecurityException, IOExce return store; } + @SuppressForbidden + private KeyStore getKeyStoreInstance(String type) throws KeyStoreException { + return KeyStore.getInstance(type); + } + private SSLContext createSslContext(KeyStore trustStore) throws GeneralSecurityException { checkForTrustEntry(trustStore); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); diff --git a/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java b/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java new file mode 100644 index 0000000000000..83aba1af2152d --- /dev/null +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java @@ -0,0 +1,35 @@ +/* + * 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.gradle.info; + +import java.util.function.Function; + +public class FipsBuildParams { + + public static final String FIPS_BUILD_PARAM = "crypto.standard"; + + public static final String FIPS_ENV_VAR = "OPENSEARCH_CRYPTO_STANDARD"; + + private static String fipsMode; + + public static void init(Function fipsValue) { + fipsMode = (String) fipsValue.apply(FIPS_BUILD_PARAM); + } + + private FipsBuildParams() {} + + public static boolean isInFipsMode() { + return "FIPS-140-3".equals(fipsMode); + } + + public static String getFipsMode() { + return fipsMode; + } + +} diff --git a/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java b/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java index 570ab4a9f70e1..4c3a09c394278 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java @@ -109,6 +109,8 @@ public void apply(Project project) { File rootDir = project.getRootDir(); GitInfo gitInfo = gitInfo(rootDir); + FipsBuildParams.init(project::findProperty); + BuildParams.init(params -> { // Initialize global build parameters boolean isInternal = GlobalBuildInfoPlugin.class.getResource("/buildSrc.marker") != null; @@ -129,7 +131,7 @@ public void apply(Project project) { params.setIsCi(System.getenv("JENKINS_URL") != null); params.setIsInternal(isInternal); params.setDefaultParallel(findDefaultParallel(project)); - params.setInFipsJvm(Util.getBooleanProperty("tests.fips.enabled", false)); + params.setInFipsJvm(FipsBuildParams.isInFipsMode()); params.setIsSnapshotBuild(Util.getBooleanProperty("build.snapshot", true)); if (isInternal) { params.setBwcVersions(resolveBwcVersions(rootDir)); @@ -179,7 +181,11 @@ private void logGlobalBuildInfo() { LOGGER.quiet(" JAVA_HOME : " + gradleJvm.getJavaHome()); } LOGGER.quiet(" Random Testing Seed : " + BuildParams.getTestSeed()); - LOGGER.quiet(" In FIPS 140 mode : " + BuildParams.isInFipsJvm()); + if (FipsBuildParams.isInFipsMode()) { + LOGGER.quiet(" Crypto Standard : " + FipsBuildParams.getFipsMode()); + } else { + LOGGER.quiet(" Crypto Standard : any-supported"); + } LOGGER.quiet("======================================="); } diff --git a/buildSrc/src/main/java/org/opensearch/gradle/precommit/TestingConventionsTasks.java b/buildSrc/src/main/java/org/opensearch/gradle/precommit/TestingConventionsTasks.java index 9c1285914a03e..d6812704fc8f3 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/precommit/TestingConventionsTasks.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/precommit/TestingConventionsTasks.java @@ -40,6 +40,7 @@ import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; import org.gradle.api.Task; +import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTree; import org.gradle.api.plugins.jvm.JvmTestSuite; @@ -87,6 +88,7 @@ public class TestingConventionsTasks extends DefaultTask { private final NamedDomainObjectContainer naming; private final Project project; + private final ConfigurableFileCollection extraClassPath = getProject().files(); @Inject public TestingConventionsTasks(Project project) { @@ -398,7 +400,16 @@ private boolean isAnnotated(Method method, Class annotation) { @Classpath public FileCollection getTestsClassPath() { - return Util.getJavaTestSourceSet(project).get().getRuntimeClasspath(); + return Util.getJavaTestSourceSet(project).get().getRuntimeClasspath().plus(extraClassPath); + } + + @Classpath + public ConfigurableFileCollection getExtraClassPath() { + return extraClassPath; + } + + public void addExtraClassPath(Object... paths) { + extraClassPath.from(paths); } private Map walkPathAndLoadClasses(File testRoot) { 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 c7af3d0a155f7..e86496766abf9 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java @@ -46,6 +46,7 @@ import org.opensearch.gradle.Version; import org.opensearch.gradle.VersionProperties; import org.opensearch.gradle.info.BuildParams; +import org.opensearch.gradle.info.FipsBuildParams; import org.gradle.api.Action; import org.gradle.api.Named; import org.gradle.api.NamedDomainObjectContainer; @@ -546,6 +547,10 @@ public synchronized void start() { logToProcessStdout("installed plugins"); } + if (FipsBuildParams.isInFipsMode() && keystorePassword.isEmpty()) { + throw new TestClustersException("Can not start " + this + " in FIPS JVM, missing keystore password"); + } + logToProcessStdout("Creating opensearch keystore with password set to [" + keystorePassword + "]"); if (keystorePassword.length() > 0) { runOpenSearchBinScriptWithInput(keystorePassword + "\n" + keystorePassword + "\n", "opensearch-keystore", "create", "-p"); @@ -791,6 +796,9 @@ private Map getOpenSearchEnvironment() { // Override the system hostname variables for testing defaultEnv.put("HOSTNAME", HOSTNAME_OVERRIDE); defaultEnv.put("COMPUTERNAME", COMPUTERNAME_OVERRIDE); + if (FipsBuildParams.isInFipsMode()) { + defaultEnv.put(FipsBuildParams.FIPS_ENV_VAR, FipsBuildParams.getFipsMode()); + } Set commonKeys = new HashSet<>(environment.keySet()); commonKeys.retainAll(defaultEnv.keySet()); 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/forbidden/jdk-signatures.txt b/buildSrc/src/main/resources/forbidden/jdk-signatures.txt index b2fd479dce5ff..1ece009c57ecd 100644 --- a/buildSrc/src/main/resources/forbidden/jdk-signatures.txt +++ b/buildSrc/src/main/resources/forbidden/jdk-signatures.txt @@ -37,6 +37,12 @@ java.nio.file.Path#toFile() java.nio.file.Files#createTempDirectory(java.lang.String,java.nio.file.attribute.FileAttribute[]) java.nio.file.Files#createTempFile(java.lang.String,java.lang.String,java.nio.file.attribute.FileAttribute[]) +@defaultMessage Use org.opensearch.common.ssl.KeyStoreFactory instead of java.security.KeyStore +java.security.KeyStore#getInstance(java.lang.String) +java.security.KeyStore#getInstance(java.lang.String,java.lang.String) +java.security.KeyStore#getInstance(java.lang.String,java.security.Provider) +java.security.KeyStore#getInstance(java.io.File,char[]) + @defaultMessage Don't use java serialization - this can break BWC without noticing it java.io.ObjectOutputStream java.io.ObjectOutput diff --git a/buildSrc/src/main/resources/test/ssl/test-node.bcfks b/buildSrc/src/main/resources/test/ssl/test-node.bcfks new file mode 100644 index 0000000000000..141eec9d66ded Binary files /dev/null and b/buildSrc/src/main/resources/test/ssl/test-node.bcfks differ diff --git a/client/rest/build.gradle b/client/rest/build.gradle index da91206e27eed..a0fa1b370e340 100644 --- a/client/rest/build.gradle +++ b/client/rest/build.gradle @@ -29,6 +29,7 @@ */ import de.thetaphi.forbiddenapis.gradle.CheckForbiddenApis +import org.opensearch.gradle.precommit.TestingConventionsTasks apply plugin: 'opensearch.build' apply plugin: 'opensearch.publish' @@ -80,6 +81,7 @@ tasks.named("dependencyLicenses").configure { tasks.withType(CheckForbiddenApis).configureEach { //client does not depend on server, so only jdk and http signatures should be checked replaceSignatureFiles('jdk-signatures', 'http-signatures') + classpath += files(project(':libs:opensearch-ssl-config').jar.archiveFile) } tasks.named('forbiddenApisTest').configure { @@ -141,7 +143,70 @@ thirdPartyAudit { 'io.micrometer.core.instrument.composite.CompositeMeterRegistry', 'io.micrometer.core.instrument.search.Search', 'reactor.blockhound.BlockHound$Builder', - 'reactor.blockhound.integration.BlockHoundIntegration' + 'reactor.blockhound.integration.BlockHoundIntegration', + 'org.bouncycastle.asn1.ASN1Encodable', + 'org.bouncycastle.asn1.ASN1InputStream', + 'org.bouncycastle.asn1.ASN1Integer', + 'org.bouncycastle.asn1.ASN1Object', + 'org.bouncycastle.asn1.ASN1ObjectIdentifier', + 'org.bouncycastle.asn1.ASN1OctetString', + 'org.bouncycastle.asn1.ASN1Primitive', + 'org.bouncycastle.asn1.ASN1Sequence', + 'org.bouncycastle.asn1.ASN1String', + 'org.bouncycastle.asn1.DERBitString', + 'org.bouncycastle.asn1.DERNull', + 'org.bouncycastle.asn1.bsi.BSIObjectIdentifiers', + 'org.bouncycastle.asn1.cms.GCMParameters', + 'org.bouncycastle.asn1.eac.EACObjectIdentifiers', + 'org.bouncycastle.asn1.edec.EdECObjectIdentifiers', + 'org.bouncycastle.asn1.nist.NISTObjectIdentifiers', + 'org.bouncycastle.asn1.ocsp.OCSPResponse', + 'org.bouncycastle.asn1.ocsp.ResponderID', + 'org.bouncycastle.asn1.oiw.OIWObjectIdentifiers', + 'org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers', + 'org.bouncycastle.asn1.pkcs.PrivateKeyInfo', + 'org.bouncycastle.asn1.pkcs.RSASSAPSSparams', + 'org.bouncycastle.asn1.rosstandart.RosstandartObjectIdentifiers', + 'org.bouncycastle.asn1.x500.AttributeTypeAndValue', + 'org.bouncycastle.asn1.x500.RDN', + 'org.bouncycastle.asn1.x500.X500Name', + 'org.bouncycastle.asn1.x500.style.BCStyle', + 'org.bouncycastle.asn1.x509.AlgorithmIdentifier', + 'org.bouncycastle.asn1.x509.Certificate', + 'org.bouncycastle.asn1.x509.DigestInfo', + 'org.bouncycastle.asn1.x509.Extensions', + 'org.bouncycastle.asn1.x509.KeyPurposeId', + 'org.bouncycastle.asn1.x509.SubjectPublicKeyInfo', + 'org.bouncycastle.asn1.x509.X509ObjectIdentifiers', + 'org.bouncycastle.asn1.x9.ECNamedCurveTable', + 'org.bouncycastle.asn1.x9.X9ObjectIdentifiers', + 'org.bouncycastle.crypto.KDFCalculator', + 'org.bouncycastle.crypto.fips.FipsDRBG', + 'org.bouncycastle.crypto.fips.FipsDRBG$Base', + 'org.bouncycastle.crypto.fips.FipsDRBG$Builder', + 'org.bouncycastle.crypto.fips.FipsKDF', + 'org.bouncycastle.crypto.fips.FipsKDF$TLSOperatorFactory', + 'org.bouncycastle.crypto.fips.FipsKDF$TLSPRF', + 'org.bouncycastle.crypto.fips.FipsKDF$TLSParametersBuilder', + 'org.bouncycastle.crypto.fips.FipsKDF$TLSParametersWithPRFBuilder', + 'org.bouncycastle.crypto.fips.FipsNonceGenerator', + 'org.bouncycastle.crypto.fips.FipsSecureRandom', + 'org.bouncycastle.jcajce.io.OutputStreamFactory', + 'org.bouncycastle.jcajce.spec.DHDomainParameterSpec', + 'org.bouncycastle.jcajce.util.JcaJceHelper', + 'org.bouncycastle.math.ec.ECCurve', + 'org.bouncycastle.math.ec.ECFieldElement', + 'org.bouncycastle.math.ec.ECPoint', + 'org.bouncycastle.util.Arrays', + 'org.bouncycastle.util.BigIntegers', + 'org.bouncycastle.util.IPAddress', + 'org.bouncycastle.util.Integers', + 'org.bouncycastle.util.Pack', + 'org.bouncycastle.util.Shorts', + 'org.bouncycastle.util.Strings', + 'org.bouncycastle.util.Times', + 'org.bouncycastle.util.encoders.Hex', + 'org.bouncycastle.util.io.Streams' ) ignoreViolations( 'reactor.core.publisher.Traces$SharedSecretsCallSiteSupplierFactory$TracingException' @@ -149,8 +214,18 @@ thirdPartyAudit { } tasks.withType(JavaCompile) { + classpath += files(project(':libs:opensearch-ssl-config').jar.archiveFile) // Suppressing '[options] target value 8 is obsolete and will be removed in a future release' configure(options) { options.compilerArgs << '-Xlint:-options' + options.compilerArgs -= '-Werror' // use of incubator modules is reported as a warning } } + +tasks.withType(Test).configureEach { + classpath += files(project(':libs:opensearch-ssl-config').jar.archiveFile) +} + +tasks.named("testingConventions", TestingConventionsTasks).configure { + addExtraClassPath(project(":libs:opensearch-ssl-config").jar.archiveFile) +} diff --git a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java index 4f0ce6404e587..6d674347924f1 100644 --- a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java +++ b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java @@ -39,6 +39,8 @@ import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -56,22 +58,22 @@ import java.security.PrivilegedAction; import java.security.SecureRandom; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; /** * Integration test to validate the builder builds a client with the correct configuration */ -public class RestClientBuilderIntegTests extends RestClientTestCase { +public class RestClientBuilderIntegTests extends RestClientTestCase implements RestClientFipsAwareTestCase { private static HttpsServer httpsServer; @BeforeClass public static void startHttpServer() throws Exception { httpsServer = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); - httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext(true))); + httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext(true, KeyStoreType.BCFKS))); httpsServer.createContext("/", new ResponseHandler()); httpsServer.start(); } @@ -91,7 +93,11 @@ public static void stopHttpServers() throws IOException { } public void testBuilderUsesDefaultSSLContext() throws Exception { - assumeFalse("https://github.com/elastic/elasticsearch/issues/49094", inFipsJvm()); + makeRequest(); + } + + @Override + public void makeRequest(KeyStoreType keyStoreType) throws Exception { final SSLContext defaultSSLContext = SSLContext.getDefault(); try { try (RestClient client = buildRestClient()) { @@ -103,7 +109,7 @@ public void testBuilderUsesDefaultSSLContext() throws Exception { } } - SSLContext.setDefault(getSslContext(false)); + SSLContext.setDefault(getSslContext(false, keyStoreType)); try (RestClient client = buildRestClient()) { Response response = client.performRequest(new Request("GET", "/")); assertEquals(200, response.getStatusLine().getStatusCode()); @@ -118,22 +124,22 @@ private RestClient buildRestClient() { return RestClient.builder(new HttpHost("https", address.getHostString(), address.getPort())).build(); } - private static SSLContext getSslContext(boolean server) throws Exception { + private static SSLContext getSslContext(boolean server, KeyStoreType keyStoreType) throws Exception { SSLContext sslContext; char[] password = "password".toCharArray(); SecureRandom secureRandom = SecureRandom.getInstance("DEFAULT", "BCFIPS"); - String fileExtension = ".jks"; + String fileExtension = KeyStoreType.TYPE_TO_EXTENSION_MAP.get(keyStoreType).get(0); try ( InputStream trustStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test_truststore" + fileExtension); InputStream keyStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/testks" + fileExtension) ) { - KeyStore keyStore = KeyStore.getInstance("JKS"); + KeyStore keyStore = KeyStoreFactory.getInstance(keyStoreType); keyStore.load(keyStoreFile, password); KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX", "BCJSSE"); kmf.init(keyStore, password); - KeyStore trustStore = KeyStore.getInstance("JKS"); + KeyStore trustStore = KeyStoreFactory.getInstance(keyStoreType); trustStore.load(trustStoreFile, password); TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", "BCJSSE"); tmf.init(trustStore); diff --git a/client/rest/src/test/java/org/opensearch/client/RestClientFipsAwareTestCase.java b/client/rest/src/test/java/org/opensearch/client/RestClientFipsAwareTestCase.java new file mode 100644 index 0000000000000..2f8e4718da5f8 --- /dev/null +++ b/client/rest/src/test/java/org/opensearch/client/RestClientFipsAwareTestCase.java @@ -0,0 +1,26 @@ +/* + * 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.client; + +import org.opensearch.common.ssl.KeyStoreType; + +import static org.opensearch.client.RestClientTestCase.inFipsJvm; + +public interface RestClientFipsAwareTestCase { + + default void makeRequest() throws Exception { + if (inFipsJvm()) { + makeRequest(KeyStoreType.BCFKS); + } else { + makeRequest(KeyStoreType.JKS); + } + } + + void makeRequest(KeyStoreType keyStoreType) throws Exception; +} diff --git a/client/rest/src/test/java/org/opensearch/client/documentation/RestClientDocumentation.java b/client/rest/src/test/java/org/opensearch/client/documentation/RestClientDocumentation.java index d9c82307cae8a..4a642cc6fe300 100644 --- a/client/rest/src/test/java/org/opensearch/client/documentation/RestClientDocumentation.java +++ b/client/rest/src/test/java/org/opensearch/client/documentation/RestClientDocumentation.java @@ -67,6 +67,7 @@ import org.opensearch.client.RestClient; import org.opensearch.client.RestClientBuilder; import org.opensearch.client.RestClientBuilder.HttpClientConfigCallback; +import org.opensearch.common.ssl.KeyStoreFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -84,6 +85,8 @@ import java.util.Iterator; import java.util.concurrent.CountDownLatch; +import static org.opensearch.common.ssl.KeyStoreType.PKCS_12; + /** * This class is used to generate the Java low-level REST client documentation. * You need to wrap your code between two tags like: @@ -436,7 +439,7 @@ public HttpAsyncClientBuilder customizeHttpClient( String keyStorePass = ""; //tag::rest-client-config-encrypted-communication Path trustStorePath = Paths.get("/path/to/truststore.p12"); - KeyStore truststore = KeyStore.getInstance("pkcs12"); + KeyStore truststore = KeyStoreFactory.getInstance(PKCS_12); try (InputStream is = Files.newInputStream(trustStorePath)) { truststore.load(is, keyStorePass.toCharArray()); } @@ -478,7 +481,7 @@ public TlsDetails create(final SSLEngine sslEngine) { try (InputStream is = Files.newInputStream(caCertificatePath)) { trustedCa = factory.generateCertificate(is); } - KeyStore trustStore = KeyStore.getInstance("pkcs12"); + KeyStore trustStore = KeyStoreFactory.getInstance(PKCS_12); trustStore.load(null, null); trustStore.setCertificateEntry("ca", trustedCa); SSLContextBuilder sslContextBuilder = SSLContexts.custom() @@ -516,8 +519,8 @@ public TlsDetails create(final SSLEngine sslEngine) { //tag::rest-client-config-mutual-tls-authentication Path trustStorePath = Paths.get("/path/to/your/truststore.p12"); Path keyStorePath = Paths.get("/path/to/your/keystore.p12"); - KeyStore trustStore = KeyStore.getInstance("pkcs12"); - KeyStore keyStore = KeyStore.getInstance("pkcs12"); + KeyStore trustStore = KeyStoreFactory.getInstance(PKCS_12); + KeyStore keyStore = KeyStoreFactory.getInstance(PKCS_12); try (InputStream is = Files.newInputStream(trustStorePath)) { trustStore.load(is, trustStorePass.toCharArray()); } diff --git a/client/rest/src/test/resources/test_truststore.bcfks b/client/rest/src/test/resources/test_truststore.bcfks new file mode 100644 index 0000000000000..dfa168caf70ef Binary files /dev/null and b/client/rest/src/test/resources/test_truststore.bcfks differ diff --git a/client/rest/src/test/resources/testks.bcfks b/client/rest/src/test/resources/testks.bcfks new file mode 100644 index 0000000000000..988242f0db6b8 Binary files /dev/null and b/client/rest/src/test/resources/testks.bcfks differ diff --git a/client/test/build.gradle b/client/test/build.gradle index b77865df6decf..afecaf685c51c 100644 --- a/client/test/build.gradle +++ b/client/test/build.gradle @@ -43,6 +43,8 @@ dependencies { api "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" api "junit:junit:${versions.junit}" api "org.hamcrest:hamcrest:${versions.hamcrest}" + api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" + runtimeOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" } tasks.named('forbiddenApisMain').configure { diff --git a/client/test/src/main/java/org/opensearch/client/RestClientTestCase.java b/client/test/src/main/java/org/opensearch/client/RestClientTestCase.java index b4eacdbf88827..9d52ee4cb3da7 100644 --- a/client/test/src/main/java/org/opensearch/client/RestClientTestCase.java +++ b/client/test/src/main/java/org/opensearch/client/RestClientTestCase.java @@ -45,6 +45,7 @@ import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite; import org.apache.hc.core5.http.Header; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import java.util.ArrayList; import java.util.HashMap; @@ -116,6 +117,10 @@ protected static void assertHeaders( assertTrue("some headers that were sent weren't returned " + expectedHeaders, expectedHeaders.isEmpty()); } + protected static boolean inFipsJvm() { + return CryptoServicesRegistrar.isInApprovedOnlyMode(); + } + private static void addValueToListEntry(final Map> map, final String name, final String value) { List values = map.get(name); if (values == null) { @@ -125,7 +130,4 @@ private static void addValueToListEntry(final Map> map, fin values.add(value); } - public static boolean inFipsJvm() { - return Boolean.parseBoolean(System.getProperty("tests.fips.enabled")); - } } diff --git a/distribution/src/bin/opensearch-cli b/distribution/src/bin/opensearch-cli index 19f5d3e46d6c4..0e7cbc23a31d6 100644 --- a/distribution/src/bin/opensearch-cli +++ b/distribution/src/bin/opensearch-cli @@ -20,6 +20,12 @@ done # avoid stealing many CPU cycles; a user can override by setting OPENSEARCH_JAVA_OPTS OPENSEARCH_JAVA_OPTS="-Xms4m -Xmx64m -XX:+UseSerialGC ${OPENSEARCH_JAVA_OPTS}" +if [ "$OPENSEARCH_CRYPTO_STANDARD" = "FIPS-140-3" ]; then + OPENSEARCH_JAVA_OPTS="-Dorg.bouncycastle.fips.approved_only=true \ + -Djava.security.properties=${OPENSEARCH_PATH_CONF}/fips_java.security \ + ${OPENSEARCH_JAVA_OPTS}" +fi + exec \ "$JAVA" \ "$XSHARE" \ diff --git a/distribution/src/bin/opensearch-cli.bat b/distribution/src/bin/opensearch-cli.bat index f080346a4478a..4eb17d146393d 100644 --- a/distribution/src/bin/opensearch-cli.bat +++ b/distribution/src/bin/opensearch-cli.bat @@ -16,6 +16,12 @@ rem use a small heap size for the CLI tools, and thus the serial collector to rem avoid stealing many CPU cycles; a user can override by setting OPENSEARCH_JAVA_OPTS set OPENSEARCH_JAVA_OPTS=-Xms4m -Xmx64m -XX:+UseSerialGC %OPENSEARCH_JAVA_OPTS% +if "%OPENSEARCH_CRYPTO_STANDARD%"=="FIPS-140-3" ( + set OPENSEARCH_JAVA_OPTS=-Dorg.bouncycastle.fips.approved_only=true ^ + -Djava.security.properties="%OPENSEARCH_PATH_CONF%\fips_java.security" ^ + %OPENSEARCH_JAVA_OPTS% +) + "%JAVA%" ^ %OPENSEARCH_JAVA_OPTS% ^ -Dopensearch.path.home="%OPENSEARCH_HOME%" ^ diff --git a/buildSrc/src/main/resources/fips_java_bcjsse_11.security b/distribution/src/config/fips_java.security similarity index 76% rename from buildSrc/src/main/resources/fips_java_bcjsse_11.security rename to distribution/src/config/fips_java.security index e6a025e7eb10d..2e5bf13cf8d0e 100644 --- a/buildSrc/src/main/resources/fips_java_bcjsse_11.security +++ b/distribution/src/config/fips_java.security @@ -1,41 +1,42 @@ -# Security Properties for JDK 11 and higher, with BouncyCastle FIPS provider and BouncyCastleJsseProvider in FIPS mode +# Security Properties for JDK 11 and higher, with BouncyCastle FIPS provider and BouncyCastleJsseProvider in approved-only 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.4=SunJGSS -securerandom.source=file:/dev/urandom -securerandom.strongAlgorithms=NativePRNGBlocking:SUN,DRBG:SUN -securerandom.drbg.config= + login.configuration.provider=sun.security.provider.ConfigFile 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. package.definition=sun.misc.,\ sun.reflect. + security.overridePropertiesFile=true + 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 < 2048, DSA keySize < 2048, EC keySize < 224 +jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 2048, DSA keySize < 2048 +jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, MD5withRSA, DH keySize < 2048, 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 jdk.tls.keyLimits=AES/GCM/NoPadding KeyUpdate 2^37 + 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,\ @@ -49,5 +50,6 @@ jdk.xml.dsig.secureValidationPolicy=\ minKeySize EC 224,\ noDuplicateIds,\ noRetrievalMethodLoops + jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep;\ java.base/java.security.KeyRep$Type;java.base/javax.crypto.spec.SecretKeySpec;!* diff --git a/distribution/src/config/java.security b/distribution/src/config/java.security index d3682533af407..dd4256e70945f 100644 --- a/distribution/src/config/java.security +++ b/distribution/src/config/java.security @@ -6,5 +6,9 @@ security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider security.provider.3=SUN security.provider.4=SunJGSS +securerandom.source=file:/dev/urandom +securerandom.strongAlgorithms=NativePRNGBlocking:SUN,DRBG:SUN +securerandom.drbg.config= + ssl.KeyManagerFactory.algorithm=PKIX ssl.TrustManagerFactory.algorithm=PKIX diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/bootstrap/BootstrapTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/bootstrap/BootstrapTests.java index 149145bb2d66b..83303b85bd36a 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/bootstrap/BootstrapTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/bootstrap/BootstrapTests.java @@ -71,6 +71,7 @@ public void setupEnv() throws IOException { } public void testLoadSecureSettings() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); final Path configPath = env.configDir(); final SecureString seed; try (KeyStoreWrapper keyStoreWrapper = KeyStoreWrapper.create()) { diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddFileKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddFileKeyStoreCommandTests.java index db6bb2d5473f4..e017ac2241fc9 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddFileKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddFileKeyStoreCommandTests.java @@ -78,6 +78,7 @@ private void addFile(KeyStoreWrapper keystore, String setting, Path file, String } public void testMissingCreateWithEmptyPasswordWhenPrompted() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; Path file1 = createRandomFile(); terminal.addTextInput("y"); @@ -86,6 +87,7 @@ public void testMissingCreateWithEmptyPasswordWhenPrompted() throws Exception { } public void testMissingCreateWithEmptyPasswordWithoutPromptIfForced() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; Path file1 = createRandomFile(); execute("-f", "foo", file1.toString()); @@ -93,7 +95,8 @@ public void testMissingCreateWithEmptyPasswordWithoutPromptIfForced() throws Exc } public void testMissingNoCreate() throws Exception { - terminal.addSecretInput(randomFrom("", "keystorepassword")); + var password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); + terminal.addSecretInput(password); terminal.addTextInput("n"); // explicit no execute("foo"); assertNull(KeyStoreWrapper.load(env.configDir())); @@ -221,6 +224,7 @@ public void testIncorrectPassword() throws Exception { } public void testAddToUnprotectedKeystore() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; Path file = createRandomFile(); KeyStoreWrapper keystore = createKeystore(password); diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddStringKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddStringKeyStoreCommandTests.java index 41ab7c45690dc..305dd50a0af21 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddStringKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddStringKeyStoreCommandTests.java @@ -82,6 +82,7 @@ public void testInvalidPassphrease() throws Exception { } public void testMissingPromptCreateWithoutPasswordWhenPrompted() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); terminal.addTextInput("y"); terminal.addSecretInput("bar"); execute("foo"); @@ -89,6 +90,7 @@ public void testMissingPromptCreateWithoutPasswordWhenPrompted() throws Exceptio } public void testMissingPromptCreateWithoutPasswordWithoutPromptIfForced() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); terminal.addSecretInput("bar"); execute("-f", "foo"); assertSecureString("foo", "bar", ""); @@ -260,7 +262,9 @@ public void testMissingSettingName() throws Exception { } public void testSpecialCharacterInName() throws Exception { - createKeystore(""); + String password = "keystorepassword"; + createKeystore(password); + terminal.addSecretInput(password); terminal.addSecretInput("value"); final String key = randomAlphaOfLength(4) + '@' + randomAlphaOfLength(4); final UserException e = expectThrows(UserException.class, () -> execute(key)); @@ -269,6 +273,7 @@ public void testSpecialCharacterInName() throws Exception { } public void testAddToUnprotectedKeystore() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; createKeystore(password, "foo", "bar"); terminal.addTextInput(""); diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ChangeKeyStorePasswordCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ChangeKeyStorePasswordCommandTests.java index 1aa62cf71ed65..b3be29517766e 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ChangeKeyStorePasswordCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ChangeKeyStorePasswordCommandTests.java @@ -54,6 +54,7 @@ protected Environment createEnv(Map settings) throws UserExcepti } public void testSetKeyStorePassword() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); createKeystore(""); loadKeystore(""); terminal.addSecretInput("thepassword"); @@ -67,14 +68,15 @@ public void testChangeKeyStorePassword() throws Exception { createKeystore("theoldpassword"); loadKeystore("theoldpassword"); terminal.addSecretInput("theoldpassword"); - terminal.addSecretInput("thepassword"); - terminal.addSecretInput("thepassword"); + terminal.addSecretInput("thenewpassword"); + terminal.addSecretInput("thenewpassword"); // Prompted thrice: Once for the existing and twice for the new password execute(); - loadKeystore("thepassword"); + loadKeystore("thenewpassword"); } public void testChangeKeyStorePasswordToEmpty() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); createKeystore("theoldpassword"); loadKeystore("theoldpassword"); terminal.addSecretInput("theoldpassword"); diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java index 5a06bc2400176..1596f24c024fd 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java @@ -58,7 +58,7 @@ protected Environment createEnv(Map settings) throws UserExcepti } public void testNotMatchingPasswords() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput("notthekeystorepasswordyouarelookingfor"); UserException e = expectThrows(UserException.class, () -> execute(randomFrom("-p", "--password"))); @@ -67,32 +67,34 @@ public void testNotMatchingPasswords() throws Exception { } public void testDefaultNotPromptForPassword() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); execute(); Path configDir = env.configDir(); assertNotNull(KeyStoreWrapper.load(configDir)); } public void testPosix() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput(password); - execute(); + execute(randomFrom("-p", "--password")); Path configDir = env.configDir(); assertNotNull(KeyStoreWrapper.load(configDir)); } public void testNotPosix() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput(password); env = setupEnv(false, fileSystems); - execute(); + execute(randomFrom("-p", "--password")); Path configDir = env.configDir(); assertNotNull(KeyStoreWrapper.load(configDir)); } public void testOverwrite() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); + Path keystoreFile = KeyStoreWrapper.keystorePath(env.configDir()); byte[] content = "not a keystore".getBytes(StandardCharsets.UTF_8); Files.write(keystoreFile, content); @@ -106,9 +108,9 @@ public void testOverwrite() throws Exception { assertArrayEquals(content, Files.readAllBytes(keystoreFile)); terminal.addTextInput("y"); - terminal.addSecretInput(password); - terminal.addSecretInput(password); - execute(); + terminal.addSecretInput(password); // enter password + terminal.addSecretInput(password); // enter password again + execute(randomFrom("-p", "--password")); assertNotNull(KeyStoreWrapper.load(env.configDir())); } } diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java index 9ebc92c55530b..39f645e587d3a 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java @@ -61,6 +61,7 @@ public void testFailsWithNoKeystore() throws Exception { } public void testFailsWhenKeystoreLacksPassword() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); createKeystore(""); UserException e = expectThrows(UserException.class, this::execute); assertEquals("Unexpected exit code", HasPasswordKeyStoreCommand.NO_PASSWORD_EXIT_CODE, e.exitCode); @@ -68,13 +69,13 @@ public void testFailsWhenKeystoreLacksPassword() throws Exception { } public void testSucceedsWhenKeystoreHasPassword() throws Exception { - createKeystore("password"); + createKeystore("thenewpassword"); String output = execute(); assertThat(output, containsString("Keystore is password-protected")); } public void testSilentSucceedsWhenKeystoreHasPassword() throws Exception { - createKeystore("password"); + createKeystore("thenewpassword"); String output = execute("--silent"); assertThat(output, is(emptyString())); } diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/KeyStoreWrapperTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/KeyStoreWrapperTests.java index e6cb77336c6e7..49ff63c78876d 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/KeyStoreWrapperTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/KeyStoreWrapperTests.java @@ -38,8 +38,10 @@ import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexOutput; import org.apache.lucene.store.NIOFSDirectory; -import org.opensearch.common.Randomness; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.settings.KeyStoreWrapper; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.common.util.io.IOUtils; import org.opensearch.core.common.settings.SecureString; import org.opensearch.env.Environment; @@ -81,6 +83,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.function.Supplier; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; @@ -90,6 +93,8 @@ public class KeyStoreWrapperTests extends OpenSearchTestCase { + String STRONG_PASSWORD = "6!6428DQXwPpi7@$ggeg/="; // has to be at least 112 bit long. + Supplier passphraseSupplier = () -> inFipsJvm() ? STRONG_PASSWORD.toCharArray() : new char[0]; Environment env; List fileSystems = new ArrayList<>(); @@ -110,9 +115,9 @@ public void testFileSettingExhaustiveBytes() throws Exception { bytes[i] = (byte) i; } keystore.setFile("foo", bytes); - keystore.save(env.configDir(), new char[0]); + keystore.save(env.configDir(), passphraseSupplier.get()); keystore = KeyStoreWrapper.load(env.configDir()); - keystore.decrypt(new char[0]); + keystore.decrypt(passphraseSupplier.get()); try (InputStream stream = keystore.getFile("foo")) { for (int i = 0; i < 256; ++i) { int got = stream.read(); @@ -132,11 +137,11 @@ public void testCreate() throws Exception { public void testDecryptKeyStoreWithWrongPassword() throws Exception { KeyStoreWrapper keystore = KeyStoreWrapper.create(); - keystore.save(env.configDir(), new char[0]); + keystore.save(env.configDir(), passphraseSupplier.get()); final KeyStoreWrapper loadedKeystore = KeyStoreWrapper.load(env.configDir()); final SecurityException exception = expectThrows( SecurityException.class, - () -> loadedKeystore.decrypt(new char[] { 'i', 'n', 'v', 'a', 'l', 'i', 'd' }) + () -> loadedKeystore.decrypt("wrong_password_<1234567890%&!\"/>_but_a_strong_one".toCharArray()) ); assertThat( exception.getMessage(), @@ -186,12 +191,12 @@ public void testValueSHA256Digest() throws Exception { public void testUpgradeNoop() throws Exception { KeyStoreWrapper keystore = KeyStoreWrapper.create(); SecureString seed = keystore.getString(KeyStoreWrapper.SEED_SETTING.getKey()); - keystore.save(env.configDir(), new char[0]); + keystore.save(env.configDir(), passphraseSupplier.get()); // upgrade does not overwrite seed KeyStoreWrapper.upgrade(keystore, env.configDir(), new char[0]); assertEquals(seed.toString(), keystore.getString(KeyStoreWrapper.SEED_SETTING.getKey()).toString()); keystore = KeyStoreWrapper.load(env.configDir()); - keystore.decrypt(new char[0]); + keystore.decrypt(passphraseSupplier.get()); assertEquals(seed.toString(), keystore.getString(KeyStoreWrapper.SEED_SETTING.getKey()).toString()); } @@ -201,7 +206,7 @@ public void testFailWhenCannotConsumeSecretStream() throws Exception { try (IndexOutput indexOutput = directory.createOutput("opensearch.keystore", IOContext.DEFAULT)) { CodecUtil.writeHeader(indexOutput, "opensearch.keystore", 3); indexOutput.writeByte((byte) 0); // No password - SecureRandom random = Randomness.createSecure(); + SecureRandom random = CryptoServicesRegistrar.getSecureRandom(); byte[] salt = new byte[64]; random.nextBytes(salt); byte[] iv = new byte[12]; @@ -218,7 +223,7 @@ public void testFailWhenCannotConsumeSecretStream() throws Exception { } KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir); - SecurityException e = expectThrows(SecurityException.class, () -> keystore.decrypt(new char[0])); + SecurityException e = expectThrows(SecurityException.class, () -> keystore.decrypt(passphraseSupplier.get())); assertThat(e.getMessage(), containsString("Keystore has been corrupted or tampered with")); assertThat(e.getCause(), instanceOf(EOFException.class)); } @@ -229,7 +234,7 @@ public void testFailWhenCannotConsumeEncryptedBytesStream() throws Exception { try (IndexOutput indexOutput = directory.createOutput("opensearch.keystore", IOContext.DEFAULT)) { CodecUtil.writeHeader(indexOutput, "opensearch.keystore", 3); indexOutput.writeByte((byte) 0); // No password - SecureRandom random = Randomness.createSecure(); + SecureRandom random = CryptoServicesRegistrar.getSecureRandom(); byte[] salt = new byte[64]; random.nextBytes(salt); byte[] iv = new byte[12]; @@ -258,7 +263,7 @@ public void testFailWhenSecretStreamNotConsumed() throws Exception { try (IndexOutput indexOutput = directory.createOutput("opensearch.keystore", IOContext.DEFAULT)) { CodecUtil.writeHeader(indexOutput, "opensearch.keystore", 3); indexOutput.writeByte((byte) 0); // No password - SecureRandom random = Randomness.createSecure(); + SecureRandom random = CryptoServicesRegistrar.getSecureRandom(); byte[] salt = new byte[64]; random.nextBytes(salt); byte[] iv = new byte[12]; @@ -275,7 +280,7 @@ public void testFailWhenSecretStreamNotConsumed() throws Exception { } KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir); - SecurityException e = expectThrows(SecurityException.class, () -> keystore.decrypt(new char[0])); + SecurityException e = expectThrows(SecurityException.class, () -> keystore.decrypt(passphraseSupplier.get())); assertThat(e.getMessage(), containsString("Keystore has been corrupted or tampered with")); } @@ -285,7 +290,7 @@ public void testFailWhenEncryptedBytesStreamIsNotConsumed() throws Exception { try (IndexOutput indexOutput = directory.createOutput("opensearch.keystore", IOContext.DEFAULT)) { CodecUtil.writeHeader(indexOutput, "opensearch.keystore", 3); indexOutput.writeByte((byte) 0); // No password - SecureRandom random = Randomness.createSecure(); + SecureRandom random = CryptoServicesRegistrar.getSecureRandom(); byte[] salt = new byte[64]; random.nextBytes(salt); byte[] iv = new byte[12]; @@ -306,7 +311,7 @@ public void testFailWhenEncryptedBytesStreamIsNotConsumed() throws Exception { } private CipherOutputStream getCipherStream(ByteArrayOutputStream bytes, byte[] salt, byte[] iv) throws Exception { - PBEKeySpec keySpec = new PBEKeySpec(new char[0], salt, 10000, 128); + PBEKeySpec keySpec = new PBEKeySpec(passphraseSupplier.get(), salt, 10000, 128); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); SecretKey secretKey = keyFactory.generateSecret(keySpec); SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); @@ -346,12 +351,12 @@ private void possiblyAlterEncryptedBytes( public void testUpgradeAddsSeed() throws Exception { KeyStoreWrapper keystore = KeyStoreWrapper.create(); keystore.remove(KeyStoreWrapper.SEED_SETTING.getKey()); - keystore.save(env.configDir(), new char[0]); - KeyStoreWrapper.upgrade(keystore, env.configDir(), new char[0]); + keystore.save(env.configDir(), passphraseSupplier.get()); + KeyStoreWrapper.upgrade(keystore, env.configDir(), passphraseSupplier.get()); SecureString seed = keystore.getString(KeyStoreWrapper.SEED_SETTING.getKey()); assertNotNull(seed); keystore = KeyStoreWrapper.load(env.configDir()); - keystore.decrypt(new char[0]); + keystore.decrypt(passphraseSupplier.get()); assertEquals(seed.toString(), keystore.getString(KeyStoreWrapper.SEED_SETTING.getKey()).toString()); } @@ -365,6 +370,20 @@ public void testIllegalSettingName() throws Exception { assertTrue(e.getMessage().contains("does not match the allowed setting name pattern")); } + public void testFailLoadV1KeystoresInFipsJvm() throws Exception { + assumeTrue("Test in FIPS JVM", inFipsJvm()); + + Exception e = assertThrows(NoSuchProviderException.class, this::generateV1); + assertThat(e.getMessage(), containsString("no such provider: SunJCE")); + } + + public void testFailLoadV2KeystoresInFipsJvm() throws Exception { + assumeTrue("Test in FIPS JVM", inFipsJvm()); + + Exception e = assertThrows(NoSuchProviderException.class, this::generateV2); + assertThat(e.getMessage(), containsString("no such provider: SunJCE")); + } + public void testBackcompatV1() throws Exception { assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); generateV1(); @@ -400,12 +419,12 @@ public void testStringAndFileDistinction() throws Exception { final Path temp = createTempDir(); Files.write(temp.resolve("file_setting"), "file_value".getBytes(StandardCharsets.UTF_8)); wrapper.setFile("file_setting", Files.readAllBytes(temp.resolve("file_setting"))); - wrapper.save(env.configDir(), new char[0]); + wrapper.save(env.configDir(), passphraseSupplier.get()); wrapper.close(); final KeyStoreWrapper afterSave = KeyStoreWrapper.load(env.configDir()); assertNotNull(afterSave); - afterSave.decrypt(new char[0]); + afterSave.decrypt(passphraseSupplier.get()); assertThat(afterSave.getSettingNames(), equalTo(new HashSet<>(Arrays.asList("keystore.seed", "string_setting", "file_setting")))); assertThat(afterSave.getString("string_setting"), equalTo("string_value")); assertThat(toByteArray(afterSave.getFile("string_setting")), equalTo("string_value".getBytes(StandardCharsets.UTF_8))); diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ListKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ListKeyStoreCommandTests.java index 36bef3a82281d..4ca5e88c47b30 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ListKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ListKeyStoreCommandTests.java @@ -61,7 +61,7 @@ public void testMissing() throws Exception { } public void testEmpty() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password); terminal.addSecretInput(password); execute(); @@ -69,7 +69,7 @@ public void testEmpty() throws Exception { } public void testOne() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password, "foo", "bar"); terminal.addSecretInput(password); execute(); @@ -77,7 +77,7 @@ public void testOne() throws Exception { } public void testMultiple() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password, "foo", "1", "baz", "2", "bar", "3"); terminal.addSecretInput(password); execute(); @@ -100,6 +100,7 @@ public void testListWithIncorrectPassword() throws Exception { } public void testListWithUnprotectedKeystore() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); createKeystore("", "foo", "bar"); execute(); // Not prompted for a password diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/RemoveSettingKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/RemoveSettingKeyStoreCommandTests.java index 276af6cfa659f..7fe189dce84e7 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/RemoveSettingKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/RemoveSettingKeyStoreCommandTests.java @@ -117,6 +117,7 @@ public void testRemoveWithIncorrectPassword() throws Exception { } public void testRemoveFromUnprotectedKeystore() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; createKeystore(password, "foo", "bar"); // will not be prompted for a password diff --git a/distribution/tools/launchers/build.gradle b/distribution/tools/launchers/build.gradle index aee205a24dea3..76e447953c95d 100644 --- a/distribution/tools/launchers/build.gradle +++ b/distribution/tools/launchers/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" testImplementation "junit:junit:${versions.junit}" testImplementation "org.hamcrest:hamcrest:${versions.hamcrest}" + testImplementation "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" } base { 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..88654e8826a4c 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, Runtime.version()); 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..5349c81a8a851 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,9 @@ package org.opensearch.tools.launchers; +import java.io.FileNotFoundException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -39,7 +42,12 @@ final class SystemJvmOptions { - static List systemJvmOptions() { + static final String OPENSEARCH_CRYPTO_STANDARD = "OPENSEARCH_CRYPTO_STANDARD"; + static final String FIPS_140_3 = "FIPS-140-3"; + static final boolean IS_IN_FIPS_JVM = FIPS_140_3.equals(System.getenv(OPENSEARCH_CRYPTO_STANDARD)) + || "true".equalsIgnoreCase(System.getProperty("org.bouncycastle.fips.approved_only")); + + static List systemJvmOptions(final Path config, Runtime.Version runtimeVersion) throws FileNotFoundException { return Collections.unmodifiableList( Arrays.asList( /* @@ -68,7 +76,7 @@ static List systemJvmOptions() { */ "-XX:-OmitStackTraceInFastThrow", // enable helpful NullPointerExceptions (https://openjdk.java.net/jeps/358), if they are supported - maybeShowCodeDetailsInExceptionMessages(), + maybeShowCodeDetailsInExceptionMessages(runtimeVersion), // flags to configure Netty "-Dio.netty.noUnsafe=true", "-Dio.netty.noKeySetOptimization=true", @@ -77,23 +85,39 @@ static List systemJvmOptions() { // log4j 2 "-Dlog4j.shutdownHookEnabled=false", "-Dlog4j2.disable.jmx=true", - // security manager - allowSecurityManagerOption(), + // security settings + enableFips(), + allowSecurityManagerOption(runtimeVersion), + loadJavaSecurityProperties(config), javaLocaleProviders() ) ).stream().filter(e -> e.isEmpty() == false).collect(Collectors.toList()); } - private static String allowSecurityManagerOption() { - if (Runtime.version().feature() > 17) { + private static String enableFips() { + return IS_IN_FIPS_JVM ? "-Dorg.bouncycastle.fips.approved_only=true" : ""; + } + + private static String loadJavaSecurityProperties(final Path config) throws FileNotFoundException { + String securityFile = IS_IN_FIPS_JVM ? "fips_java.security" : "java.security"; + var securityFilePath = config.resolve(securityFile); + + if (!Files.exists(securityFilePath)) { + throw new FileNotFoundException("Security file not found: " + securityFilePath.toAbsolutePath()); + } + return "-Djava.security.properties=" + securityFilePath.toAbsolutePath(); + } + + private static String allowSecurityManagerOption(Runtime.Version runtimeVersion) { + if (runtimeVersion.feature() > 17) { return "-Djava.security.manager=allow"; } else { return ""; } } - private static String maybeShowCodeDetailsInExceptionMessages() { - if (Runtime.version().feature() >= 14) { + private static String maybeShowCodeDetailsInExceptionMessages(Runtime.Version runtimeVersion) { + if (runtimeVersion.feature() >= 14) { return "-XX:+ShowCodeDetailsInExceptionMessages"; } else { return ""; diff --git a/distribution/tools/launchers/src/test/java/org/opensearch/tools/launchers/SystemJvmOptionsTests.java b/distribution/tools/launchers/src/test/java/org/opensearch/tools/launchers/SystemJvmOptionsTests.java new file mode 100644 index 0000000000000..fb9f0bcbd5936 --- /dev/null +++ b/distribution/tools/launchers/src/test/java/org/opensearch/tools/launchers/SystemJvmOptionsTests.java @@ -0,0 +1,87 @@ +/* + * 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.tools.launchers; + +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.junit.After; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThrows; + +public class SystemJvmOptionsTests extends LaunchersTestCase { + + private static final String FILE_NAME = CryptoServicesRegistrar.isInApprovedOnlyMode() ? "fips_java.security" : "java.security"; + private static final int MIN_RUNTIME_VERSION = 11; + private static final int MAX_RUNTIME_VERSION = 21; + + private Path tempFile; + + @After + public void tearDown() throws IOException { + Files.deleteIfExists(tempFile); + } + + public void testSetJavaSecurityProperties() throws Exception { + createSecurityFile(FILE_NAME); + var jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir(), Runtime.version()); + assertThat(jvmOptions, hasItem("-Djava.security.properties=" + globalTempDir().toAbsolutePath() + "/" + FILE_NAME)); + } + + public void testFailSetJavaSecurityProperties() throws Exception { + createSecurityFile("unknown.security"); + assertThrows( + FileNotFoundException.class, + () -> SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), Runtime.version()) + ); + } + + public void testFipsOption() throws Exception { + createSecurityFile(FILE_NAME); + var jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), Runtime.version()); + var fipsProperty = "-Dorg.bouncycastle.fips.approved_only=true"; + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + assertThat(jvmOptions, hasItem(fipsProperty)); + } else { + assertThat(jvmOptions, not(hasItem(fipsProperty))); + } + } + + public void testSecurityManagerOption() throws Exception { + createSecurityFile(FILE_NAME); + var runtimeVersion = Runtime.Version.parse(String.valueOf(randomIntBetween(MIN_RUNTIME_VERSION, 17))); + var jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), runtimeVersion); + assertThat(jvmOptions, not(hasItem("-Djava.security.manager=allow"))); + + runtimeVersion = Runtime.Version.parse(String.valueOf(randomIntBetween(18, MAX_RUNTIME_VERSION))); + jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), runtimeVersion); + assertThat(jvmOptions, hasItem("-Djava.security.manager=allow")); + } + + public void testShowCodeDetailsOption() throws Exception { + createSecurityFile(FILE_NAME); + var runtimeVersion = Runtime.Version.parse(String.valueOf(randomIntBetween(MIN_RUNTIME_VERSION, 13))); + var jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), runtimeVersion); + assertThat(jvmOptions, not(hasItem("-XX:+ShowCodeDetailsInExceptionMessages"))); + + runtimeVersion = Runtime.Version.parse(String.valueOf(randomIntBetween(14, MAX_RUNTIME_VERSION))); + jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), runtimeVersion); + assertThat(jvmOptions, hasItem("-XX:+ShowCodeDetailsInExceptionMessages")); + } + + private void createSecurityFile(String fileName) throws Exception { + tempFile = Files.createFile(globalTempDir().resolve(fileName)); + } +} diff --git a/distribution/tools/plugin-cli/licenses/bc-fips-2.0.0.jar.sha1 b/distribution/tools/plugin-cli/licenses/bc-fips-2.0.0.jar.sha1 index 79f0e3e9930bb..d635985983ebc 100644 --- a/distribution/tools/plugin-cli/licenses/bc-fips-2.0.0.jar.sha1 +++ b/distribution/tools/plugin-cli/licenses/bc-fips-2.0.0.jar.sha1 @@ -1 +1 @@ -ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab \ No newline at end of file +ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab diff --git a/distribution/tools/plugin-cli/licenses/bcpg-fips-2.0.8.jar.sha1 b/distribution/tools/plugin-cli/licenses/bcpg-fips-2.0.8.jar.sha1 new file mode 100644 index 0000000000000..758ee2fdf9de6 --- /dev/null +++ b/distribution/tools/plugin-cli/licenses/bcpg-fips-2.0.8.jar.sha1 @@ -0,0 +1 @@ +51c2f633e0c32d10de1ebab4c86f93310ff820f8 \ No newline at end of file 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 index 1ce2cb89176f6..e69de29bb2d1d 100644 --- a/gradle/fips.gradle +++ b/gradle/fips.gradle @@ -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/common/build.gradle b/libs/common/build.gradle index 576e78bbe19f4..41c145bc890fb 100644 --- a/libs/common/build.gradle +++ b/libs/common/build.gradle @@ -25,7 +25,6 @@ base { dependencies { // This dependency is used only by :libs:core for null-checking interop with other tools compileOnly "com.google.code.findbugs:jsr305:3.0.2" - /******* * !!!! NO THIRD PARTY DEPENDENCIES !!!! *******/ 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..4b26c123ac026 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 @@ -95,7 +95,7 @@ public X509ExtendedTrustManager createTrustManager() { private KeyStore getSystemTrustStore() { if (isPkcs11Truststore(systemProperties) && trustStorePassword != null) { try { - KeyStore keyStore = KeyStore.getInstance("PKCS11"); + KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_11); keyStore.load(null, trustStorePassword); return keyStore; } catch (GeneralSecurityException | IOException e) { diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreFactory.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreFactory.java new file mode 100644 index 0000000000000..2cc41065aecdd --- /dev/null +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreFactory.java @@ -0,0 +1,74 @@ +/* + * 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.ssl; + +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.opensearch.common.SuppressForbidden; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchProviderException; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.opensearch.common.ssl.KeyStoreType.SECURE_KEYSTORE_TYPES; +import static org.opensearch.common.ssl.KeyStoreType.inferStoreType; + +/** + * Restricts types of keystores to PKCS#11 and BCFKS when running in FIPS JVM. + * Returns the keystore from specified provider or otherwise follows the priority of + * declared security providers and their support for different keystores. + */ +public final class KeyStoreFactory { + + private static final String FIPS_PROVIDER = "BCFIPS"; + + /** + * Makes best guess about the "type" (see {@link KeyStore#getType()}) of the keystore file located at the given {@code Path}. + * This method only references the file name of the keystore, it does not look at its contents. + */ + public static KeyStore getInstanceBasedOnFileExtension(String filePath) { + return getInstance(inferStoreType(filePath)); + } + + public static KeyStore getInstance(KeyStoreType type) { + return getInstance(type, null); + } + + /** + * Creates KeyStore instance with submitted provider and type. In FIPS enabled environment the parameters are limited to FIPS supported + * KeyStore-Types (see {@link KeyStoreType#SECURE_KEYSTORE_TYPES}) and also FIPS provider (see {@link KeyStoreFactory#FIPS_PROVIDER}). + */ + public static KeyStore getInstance(KeyStoreType type, String provider) { + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + if (!SECURE_KEYSTORE_TYPES.contains(type)) { + var secureKeyStoreNames = SECURE_KEYSTORE_TYPES.stream().map(KeyStoreType::name).collect(Collectors.joining(", ")); + throw new SecurityException("Only " + secureKeyStoreNames + " keystores are allowed in FIPS JVM"); + } + if (provider != null && !Objects.equals(provider, FIPS_PROVIDER)) { + throw new SecurityException("FIPS JVM does not support creation of KeyStore with any other provider than " + FIPS_PROVIDER); + } + provider = FIPS_PROVIDER; + } + return get(type, provider); + } + + @SuppressForbidden(reason = "centralized instantiation of a KeyStore") + private static KeyStore get(KeyStoreType type, String provider) { + try { + if (provider == null) { + return KeyStore.getInstance(type.getJcaName()); + } + return KeyStore.getInstance(type.getJcaName(), provider); + } catch (KeyStoreException | NoSuchProviderException e) { + throw new SecurityException(e); + } + } + +} diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreType.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreType.java new file mode 100644 index 0000000000000..df106f1b5017e --- /dev/null +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreType.java @@ -0,0 +1,64 @@ +/* + * 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.ssl; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Enum representing the types of KeyStores supported by {@link KeyStoreFactory}. + */ +public enum KeyStoreType { + + JKS("JKS"), + PKCS_12("PKCS12"), + PKCS_11("PKCS11"), + BCFKS("BCFKS"); + + public static final Map> TYPE_TO_EXTENSION_MAP = new HashMap<>(); + + static { + TYPE_TO_EXTENSION_MAP.put(JKS, List.of(".jks", ".ks")); + TYPE_TO_EXTENSION_MAP.put(PKCS_12, List.of(".p12", ".pkcs12", ".pfx")); + TYPE_TO_EXTENSION_MAP.put(BCFKS, List.of(".bcfks")); // Bouncy Castle FIPS Keystore + } + + /** + * Specifies KeyStore formats that are appropriate for use in a FIPS-compliant JVM: + * - BCFKS KeyStore is specifically designed for FIPS compliance. + * - PKCS#11 is vendor-specific and requires proper configuration to operate in FIPS mode. + */ + public static final List SECURE_KEYSTORE_TYPES = List.of(PKCS_11, BCFKS); + + private final String jcaName; + + KeyStoreType(String jks) { + jcaName = jks; + } + + public String getJcaName() { + return jcaName; + } + + public static KeyStoreType inferStoreType(String filePath) { + return TYPE_TO_EXTENSION_MAP.entrySet() + .stream() + .filter(entry -> entry.getValue().stream().anyMatch(filePath::endsWith)) + .map(Map.Entry::getKey) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Unknown keystore type for file path: " + filePath)); + } + + public static KeyStoreType getByJcaName(String value) { + return Stream.of(KeyStoreType.values()).filter(type -> type.getJcaName().equals(value)).findFirst().orElse(null); + } + +} 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..48d890c25525d 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 @@ -52,7 +52,7 @@ import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.Collection; -import java.util.Locale; +import java.util.Objects; /** * A variety of utility methods for working with or constructing {@link KeyStore} instances. @@ -63,42 +63,30 @@ private KeyStoreUtil() { throw new IllegalStateException("Utility class should not be instantiated"); } - /** - * Make a best guess about the "type" (see {@link KeyStore#getType()}) of the keystore file located at the given {@code Path}. - * This method only references the file name of the keystore, it does not look at its contents. - */ - 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 { - return "jks"; - } - } - /** * Read the given keystore file. * * @throws SslConfigException If there is a problem reading from the provided path * @throws GeneralSecurityException If there is a problem with the keystore contents */ - static KeyStore readKeyStore(Path path, String type, char[] password) throws GeneralSecurityException { + static KeyStore readKeyStore(Path path, KeyStoreType type, char[] password) throws GeneralSecurityException { if (Files.notExists(path)) { throw new SslConfigException( "cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + "] because the file does not exist" ); } try { - KeyStore keyStore = KeyStore.getInstance(type); + KeyStore keyStore = KeyStoreFactory.getInstance(type); try (InputStream in = Files.newInputStream(path)) { keyStore.load(in, password); } return keyStore; } catch (IOException e) { - throw new SslConfigException( - "cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + "] - " + e.getMessage(), - e - ); + var finalMessage = e.getMessage(); + if (Objects.equals(e.getMessage(), "BCFKS KeyStore corrupted: MAC calculation failed.")) { + finalMessage = "incorrect password or corrupt file."; + } + throw new SslConfigException("cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + "] - " + finalMessage, e); } } @@ -133,7 +121,7 @@ static KeyStore buildTrustStore(Iterable certificates) throws Gener } private static KeyStore buildNewKeyStore() throws GeneralSecurityException { - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); try { keyStore.load(null, null); } catch (IOException e) { 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 d957ffa457149..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 @@ -84,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 + "]"); } 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 546d7f0ebd994..224699660b65b 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; @@ -162,8 +164,12 @@ public SSLContext createSslContext() { * {@link #getSupportedProtocols() configured protocols}. */ private String contextProtocol() { - if (supportedProtocols.isEmpty()) { - throw new SslConfigException("no SSL/TLS protocols have been configured"); + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + if (!new HashSet<>(SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS).containsAll(supportedProtocols)) { + throw new SslConfigException( + "in FIPS mode only the following SSL/TLS protocols are allowed: " + SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS + ); + } } for (Entry entry : ORDERED_PROTOCOL_ALGORITHM_MAP.entrySet()) { if (supportedProtocols.contains(entry.getKey())) { diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfigurationLoader.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfigurationLoader.java index 433bec734e0b8..3733b1bcaecc7 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfigurationLoader.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfigurationLoader.java @@ -40,14 +40,14 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Objects; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; -import static org.opensearch.common.ssl.KeyStoreUtil.inferKeyStoreType; -import static org.opensearch.common.ssl.SslConfiguration.ORDERED_PROTOCOL_ALGORITHM_MAP; +import static org.opensearch.common.ssl.KeyStoreType.inferStoreType; import static org.opensearch.common.ssl.SslConfigurationKeys.CERTIFICATE; import static org.opensearch.common.ssl.SslConfigurationKeys.CERTIFICATE_AUTHORITIES; import static org.opensearch.common.ssl.SslConfigurationKeys.CIPHERS; @@ -84,11 +84,7 @@ */ public abstract class SslConfigurationLoader { - static final List DEFAULT_PROTOCOLS = Collections.unmodifiableList( - ORDERED_PROTOCOL_ALGORITHM_MAP.containsKey("TLSv1.3") - ? Arrays.asList("TLSv1.3", "TLSv1.2", "TLSv1.1") - : Arrays.asList("TLSv1.2", "TLSv1.1") - ); + static final List FIPS_APPROVED_PROTOCOLS = List.of("TLSv1.3", "TLSv1.2"); static final List DEFAULT_CIPHERS = loadDefaultCiphers(); private static final char[] EMPTY_PASSWORD = new char[0]; @@ -119,7 +115,7 @@ public SslConfigurationLoader(String settingPrefix) { this.defaultKeyConfig = EmptyKeyConfig.INSTANCE; this.defaultVerificationMode = SslVerificationMode.FULL; this.defaultClientAuth = SslClientAuthenticationMode.OPTIONAL; - this.defaultProtocols = DEFAULT_PROTOCOLS; + this.defaultProtocols = FIPS_APPROVED_PROTOCOLS; this.defaultCiphers = DEFAULT_CIPHERS; } @@ -167,7 +163,7 @@ public void setDefaultCiphers(List defaultCiphers) { /** * Change the default SSL/TLS protocol list. - * The initial protocol list is defined by {@link #DEFAULT_PROTOCOLS} + * The initial protocol list is defined by {@link #FIPS_APPROVED_PROTOCOLS} */ public void setDefaultProtocols(List defaultProtocols) { this.defaultProtocols = defaultProtocols; @@ -248,7 +244,10 @@ private SslTrustConfig buildTrustConfig(Path basePath, SslVerificationMode verif } if (trustStorePath != null) { final char[] password = resolvePasswordSetting(TRUSTSTORE_SECURE_PASSWORD, TRUSTSTORE_LEGACY_PASSWORD); - final String storeType = resolveSetting(TRUSTSTORE_TYPE, Function.identity(), inferKeyStoreType(trustStorePath)); + final Optional maybeStoreType = Optional.ofNullable(resolveSetting(TRUSTSTORE_TYPE, Function.identity(), null)); + final KeyStoreType storeType = maybeStoreType.map(KeyStoreType::getByJcaName) + .orElse(inferStoreType(trustStorePath.toString().toLowerCase(Locale.ROOT))); + final String algorithm = resolveSetting(TRUSTSTORE_ALGORITHM, Function.identity(), TrustManagerFactory.getDefaultAlgorithm()); return new StoreTrustConfig(trustStorePath, password, storeType, algorithm); } @@ -287,7 +286,11 @@ private SslKeyConfig buildKeyConfig(Path basePath) { if (keyPassword.length == 0) { keyPassword = storePassword; } - final String storeType = resolveSetting(KEYSTORE_TYPE, Function.identity(), inferKeyStoreType(keyStorePath)); + + final Optional maybeStoreType = Optional.ofNullable(resolveSetting(KEYSTORE_TYPE, Function.identity(), null)); + final KeyStoreType storeType = maybeStoreType.map(KeyStoreType::getByJcaName) + .orElse(inferStoreType(keyStorePath.toString().toLowerCase(Locale.ROOT))); + final String algorithm = resolveSetting(KEYSTORE_ALGORITHM, Function.identity(), KeyManagerFactory.getDefaultAlgorithm()); return new StoreKeyConfig(keyStorePath, storePassword, storeType, keyPassword, algorithm); } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreKeyConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreKeyConfig.java index b3b7b7dc346a6..858f68b3f19a2 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreKeyConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreKeyConfig.java @@ -51,7 +51,7 @@ public class StoreKeyConfig implements SslKeyConfig { private final Path path; private final char[] storePassword; - private final String type; + private final KeyStoreType type; private final char[] keyPassword; private final String algorithm; @@ -59,12 +59,12 @@ public class StoreKeyConfig implements SslKeyConfig { * @param path The path to the keystore file * @param storePassword The password for the keystore * @param type The {@link KeyStore#getType() type} of the keystore (typically "PKCS12" or "jks"). - * See {@link KeyStoreUtil#inferKeyStoreType(Path)}. + * See {@link KeyStoreType#inferStoreType(String)}. * @param keyPassword The password for the key(s) within the keystore * (see {@link javax.net.ssl.KeyManagerFactory#init(KeyStore, char[])}). * @param algorithm The algorithm to use for the Key Manager (see {@link KeyManagerFactory#getAlgorithm()}). */ - StoreKeyConfig(Path path, char[] storePassword, String type, char[] keyPassword, String algorithm) { + StoreKeyConfig(Path path, char[] storePassword, KeyStoreType type, char[] keyPassword, String algorithm) { this.path = path; this.storePassword = storePassword; this.type = type; diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreTrustConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreTrustConfig.java index 556cb052c4391..709ea1adc9acb 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreTrustConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreTrustConfig.java @@ -47,17 +47,17 @@ final class StoreTrustConfig implements SslTrustConfig { private final Path path; private final char[] password; - private final String type; + private final KeyStoreType type; private final String algorithm; /** * @param path The path to the keystore file * @param password The password for the keystore * @param type The {@link KeyStore#getType() type} of the keystore (typically "PKCS12" or "jks"). - * See {@link KeyStoreUtil#inferKeyStoreType(Path)}. + * See {@link KeyStoreType#inferStoreType(String)}. * @param algorithm The algorithm to use for the Trust Manager (see {@link javax.net.ssl.TrustManagerFactory#getAlgorithm()}). */ - StoreTrustConfig(Path path, char[] password, String type, String algorithm) { + StoreTrustConfig(Path path, char[] password, KeyStoreType type, String algorithm) { this.path = path; this.type = type; this.algorithm = algorithm; diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/TrustEverythingConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/TrustEverythingConfig.java index c366210133687..dd58606ec44c4 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/TrustEverythingConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/TrustEverythingConfig.java @@ -32,6 +32,8 @@ package org.opensearch.common.ssl; +import org.bouncycastle.crypto.CryptoServicesRegistrar; + import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; @@ -40,6 +42,7 @@ import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Collections; +import java.util.Locale; /** * A {@link SslTrustConfig} that trusts all certificates. Used when {@link SslVerificationMode#isCertificateVerificationEnabled()} is @@ -90,6 +93,15 @@ public Collection getDependentFiles() { @Override public X509ExtendedTrustManager createTrustManager() { + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + var message = String.format( + Locale.ROOT, + "The use of %s is not permitted in FIPS mode. This issue may be caused by the '%s' setting.", + TRUST_EVERYTHING.getClass().getSimpleName(), + "ssl.verification_mode=NONE" + ); + throw new IllegalStateException(message); + } return TRUST_MANAGER; } diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/KeyStoreFactoryTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/KeyStoreFactoryTests.java new file mode 100644 index 0000000000000..07d3ebfaa8fba --- /dev/null +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/KeyStoreFactoryTests.java @@ -0,0 +1,76 @@ +/* + * 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.ssl; + +import com.carrotsearch.randomizedtesting.generators.RandomStrings; + +import org.opensearch.test.OpenSearchTestCase; + +import static org.hamcrest.Matchers.equalTo; + +public class KeyStoreFactoryTests extends OpenSearchTestCase { + + public void testPKCS12KeyStore() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12).getType(), equalTo("PKCS12")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12).getProvider().getName(), equalTo("BCFIPS")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "BCFIPS").getType(), equalTo("PKCS12")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "BCFIPS").getProvider().getName(), equalTo("BCFIPS")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN").getType(), equalTo("PKCS12")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN").getProvider().getName(), equalTo("SUN")); + + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(KeyStoreType.PKCS_12).forEach(extension -> { + var keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName(extension)); + assertThat(keyStore.getType(), equalTo(KeyStoreType.PKCS_12.getJcaName())); + }); + } + + public void testJKSKeyStore() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS).getType(), equalTo("JKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS).getProvider().getName(), equalTo("SUN")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS, "SUN").getType(), equalTo("JKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS, "SUN").getProvider().getName(), equalTo("SUN")); + + assertThrows("BCFKS not found", SecurityException.class, () -> KeyStoreFactory.getInstance(KeyStoreType.JKS, "BCFIPS")); + + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(KeyStoreType.JKS).forEach(extension -> { + var keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName(extension)); + assertThat(keyStore.getType(), equalTo(KeyStoreType.JKS.getJcaName())); + }); + } + + public void testBCFIPSKeyStore() { + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS).getType(), equalTo("BCFKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS).getProvider().getName(), equalTo("BCFIPS")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "BCFIPS").getType(), equalTo("BCFKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "BCFIPS").getProvider().getName(), equalTo("BCFIPS")); + + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(KeyStoreType.BCFKS).forEach(extension -> { + var keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName(extension)); + assertThat(keyStore.getType(), equalTo(KeyStoreType.BCFKS.getJcaName())); + }); + } + + public void testUnknownKeyStoreType() { + assertThrows( + "Unknown keystore type for file path: keystore.unknown", + IllegalArgumentException.class, + () -> KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName("unknown")) + ); + } + + private String createRandomFileName(String extension) { + return RandomStrings.randomAsciiAlphanumOfLengthBetween(random(), 0, 10) + "." + extension; + } +} diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java index 70cb76ceaec51..a685d0b346161 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java @@ -69,6 +69,7 @@ public void testBuildKeyConfigFromPkcs1PemFilesWithoutPassword() throws Exceptio } public void testBuildKeyConfigFromPkcs1PemFilesWithPassword() throws Exception { + assumeFalse("Can't run in a FIPS JVM, PBKDF-OPENSSL KeySpec is not available", inFipsJvm()); final Path cert = getDataPath("/certs/cert2/cert2-pkcs1.crt"); final Path key = getDataPath("/certs/cert2/cert2-pkcs1.key"); final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, "c2-pass".toCharArray()); @@ -85,7 +86,6 @@ public void testBuildKeyConfigFromPkcs8PemFilesWithoutPassword() throws Exceptio } public void testBuildKeyConfigFromPkcs8PemFilesWithPassword() throws Exception { - assumeFalse("Can't run in a FIPS JVM, PBE KeySpec is not available", inFipsJvm()); final Path cert = getDataPath("/certs/cert2/cert2.crt"); final Path key = getDataPath("/certs/cert2/cert2.key"); final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, STRONG_PRIVATE_SECRET.get()); @@ -132,7 +132,7 @@ public void testKeyConfigReloadsFileContents() throws Exception { Files.copy(cert2, cert, StandardCopyOption.REPLACE_EXISTING); Files.copy(key2, key, StandardCopyOption.REPLACE_EXISTING); - assertPasswordIsIncorrect(keyConfig, key); + assertPasswordNotSet(keyConfig, key); Files.copy(cert1, cert, StandardCopyOption.REPLACE_EXISTING); Files.copy(key1, key, StandardCopyOption.REPLACE_EXISTING); @@ -170,6 +170,14 @@ private void assertPasswordIsIncorrect(PemKeyConfig keyConfig, Path key) { assertThat(exception, instanceOf(SslConfigException.class)); } + private void assertPasswordNotSet(PemKeyConfig keyConfig, Path key) { + final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); + assertThat(exception.getMessage(), containsString("cannot read encrypted key")); + assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); + assertThat(exception.getMessage(), containsString("without a password")); + assertThat(exception, instanceOf(SslConfigException.class)); + } + private void assertFileNotFound(PemKeyConfig keyConfig, String type, Path file) { final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); assertThat(exception.getMessage(), containsString(type + " file")); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemUtilsTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemUtilsTests.java index f1255ab64f672..243400098d1a6 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemUtilsTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemUtilsTests.java @@ -32,17 +32,12 @@ package org.opensearch.common.ssl; -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.opensearch.test.OpenSearchTestCase; -import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.security.AlgorithmParameters; import java.security.Key; -import java.security.KeyStore; import java.security.PrivateKey; import java.security.interfaces.ECPrivateKey; import java.security.spec.ECGenParameterSpec; @@ -59,10 +54,15 @@ public class PemUtilsTests extends OpenSearchTestCase { private static final Supplier EMPTY_PASSWORD = () -> new char[0]; private static final Supplier TESTNODE_PASSWORD = "testnode"::toCharArray; - private static final Supplier STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="::toCharArray; + private static final Supplier STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="::toCharArray; // has to be at least 112 bit long. + + public void testInstantiateWithDefaultConstructor() { + assertThrows("Utility class should not be instantiated", IllegalStateException.class, PemUtils::new); + } public void testReadPKCS8RsaKey() throws Exception { - Key key = getKeyFromKeystore("RSA"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("RSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/rsa_key_pkcs8_plain.pem"), EMPTY_PASSWORD); @@ -71,7 +71,8 @@ public void testReadPKCS8RsaKey() throws Exception { } public void testReadPKCS8RsaKeyWithBagAttrs() throws Exception { - Key key = getKeyFromKeystore("RSA"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("RSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/testnode_with_bagattrs.pem"), EMPTY_PASSWORD); @@ -79,7 +80,8 @@ public void testReadPKCS8RsaKeyWithBagAttrs() throws Exception { } public void testReadPKCS8DsaKey() throws Exception { - Key key = getKeyFromKeystore("DSA"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("DSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/dsa_key_pkcs8_plain.pem"), EMPTY_PASSWORD); @@ -109,7 +111,8 @@ public void testReadEcKeyCurves() throws Exception { } public void testReadPKCS8EcKey() throws Exception { - Key key = getKeyFromKeystore("EC"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("EC", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/ec_key_pkcs8_plain.pem"), EMPTY_PASSWORD); @@ -129,7 +132,7 @@ public void testReadEncryptedPKCS8EcKey() throws Exception { public void testReadEncryptedPKCS8Key() throws Exception { assumeFalse("Can't run in a FIPS JVM, PBE KeySpec is not available", inFipsJvm()); - Key key = getKeyFromKeystore("RSA"); + Key key = getKeyFromKeystore("RSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/key_pkcs8_encrypted.pem"), TESTNODE_PASSWORD); @@ -138,7 +141,8 @@ public void testReadEncryptedPKCS8Key() throws Exception { } public void testReadDESEncryptedPKCS1Key() throws Exception { - Key key = getKeyFromKeystore("RSA"); + assumeFalse("Can't run in a FIPS JVM, PBKDF-OPENSSL KeySpec is not available", inFipsJvm()); + Key key = getKeyFromKeystore("RSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/testnode.pem"), TESTNODE_PASSWORD); @@ -147,7 +151,8 @@ public void testReadDESEncryptedPKCS1Key() throws Exception { } public void testReadAESEncryptedPKCS1Key() throws Exception { - Key key = getKeyFromKeystore("RSA"); + assumeFalse("Can't run in a FIPS JVM, PBKDF-OPENSSL KeySpec is not available", inFipsJvm()); + Key key = getKeyFromKeystore("RSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); String bits = randomFrom("128", "192", "256"); @@ -158,7 +163,8 @@ public void testReadAESEncryptedPKCS1Key() throws Exception { } public void testReadPKCS1RsaKey() throws Exception { - Key key = getKeyFromKeystore("RSA"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("RSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/testnode-unprotected.pem"), TESTNODE_PASSWORD); @@ -168,7 +174,8 @@ public void testReadPKCS1RsaKey() throws Exception { } public void testReadOpenSslDsaKey() throws Exception { - Key key = getKeyFromKeystore("DSA"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("DSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/dsa_key_openssl_plain.pem"), EMPTY_PASSWORD); @@ -178,7 +185,8 @@ public void testReadOpenSslDsaKey() throws Exception { } public void testReadOpenSslDsaKeyWithParams() throws Exception { - Key key = getKeyFromKeystore("DSA"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("DSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey( @@ -191,7 +199,8 @@ public void testReadOpenSslDsaKeyWithParams() throws Exception { } public void testReadEncryptedOpenSslDsaKey() throws Exception { - Key key = getKeyFromKeystore("DSA"); + assumeFalse("Can't run in a FIPS JVM, PBKDF-OPENSSL KeySpec is not available", inFipsJvm()); + Key key = getKeyFromKeystore("DSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/dsa_key_openssl_encrypted.pem"), TESTNODE_PASSWORD); @@ -201,16 +210,19 @@ public void testReadEncryptedOpenSslDsaKey() throws Exception { } public void testReadOpenSslEcKey() throws Exception { - var key = getKeyFromKeystore("EC"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("EC", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); - var privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/ec_key_openssl_plain.pem"), EMPTY_PASSWORD); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/ec_key_openssl_plain.pem"), EMPTY_PASSWORD); - assertTrue(isCryptographicallyEqual((ECPrivateKey) key, (ECPrivateKey) privateKey)); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); } public void testReadOpenSslEcKeyWithParams() throws Exception { - Key key = getKeyFromKeystore("EC"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("EC", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey( @@ -218,16 +230,19 @@ public void testReadOpenSslEcKeyWithParams() throws Exception { EMPTY_PASSWORD ); - assertTrue(isCryptographicallyEqual((ECPrivateKey) key, (ECPrivateKey) privateKey)); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); } public void testReadEncryptedOpenSslEcKey() throws Exception { - var key = getKeyFromKeystore("EC"); + assumeFalse("Can't run in a FIPS JVM, PBKDF-OPENSSL KeySpec is not available", inFipsJvm()); + Key key = getKeyFromKeystore("EC", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); - var privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/ec_key_openssl_encrypted.pem"), TESTNODE_PASSWORD); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/ec_key_openssl_encrypted.pem"), TESTNODE_PASSWORD); - assertTrue(isCryptographicallyEqual((ECPrivateKey) key, (ECPrivateKey) privateKey)); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); } public void testReadEncryptedPKCS8KeyWithPBKDF2() throws Exception { @@ -252,7 +267,7 @@ public void testReadEncryptedEcKeyWithPBKDF2() throws Exception { Key key = getKeyFromKeystore("EC_PBKDF2"); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); - PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/key_EC_enc_pbkdf2.pem"), EMPTY_PASSWORD); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/key_EC_enc_pbkdf2.pem"), STRONG_PRIVATE_SECRET); assertThat(privateKey, notNullValue()); assertThat(privateKey, equalTo(key)); } @@ -288,27 +303,17 @@ public void testReadEmptyFile() { } private Key getKeyFromKeystore(String algo) throws Exception { - var keystorePath = getDataPath("/certs/pem-utils/testnode.jks"); + return getKeyFromKeystore(algo, inFipsJvm() ? KeyStoreType.BCFKS : KeyStoreType.JKS); + } + + private Key getKeyFromKeystore(String algo, KeyStoreType keyStoreType) throws Exception { + var keystorePath = getDataPath("/certs/pem-utils/testnode" + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(keyStoreType).get(0)); var alias = "testnode_" + algo.toLowerCase(Locale.ROOT); var password = "testnode".toCharArray(); - try (InputStream in = Files.newInputStream(keystorePath)) { - KeyStore keyStore = KeyStore.getInstance("jks"); + try (var in = Files.newInputStream(keystorePath)) { + var keyStore = KeyStoreFactory.getInstance(keyStoreType); keyStore.load(in, password); return keyStore.getKey(alias, password); } } - - private boolean isCryptographicallyEqual(ECPrivateKey key1, ECPrivateKey key2) throws IOException { - var pki1 = PrivateKeyInfo.getInstance(key1.getEncoded()); - var pki2 = PrivateKeyInfo.getInstance(key2.getEncoded()); - - var privateKey1 = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(pki1.parsePrivateKey()).getKey(); - var privateKey2 = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(pki2.parsePrivateKey()).getKey(); - - var oid1 = ASN1ObjectIdentifier.getInstance(pki1.getPrivateKeyAlgorithm().getParameters()); - var oid2 = ASN1ObjectIdentifier.getInstance(pki2.getPrivateKeyAlgorithm().getParameters()); - - return privateKey1.equals(privateKey2) && oid1.equals(oid2); - } - } diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java index 366e936ca4852..2aef139eba7a0 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java @@ -53,6 +53,7 @@ public class SslConfigurationLoaderTests extends OpenSearchTestCase { + protected static final String BCFKS = "BCFKS"; private final String STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="; private final Path certRoot = getDataPath("/certs/ca1/ca.crt").getParent().getParent(); @@ -99,6 +100,14 @@ public void testBasicConfigurationOptions() { if (verificationMode == SslVerificationMode.NONE) { final SslTrustConfig trustConfig = configuration.getTrustConfig(); assertThat(trustConfig, instanceOf(TrustEverythingConfig.class)); + + if (inFipsJvm()) { + assertThrows( + "The use of TrustEverythingConfig is not permitted in FIPS mode. This issue may be caused by the 'ssl.verification_mode=NONE' setting.", + IllegalStateException.class, + trustConfig::createTrustManager + ); + } } } @@ -114,7 +123,30 @@ public void testLoadTrustFromPemCAs() { assertThat(trustConfig.createTrustManager(), notNullValue()); } + public void testLoadTrustFromBCFKS() { + final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.bcfks"); + if (randomBoolean()) { + builder.put("test.ssl.truststore.password", "bcfks-pass"); + } else { + secureSettings.setString("test.ssl.truststore.secure_password", "bcfks-pass"); + } + if (randomBoolean()) { + // If this is not set, the loader will guess from the extension + builder.put("test.ssl.truststore.type", BCFKS); + } + if (randomBoolean()) { + builder.put("test.ssl.truststore.algorithm", TrustManagerFactory.getDefaultAlgorithm()); + } + settings = builder.build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslTrustConfig trustConfig = configuration.getTrustConfig(); + assertThat(trustConfig, instanceOf(StoreTrustConfig.class)); + assertThat(trustConfig.getDependentFiles(), containsInAnyOrder(getDataPath("/certs/ca-all/ca.bcfks"))); + assertThat(trustConfig.createTrustManager(), notNullValue()); + } + public void testLoadTrustFromPkcs12() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.p12"); if (randomBoolean()) { builder.put("test.ssl.truststore.password", "p12-pass"); @@ -137,6 +169,7 @@ public void testLoadTrustFromPkcs12() { } public void testLoadTrustFromJKS() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.jks"); if (randomBoolean()) { builder.put("test.ssl.truststore.password", "jks-pass"); @@ -186,7 +219,30 @@ public void testLoadKeysFromPemFiles() { assertThat(keyConfig.createKeyManager(), notNullValue()); } + public void testLoadKeysFromBCFKS() { + final Settings.Builder builder = Settings.builder().put("test.ssl.keystore.path", "cert-all/certs.bcfks"); + if (randomBoolean()) { + builder.put("test.ssl.keystore.password", "bcfks-pass"); + } else { + secureSettings.setString("test.ssl.keystore.secure_password", "bcfks-pass"); + } + if (randomBoolean()) { + // If this is not set, the loader will guess from the extension + builder.put("test.ssl.keystore.type", BCFKS); + } + if (randomBoolean()) { + builder.put("test.ssl.keystore.algorithm", KeyManagerFactory.getDefaultAlgorithm()); + } + settings = builder.build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslKeyConfig keyConfig = configuration.getKeyConfig(); + assertThat(keyConfig, instanceOf(StoreKeyConfig.class)); + assertThat(keyConfig.getDependentFiles(), containsInAnyOrder(getDataPath("/certs/cert-all/certs.bcfks"))); + assertThat(keyConfig.createKeyManager(), notNullValue()); + } + public void testLoadKeysFromPKCS12() { + assumeFalse("Can't use PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Settings.Builder builder = Settings.builder().put("test.ssl.keystore.path", "cert-all/certs.p12"); if (randomBoolean()) { builder.put("test.ssl.keystore.password", "p12-pass"); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java index ee907952c52ff..410238e57984c 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java @@ -42,10 +42,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import org.mockito.Mockito; import static org.opensearch.common.ssl.SslConfigurationLoader.DEFAULT_CIPHERS; +import static org.opensearch.common.ssl.SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -166,7 +168,7 @@ public void testDependentFiles() { randomFrom(SslVerificationMode.values()), randomFrom(SslClientAuthenticationMode.values()), DEFAULT_CIPHERS, - SslConfigurationLoader.DEFAULT_PROTOCOLS + SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS ); final Path dir = createTempDir(); @@ -184,7 +186,7 @@ public void testDependentFiles() { public void testBuildSslContext() { final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); - final String protocol = randomFrom(SslConfigurationLoader.DEFAULT_PROTOCOLS); + final String protocol = randomFrom(SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS); final SslConfiguration configuration = new SslConfiguration( trustConfig, keyConfig, @@ -204,4 +206,80 @@ public void testBuildSslContext() { Mockito.verifyNoMoreInteractions(trustConfig, keyConfig); } + public void testCreateSslContextWithUnsupportedProtocols() { + assumeFalse("Test not in FIPS JVM", inFipsJvm()); + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + SslConfiguration configuration = new SslConfiguration( + trustConfig, + keyConfig, + randomFrom(SslVerificationMode.values()), + randomFrom(SslClientAuthenticationMode.values()), + DEFAULT_CIPHERS, + Collections.singletonList("DTLSv1.2") + ); + + Exception e = assertThrows(SslConfigException.class, configuration::createSslContext); + assertThat( + e.getMessage(), + containsString("no supported SSL/TLS protocol was found in the configured supported protocols: [DTLSv1.2]") + ); + } + + public void testNotSupportedProtocolsInFipsJvm() { + assumeTrue("Test in FIPS JVM", inFipsJvm()); + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + final String protocol = randomFrom(List.of("TLSv1.1", "TLSv1", "SSLv3", "SSLv2Hello", "SSLv2")); + final SslConfiguration configuration = new SslConfiguration( + trustConfig, + keyConfig, + randomFrom(SslVerificationMode.values()), + randomFrom(SslClientAuthenticationMode.values()), + DEFAULT_CIPHERS, + Collections.singletonList(protocol) + ); + + Mockito.when(trustConfig.createTrustManager()).thenReturn(null); + Mockito.when(keyConfig.createKeyManager()).thenReturn(null); + var exception = assertThrows(SslConfigException.class, configuration::createSslContext); + assertThat( + exception.getMessage(), + equalTo( + String.format(Locale.ROOT, "in FIPS mode only the following SSL/TLS protocols are allowed: %s", FIPS_APPROVED_PROTOCOLS) + ) + ); + } + + public void testInitValuesExist() { + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + + assertThrows( + "cannot configure SSL/TLS without any supported cipher suites", + SslConfigException.class, + () -> new SslConfiguration( + trustConfig, + keyConfig, + SslVerificationMode.CERTIFICATE, + SslClientAuthenticationMode.REQUIRED, + Collections.emptyList(), + List.of("SSLv2") + ) + ); + + assertThrows( + "cannot configure SSL/TLS without any supported protocols", + SslConfigException.class, + () -> new SslConfiguration( + trustConfig, + keyConfig, + SslVerificationMode.CERTIFICATE, + SslClientAuthenticationMode.REQUIRED, + DEFAULT_CIPHERS, + Collections.emptyList() + ) + ); + } + } diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreKeyConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreKeyConfigTests.java index 1745c547d04ee..a55774e14ed41 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreKeyConfigTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreKeyConfigTests.java @@ -48,6 +48,9 @@ import java.security.cert.X509Certificate; import java.util.Arrays; +import static org.opensearch.common.ssl.KeyStoreType.BCFKS; +import static org.opensearch.common.ssl.KeyStoreType.JKS; +import static org.opensearch.common.ssl.KeyStoreType.PKCS_12; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -65,11 +68,12 @@ public class StoreKeyConfigTests extends OpenSearchTestCase { private static final char[] P12_PASS = "p12-pass".toCharArray(); private static final char[] JKS_PASS = "jks-pass".toCharArray(); + private static final char[] BCFKS_PASS = "bcfks-pass".toCharArray(); public void testLoadSingleKeyPKCS12() throws Exception { - assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + assumeFalse("Can't use PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path p12 = getDataPath("/certs/cert1/cert1.p12"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, PKCS_12, P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(p12)); assertKeysLoaded(keyConfig, "cert1"); } @@ -77,7 +81,7 @@ public void testLoadSingleKeyPKCS12() throws Exception { public void testLoadMultipleKeyPKCS12() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path p12 = getDataPath("/certs/cert-all/certs.p12"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, PKCS_12, P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(p12)); assertKeysLoaded(keyConfig, "cert1", "cert2"); } @@ -88,7 +92,7 @@ public void testLoadMultipleKeyJksWithSeparateKeyPassword() throws Exception { final StoreKeyConfig keyConfig = new StoreKeyConfig( jks, JKS_PASS, - "jks", + JKS, "key-pass".toCharArray(), KeyManagerFactory.getDefaultAlgorithm() ); @@ -96,13 +100,20 @@ public void testLoadMultipleKeyJksWithSeparateKeyPassword() throws Exception { assertKeysLoaded(keyConfig, "cert1", "cert2"); } + public void testLoadMultipleKeyBcfks() throws CertificateParsingException { + final Path bcfks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(bcfks, BCFKS_PASS, BCFKS, BCFKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(bcfks)); + assertKeysLoaded(keyConfig, "cert1", "cert2"); + } + public void testKeyManagerFailsWithIncorrectJksStorePassword() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path jks = getDataPath("/certs/cert-all/certs.jks"); final StoreKeyConfig keyConfig = new StoreKeyConfig( jks, P12_PASS, - "jks", + JKS, "key-pass".toCharArray(), KeyManagerFactory.getDefaultAlgorithm() ); @@ -110,18 +121,37 @@ public void testKeyManagerFailsWithIncorrectJksStorePassword() throws Exception assertPasswordIsIncorrect(keyConfig, jks); } + public void testKeyManagerFailsWithIncorrectBcfksStorePassword() throws Exception { + final Path bcfks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(bcfks, P12_PASS, BCFKS, BCFKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(bcfks)); + assertPasswordIsIncorrect(keyConfig, bcfks); + } + public void testKeyManagerFailsWithIncorrectJksKeyPassword() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path jks = getDataPath("/certs/cert-all/certs.jks"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, JKS_PASS, "jks", JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, JKS_PASS, JKS, JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(jks)); assertPasswordIsIncorrect(keyConfig, jks); } + public void testKeyManagerFailsWithIncorrectBcfksKeyPassword() throws Exception { + final Path bcfks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig( + bcfks, + BCFKS_PASS, + BCFKS, + "nonsense".toCharArray(), + KeyManagerFactory.getDefaultAlgorithm() + ); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(bcfks)); + assertPasswordIsIncorrect(keyConfig, bcfks); + } + public void testKeyManagerFailsWithMissingKeystoreFile() throws Exception { - assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path path = getDataPath("/certs/cert-all/certs.jks").getParent().resolve("dne.jks"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(path, JKS_PASS, "jks", JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(path, JKS_PASS, JKS, JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(path)); assertFileNotFound(keyConfig, path); } @@ -130,16 +160,24 @@ public void testMissingKeyEntriesFailsForJksWithMeaningfulMessage() throws Excep assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca-all/ca.jks"); final char[] password = JKS_PASS; - final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, "jks", password, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, JKS, password, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertNoPrivateKeyEntries(keyConfig, ks); } public void testMissingKeyEntriesFailsForP12WithMeaningfulMessage() throws Exception { - assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + assumeFalse("Can't use PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca-all/ca.p12"); final char[] password = P12_PASS; - final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, "PKCS12", password, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, PKCS_12, password, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertNoPrivateKeyEntries(keyConfig, ks); + } + + public void testMissingKeyEntriesFailsForBcfksWithMeaningfulMessage() throws Exception { + final Path ks = getDataPath("/certs/ca-all/ca.bcfks"); + final char[] password = BCFKS_PASS; + final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, BCFKS, password, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertNoPrivateKeyEntries(keyConfig, ks); } @@ -152,7 +190,7 @@ public void testKeyConfigReloadsFileContents() throws Exception { final Path p12 = createTempFile("cert", ".p12"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, PKCS_12, P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); Files.copy(cert1, p12, StandardCopyOption.REPLACE_EXISTING); assertKeysLoaded(keyConfig, "cert1"); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreTrustConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreTrustConfigTests.java index 8058ffe95dc93..0614e36ccfb1d 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreTrustConfigTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreTrustConfigTests.java @@ -49,6 +49,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.opensearch.common.ssl.KeyStoreType.BCFKS; +import static org.opensearch.common.ssl.KeyStoreType.JKS; +import static org.opensearch.common.ssl.KeyStoreType.PKCS_12; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.nullValue; @@ -56,12 +59,13 @@ public class StoreTrustConfigTests extends OpenSearchTestCase { private static final char[] P12_PASS = "p12-pass".toCharArray(); private static final char[] JKS_PASS = "jks-pass".toCharArray(); + private static final char[] BCFKS_PASS = "bcfks-pass".toCharArray(); private static final String DEFAULT_ALGORITHM = TrustManagerFactory.getDefaultAlgorithm(); public void testBuildTrustConfigFromP12() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca1/ca.p12"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, "PKCS12", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, PKCS_12, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertCertificateChain(trustConfig, "CN=Test CA 1"); } @@ -69,7 +73,14 @@ public void testBuildTrustConfigFromP12() throws Exception { public void testBuildTrustConfigFromJks() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca-all/ca.jks"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, "jks", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, JKS, DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 2", "CN=Test CA 3"); + } + + public void testBuildTrustConfigFromBcfks() throws Exception { + final Path ks = getDataPath("/certs/ca-all/ca.bcfks"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, BCFKS_PASS, BCFKS, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 2", "CN=Test CA 3"); } @@ -78,7 +89,7 @@ public void testBadKeyStoreFormatFails() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = createTempFile("ca", ".p12"); Files.write(ks, randomByteArrayOfLength(128), StandardOpenOption.APPEND); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], randomFrom("PKCS12", "jks"), DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], randomFrom(PKCS_12, JKS), DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertInvalidFileFormat(trustConfig, ks); } @@ -86,14 +97,27 @@ public void testBadKeyStoreFormatFails() throws Exception { public void testMissingKeyStoreFailsWithMeaningfulMessage() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca-all/ca.p12").getParent().resolve("keystore.dne"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], randomFrom("PKCS12", "jks"), DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], randomFrom(PKCS_12, JKS), DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertFileNotFound(trustConfig, ks); } public void testIncorrectPasswordFailsForP12WithMeaningfulMessage() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca1/ca.p12"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], "PKCS12", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], PKCS_12, DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertPasswordIsIncorrect(trustConfig, ks); + } + + public void testIncorrectPasswordFailsForBcfksWithMeaningfulMessage() throws Exception { + final Path ks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreTrustConfig trustConfig = new StoreTrustConfig( + ks, + randomAlphaOfLengthBetween(6, 8).toCharArray(), + BCFKS, + DEFAULT_ALGORITHM + ); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertPasswordIsIncorrect(trustConfig, ks); } @@ -101,7 +125,7 @@ public void testIncorrectPasswordFailsForP12WithMeaningfulMessage() throws Excep public void testMissingTrustEntriesFailsForJksKeystoreWithMeaningfulMessage() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/cert-all/certs.jks"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, "jks", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, JKS, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertNoCertificateEntries(trustConfig, ks); } @@ -109,7 +133,14 @@ public void testMissingTrustEntriesFailsForJksKeystoreWithMeaningfulMessage() th public void testMissingTrustEntriesFailsForP12KeystoreWithMeaningfulMessage() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/cert-all/certs.p12"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, "PKCS12", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, PKCS_12, DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertNoCertificateEntries(trustConfig, ks); + } + + public void testMissingTrustEntriesFailsForBcfksKeystoreWithMeaningfulMessage() throws Exception { + final Path ks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, BCFKS_PASS, BCFKS, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertNoCertificateEntries(trustConfig, ks); } @@ -121,7 +152,7 @@ public void testTrustConfigReloadsKeysStoreContents() throws Exception { final Path ks = createTempFile("ca", "p12"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, "PKCS12", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, PKCS_12, DEFAULT_ALGORITHM); Files.copy(ks1, ks, StandardCopyOption.REPLACE_EXISTING); assertCertificateChain(trustConfig, "CN=Test CA 1"); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/TrustEverythingConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/TrustEverythingConfigTests.java new file mode 100644 index 0000000000000..1ea237954d41e --- /dev/null +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/TrustEverythingConfigTests.java @@ -0,0 +1,30 @@ +/* + * 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.ssl; + +import org.opensearch.test.OpenSearchTestCase; + +import static org.hamcrest.Matchers.containsString; + +public class TrustEverythingConfigTests extends OpenSearchTestCase { + + public void testGetDependentFiles() { + assertTrue(TrustEverythingConfig.TRUST_EVERYTHING.getDependentFiles().isEmpty()); + } + + public void testCreateTrustManager() { + if (inFipsJvm()) { + Exception e = assertThrows(IllegalStateException.class, TrustEverythingConfig.TRUST_EVERYTHING::createTrustManager); + assertThat(e.getMessage(), containsString("not permitted in FIPS mode")); + } else { + var trustManager = TrustEverythingConfig.TRUST_EVERYTHING.createTrustManager(); + assertNotNull(trustManager); + } + } +} diff --git a/libs/ssl-config/src/test/resources/certs/README.md b/libs/ssl-config/src/test/resources/certs/README.md index 79790a4918f3e..d00fd3206c3db 100644 --- a/libs/ssl-config/src/test/resources/certs/README.md +++ b/libs/ssl-config/src/test/resources/certs/README.md @@ -105,6 +105,14 @@ do openssl pkcs12 -export -out $Cert/$Cert.p12 -inkey $Cert/$Cert.key -in $Cert/$Cert.crt -name $Cert -passout pass:p12-pass done ``` +# Convert CAs to BCFKS + +```bash +for n in 1 2 3 +do +keytool -importcert -file ca${n}/ca.crt -alias ca${n} -keystore ca-all/ca.bcfks -storetype BCFKS -storepass bcfks-pass -providername BCFIPS -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -providerpath $LIB_PATH/bc-fips-2.0.0.jar -v +done +``` # Import Certs into single PKCS#12 keystore @@ -129,6 +137,34 @@ do done ``` +# Import Certs into single BCFKS keystore with separate key-password + +```bash +for Cert in cert1 cert2 +do + keytool -importkeystore -noprompt \ + -srckeystore $Cert/$Cert.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass p12-pass \ + -destkeystore cert-all/certs.bcfks \ + -deststoretype BCFKS \ + -deststorepass bcfks-pass \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar + keytool -keypasswd -noprompt \ + -keystore cert-all/certs.bcfks \ + -alias $Cert \ + -keypass p12-pass \ + -new bcfks-pass \ + -storepass bcfks-pass \ + -storetype BCFKS \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar +done +``` + # Create a mimic of the first CA ("ca1b") for testing certificates with the same name but different keys ```bash diff --git a/libs/ssl-config/src/test/resources/certs/ca-all/ca.bcfks b/libs/ssl-config/src/test/resources/certs/ca-all/ca.bcfks new file mode 100644 index 0000000000000..f27717d0ce67a Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/ca-all/ca.bcfks differ diff --git a/libs/ssl-config/src/test/resources/certs/cert-all/certs.bcfks b/libs/ssl-config/src/test/resources/certs/cert-all/certs.bcfks new file mode 100644 index 0000000000000..1eab5af506b2c Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/cert-all/certs.bcfks differ diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/README.md b/libs/ssl-config/src/test/resources/certs/pem-utils/README.md index 576b34317bd0a..9354e4eeedf17 100644 --- a/libs/ssl-config/src/test/resources/certs/pem-utils/README.md +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/README.md @@ -179,3 +179,56 @@ and the respective certificates ```bash openssl genpkey -algorithm EC -out key_EC_enc_pbkdf2.pem -pkeyopt ec_paramgen_curve:secp384r1 -pkeyopt ec_param_enc:named_curve -pass stdin ``` + +```bash +export KEY_PW='6!6428DQXwPpi7@$ggeg/=' +export LIB_PATH="/path/to/lib/folder" + +for key_file in key*pbkdf2.pem; do + # generate self-signed certificate + openssl req -x509 -key "$key_file" -sha256 -days 3650 -subj "/CN=OpenSearch Test Node" -passin pass:"$KEY_PW" \ + -addext "subjectAltName=DNS:localhost,DNS:localhost.localdomain,DNS:localhost4,DNS:localhost4.localdomain4,DNS:localhost6,DNS:localhost6.localdomain6,IP:127.0.0.1,IP:0:0:0:0:0:0:0:1" \ + -out ca_temp.pem + if [ $? -ne 0 ]; then + echo "An error occurred while generating cert for $key_file" + exit 1 + fi + # create a new P12 keystore with key + cert + algo=$(echo "$key_file" | sed -n 's/key_\(.*\)_enc_pbkdf2.pem/\1/p') + openssl pkcs12 -export -inkey "$key_file" -in ca_temp.pem -name "testnode_${algo}_pbkdf2" -out testnode.p12 \ + -passin pass:"$KEY_PW" \ + -passout pass:"$STORE_PW" + if [ $? -ne 0 ]; then + echo "An error occurred while adding key + cert to P12 keystore for $key_file" + exit 1 + fi + # migrate from P12 to BCFKS keystore (keytool 21.0.2) + keytool -importkeystore -noprompt \ + -srckeystore testnode.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass "$STORE_PW" \ + -destkeystore testnode.bcfks \ + -deststoretype BCFKS \ + -deststorepass "$STORE_PW" \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar + if [ $? -ne 0 ]; then + echo "An error occurred while migrating to BCFKS for $key_file" + exit 1 + fi + # import from P12 to JKS keystore (keytool 21.0.2) + keytool -importkeystore -noprompt \ + -srckeystore testnode.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass "$STORE_PW" \ + -destkeystore testnode.jks \ + -deststoretype JKS \ + -deststorepass "$STORE_PW" + if [ $? -ne 0 ]; then + echo "An error occurred while migrating to JKS for $key_file" + exit 1 + fi +done +rm ca_temp.pem testnode.p12 +``` diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.bcfks b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.bcfks new file mode 100644 index 0000000000000..8d1a016d790b1 Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.bcfks differ diff --git a/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java b/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java index 8a4f3b4a898b4..eeb942166eeec 100644 --- a/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java +++ b/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java @@ -164,7 +164,6 @@ public void testInvalidJavaPattern() { } public void testJavaPatternLocale() { - assumeFalse("Can't run in a FIPS JVM, Joda parse date error", inFipsJvm()); DateProcessor dateProcessor = new DateProcessor( randomAlphaOfLength(10), null, diff --git a/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexRestClientSslTests.java b/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexRestClientSslTests.java index 170f89838dd0d..35fdcd07626d1 100644 --- a/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexRestClientSslTests.java +++ b/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexRestClientSslTests.java @@ -70,6 +70,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -84,7 +87,7 @@ @SuppressForbidden(reason = "use http server") public class ReindexRestClientSslTests extends OpenSearchTestCase { - private static final String STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="; + private static final String STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="; // has to be at least 112 bit long. private static HttpsServer server; private static Consumer handler = ignore -> {}; @@ -109,9 +112,23 @@ public static void setupHttpServer() throws Exception { @AfterClass public static void shutdownHttpServer() { - server.stop(0); - server = null; - handler = null; + if (server != null) { + server.stop(0); // Stop the server + Executor executor = server.getExecutor(); + if (executor instanceof ExecutorService) { + ((ExecutorService) executor).shutdown(); // Shutdown the executor + try { + if (!((ExecutorService) executor).awaitTermination(15, TimeUnit.SECONDS)) { + ((ExecutorService) executor).shutdownNow(); // Force shutdown if not terminated + } + } catch (InterruptedException ex) { + ((ExecutorService) executor).shutdownNow(); // Force shutdown on interruption + Thread.currentThread().interrupt(); + } + } + server = null; + handler = null; + } } private static SSLContext buildServerSslContext() throws Exception { @@ -129,7 +146,6 @@ private static SSLContext buildServerSslContext() throws Exception { } public void testClientFailsWithUntrustedCertificate() throws IOException { - assumeFalse("https://github.com/elastic/elasticsearch/issues/49094", inFipsJvm()); final List threads = new ArrayList<>(); final Settings settings = Settings.builder() .put("path.home", createTempDir()) @@ -165,7 +181,6 @@ public void testClientSucceedsWithCertificateAuthorities() throws IOException { } public void testClientSucceedsWithVerificationDisabled() throws IOException { - assumeFalse("Cannot disable verification in FIPS JVM", inFipsJvm()); final List threads = new ArrayList<>(); final Settings settings = Settings.builder() .put("path.home", createTempDir()) @@ -173,10 +188,21 @@ public void testClientSucceedsWithVerificationDisabled() throws IOException { .put("reindex.ssl.supported_protocols", "TLSv1.2") .build(); final Environment environment = TestEnvironment.newEnvironment(settings); - final ReindexSslConfig ssl = new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class)); - try (RestClient client = Reindexer.buildRestClient(getRemoteInfo(), ssl, 1L, threads)) { - final Response response = client.performRequest(new Request("GET", "/")); - assertThat(response.getStatusLine().getStatusCode(), Matchers.is(200)); + + if (inFipsJvm()) { + try { + new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class)); + fail("expected IllegalStateException"); + } catch (Exception e) { + assertThat(e, Matchers.instanceOf(IllegalStateException.class)); + assertThat(e.getMessage(), Matchers.containsString("The use of TrustEverythingConfig is not permitted in FIPS mode")); + } + } else { + final ReindexSslConfig ssl = new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class)); + try (RestClient client = Reindexer.buildRestClient(getRemoteInfo(), ssl, 1L, threads)) { + final Response response = client.performRequest(new Request("GET", "/")); + assertThat(response.getStatusLine().getStatusCode(), Matchers.is(200)); + } } } diff --git a/modules/transport-netty4/build.gradle b/modules/transport-netty4/build.gradle index 4e68a4ce17f73..95e9ee2e80f8c 100644 --- a/modules/transport-netty4/build.gradle +++ b/modules/transport-netty4/build.gradle @@ -147,17 +147,6 @@ thirdPartyAudit { 'io.netty.internal.tcnative.SSLContext', 'io.netty.internal.tcnative.SSLPrivateKeyMethod', - // from io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator (netty) - 'org.bouncycastle.cert.X509v3CertificateBuilder', - 'org.bouncycastle.cert.jcajce.JcaX509CertificateConverter', - 'org.bouncycastle.operator.jcajce.JcaContentSignerBuilder', - 'org.bouncycastle.openssl.PEMEncryptedKeyPair', - 'org.bouncycastle.openssl.PEMParser', - 'org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter', - 'org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder', - 'org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder', - 'org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo', - // from io.netty.handler.ssl.JettyNpnSslEngine (netty) 'org.eclipse.jetty.npn.NextProtoNego$ClientProvider', 'org.eclipse.jetty.npn.NextProtoNego$ServerProvider', diff --git a/modules/transport-netty4/src/test/java/org/opensearch/http/netty4/ssl/SecureNetty4HttpServerTransportTests.java b/modules/transport-netty4/src/test/java/org/opensearch/http/netty4/ssl/SecureNetty4HttpServerTransportTests.java index 563f89b70545e..949b5e4ddd303 100644 --- a/modules/transport-netty4/src/test/java/org/opensearch/http/netty4/ssl/SecureNetty4HttpServerTransportTests.java +++ b/modules/transport-netty4/src/test/java/org/opensearch/http/netty4/ssl/SecureNetty4HttpServerTransportTests.java @@ -15,6 +15,8 @@ import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.MockBigArrays; import org.opensearch.common.util.MockPageCacheRecycler; @@ -121,9 +123,9 @@ public Optional buildHttpServerExceptionHandler(Setti @Override public Optional buildSecureHttpServerEngine(Settings settings, HttpServerTransport transport) throws SSLException { try { - final KeyStore keyStore = KeyStore.getInstance("PKCS12"); + final KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); keyStore.load( - SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-secure.jks"), + SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-secure.bcfks"), "password".toCharArray() ); diff --git a/modules/transport-netty4/src/test/java/org/opensearch/transport/netty4/ssl/SimpleSecureNetty4TransportTests.java b/modules/transport-netty4/src/test/java/org/opensearch/transport/netty4/ssl/SimpleSecureNetty4TransportTests.java index e573a9d018862..c938052d321ca 100644 --- a/modules/transport-netty4/src/test/java/org/opensearch/transport/netty4/ssl/SimpleSecureNetty4TransportTests.java +++ b/modules/transport-netty4/src/test/java/org/opensearch/transport/netty4/ssl/SimpleSecureNetty4TransportTests.java @@ -13,6 +13,8 @@ import org.opensearch.common.network.NetworkService; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.common.util.PageCacheRecycler; import org.opensearch.common.util.io.IOUtils; import org.opensearch.common.util.net.NetUtils; @@ -77,9 +79,11 @@ public Optional buildServerTransportExceptionHandler( @Override public Optional buildSecureServerTransportEngine(Settings settings, Transport transport) throws SSLException { try { - final KeyStore keyStore = KeyStore.getInstance("PKCS12"); + var keyStoreType = inFipsJvm() ? KeyStoreType.BCFKS : KeyStoreType.JKS; + var fileExtension = KeyStoreType.TYPE_TO_EXTENSION_MAP.get(keyStoreType).get(0); + final KeyStore keyStore = KeyStoreFactory.getInstance(keyStoreType); keyStore.load( - SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-secure.jks"), + SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-secure" + fileExtension), "password".toCharArray() ); diff --git a/modules/transport-netty4/src/test/resources/README.md b/modules/transport-netty4/src/test/resources/README.md index 50cbd432d32c6..07b4b171c3884 100644 --- a/modules/transport-netty4/src/test/resources/README.md +++ b/modules/transport-netty4/src/test/resources/README.md @@ -20,7 +20,23 @@ keytool -importkeystore -noprompt \ -srcstoretype PKCS12 \ -srcstorepass password \ -alias netty4-secure \ - -destkeystore netty4-secure.jks \ + -destkeystore netty4-secure.jks \ -deststoretype JKS \ -deststorepass password ``` + +# 4. Migrate from P12 to BCFIPS keystore + +``` +keytool -importkeystore -noprompt \ + -srckeystore netty4-secure.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass password \ + -alias netty4-secure \ + -destkeystore netty4-secure.bcfks \ + -deststoretype BCFKS \ + -deststorepass password \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar +``` diff --git a/modules/transport-netty4/src/test/resources/netty4-secure.bcfks b/modules/transport-netty4/src/test/resources/netty4-secure.bcfks new file mode 100644 index 0000000000000..034f2a1331729 Binary files /dev/null and b/modules/transport-netty4/src/test/resources/netty4-secure.bcfks differ diff --git a/modules/transport-netty4/src/yamlRestTest/java/org/opensearch/http/netty4/Netty4ClientYamlTestSuiteIT.java b/modules/transport-netty4/src/yamlRestTest/java/org/opensearch/http/netty4/Netty4ClientYamlTestSuiteIT.java index 45693078174a8..9c10dc98202eb 100644 --- a/modules/transport-netty4/src/yamlRestTest/java/org/opensearch/http/netty4/Netty4ClientYamlTestSuiteIT.java +++ b/modules/transport-netty4/src/yamlRestTest/java/org/opensearch/http/netty4/Netty4ClientYamlTestSuiteIT.java @@ -39,15 +39,10 @@ import org.apache.lucene.tests.util.TimeUnits; import org.opensearch.test.rest.yaml.ClientYamlTestCandidate; import org.opensearch.test.rest.yaml.OpenSearchClientYamlSuiteTestCase; -import org.junit.BeforeClass; //TODO: This is a *temporary* workaround to ensure a timeout does not mask other problems @TimeoutSuite(millis = 30 * TimeUnits.MINUTE) public class Netty4ClientYamlTestSuiteIT extends OpenSearchClientYamlSuiteTestCase { - @BeforeClass - public static void muteInFips() { - assumeFalse("We run with DEFAULT distribution in FIPS mode and default to security4 instead of netty4", inFipsJvm()); - } public Netty4ClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); diff --git a/plugins/analysis-icu/src/test/java/org/opensearch/index/analysis/IcuAnalyzerTests.java b/plugins/analysis-icu/src/test/java/org/opensearch/index/analysis/IcuAnalyzerTests.java index c363bc6eb43f8..c74dbb22bc21e 100644 --- a/plugins/analysis-icu/src/test/java/org/opensearch/index/analysis/IcuAnalyzerTests.java +++ b/plugins/analysis-icu/src/test/java/org/opensearch/index/analysis/IcuAnalyzerTests.java @@ -35,6 +35,7 @@ import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.tests.analysis.BaseTokenStreamTestCase; import org.opensearch.Version; +import org.opensearch.bootstrap.SecureRandomInitializer; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.common.settings.Settings; import org.opensearch.index.IndexSettings; @@ -47,6 +48,10 @@ public class IcuAnalyzerTests extends BaseTokenStreamTestCase { + static { + SecureRandomInitializer.init(); + } + public void testMixedAlphabetTokenization() throws IOException { Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build(); diff --git a/plugins/arrow-flight-rpc/build.gradle b/plugins/arrow-flight-rpc/build.gradle index f3a166bc39ae7..c9888b919716a 100644 --- a/plugins/arrow-flight-rpc/build.gradle +++ b/plugins/arrow-flight-rpc/build.gradle @@ -170,17 +170,6 @@ tasks.named('thirdPartyAudit').configure { 'org.apache.log4j.Level', 'org.apache.log4j.Logger', - // from io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator (netty) - 'org.bouncycastle.cert.X509v3CertificateBuilder', - 'org.bouncycastle.cert.jcajce.JcaX509CertificateConverter', - 'org.bouncycastle.operator.jcajce.JcaContentSignerBuilder', - 'org.bouncycastle.openssl.PEMEncryptedKeyPair', - 'org.bouncycastle.openssl.PEMParser', - 'org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter', - 'org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder', - 'org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder', - 'org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo', - // from io.netty.handler.ssl.JettyNpnSslEngine (netty) 'org.eclipse.jetty.npn.NextProtoNego$ClientProvider', 'org.eclipse.jetty.npn.NextProtoNego$ServerProvider', diff --git a/plugins/discovery-azure-classic/src/internalClusterTest/java/org/opensearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java b/plugins/discovery-azure-classic/src/internalClusterTest/java/org/opensearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java index a4a2e672f3afe..fcc116f993181 100644 --- a/plugins/discovery-azure-classic/src/internalClusterTest/java/org/opensearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java +++ b/plugins/discovery-azure-classic/src/internalClusterTest/java/org/opensearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java @@ -43,6 +43,8 @@ import org.opensearch.common.SuppressForbidden; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.core.util.FileSystemUtils; import org.opensearch.discovery.DiscoveryModule; import org.opensearch.env.Environment; @@ -278,7 +280,7 @@ public static void startHttpd() throws Exception { private static SSLContext getSSLContext() throws Exception { char[] passphrase = "keypass".toCharArray(); - KeyStore ks = KeyStore.getInstance("JKS"); + KeyStore ks = KeyStoreFactory.getInstance(KeyStoreType.JKS); try (InputStream stream = AzureDiscoveryClusterFormationTests.class.getResourceAsStream("/test-node.jks")) { assertNotNull("can't find keystore file", stream); ks.load(stream, passphrase); diff --git a/plugins/ingest-attachment/build.gradle b/plugins/ingest-attachment/build.gradle index 47a15b75234dc..97a78261f85ba 100644 --- a/plugins/ingest-attachment/build.gradle +++ b/plugins/ingest-attachment/build.gradle @@ -142,12 +142,3 @@ thirdPartyAudit { 'com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper$1' ) } - -if (BuildParams.inFipsJvm) { - // FIPS JVM includes many classes from bouncycastle which count as jar hell for the third party audit, - // rather than provide a long list of exclusions, disable the check on FIPS. - jarHell.enabled = false - test.enabled = false - yamlRestTest.enabled = false; - testingConventions.enabled = false; -} diff --git a/plugins/repository-azure/build.gradle b/plugins/repository-azure/build.gradle index c2fc2233c0473..73d74350ab2d1 100644 --- a/plugins/repository-azure/build.gradle +++ b/plugins/repository-azure/build.gradle @@ -313,6 +313,10 @@ Map expansions = [ 'base_path': azureBasePath + "_integration_tests" ] +tasks.withType(Test).configureEach { + onlyIf { BuildParams.inFipsJvm == false } +} + processYamlRestTestResources { inputs.properties(expansions) MavenFilteringHack.filter(it, expansions) diff --git a/plugins/repository-gcs/src/main/java/org/opensearch/repositories/gcs/GoogleCloudStorageService.java b/plugins/repository-gcs/src/main/java/org/opensearch/repositories/gcs/GoogleCloudStorageService.java index 83a4146c99b99..4d6c6e2e425a2 100644 --- a/plugins/repository-gcs/src/main/java/org/opensearch/repositories/gcs/GoogleCloudStorageService.java +++ b/plugins/repository-gcs/src/main/java/org/opensearch/repositories/gcs/GoogleCloudStorageService.java @@ -32,10 +32,10 @@ package org.opensearch.repositories.gcs; -import com.google.api.client.googleapis.GoogleUtils; import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.util.SecurityUtils; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.ServiceOptions; @@ -46,10 +46,13 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.common.collect.MapBuilder; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.common.unit.TimeValue; import org.opensearch.core.common.Strings; import java.io.IOException; +import java.io.InputStream; import java.net.Authenticator; import java.net.PasswordAuthentication; import java.net.Proxy; @@ -185,9 +188,12 @@ public HttpRequestInitializer getHttpRequestInitializer(ServiceOptions ser private HttpTransport createHttpTransport(final GoogleCloudStorageClientSettings clientSettings) throws IOException { return SocketAccess.doPrivilegedIOException(() -> { final NetHttpTransport.Builder builder = new NetHttpTransport.Builder(); - // requires java.lang.RuntimePermission "setFactory" - // Pin the TLS trust certificates. - builder.trustCertificates(GoogleUtils.getCertificateTrustStore()); + // use the BCFIPS trustStore format instead of PKCS#12 to ensure compatibility with BC-FIPS + var certTrustStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); + InputStream keyStoreStream = getClass().getResourceAsStream("/google.bcfks"); + SecurityUtils.loadKeyStore(certTrustStore, keyStoreStream, "notasecret"); + + builder.trustCertificates(certTrustStore); final ProxySettings proxySettings = clientSettings.getProxySettings(); if (proxySettings != ProxySettings.NO_PROXY_SETTINGS) { if (proxySettings.isAuthenticated()) { diff --git a/plugins/repository-gcs/src/main/resources/README.md b/plugins/repository-gcs/src/main/resources/README.md new file mode 100644 index 0000000000000..bda781bcea537 --- /dev/null +++ b/plugins/repository-gcs/src/main/resources/README.md @@ -0,0 +1,19 @@ +# +# This is README describes how the certificates in this directory were created. +# This file can also be executed as a script. +# google-api-java-client provides its own trusted certificates inside google keystore which comes in JKS or PKCS#12 formats. +# Since BCFIPS requires its own BCFKS format this script creates it. +# + +``` +keytool -importkeystore -noprompt \ + -srckeystore $KEY_STORE_PATH/google.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass notasecret \ + -destkeystore google.bcfks \ + -deststoretype BCFKS \ + -deststorepass notasecret \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar +``` diff --git a/plugins/repository-gcs/src/main/resources/google.bcfks b/plugins/repository-gcs/src/main/resources/google.bcfks new file mode 100644 index 0000000000000..13202c5c21eb3 Binary files /dev/null and b/plugins/repository-gcs/src/main/resources/google.bcfks differ diff --git a/plugins/repository-s3/build.gradle b/plugins/repository-s3/build.gradle index 25d910052b9a0..6e4a8c70b66af 100644 --- a/plugins/repository-s3/build.gradle +++ b/plugins/repository-s3/build.gradle @@ -34,6 +34,7 @@ import org.opensearch.gradle.test.RestIntegTestTask import org.opensearch.gradle.test.TestTask import org.opensearch.gradle.test.rest.YamlRestTestPlugin import org.opensearch.gradle.test.InternalClusterTestPlugin +import org.opensearch.gradle.testclusters.OpenSearchCluster import static org.opensearch.gradle.PropertyNormalization.IGNORE_VALUE @@ -303,7 +304,7 @@ testClusters.yamlRestTest { setting 's3.client.integration_test_eks.region', { "us-east-2" }, IGNORE_VALUE // to redirect InstanceProfileCredentialsProvider to custom auth point - systemProperty "aws.ec2MetadataServiceEndpointOverride", { "${-> fixtureAddress('s3-fixture', 's3-fixture-with-ec2', '80')}" }, IGNORE_VALUE + systemProperty "aws.ec2MetadataServiceEndpoint", { "${-> fixtureAddress('s3-fixture', 's3-fixture-with-ec2', '80')}" }, IGNORE_VALUE // to redirect AWSSecurityTokenServiceClient to custom auth point systemProperty "aws.stsEndpointOverride", { "${-> fixtureAddress('s3-fixture', 's3-fixture-with-eks', '80')}/eks_credentials_endpoint" }, IGNORE_VALUE } else { @@ -486,17 +487,6 @@ thirdPartyAudit { 'net.jpountz.xxhash.XXHash32', 'net.jpountz.xxhash.XXHashFactory', - // from io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator (netty) - 'org.bouncycastle.cert.X509v3CertificateBuilder', - 'org.bouncycastle.cert.jcajce.JcaX509CertificateConverter', - 'org.bouncycastle.operator.jcajce.JcaContentSignerBuilder', - 'org.bouncycastle.openssl.PEMEncryptedKeyPair', - 'org.bouncycastle.openssl.PEMParser', - 'org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter', - 'org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder', - 'org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder', - 'org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo', - 'org.conscrypt.AllocatedBuffer', 'org.conscrypt.BufferAllocator', 'org.conscrypt.Conscrypt', diff --git a/plugins/repository-s3/src/internalClusterTest/java/org/opensearch/repositories/s3/S3BlobStoreRepositoryTests.java b/plugins/repository-s3/src/internalClusterTest/java/org/opensearch/repositories/s3/S3BlobStoreRepositoryTests.java index 5bea51706cfae..689023f81658a 100644 --- a/plugins/repository-s3/src/internalClusterTest/java/org/opensearch/repositories/s3/S3BlobStoreRepositoryTests.java +++ b/plugins/repository-s3/src/internalClusterTest/java/org/opensearch/repositories/s3/S3BlobStoreRepositoryTests.java @@ -144,7 +144,7 @@ protected HttpHandler createErroneousHttpHandler(final HttpHandler delegate) { protected Settings nodeSettings(int nodeOrdinal) { final MockSecureSettings secureSettings = new MockSecureSettings(); secureSettings.setString(S3ClientSettings.ACCESS_KEY_SETTING.getConcreteSettingForNamespace("test").getKey(), "access"); - secureSettings.setString(S3ClientSettings.SECRET_KEY_SETTING.getConcreteSettingForNamespace("test").getKey(), "secret"); + secureSettings.setString(S3ClientSettings.SECRET_KEY_SETTING.getConcreteSettingForNamespace("test").getKey(), "secret_password"); final Settings.Builder builder = Settings.builder() .put(ThreadPool.ESTIMATED_TIME_INTERVAL_SETTING.getKey(), 0) // We have tests that verify an exact wait time diff --git a/plugins/repository-s3/src/main/java/org/opensearch/repositories/s3/S3Service.java b/plugins/repository-s3/src/main/java/org/opensearch/repositories/s3/S3Service.java index 3d5e121778ba9..e17234f6921e0 100644 --- a/plugins/repository-s3/src/main/java/org/opensearch/repositories/s3/S3Service.java +++ b/plugins/repository-s3/src/main/java/org/opensearch/repositories/s3/S3Service.java @@ -64,6 +64,7 @@ import org.apache.http.protocol.HttpContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.cluster.metadata.RepositoryMetadata; import org.opensearch.common.Nullable; import org.opensearch.common.SuppressForbidden; @@ -88,7 +89,6 @@ import java.nio.file.Path; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -341,7 +341,8 @@ private static SSLConnectionSocketFactory createSocksSslConnectionSocketFactory( // This part was taken from AWS settings try { final SSLContext sslCtx = SSLContext.getInstance("TLS"); - sslCtx.init(SystemPropertyTlsKeyManagersProvider.create().keyManagers(), null, new SecureRandom()); + sslCtx.init(SystemPropertyTlsKeyManagersProvider.create().keyManagers(), null, CryptoServicesRegistrar.getSecureRandom()); + return new SdkTlsSocketFactory(sslCtx, new DefaultHostnameVerifier()) { @Override public Socket createSocket(final HttpContext ctx) throws IOException { diff --git a/plugins/repository-s3/src/test/java/org/opensearch/repositories/s3/S3BlobContainerRetriesTests.java b/plugins/repository-s3/src/test/java/org/opensearch/repositories/s3/S3BlobContainerRetriesTests.java index 96ef28d24c14f..dbecc7c7eb417 100644 --- a/plugins/repository-s3/src/test/java/org/opensearch/repositories/s3/S3BlobContainerRetriesTests.java +++ b/plugins/repository-s3/src/test/java/org/opensearch/repositories/s3/S3BlobContainerRetriesTests.java @@ -222,7 +222,10 @@ protected AsyncMultiStreamBlobContainer createBlobContainer( final MockSecureSettings secureSettings = new MockSecureSettings(); secureSettings.setString(S3ClientSettings.ACCESS_KEY_SETTING.getConcreteSettingForNamespace(clientName).getKey(), "access"); - secureSettings.setString(S3ClientSettings.SECRET_KEY_SETTING.getConcreteSettingForNamespace(clientName).getKey(), "secret"); + secureSettings.setString( + S3ClientSettings.SECRET_KEY_SETTING.getConcreteSettingForNamespace(clientName).getKey(), + "secret_password" + ); clientSettings.setSecureSettings(secureSettings); service.refreshAndClearCache(S3ClientSettings.load(clientSettings.build(), configPath())); asyncService.refreshAndClearCache(S3ClientSettings.load(clientSettings.build(), configPath())); diff --git a/plugins/telemetry-otel/build.gradle b/plugins/telemetry-otel/build.gradle index 54f4f2f897562..0ac66eef40795 100644 --- a/plugins/telemetry-otel/build.gradle +++ b/plugins/telemetry-otel/build.gradle @@ -44,13 +44,13 @@ dependencies { thirdPartyAudit { ignoreViolations( - 'io.opentelemetry.internal.shaded.jctools.queues.MpscArrayQueueConsumerIndexField', - 'io.opentelemetry.internal.shaded.jctools.queues.MpscArrayQueueProducerIndexField', - 'io.opentelemetry.internal.shaded.jctools.queues.MpscArrayQueueProducerLimitField', - 'io.opentelemetry.internal.shaded.jctools.util.UnsafeAccess', - 'io.opentelemetry.internal.shaded.jctools.util.UnsafeRefArrayAccess', - 'io.opentelemetry.exporter.internal.marshal.UnsafeAccess', - 'io.opentelemetry.exporter.internal.marshal.UnsafeAccess$UnsafeHolder' + 'io.opentelemetry.internal.shaded.jctools.queues.MpscArrayQueueConsumerIndexField', + 'io.opentelemetry.internal.shaded.jctools.queues.MpscArrayQueueProducerIndexField', + 'io.opentelemetry.internal.shaded.jctools.queues.MpscArrayQueueProducerLimitField', + 'io.opentelemetry.internal.shaded.jctools.util.UnsafeAccess', + 'io.opentelemetry.internal.shaded.jctools.util.UnsafeRefArrayAccess', + 'io.opentelemetry.exporter.internal.marshal.UnsafeAccess', + 'io.opentelemetry.exporter.internal.marshal.UnsafeAccess$UnsafeHolder' ) ignoreMissingClasses( diff --git a/plugins/transport-grpc/build.gradle b/plugins/transport-grpc/build.gradle index 5c6bc8efe1098..c94b71f2e0b6c 100644 --- a/plugins/transport-grpc/build.gradle +++ b/plugins/transport-grpc/build.gradle @@ -51,17 +51,6 @@ thirdPartyAudit { 'org.apache.log4j.Level', 'org.apache.log4j.Logger', - // from io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator (netty) - 'org.bouncycastle.cert.X509v3CertificateBuilder', - 'org.bouncycastle.cert.jcajce.JcaX509CertificateConverter', - 'org.bouncycastle.operator.jcajce.JcaContentSignerBuilder', - 'org.bouncycastle.openssl.PEMEncryptedKeyPair', - 'org.bouncycastle.openssl.PEMParser', - 'org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter', - 'org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder', - 'org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder', - 'org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo', - // from io.netty.handler.ssl.JettyNpnSslEngine (netty) 'org.eclipse.jetty.npn.NextProtoNego$ClientProvider', 'org.eclipse.jetty.npn.NextProtoNego$ServerProvider', diff --git a/plugins/transport-reactor-netty4/build.gradle b/plugins/transport-reactor-netty4/build.gradle index ba8b17c5877a7..6e5f767358383 100644 --- a/plugins/transport-reactor-netty4/build.gradle +++ b/plugins/transport-reactor-netty4/build.gradle @@ -99,17 +99,6 @@ thirdPartyAudit { 'io.netty.internal.tcnative.SSLContext', 'io.netty.internal.tcnative.SSLPrivateKeyMethod', - // from io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator (netty) - 'org.bouncycastle.cert.X509v3CertificateBuilder', - 'org.bouncycastle.cert.jcajce.JcaX509CertificateConverter', - 'org.bouncycastle.operator.jcajce.JcaContentSignerBuilder', - 'org.bouncycastle.openssl.PEMEncryptedKeyPair', - 'org.bouncycastle.openssl.PEMParser', - 'org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter', - 'org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder', - 'org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder', - 'org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo', - // from io.netty.handler.ssl.JettyNpnSslEngine (netty) 'org.eclipse.jetty.npn.NextProtoNego$ClientProvider', 'org.eclipse.jetty.npn.NextProtoNego$ServerProvider', diff --git a/qa/os/build.gradle b/qa/os/build.gradle index 082ed5277575a..0ab861e96d79a 100644 --- a/qa/os/build.gradle +++ b/qa/os/build.gradle @@ -49,6 +49,7 @@ dependencies { api project(':libs:opensearch-common') api project(':libs:opensearch-core') + api project(':libs:opensearch-ssl-config') testImplementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" testImplementation "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" diff --git a/qa/os/src/test/java/org/opensearch/packaging/util/ServerUtils.java b/qa/os/src/test/java/org/opensearch/packaging/util/ServerUtils.java index 42eac9fdf4961..4c6e0319c7b1c 100644 --- a/qa/os/src/test/java/org/opensearch/packaging/util/ServerUtils.java +++ b/qa/os/src/test/java/org/opensearch/packaging/util/ServerUtils.java @@ -48,6 +48,8 @@ import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -95,7 +97,7 @@ private static HttpResponse execute(Request request, String username, String pas try (InputStream inStream = Files.newInputStream(caCert)) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream); - KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType()); + KeyStore truststore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); truststore.load(null, null); truststore.setCertificateEntry("myClusterCA", cert); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); diff --git a/qa/smoke-test-plugins/build.gradle b/qa/smoke-test-plugins/build.gradle index 89f9d071e7e83..fd0821f5685e5 100644 --- a/qa/smoke-test-plugins/build.gradle +++ b/qa/smoke-test-plugins/build.gradle @@ -29,7 +29,6 @@ */ import org.opensearch.gradle.MavenFilteringHack -import org.opensearch.gradle.info.BuildParams apply plugin: 'opensearch.testclusters' apply plugin: 'opensearch.standalone-rest-test' diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/10_basic.yml index 0866c71b87e12..6b5c75fdd76ab 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/10_basic.yml @@ -24,6 +24,9 @@ setup: --- "node_reload_secure_settings test correct(empty) password": + - skip: + version: "3.0.0 - " + reason: "Running this test in active FIPS mode is not supported" - do: nodes.reload_secure_settings: {} diff --git a/server/build.gradle b/server/build.gradle index fd2cac4c7506f..4e7f19240ea28 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -69,6 +69,7 @@ dependencies { api project(":libs:opensearch-geo") api project(":libs:opensearch-telemetry") api project(":libs:opensearch-task-commons") + api project(":libs:opensearch-ssl-config") implementation project(':libs:opensearch-arrow-spi') compileOnly project(':libs:opensearch-plugin-classloader') diff --git a/server/licenses/bcpkix-fips-2.0.7.jar.sha1 b/server/licenses/bcpkix-fips-2.0.7.jar.sha1 new file mode 100644 index 0000000000000..5df930b54fe44 --- /dev/null +++ b/server/licenses/bcpkix-fips-2.0.7.jar.sha1 @@ -0,0 +1 @@ +01eea0f325315ca6295b0a6926ff862d8001cdf9 \ No newline at end of file diff --git a/server/src/internalClusterTest/java/org/opensearch/action/admin/ReloadSecureSettingsIT.java b/server/src/internalClusterTest/java/org/opensearch/action/admin/ReloadSecureSettingsIT.java index c81d491719e4b..2bb14e09beaf6 100644 --- a/server/src/internalClusterTest/java/org/opensearch/action/admin/ReloadSecureSettingsIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/action/admin/ReloadSecureSettingsIT.java @@ -68,6 +68,9 @@ @OpenSearchIntegTestCase.ClusterScope(minNumDataNodes = 2) public class ReloadSecureSettingsIT extends OpenSearchIntegTestCase { + // Minimal required characters to fulfill the requirement of 112 bit strong passwords + protected static final int MIN_112_BIT_STRONG = 14; + public void testMissingKeystoreFile() throws Exception { final PluginsService pluginsService = internalCluster().getInstance(PluginsService.class); final MockReloadablePlugin mockReloadablePlugin = pluginsService.filterPlugins(MockReloadablePlugin.class) @@ -182,7 +185,7 @@ public void testReloadAllNodesWithPasswordWithoutTLSFails() throws Exception { final Environment environment = internalCluster().getInstance(Environment.class); final AtomicReference reloadSettingsError = new AtomicReference<>(); final int initialReloadCount = mockReloadablePlugin.getReloadCount(); - final char[] password = randomAlphaOfLength(12).toCharArray(); + final char[] password = randomAlphaOfLength(MIN_112_BIT_STRONG).toCharArray(); writeEmptyKeystore(environment, password); final CountDownLatch latch = new CountDownLatch(1); client().admin() @@ -229,7 +232,7 @@ public void onFailure(Exception e) { public void testReloadLocalNodeWithPasswordWithoutTLSSucceeds() throws Exception { final Environment environment = internalCluster().getInstance(Environment.class); final AtomicReference reloadSettingsError = new AtomicReference<>(); - final char[] password = randomAlphaOfLength(12).toCharArray(); + final char[] password = randomAlphaOfLength(MIN_112_BIT_STRONG).toCharArray(); writeEmptyKeystore(environment, password); final CountDownLatch latch = new CountDownLatch(1); client().admin() @@ -275,14 +278,15 @@ public void testWrongKeystorePassword() throws Exception { final Environment environment = internalCluster().getInstance(Environment.class); final AtomicReference reloadSettingsError = new AtomicReference<>(); final int initialReloadCount = mockReloadablePlugin.getReloadCount(); + final char[] password = inFipsJvm() ? randomAlphaOfLength(MIN_112_BIT_STRONG).toCharArray() : new char[0]; // "some" keystore should be present in this case - writeEmptyKeystore(environment, new char[0]); + writeEmptyKeystore(environment, password); final CountDownLatch latch = new CountDownLatch(1); client().admin() .cluster() .prepareReloadSecureSettings() .setNodesIds("_local") - .setSecureStorePassword(new SecureString(new char[] { 'W', 'r', 'o', 'n', 'g' })) + .setSecureStorePassword(new SecureString("thewrongkeystorepassword".toCharArray())) .execute(new ActionListener() { @Override public void onResponse(NodesReloadSecureSettingsResponse nodesReloadResponse) { @@ -316,6 +320,7 @@ public void onFailure(Exception e) { } public void testMisbehavingPlugin() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); final Environment environment = internalCluster().getInstance(Environment.class); final PluginsService pluginsService = internalCluster().getInstance(PluginsService.class); final MockReloadablePlugin mockReloadablePlugin = pluginsService.filterPlugins(MockReloadablePlugin.class) @@ -382,6 +387,7 @@ public void onFailure(Exception e) { } public void testReloadWhileKeystoreChanged() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); final PluginsService pluginsService = internalCluster().getInstance(PluginsService.class); final MockReloadablePlugin mockReloadablePlugin = pluginsService.filterPlugins(MockReloadablePlugin.class) .stream() diff --git a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java index b40ac67b04917..b3068338c0c8a 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -196,6 +196,14 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot BootstrapSettings.CTRLHANDLER_SETTING.get(settings) ); + SecureRandomInitializer.init(); + + var cryptoStandard = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); + if ("FIPS-140-3".equals(cryptoStandard) || "true".equalsIgnoreCase(System.getProperty("org.bouncycastle.fips.approved_only"))) { + LogManager.getLogger(Bootstrap.class).info("running in FIPS-140-3 mode"); + SecurityProviderManager.excludeSunJCE(); + } + // initialize probes before the security manager is installed initializeProbes(); diff --git a/server/src/main/java/org/opensearch/bootstrap/SecureRandomInitializer.java b/server/src/main/java/org/opensearch/bootstrap/SecureRandomInitializer.java new file mode 100644 index 0000000000000..633694556847c --- /dev/null +++ b/server/src/main/java/org/opensearch/bootstrap/SecureRandomInitializer.java @@ -0,0 +1,46 @@ +/* + * 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.bootstrap; + +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.fips.FipsDRBG; +import org.bouncycastle.crypto.util.BasicEntropySourceProvider; + +import java.security.GeneralSecurityException; +import java.security.SecureRandom; + +/** + * Instantiates {@link SecureRandom} + */ +public class SecureRandomInitializer { + + private SecureRandomInitializer() {} + + /** + * Instantiates a new {@link SecureRandom} if it is not already set. The specific implementation used depends on whether the JVM + * is running in a FIPS-approved mode or not. An instance of {@link SecureRandom} can be obtained from + * {@link CryptoServicesRegistrar#getSecureRandom()} + */ + public static void init() { + CryptoServicesRegistrar.setSecureRandom(CryptoServicesRegistrar.getSecureRandomIfSet(SecureRandomInitializer::getSecureRandom)); + } + + private static SecureRandom getSecureRandom() { + try { + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + var entropySource = SecureRandom.getInstance("DEFAULT", "BCFIPS"); + return FipsDRBG.SHA512_HMAC.fromEntropySource(new BasicEntropySourceProvider(entropySource, true)).build(null, true); + } + return SecureRandom.getInstanceStrong(); + } catch (GeneralSecurityException e) { + throw new SecurityException("Failed to instantiate SecureRandom: " + e.getMessage(), e); + } + } + +} diff --git a/server/src/main/java/org/opensearch/bootstrap/SecurityProviderManager.java b/server/src/main/java/org/opensearch/bootstrap/SecurityProviderManager.java new file mode 100644 index 0000000000000..0b53d48b88f55 --- /dev/null +++ b/server/src/main/java/org/opensearch/bootstrap/SecurityProviderManager.java @@ -0,0 +1,48 @@ +/* + * 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.bootstrap; + +import java.security.Security; + +/** + * Provides additional control over declared security providers in 'java.security' file. + */ +public class SecurityProviderManager { + + public static final String SUN_JCE = "SunJCE"; + + private SecurityProviderManager() { + // singleton constructor + } + + /** + * Removes the SunJCE provider from the list of installed security providers. This method is intended to be used when running + * in a FIPS JVM and when the security file specifies additional configuration, instead of a complete replacement. + */ + public static void excludeSunJCE() { + Security.removeProvider(SUN_JCE); + } + + /** + * Returns the position at which the provider is found by its name, otherwise returns -1. + * Provider's position starts by 1 and will not always represent the configured value at 'java.security' file. + */ + public static int getPosition(String providerName) { + var provider = java.security.Security.getProvider(providerName); + if (provider != null) { + var providers = java.security.Security.getProviders(); + for (int i = 0; i < providers.length; i++) { + if (providers[i].getName().equals(providerName)) { + return i + 1; // provider positions starts at 1 + } + } + } + return -1; + } +} diff --git a/server/src/main/java/org/opensearch/common/Randomness.java b/server/src/main/java/org/opensearch/common/Randomness.java index 221bc95c41f31..479d3b195bee6 100644 --- a/server/src/main/java/org/opensearch/common/Randomness.java +++ b/server/src/main/java/org/opensearch/common/Randomness.java @@ -36,7 +36,6 @@ import org.opensearch.common.settings.Settings; import java.lang.reflect.Method; -import java.security.SecureRandom; import java.util.Collections; import java.util.List; import java.util.Random; @@ -125,22 +124,6 @@ public static Random get() { } } - /** - * Provides a secure source of randomness. - *

- * This acts exactly similar to {@link #get()}, but returning a new {@link SecureRandom}. - */ - public static SecureRandom createSecure() { - if (currentMethod != null && getRandomMethod != null) { - // tests, so just use a seed from the non secure random - byte[] seed = new byte[16]; - get().nextBytes(seed); - return new SecureRandom(seed); - } else { - return new SecureRandom(); - } - } - @SuppressForbidden(reason = "ThreadLocalRandom is okay when not running tests") private static Random getWithoutSeed() { assert currentMethod == null && getRandomMethod == null : "running under tests but tried to create non-reproducible random"; diff --git a/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java b/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java index 81fb1309df310..dae5c78b9645f 100644 --- a/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java +++ b/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java @@ -40,11 +40,14 @@ import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexOutput; import org.apache.lucene.store.NIOFSDirectory; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.opensearch.bootstrap.SecureRandomInitializer; import org.opensearch.cli.ExitCodes; import org.opensearch.cli.UserException; -import org.opensearch.common.Randomness; import org.opensearch.common.SetOnce; import org.opensearch.common.hash.MessageDigests; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.core.common.settings.SecureString; import javax.crypto.AEADBadTagException; @@ -75,7 +78,6 @@ import java.nio.file.attribute.PosixFilePermissions; import java.security.GeneralSecurityException; import java.security.KeyStore; -import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; import java.util.Enumeration; @@ -119,6 +121,11 @@ private static class Entry { } } + static { + // Instantiates new SecureRandom if caller is KeyStoreCli, otherwise obtains the existent. + SecureRandomInitializer.init(); + } + /** * A regex for the valid characters that a setting name in the keystore may use. */ @@ -218,11 +225,10 @@ public static KeyStoreWrapper create() { /** Add the bootstrap seed setting, which may be used as a unique, secure, random value by the node */ public static void addBootstrapSeed(KeyStoreWrapper wrapper) { assert wrapper.getSettingNames().contains(SEED_SETTING.getKey()) == false; - SecureRandom random = Randomness.createSecure(); int passwordLength = 20; // Generate 20 character passwords char[] characters = new char[passwordLength]; for (int i = 0; i < passwordLength; ++i) { - characters[i] = SEED_CHARS[random.nextInt(SEED_CHARS.length)]; + characters[i] = SEED_CHARS[CryptoServicesRegistrar.getSecureRandom().nextInt(SEED_CHARS.length)]; } wrapper.setString(SEED_SETTING.getKey(), characters); Arrays.fill(characters, (char) 0); @@ -448,8 +454,11 @@ private byte[] encrypt(char[] password, byte[] salt, byte[] iv) throws GeneralSe } private void decryptLegacyEntries() throws GeneralSecurityException, IOException { + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + throw new SecurityException("Legacy KeyStore formats v1 & v2 are not supported in FIPS JVM"); + } // v1 and v2 keystores never had passwords actually used, so we always use an empty password - KeyStore keystore = KeyStore.getInstance("PKCS12", "SUN"); + KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); Map settingTypes = new HashMap<>(); ByteArrayInputStream inputBytes = new ByteArrayInputStream(dataBytes); try (DataInputStream input = new DataInputStream(inputBytes)) { @@ -532,15 +541,14 @@ public synchronized void save(Path configDir, char[] password) throws Exception output.writeByte(password.length == 0 ? (byte) 0 : (byte) 1); // new cipher params - SecureRandom random = Randomness.createSecure(); // use 64 bytes salt, which surpasses that recommended by OWASP // see https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet byte[] salt = new byte[64]; - random.nextBytes(salt); + CryptoServicesRegistrar.getSecureRandom().nextBytes(salt); // use 96 bits (12 bytes) for IV as recommended by NIST // see http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf section 5.2.1.1 byte[] iv = new byte[12]; - random.nextBytes(iv); + CryptoServicesRegistrar.getSecureRandom().nextBytes(iv); // encrypted data byte[] encryptedBytes = encrypt(password, salt, iv); diff --git a/server/src/main/resources/org/opensearch/bootstrap/test.policy b/server/src/main/resources/org/opensearch/bootstrap/test.policy index f1a6e73fa5335..36b87b80ef136 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/test.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/test.policy @@ -20,6 +20,7 @@ grant { permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; permission java.lang.RuntimePermission "closeClassLoader"; permission java.lang.RuntimePermission "getProtectionDomain"; + permission java.lang.RuntimePermission "shutdownHooks"; permission java.security.SecurityPermission "getProperty.jdk.certpath.disabledAlgorithms"; permission java.security.SecurityPermission "getProperty.jdk.tls.disabledAlgorithms"; permission java.security.SecurityPermission "getProperty.jdk.tls.server.defaultDHEParameters"; @@ -32,4 +33,8 @@ grant { permission org.bouncycastle.crypto.CryptoServicesPermission "defaultRandomConfig"; permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; + permission java.security.SecurityPermission "getProperty.os.arch"; + permission java.security.SecurityPermission "getProperty.os.name"; + permission java.security.SecurityPermission "getProperty.java.io.tmpdir"; + permission java.security.SecurityPermission "getProperty.org.bouncycastle.drbg.pool_size"; }; diff --git a/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java b/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java new file mode 100644 index 0000000000000..34dc60030c40c --- /dev/null +++ b/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java @@ -0,0 +1,59 @@ +/* + * 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.bootstrap; + +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.fips.FipsSecureRandom; +import org.opensearch.test.OpenSearchTestCase; +import org.junit.BeforeClass; + +import java.security.SecureRandom; +import java.util.Arrays; + +public class SecureRandomInitializerTests extends OpenSearchTestCase { + + @BeforeClass + public static void setup() { + CryptoServicesRegistrar.setSecureRandom(null); + } + + public void testInitInNonFipsMode() { + // given + assertThrows(IllegalStateException.class, CryptoServicesRegistrar::getSecureRandom); + + // when + SecureRandomInitializer.init(); + SecureRandom secureRandom = CryptoServicesRegistrar.getSecureRandom(); + + // then + assertNotNull("SecureRandom should be initialized in non-FIPS mode", secureRandom); + byte[] randomBytes = new byte[16]; + secureRandom.nextBytes(randomBytes); + assertEquals(inFipsJvm() ? "BCFIPS_RNG" : "SUN", secureRandom.getProvider().getName()); + // BCFIPS 'DEFAULT' RNG algorithm defaults to 'HMAC-DRBG-SHA512' + assertEquals(inFipsJvm() ? "HMAC-DRBG-SHA512" : "NativePRNGBlocking", secureRandom.getAlgorithm()); + assertEquals(inFipsJvm() ? FipsSecureRandom.class : SecureRandom.class, secureRandom.getClass()); + assertFalse("Random bytes should not be all zeros", allZeros(randomBytes)); + + byte[] seed1 = secureRandom.generateSeed(16); + byte[] seed2 = secureRandom.generateSeed(16); + assertNotNull(seed1); + assertNotNull(seed2); + assertFalse("Seeds should not be identical", Arrays.equals(seed1, seed2)); + } + + private boolean allZeros(byte[] data) { + for (byte b : data) { + if (b != 0) { + return false; + } + } + return true; + } +} diff --git a/server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java b/server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java new file mode 100644 index 0000000000000..2a92f35abae5b --- /dev/null +++ b/server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java @@ -0,0 +1,171 @@ +/* + * 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.bootstrap; + +import org.opensearch.test.OpenSearchTestCase; +import org.junit.AfterClass; +import org.junit.Before; + +import javax.crypto.Cipher; + +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.util.Arrays; +import java.util.Locale; + +import static org.opensearch.bootstrap.BootstrapForTesting.sunJceInsertFunction; + +public class SecurityProviderManagerTests extends OpenSearchTestCase { + + private static final String BC_FIPS = "BCFIPS"; + private static final String SUN_JCE = "SunJCE"; + + // BCFIPS will only provide legacy ciphers when running in general mode, otherwise approved-only mode forbids the use. + private static final String MODE_DEPENDENT_CIPHER_PROVIDER = inFipsJvm() ? SUN_JCE : BC_FIPS; + + private static final String AES = "AES"; + private static final String RC_4 = "RC4"; + private static final String TRIPLE_DES = "DESedeWrap"; + private static final String DES = "DES"; + private static final String PBE = "PBE"; + private static final String BLOWFISH = "Blowfish"; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + var notInstalled = Arrays.stream(Security.getProviders()).noneMatch(provider -> SUN_JCE.equals(provider.getName())); + if (notInstalled && sunJceInsertFunction != null) { + sunJceInsertFunction.get(); + } + assumeTrue( + String.format( + Locale.ROOT, + "SunJCE provider has to be initially installed through '%s' file", + System.getProperty("java.security.properties", "UNDEFINED") + ), + Arrays.stream(Security.getProviders()).anyMatch(provider -> SUN_JCE.equals(provider.getName())) + ); + } + + @AfterClass + // restore the same state as before running the tests. + public static void removeSunJCE() { + if (inFipsJvm()) { + SecurityProviderManager.excludeSunJCE(); + } + } + + public void testCipherRC4() throws Exception { + // given + var cipher = Cipher.getInstance(RC_4); + assertEquals(RC_4, cipher.getAlgorithm()); + assertEquals(MODE_DEPENDENT_CIPHER_PROVIDER, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + if (inFipsJvm()) { + expectThrows(NoSuchAlgorithmException.class, () -> Cipher.getInstance(RC_4)); + } else { + cipher = Cipher.getInstance(RC_4); + assertEquals(RC_4, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + } + } + + public void testCipherAES() throws Exception { + // given + var cipher = Cipher.getInstance(AES); + assertEquals(AES, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + cipher = Cipher.getInstance(AES); + assertEquals(AES, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + } + + public void testCipher3Des() throws Exception { + // given + var cipher = Cipher.getInstance(TRIPLE_DES); + assertEquals(TRIPLE_DES, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + cipher = Cipher.getInstance(TRIPLE_DES); + assertEquals(TRIPLE_DES, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + } + + public void testCipherDes() throws Exception { + // given + var cipher = Cipher.getInstance(DES); + assertEquals(DES, cipher.getAlgorithm()); + assertEquals(MODE_DEPENDENT_CIPHER_PROVIDER, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + if (inFipsJvm()) { + expectThrows(NoSuchAlgorithmException.class, () -> Cipher.getInstance(DES)); + } else { + cipher = Cipher.getInstance(DES); + assertEquals(DES, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + } + } + + public void testCipherPBE() throws Exception { + // given + var cipher = Cipher.getInstance(PBE); + assertEquals(PBE, cipher.getAlgorithm()); + assertEquals(SUN_JCE, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + expectThrows(NoSuchAlgorithmException.class, () -> Cipher.getInstance(PBE)); + } + + public void testCipherBlowfish() throws Exception { + // given + var cipher = Cipher.getInstance(BLOWFISH); + assertEquals(BLOWFISH, cipher.getAlgorithm()); + assertEquals(MODE_DEPENDENT_CIPHER_PROVIDER, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + if (inFipsJvm()) { + expectThrows(NoSuchAlgorithmException.class, () -> Cipher.getInstance(BLOWFISH)); + } else { + cipher = Cipher.getInstance(BLOWFISH); + assertEquals(BLOWFISH, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + } + } + + public void testGetPosition() { + assertTrue(SUN_JCE + " is installed", SecurityProviderManager.getPosition(SUN_JCE) > 0); + SecurityProviderManager.excludeSunJCE(); + assertTrue(SUN_JCE + " is uninstalled", SecurityProviderManager.getPosition(SUN_JCE) < 0); + } + +} diff --git a/server/src/test/resources/org/opensearch/bootstrap/test.policy b/server/src/test/resources/org/opensearch/bootstrap/test.policy index c2b5a8e9c0a4e..fe319686c38a6 100644 --- a/server/src/test/resources/org/opensearch/bootstrap/test.policy +++ b/server/src/test/resources/org/opensearch/bootstrap/test.policy @@ -10,4 +10,5 @@ grant { // allow to test Security policy and codebases permission java.util.PropertyPermission "*", "read,write"; permission java.security.SecurityPermission "createPolicy.JavaPolicy"; + permission java.security.SecurityPermission "insertProvider"; }; diff --git a/test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpFixtureWithEC2.java b/test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpFixtureWithEC2.java index 9e02f9ee86744..6a901e686b8c5 100644 --- a/test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpFixtureWithEC2.java +++ b/test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpFixtureWithEC2.java @@ -92,7 +92,7 @@ protected String buildCredentialResponse(final String ec2AccessKey, final String + "\"AccessKeyId\": \"" + ec2AccessKey + "\"," + "\"Expiration\": \"" + ZonedDateTime.now().plusDays(1L).format(DateTimeFormatter.ISO_DATE_TIME) + "\"," + "\"RoleArn\": \"arn\"," - + "\"SecretAccessKey\": \"secret\"," + + "\"SecretAccessKey\": \"secret_key\"," + "\"Token\": \"" + ec2SessionToken + "\"" + "}"; } diff --git a/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java b/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java index 76c7ce0628aac..1a5f18e8c0920 100644 --- a/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java +++ b/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java @@ -37,6 +37,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.lucene.tests.util.LuceneTestCase; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.Booleans; import org.opensearch.common.SuppressForbidden; import org.opensearch.common.bootstrap.JarHell; @@ -73,8 +74,10 @@ import java.util.Optional; import java.util.Properties; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; +import static org.opensearch.bootstrap.SecurityProviderManager.SUN_JCE; import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAsBoolean; /** @@ -125,6 +128,20 @@ public class BootstrapForTesting { // Log ifconfig output before SecurityManager is installed IfConfig.logIfNecessary(); + SecureRandomInitializer.init(); + + var sunJceProvider = java.security.Security.getProvider(SUN_JCE); + if (sunJceProvider != null) { + sunJceInsertFunction = () -> java.security.Security.insertProviderAt( + sunJceProvider, + SecurityProviderManager.getPosition(SUN_JCE) + ); + + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + SecurityProviderManager.excludeSunJCE(); + } + } + // install security manager if requested if (systemPropertyAsBoolean("tests.security.manager", true)) { try { @@ -202,6 +219,8 @@ public boolean implies(ProtectionDomain domain, Permission permission) { } } + static Supplier sunJceInsertFunction; + /** Add the codebase url of the given classname to the codebases map, if the class exists. */ private static void addClassCodebase(Map codebases, String name, String classname) { try { diff --git a/test/framework/src/main/java/org/opensearch/test/KeyStoreUtils.java b/test/framework/src/main/java/org/opensearch/test/KeyStoreUtils.java index c90b2b872f8ba..d30528f9da132 100644 --- a/test/framework/src/main/java/org/opensearch/test/KeyStoreUtils.java +++ b/test/framework/src/main/java/org/opensearch/test/KeyStoreUtils.java @@ -12,6 +12,8 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500PrivateCredential; @@ -29,7 +31,7 @@ public class KeyStoreUtils { public static KeyStore createServerKeyStore() throws Exception { var serverCred = createCredential(); - var keyStore = KeyStore.getInstance("JKS"); + var keyStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); keyStore.load(null, null); keyStore.setKeyEntry( serverCred.getAlias(), diff --git a/test/framework/src/main/java/org/opensearch/test/OpenSearchIntegTestCase.java b/test/framework/src/main/java/org/opensearch/test/OpenSearchIntegTestCase.java index e69b5984bce8d..e3995f5071b3b 100644 --- a/test/framework/src/main/java/org/opensearch/test/OpenSearchIntegTestCase.java +++ b/test/framework/src/main/java/org/opensearch/test/OpenSearchIntegTestCase.java @@ -2399,10 +2399,6 @@ public static String resolveCustomDataPath(String index) { return getIndexResponse.getSettings().get(index).get(IndexMetadata.SETTING_DATA_PATH); } - public static boolean inFipsJvm() { - return Boolean.parseBoolean(System.getProperty(FIPS_SYSPROP)); - } - /** * On Debian 8 the "memory" subsystem is not mounted by default * when cgroups are enabled, and this confuses many versions of diff --git a/test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java b/test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java index 0bd5d8afda91e..5564f9dfedf81 100644 --- a/test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java +++ b/test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java @@ -61,6 +61,7 @@ import org.apache.lucene.tests.util.TestRuleMarkFailure; import org.apache.lucene.tests.util.TestUtil; import org.apache.lucene.tests.util.TimeUnits; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.Version; import org.opensearch.bootstrap.BootstrapForTesting; import org.opensearch.cluster.ClusterModule; @@ -253,8 +254,6 @@ public void tearDown() throws Exception { public static final String DEFAULT_TEST_WORKER_ID = "--not-gradle--"; - public static final String FIPS_SYSPROP = "tests.fips.enabled"; - static { TEST_WORKER_VM_ID = System.getProperty(TEST_WORKER_SYS_PROPERTY, DEFAULT_TEST_WORKER_ID); setTestSysProps(); @@ -1758,7 +1757,7 @@ private static boolean isUnusableLocale() { } public static boolean inFipsJvm() { - return Boolean.parseBoolean(System.getProperty(FIPS_SYSPROP)); + return CryptoServicesRegistrar.isInApprovedOnlyMode(); } /** diff --git a/test/framework/src/main/java/org/opensearch/test/junit/listeners/ReproduceInfoPrinter.java b/test/framework/src/main/java/org/opensearch/test/junit/listeners/ReproduceInfoPrinter.java index e2d59773a76cb..42ae6a57b829a 100644 --- a/test/framework/src/main/java/org/opensearch/test/junit/listeners/ReproduceInfoPrinter.java +++ b/test/framework/src/main/java/org/opensearch/test/junit/listeners/ReproduceInfoPrinter.java @@ -193,7 +193,6 @@ private ReproduceErrorMessageBuilder appendESProperties() { appendOpt("tests.locale", Locale.getDefault().toLanguageTag()); appendOpt("tests.timezone", TimeZone.getDefault().getID()); appendOpt("runtime.java", Integer.toString(Runtime.version().version().get(0))); - appendOpt(OpenSearchTestCase.FIPS_SYSPROP, System.getProperty(OpenSearchTestCase.FIPS_SYSPROP)); return this; } diff --git a/test/framework/src/main/java/org/opensearch/test/rest/OpenSearchRestTestCase.java b/test/framework/src/main/java/org/opensearch/test/rest/OpenSearchRestTestCase.java index 8c612d258f183..32af1e39628e2 100644 --- a/test/framework/src/main/java/org/opensearch/test/rest/OpenSearchRestTestCase.java +++ b/test/framework/src/main/java/org/opensearch/test/rest/OpenSearchRestTestCase.java @@ -62,6 +62,7 @@ import org.opensearch.common.SetOnce; import org.opensearch.common.io.PathUtils; import org.opensearch.common.settings.Settings; +import org.opensearch.common.ssl.KeyStoreFactory; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.util.io.IOUtils; @@ -841,8 +842,7 @@ protected static void configureClient(RestClientBuilder builder, Settings settin throw new IllegalStateException(TRUSTSTORE_PATH + " is set but points to a non-existing file"); } try { - final String keyStoreType = keystorePath.endsWith(".p12") ? "PKCS12" : "jks"; - KeyStore keyStore = KeyStore.getInstance(keyStoreType); + KeyStore keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(keystorePath); try (InputStream is = Files.newInputStream(path)) { keyStore.load(is, keystorePass.toCharArray()); }