From 128fb795277569ed070f3fe3bc9c0662578b3776 Mon Sep 17 00:00:00 2001 From: Iwan Date: Mon, 4 Mar 2024 12:36:07 +0100 Subject: [PATCH 01/21] Draft to allow run in FIPS compliace mode Signed-off-by: Iwan Igonin # Conflicts: # server/build.gradle # Conflicts: # server/build.gradle # Conflicts: # server/build.gradle --- build.gradle | 1 - buildSrc/build.gradle | 2 + .../opensearch/gradle/info/BuildParams.java | 1 + .../gradle/testclusters/OpenSearchNode.java | 1 + buildSrc/src/main/resources/cacerts.bcfks | Bin 103905 -> 0 bytes .../main/resources/fips_java_bcjsse_11.policy | 29 - .../main/resources/fips_java_bcjsse_8.policy | 34 - .../resources/fips_java_bcjsse_8.security | 134 ---- .../main/resources/fips_java_sunjsse.policy | 29 - .../main/resources/fips_java_sunjsse.security | 134 ---- .../src/config/fips_java.security | 10 +- .../tools/launchers/JvmOptionsParser.java | 2 +- .../tools/launchers/SystemJvmOptions.java | 9 +- .../licenses/bouncycastle-LICENSE.txt | 25 +- gradle/fips.gradle | 96 --- libs/ssl-config/build.gradle | 8 + .../licenses/bc-fips-1.0.2.5.jar.sha1 | 1 + .../licenses/bcpkix-fips-1.0.7.jar.sha1 | 1 + .../common/ssl/DefaultJdkTrustConfig.java | 22 +- .../opensearch/common/ssl/KeyStoreUtil.java | 2 + .../opensearch/common/ssl/PemKeyConfig.java | 11 +- .../org/opensearch/common/ssl/PemUtils.java | 653 ++---------------- .../common/ssl/SslConfiguration.java | 9 +- plugins/identity-shiro/build.gradle | 3 +- .../shiro/realm/BCryptPasswordMatcher.java | 41 +- plugins/ingest-attachment/build.gradle | 3 - .../licenses/bcmail-jdk18on-1.78.jar.sha1 | 1 - .../licenses/bcmail-jdk18on-LICENSE.txt | 23 - .../licenses/bcmail-jdk18on-NOTICE.txt | 0 .../licenses/bcpkix-jdk18on-1.78.jar.sha1 | 1 - .../licenses/bcpkix-jdk18on-LICENSE.txt | 23 - .../licenses/bcpkix-jdk18on-NOTICE.txt | 0 .../licenses/bcprov-jdk18on-1.78.jar.sha1 | 1 - .../licenses/bcprov-jdk18on-LICENSE.txt | 22 - .../licenses/bcprov-jdk18on-NOTICE.txt | 0 server/build.gradle | 5 + .../org/opensearch/bootstrap/Bootstrap.java | 13 + .../common/settings/ClusterSettings.java | 1 + .../common/settings/FipsSettings.java | 23 + .../org/opensearch/bootstrap/security.policy | 22 + 40 files changed, 252 insertions(+), 1144 deletions(-) delete mode 100644 buildSrc/src/main/resources/cacerts.bcfks delete mode 100644 buildSrc/src/main/resources/fips_java_bcjsse_11.policy delete mode 100644 buildSrc/src/main/resources/fips_java_bcjsse_8.policy delete mode 100644 buildSrc/src/main/resources/fips_java_bcjsse_8.security delete mode 100644 buildSrc/src/main/resources/fips_java_sunjsse.policy delete mode 100644 buildSrc/src/main/resources/fips_java_sunjsse.security rename buildSrc/src/main/resources/fips_java_bcjsse_11.security => distribution/src/config/fips_java.security (84%) delete mode 100644 gradle/fips.gradle create mode 100644 libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 create mode 100644 libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 delete mode 100644 plugins/ingest-attachment/licenses/bcmail-jdk18on-1.78.jar.sha1 delete mode 100644 plugins/ingest-attachment/licenses/bcmail-jdk18on-LICENSE.txt delete mode 100644 plugins/ingest-attachment/licenses/bcmail-jdk18on-NOTICE.txt delete mode 100644 plugins/ingest-attachment/licenses/bcpkix-jdk18on-1.78.jar.sha1 delete mode 100644 plugins/ingest-attachment/licenses/bcpkix-jdk18on-LICENSE.txt delete mode 100644 plugins/ingest-attachment/licenses/bcpkix-jdk18on-NOTICE.txt delete mode 100644 plugins/ingest-attachment/licenses/bcprov-jdk18on-1.78.jar.sha1 delete mode 100644 plugins/ingest-attachment/licenses/bcprov-jdk18on-LICENSE.txt delete mode 100644 plugins/ingest-attachment/licenses/bcprov-jdk18on-NOTICE.txt create mode 100644 server/src/main/java/org/opensearch/common/settings/FipsSettings.java diff --git a/build.gradle b/build.gradle index 679f7b9299248..80b60306a0917 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,6 @@ apply from: 'gradle/ide.gradle' apply from: 'gradle/forbidden-dependencies.gradle' apply from: 'gradle/formatting.gradle' apply from: 'gradle/local-distribution.gradle' -apply from: 'gradle/fips.gradle' apply from: 'gradle/run.gradle' apply from: 'gradle/missing-javadoc.gradle' apply from: 'gradle/code-coverage.gradle' diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 4f3c5de49dbc6..c227d890984f7 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -123,6 +123,8 @@ dependencies { api 'org.jruby.joni:joni:2.2.1' api "com.fasterxml.jackson.core:jackson-databind:${props.getProperty('jackson_databind')}" api "org.ajoberstar.grgit:grgit-core:5.2.1" + api "org.bouncycastle:bc-fips:1.0.2.5" + testFixturesApi "junit:junit:${props.getProperty('junit')}" testFixturesApi "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${props.getProperty('randomizedrunner')}" diff --git a/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java b/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java index a9ccb75569002..bbd06c84b30c9 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java @@ -52,6 +52,7 @@ public class BuildParams { private static JavaVersion gradleJavaVersion; private static JavaVersion runtimeJavaVersion; private static String runtimeJavaDetails; + @Deprecated private static Boolean inFipsJvm; private static String gitRevision; private static String gitOrigin; diff --git a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java index aaa2daef2a158..974ef2c89efae 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java @@ -1187,6 +1187,7 @@ private void createConfiguration() { baseConfig.put("indices.breaker.total.use_real_memory", "false"); // Don't wait for state, just start up quickly. This will also allow new and old nodes in the BWC case to become the master baseConfig.put("discovery.initial_state_timeout", "0s"); + baseConfig.put("fips.approved", "true"); HashSet overriden = new HashSet<>(baseConfig.keySet()); overriden.retainAll(settings.keySet()); diff --git a/buildSrc/src/main/resources/cacerts.bcfks b/buildSrc/src/main/resources/cacerts.bcfks deleted file mode 100644 index 9c3863eda60c51ca08b3a5eb73434b1af6508b5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103905 zcmV(*K;FMFg8`M?FoOY=9x#EF1_>&LNQU&LNQU&R|G&g z$@QPqz+fw9ASDQun{EE9+gFB9)}eY8-=Fuj-g2w67eG4I6yGBP0t5g80U$681_&yKNQUk1cL#Tf|(M)aDRZga$c>|CtbEA0ge__^=a&~8~X59dK+^G^2&kP zELWdfKCTvkNsy!cdGP%jcIt@eli)x=z^9{Ir{oQs!Z_@3%0%PiTwIfZTtYmZ)y4%%IeHodijv``GTH~qj^*sru zKi>8)GZ>K?u2dCbLgcJQViiyW8ooph;^5mdXoS9n56JqjD;uWK$-n=kr_R5Z8Rdd_aER}l9C#U=SgL`j0p z8*7ZTqVBDI8ep{H@}QsXXKey(KoOjjr`7Xo7PuZExoKQ`0&OV~*2Tr8dl4*BUqxUSJ*z#Y~XR(>58(Pwlk>l

x#1KDo;&5Kbu!>#` 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

T+*0Qk9FXWS4Lp5$ExwhEHB&l-e1 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 jvmOptions(final Path config, final String opensearchJavaOp final List substitutedJvmOptions = substitutePlaceholders(jvmOptions, Collections.unmodifiableMap(substitutions)); final List ergonomicJvmOptions = JvmErgonomics.choose(substitutedJvmOptions); - final List systemJvmOptions = SystemJvmOptions.systemJvmOptions(); + final List systemJvmOptions = SystemJvmOptions.systemJvmOptions(config); final List finalJvmOptions = new ArrayList<>( systemJvmOptions.size() + substitutedJvmOptions.size() + ergonomicJvmOptions.size() ); diff --git a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java index af7138569972a..5098d0e343f5f 100644 --- a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java +++ b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java @@ -32,6 +32,7 @@ package org.opensearch.tools.launchers; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -39,7 +40,7 @@ final class SystemJvmOptions { - static List systemJvmOptions() { + static List systemJvmOptions(final Path config) { return Collections.unmodifiableList( Arrays.asList( /* @@ -79,11 +80,17 @@ static List systemJvmOptions() { "-Dlog4j2.disable.jmx=true", // security manager allowSecurityManagerOption(), + loadJavaSecurityProperties(config), javaLocaleProviders() ) ).stream().filter(e -> e.isEmpty() == false).collect(Collectors.toList()); } + private static String loadJavaSecurityProperties(final Path config) { + var securityFile = config.resolve("fips_java.security").toFile(); + return "-Djava.security.properties=" + securityFile.getAbsolutePath(); + } + private static String allowSecurityManagerOption() { if (Runtime.version().feature() > 17) { return "-Djava.security.manager=allow"; diff --git a/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt b/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt index 1bd35a7a35c21..5c7c14696849d 100644 --- a/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt +++ b/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt @@ -1,17 +1,14 @@ -Copyright (c) 2000-2015 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2000 - 2023 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) -Permission is hereby granted, free of charge, to any person obtaining a copy of this software -and associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/gradle/fips.gradle b/gradle/fips.gradle deleted file mode 100644 index 1ce2cb89176f6..0000000000000 --- a/gradle/fips.gradle +++ /dev/null @@ -1,96 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -import org.opensearch.gradle.ExportOpenSearchBuildResourcesTask -import org.opensearch.gradle.info.BuildParams -import org.opensearch.gradle.testclusters.OpenSearchCluster - -// Common config when running with a FIPS-140 runtime JVM -if (BuildParams.inFipsJvm) { - - allprojects { - File fipsResourcesDir = new File(project.buildDir, 'fips-resources') - boolean java8 = BuildParams.runtimeJavaVersion == JavaVersion.VERSION_1_8 - boolean zulu8 = java8 && BuildParams.runtimeJavaDetails.contains("Zulu") - File fipsSecurity; - File fipsPolicy; - if (java8) { - if (zulu8) { - //Azul brings many changes from JDK 11 to their Zulu8 so we can only use BCJSSE - fipsSecurity = new File(fipsResourcesDir, "fips_java_bcjsse_8.security") - fipsPolicy = new File(fipsResourcesDir, "fips_java_bcjsse_8.policy") - } else { - fipsSecurity = new File(fipsResourcesDir, "fips_java_sunjsse.security") - fipsPolicy = new File(fipsResourcesDir, "fips_java_sunjsse.policy") - } - } else { - fipsSecurity = new File(fipsResourcesDir, "fips_java_bcjsse_11.security") - fipsPolicy = new File(fipsResourcesDir, "fips_java_bcjsse_11.policy") - } - File fipsTrustStore = new File(fipsResourcesDir, 'cacerts.bcfks') - def bcFips = dependencies.create('org.bouncycastle:bc-fips:1.0.2.1') - def bcTlsFips = dependencies.create('org.bouncycastle:bctls-fips:1.0.12.2') - - pluginManager.withPlugin('java') { - TaskProvider fipsResourcesTask = project.tasks.register('fipsResources', ExportOpenSearchBuildResourcesTask) - fipsResourcesTask.configure { - outputDir = fipsResourcesDir - copy fipsSecurity.name - copy fipsPolicy.name - copy 'cacerts.bcfks' - } - - project.afterEvaluate { - def extraFipsJars = configurations.detachedConfiguration(bcFips, bcTlsFips) - // ensure that bouncycastle is on classpath for the all of test types, must happen in evaluateAfter since the rest tests explicitly - // set the class path to help maintain pure black box testing, and here we are adding to that classpath - tasks.withType(Test).configureEach { Test test -> - test.setClasspath(test.getClasspath().plus(extraFipsJars)) - } - } - - pluginManager.withPlugin("opensearch.testclusters") { - afterEvaluate { - // This afterEvaluate hooks is required to avoid deprecated configuration resolution - // This configuration can be removed once system modules are available - def extraFipsJars = configurations.detachedConfiguration(bcFips, bcTlsFips) - testClusters.all { - extraFipsJars.files.each { - extraJarFile it - } - } - } - testClusters.all { - extraConfigFile "fips_java.security", fipsSecurity - extraConfigFile "fips_java.policy", fipsPolicy - extraConfigFile "cacerts.bcfks", fipsTrustStore - systemProperty 'java.security.properties', '=${OPENSEARCH_PATH_CONF}/fips_java.security' - systemProperty 'java.security.policy', '=${OPENSEARCH_PATH_CONF}/fips_java.policy' - systemProperty 'javax.net.ssl.trustStore', '${OPENSEARCH_PATH_CONF}/cacerts.bcfks' - systemProperty 'javax.net.ssl.trustStorePassword', 'password' - systemProperty 'javax.net.ssl.keyStorePassword', 'password' - systemProperty 'javax.net.ssl.keyStoreType', 'BCFKS' - } - } - project.tasks.withType(Test).configureEach { Test task -> - task.dependsOn('fipsResources') - task.systemProperty('javax.net.ssl.trustStorePassword', 'password') - task.systemProperty('javax.net.ssl.keyStorePassword', 'password') - task.systemProperty('javax.net.ssl.trustStoreType', 'BCFKS') - // Using the key==value format to override default JVM security settings and policy - // see also: https://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.html - task.systemProperty('java.security.properties', String.format(Locale.ROOT, "=%s", fipsSecurity)) - task.systemProperty('java.security.policy', String.format(Locale.ROOT, "=%s", fipsPolicy)) - task.systemProperty('javax.net.ssl.trustStore', fipsTrustStore) - } - } - } -} diff --git a/libs/ssl-config/build.gradle b/libs/ssl-config/build.gradle index 3226ec12ff6f7..889c278ffbfcb 100644 --- a/libs/ssl-config/build.gradle +++ b/libs/ssl-config/build.gradle @@ -35,6 +35,10 @@ apply plugin: "opensearch.publish" dependencies { api project(':libs:opensearch-common') + // bouncyCastle + implementation 'org.bouncycastle:bcpkix-fips:1.0.7' + compileOnly 'org.bouncycastle:bc-fips:1.0.2.5' + testImplementation(project(":test:framework")) { exclude group: 'org.opensearch', module: 'opensearch-ssl-config' } @@ -49,6 +53,10 @@ tasks.named('forbiddenApisMain').configure { replaceSignatureFiles 'jdk-signatures' } +tasks.named("dependencyLicenses").configure { + mapping from: /bc.*/, to: 'bouncycastle' +} + forbiddenPatterns { exclude '**/*.key' exclude '**/*.pem' diff --git a/libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 b/libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 new file mode 100644 index 0000000000000..cc29a787cee56 --- /dev/null +++ b/libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 @@ -0,0 +1 @@ +704e65f7e4fe679e5ab2aa8a840f27f8ced4c522 diff --git a/libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 b/libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 new file mode 100644 index 0000000000000..57cf4cfe10b5f --- /dev/null +++ b/libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 @@ -0,0 +1 @@ +fe07959721cfa2156be9722ba20fdfee2b5441b0 diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java index 859b74b200dc6..6d5b1b02c67ca 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java @@ -65,7 +65,7 @@ final class DefaultJdkTrustConfig implements SslTrustConfig { * Create a trust config that uses supplied {@link BiFunction} to determine the TrustStore type, and the relevant password. */ DefaultJdkTrustConfig(BiFunction systemProperties) { - this(systemProperties, isPkcs11Truststore(systemProperties) ? getSystemTrustStorePassword(systemProperties) : null); + this(systemProperties, getSystemTrustStorePassword(systemProperties)); } /** @@ -93,11 +93,16 @@ public X509ExtendedTrustManager createTrustManager() { * @return the KeyStore used as truststore for PKCS#11 initialized with the password, null otherwise */ private KeyStore getSystemTrustStore() { - if (isPkcs11Truststore(systemProperties) && trustStorePassword != null) { + if (trustStorePassword != null) { try { - KeyStore keyStore = KeyStore.getInstance("PKCS11"); - keyStore.load(null, trustStorePassword); - return keyStore; + if (isBcfksTruststore(systemProperties)) { + var path = Path.of(System.getProperty("javax.net.ssl.trustStore", "")); + KeyStoreUtil.readKeyStore(path, "BCFKS", trustStorePassword); + } else if (isPkcs11Truststore(systemProperties)) { + KeyStore keyStore = KeyStore.getInstance("PKCS11"); + keyStore.load(null, trustStorePassword); + return keyStore; + } } catch (GeneralSecurityException | IOException e) { throw new SslConfigException("failed to load the system PKCS#11 truststore", e); } @@ -105,12 +110,17 @@ private KeyStore getSystemTrustStore() { return null; } + private static boolean isBcfksTruststore(BiFunction systemProperties) { + return systemProperties.apply("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("BCFKS"); + } + private static boolean isPkcs11Truststore(BiFunction systemProperties) { return systemProperties.apply("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11"); } private static char[] getSystemTrustStorePassword(BiFunction systemProperties) { - return systemProperties.apply("javax.net.ssl.trustStorePassword", "").toCharArray(); + var password = systemProperties.apply("javax.net.ssl.trustStorePassword", ""); + return password.isEmpty() ? null : password.toCharArray(); } @Override diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java index b6b6cdd90af14..3fca84e303e27 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java @@ -71,6 +71,8 @@ static String inferKeyStoreType(Path path) { String name = path == null ? "" : path.toString().toLowerCase(Locale.ROOT); if (name.endsWith(".p12") || name.endsWith(".pfx") || name.endsWith(".pkcs12")) { return "PKCS12"; + } else if (name.endsWith(".bks")) { + return "BCFKS"; } else { return "jks"; } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java index bfc29a5801b11..1865b13d644aa 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java @@ -32,6 +32,8 @@ package org.opensearch.common.ssl; +import org.bouncycastle.pkcs.PKCSException; + import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; @@ -82,7 +84,12 @@ public X509ExtendedKeyManager createKeyManager() { private PrivateKey getPrivateKey() { try { - final PrivateKey privateKey = PemUtils.readPrivateKey(key, () -> keyPassword); + final PrivateKey privateKey = PemUtils.readPrivateKey(key, () -> { + if (keyPassword.length == 0) { + throw new SslConfigException("cannot read encrypted key [" + key.toAbsolutePath() + "] without a password"); + } + return keyPassword; + }); if (privateKey == null) { throw new SslConfigException("could not load ssl private key file [" + key + "]"); } @@ -91,7 +98,7 @@ private PrivateKey getPrivateKey() { throw new SslConfigException("the configured ssl private key file [" + key.toAbsolutePath() + "] does not exist", e); } catch (IOException e) { throw new SslConfigException("the configured ssl private key file [" + key.toAbsolutePath() + "] cannot be read", e); - } catch (GeneralSecurityException e) { + } catch (PKCSException e) { throw new SslConfigException("cannot load ssl private key file [" + key.toAbsolutePath() + "]", e); } } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java index 8a3730ee554f9..df842650d65ca 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java @@ -32,68 +32,32 @@ package org.opensearch.common.ssl; -import org.opensearch.common.CharArrays; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.pkcs.PKCSException; +import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder; -import javax.crypto.Cipher; -import javax.crypto.EncryptedPrivateKeyInfo; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; +import java.security.PrivateKey; -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; +import java.io.*; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.MessageDigest; -import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; -import java.security.interfaces.ECKey; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.DSAPrivateKeySpec; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.RSAPrivateCrtKeySpec; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Supplier; final class PemUtils { - private static final String PKCS1_HEADER = "-----BEGIN RSA PRIVATE KEY-----"; - private static final String PKCS1_FOOTER = "-----END RSA PRIVATE KEY-----"; - private static final String OPENSSL_DSA_HEADER = "-----BEGIN DSA PRIVATE KEY-----"; - private static final String OPENSSL_DSA_FOOTER = "-----END DSA PRIVATE KEY-----"; - private static final String OPENSSL_DSA_PARAMS_HEADER = "-----BEGIN DSA PARAMETERS-----"; - private static final String OPENSSL_DSA_PARAMS_FOOTER = "-----END DSA PARAMETERS-----"; - private static final String PKCS8_HEADER = "-----BEGIN PRIVATE KEY-----"; - private static final String PKCS8_FOOTER = "-----END PRIVATE KEY-----"; - private static final String PKCS8_ENCRYPTED_HEADER = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; - private static final String PKCS8_ENCRYPTED_FOOTER = "-----END ENCRYPTED PRIVATE KEY-----"; - private static final String OPENSSL_EC_HEADER = "-----BEGIN EC PRIVATE KEY-----"; - private static final String OPENSSL_EC_FOOTER = "-----END EC PRIVATE KEY-----"; - private static final String OPENSSL_EC_PARAMS_HEADER = "-----BEGIN EC PARAMETERS-----"; - private static final String OPENSSL_EC_PARAMS_FOOTER = "-----END EC PARAMETERS-----"; - private static final String HEADER = "-----BEGIN"; - private PemUtils() { throw new IllegalStateException("Utility class should not be instantiated"); } @@ -106,555 +70,88 @@ private PemUtils() { * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key * @return a private key from the contents of the file */ - public static PrivateKey readPrivateKey(Path keyPath, Supplier passwordSupplier) throws IOException, GeneralSecurityException { - try (BufferedReader bReader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) { - String line = bReader.readLine(); - while (null != line && line.startsWith(HEADER) == false) { - line = bReader.readLine(); - } - if (null == line) { - throw new SslConfigException("Error parsing Private Key [" + keyPath.toAbsolutePath() + "], file is empty"); - } - if (PKCS8_ENCRYPTED_HEADER.equals(line.trim())) { - char[] password = passwordSupplier.get(); - if (password == null) { - throw new SslConfigException("cannot read encrypted key [" + keyPath.toAbsolutePath() + "] without a password"); - } - return parsePKCS8Encrypted(bReader, password); - } else if (PKCS8_HEADER.equals(line.trim())) { - return parsePKCS8(bReader); - } else if (PKCS1_HEADER.equals(line.trim())) { - return parsePKCS1Rsa(bReader, passwordSupplier); - } else if (OPENSSL_DSA_HEADER.equals(line.trim())) { - return parseOpenSslDsa(bReader, passwordSupplier); - } else if (OPENSSL_DSA_PARAMS_HEADER.equals(line.trim())) { - return parseOpenSslDsa(removeDsaHeaders(bReader), passwordSupplier); - } else if (OPENSSL_EC_HEADER.equals(line.trim())) { - return parseOpenSslEC(bReader, passwordSupplier); - } else if (OPENSSL_EC_PARAMS_HEADER.equals(line.trim())) { - return parseOpenSslEC(removeECHeaders(bReader), passwordSupplier); - } else { - throw new SslConfigException( - "error parsing Private Key [" + keyPath.toAbsolutePath() + "], file does not contain a supported key format" - ); - } - } catch (FileNotFoundException | NoSuchFileException e) { - throw new SslConfigException("private key file [" + keyPath.toAbsolutePath() + "] does not exist", e); - } catch (IOException | GeneralSecurityException e) { - throw new SslConfigException("private key file [" + keyPath.toAbsolutePath() + "] cannot be parsed", e); - } - } - - /** - * Removes the EC Headers that OpenSSL adds to EC private keys as the information in them - * is redundant - * - * @throws IOException if the EC Parameter footer is missing - */ - private static BufferedReader removeECHeaders(BufferedReader bReader) throws IOException { - String line = bReader.readLine(); - while (line != null) { - if (OPENSSL_EC_PARAMS_FOOTER.equals(line.trim())) { - break; - } - line = bReader.readLine(); - } - if (null == line || OPENSSL_EC_PARAMS_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, EC Parameters footer is missing"); - } - // Verify that the key starts with the correct header before passing it to parseOpenSslEC - if (OPENSSL_EC_HEADER.equals(bReader.readLine()) == false) { - throw new IOException("Malformed PEM file, EC Key header is missing"); - } - return bReader; - } - - /** - * Removes the DSA Params Headers that OpenSSL adds to DSA private keys as the information in them - * is redundant - * - * @throws IOException if the EC Parameter footer is missing - */ - private static BufferedReader removeDsaHeaders(BufferedReader bReader) throws IOException { - String line = bReader.readLine(); - while (line != null) { - if (OPENSSL_DSA_PARAMS_FOOTER.equals(line.trim())) { - break; - } - line = bReader.readLine(); - } - if (null == line || OPENSSL_DSA_PARAMS_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, DSA Parameters footer is missing"); - } - // Verify that the key starts with the correct header before passing it to parseOpenSslDsa - if (OPENSSL_DSA_HEADER.equals(bReader.readLine()) == false) { - throw new IOException("Malformed PEM file, DSA Key header is missing"); - } - return bReader; - } - - /** - * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an plaintext private key encoded in - * PKCS#8 - * - * @param bReader the {@link BufferedReader} containing the key file contents - * @return {@link PrivateKey} - * @throws IOException if the file can't be read - * @throws GeneralSecurityException if the private key can't be generated from the {@link PKCS8EncodedKeySpec} - */ - private static PrivateKey parsePKCS8(BufferedReader bReader) throws IOException, GeneralSecurityException { - StringBuilder sb = new StringBuilder(); - String line = bReader.readLine(); - while (line != null) { - if (PKCS8_FOOTER.equals(line.trim())) { - break; - } - sb.append(line.trim()); - line = bReader.readLine(); - } - if (null == line || PKCS8_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); - } - byte[] keyBytes = Base64.getDecoder().decode(sb.toString()); - String keyAlgo = getKeyAlgorithmIdentifier(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance(keyAlgo); - return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); - } - - /** - * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an EC private key encoded in - * OpenSSL traditional format. - * - * @param bReader the {@link BufferedReader} containing the key file contents - * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key - * @return {@link PrivateKey} - * @throws IOException if the file can't be read - * @throws GeneralSecurityException if the private key can't be generated from the {@link ECPrivateKeySpec} - */ - private static PrivateKey parseOpenSslEC(BufferedReader bReader, Supplier passwordSupplier) throws IOException, - GeneralSecurityException { - StringBuilder sb = new StringBuilder(); - String line = bReader.readLine(); - Map pemHeaders = new HashMap<>(); - while (line != null) { - if (OPENSSL_EC_FOOTER.equals(line.trim())) { - break; - } - // Parse PEM headers according to https://www.ietf.org/rfc/rfc1421.txt - if (line.contains(":")) { - String[] header = line.split(":"); - pemHeaders.put(header[0].trim(), header[1].trim()); - } else { - sb.append(line.trim()); - } - line = bReader.readLine(); - } - if (null == line || OPENSSL_EC_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); - } - byte[] keyBytes = possiblyDecryptPKCS1Key(pemHeaders, sb.toString(), passwordSupplier); - KeyFactory keyFactory = KeyFactory.getInstance("EC"); - ECPrivateKeySpec ecSpec = parseEcDer(keyBytes); - return keyFactory.generatePrivate(ecSpec); + public static PrivateKey readPrivateKey(Path keyPath, Supplier passwordSupplier) throws IOException, PKCSException { + PrivateKeyInfo pki = loadPrivateKeyFromFile(keyPath, passwordSupplier); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + return converter.getPrivateKey(pki); } - /** - * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an RSA private key encoded in - * OpenSSL traditional format. - * - * @param bReader the {@link BufferedReader} containing the key file contents - * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key - * @return {@link PrivateKey} - * @throws IOException if the file can't be read - * @throws GeneralSecurityException if the private key can't be generated from the {@link RSAPrivateCrtKeySpec} - */ - private static PrivateKey parsePKCS1Rsa(BufferedReader bReader, Supplier passwordSupplier) throws IOException, - GeneralSecurityException { - StringBuilder sb = new StringBuilder(); - String line = bReader.readLine(); - Map pemHeaders = new HashMap<>(); - - while (line != null) { - if (PKCS1_FOOTER.equals(line.trim())) { - // Unencrypted - break; - } - // Parse PEM headers according to https://www.ietf.org/rfc/rfc1421.txt - if (line.contains(":")) { - String[] header = line.split(":"); - pemHeaders.put(header[0].trim(), header[1].trim()); - } else { - sb.append(line.trim()); + static List readCertificates(Collection certPaths) throws CertificateException, IOException { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + List certificates = new ArrayList<>(certPaths.size()); + for (Path path : certPaths) { + try (InputStream input = Files.newInputStream(path)) { + final Collection parsed = certFactory.generateCertificates(input); + if (parsed.isEmpty()) { + throw new SslConfigException("failed to parse any certificates from [" + path.toAbsolutePath() + "]"); + } + certificates.addAll(parsed); } - line = bReader.readLine(); } - if (null == line || PKCS1_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); - } - byte[] keyBytes = possiblyDecryptPKCS1Key(pemHeaders, sb.toString(), passwordSupplier); - RSAPrivateCrtKeySpec spec = parseRsaDer(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - return keyFactory.generatePrivate(spec); + return certificates; } /** - * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an DSA private key encoded in - * OpenSSL traditional format. + * Creates a {@link PrivateKey} from the private key, with or without encryption. + * When enforcing the approved-only mode in Java security settings, some functionalities might be restricted due to the limited + * set of allowed algorithms. One such restriction includes Password Based Key Derivation Functions (PBKDF) like those used by OpenSSL + * and PKCS#12 formats. Since these formats rely on PBKDF algorithms, they cannot operate correctly within the approved-only mode. + * Consequently, attempting to utilize them could result in a {@link java.security.NoSuchAlgorithmException}. * - * @param bReader the {@link BufferedReader} containing the key file contents - * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key + * @param passwordSupplier The password supplier for the encrypted (password protected) key * @return {@link PrivateKey} - * @throws IOException if the file can't be read - * @throws GeneralSecurityException if the private key can't be generated from the {@link DSAPrivateKeySpec} - */ - private static PrivateKey parseOpenSslDsa(BufferedReader bReader, Supplier passwordSupplier) throws IOException, - GeneralSecurityException { - StringBuilder sb = new StringBuilder(); - String line = bReader.readLine(); - Map pemHeaders = new HashMap<>(); - - while (line != null) { - if (OPENSSL_DSA_FOOTER.equals(line.trim())) { - // Unencrypted - break; - } - // Parse PEM headers according to https://www.ietf.org/rfc/rfc1421.txt - if (line.contains(":")) { - String[] header = line.split(":"); - pemHeaders.put(header[0].trim(), header[1].trim()); + * @throws IOException If the file can't be read + */ + private static PrivateKeyInfo loadPrivateKeyFromFile(Path keyPath, Supplier passwordSupplier) + throws IOException, PKCSException { + try (PEMParser pemParser = new PEMParser(new FileReader(keyPath.toFile()))) { + Object object = readObject(keyPath, pemParser); + + if (object instanceof PKCS8EncryptedPrivateKeyInfo) { // encrypted private key in pkcs8-format + var privateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) object; + var inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder() + .setProvider("BCFIPS") + .build(passwordSupplier.get()); + return privateKeyInfo.decryptPrivateKeyInfo(inputDecryptorProvider); + } else if (object instanceof PEMEncryptedKeyPair) { // encrypted private key + var encryptedKeyPair = (PEMEncryptedKeyPair) object; + var decryptorProvider = new JcePEMDecryptorProviderBuilder() + .setProvider("BCFIPS") + .build(passwordSupplier.get()); + var keyPair = encryptedKeyPair.decryptKeyPair(decryptorProvider); + return keyPair.getPrivateKeyInfo(); + } else if (object instanceof PEMKeyPair) { // unencrypted private key + return ((PEMKeyPair) object).getPrivateKeyInfo(); + } else if (object instanceof PrivateKeyInfo) { // unencrypted private key in pkcs8-format + return (PrivateKeyInfo) object; } else { - sb.append(line.trim()); + throw new SslConfigException(String.format( + "error parsing private key [%s], invalid encrypted private key class: [%s]", + keyPath.toAbsolutePath(), + object.getClass().getName() + )); } - line = bReader.readLine(); } - if (null == line || OPENSSL_DSA_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); - } - byte[] keyBytes = possiblyDecryptPKCS1Key(pemHeaders, sb.toString(), passwordSupplier); - DSAPrivateKeySpec spec = parseDsaDer(keyBytes); - KeyFactory keyFactory = KeyFactory.getInstance("DSA"); - return keyFactory.generatePrivate(spec); - } - - /** - * Creates a {@link PrivateKey} from the contents of {@code bReader} that contains an encrypted private key encoded in - * PKCS#8 - * - * @param bReader the {@link BufferedReader} containing the key file contents - * @param keyPassword The password for the encrypted (password protected) key - * @return {@link PrivateKey} - * @throws IOException if the file can't be read - * @throws GeneralSecurityException if the private key can't be generated from the {@link PKCS8EncodedKeySpec} - */ - private static PrivateKey parsePKCS8Encrypted(BufferedReader bReader, char[] keyPassword) throws IOException, GeneralSecurityException { - StringBuilder sb = new StringBuilder(); - String line = bReader.readLine(); - while (line != null) { - if (PKCS8_ENCRYPTED_FOOTER.equals(line.trim())) { - break; - } - sb.append(line.trim()); - line = bReader.readLine(); - } - if (null == line || PKCS8_ENCRYPTED_FOOTER.equals(line.trim()) == false) { - throw new IOException("Malformed PEM file, PEM footer is invalid or missing"); - } - byte[] keyBytes = Base64.getDecoder().decode(sb.toString()); - - EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(keyBytes); - SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); - SecretKey secretKey = secretKeyFactory.generateSecret(new PBEKeySpec(keyPassword)); - Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); - cipher.init(Cipher.DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters()); - PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher); - String keyAlgo = getKeyAlgorithmIdentifier(keySpec.getEncoded()); - KeyFactory keyFactory = KeyFactory.getInstance(keyAlgo); - return keyFactory.generatePrivate(keySpec); - } - - /** - * Decrypts the password protected contents using the algorithm and IV that is specified in the PEM Headers of the file - * - * @param pemHeaders The Proc-Type and DEK-Info PEM headers that have been extracted from the key file - * @param keyContents The key as a base64 encoded String - * @param passwordSupplier A password supplier for the encrypted (password protected) key - * @return the decrypted key bytes - * @throws GeneralSecurityException if the key can't be decrypted - * @throws IOException if the PEM headers are missing or malformed - */ - private static byte[] possiblyDecryptPKCS1Key(Map pemHeaders, String keyContents, Supplier passwordSupplier) - throws GeneralSecurityException, IOException { - byte[] keyBytes = Base64.getDecoder().decode(keyContents); - String procType = pemHeaders.get("Proc-Type"); - if ("4,ENCRYPTED".equals(procType)) { - // We only handle PEM encryption - String encryptionParameters = pemHeaders.get("DEK-Info"); - if (null == encryptionParameters) { - // malformed pem - throw new IOException("Malformed PEM File, DEK-Info header is missing"); - } - char[] password = passwordSupplier.get(); - if (password == null) { - throw new IOException("cannot read encrypted key without a password"); - } - Cipher cipher = getCipherFromParameters(encryptionParameters, password); - byte[] decryptedKeyBytes = cipher.doFinal(keyBytes); - return decryptedKeyBytes; - } - return keyBytes; - } - - /** - * Creates a {@link Cipher} from the contents of the DEK-Info header of a PEM file. RFC 1421 indicates that supported algorithms are - * defined in RFC 1423. RFC 1423 only defines DES-CBS and triple DES (EDE) in CBC mode. AES in CBC mode is also widely used though ( 3 - * different variants of 128, 192, 256 bit keys ) - * - * @param dekHeaderValue The value of the DEK-Info PEM header - * @param password The password with which the key is encrypted - * @return a cipher of the appropriate algorithm and parameters to be used for decryption - * @throws GeneralSecurityException if the algorithm is not available in the used security provider, or if the key is inappropriate - * for the cipher - * @throws IOException if the DEK-Info PEM header is invalid - */ - private static Cipher getCipherFromParameters(String dekHeaderValue, char[] password) throws GeneralSecurityException, IOException { - final String padding = "PKCS5Padding"; - final SecretKey encryptionKey; - final String[] valueTokens = dekHeaderValue.split(","); - if (valueTokens.length != 2) { - throw new IOException("Malformed PEM file, DEK-Info PEM header is invalid"); - } - final String algorithm = valueTokens[0]; - final String ivString = valueTokens[1]; - final byte[] iv; - try { - iv = hexStringToByteArray(ivString); - } catch (IllegalArgumentException e) { - throw new IOException("Malformed PEM file, DEK-Info IV is invalid", e); - } - if ("DES-CBC".equals(algorithm)) { - byte[] key = generateOpenSslKey(password, iv, 8); - encryptionKey = new SecretKeySpec(key, "DES"); - } else if ("DES-EDE3-CBC".equals(algorithm)) { - byte[] key = generateOpenSslKey(password, iv, 24); - encryptionKey = new SecretKeySpec(key, "DESede"); - } else if ("AES-128-CBC".equals(algorithm)) { - byte[] key = generateOpenSslKey(password, iv, 16); - encryptionKey = new SecretKeySpec(key, "AES"); - } else if ("AES-192-CBC".equals(algorithm)) { - byte[] key = generateOpenSslKey(password, iv, 24); - encryptionKey = new SecretKeySpec(key, "AES"); - } else if ("AES-256-CBC".equals(algorithm)) { - byte[] key = generateOpenSslKey(password, iv, 32); - encryptionKey = new SecretKeySpec(key, "AES"); - } else { - throw new GeneralSecurityException("Private Key encrypted with unsupported algorithm [" + algorithm + "]"); - } - String transformation = encryptionKey.getAlgorithm() + "/" + "CBC" + "/" + padding; - Cipher cipher = Cipher.getInstance(transformation); - cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv)); - return cipher; - } - - /** - * Performs key stretching in the same manner that OpenSSL does. This is basically a KDF - * that uses n rounds of salted MD5 (as many times as needed to get the necessary number of key bytes) - *

- * https://www.openssl.org/docs/man1.1.0/crypto/PEM_write_bio_PrivateKey_traditional.html - */ - private static byte[] generateOpenSslKey(char[] password, byte[] salt, int keyLength) { - byte[] passwordBytes = CharArrays.toUtf8Bytes(password); - MessageDigest md5 = SslUtil.messageDigest("md5"); - byte[] key = new byte[keyLength]; - int copied = 0; - int remaining; - while (copied < keyLength) { - remaining = keyLength - copied; - md5.update(passwordBytes, 0, passwordBytes.length); - md5.update(salt, 0, 8);// AES IV (salt) is longer but we only need 8 bytes - byte[] tempDigest = md5.digest(); - int bytesToCopy = (remaining > 16) ? 16 : remaining; // MD5 digests are 16 bytes - System.arraycopy(tempDigest, 0, key, copied, bytesToCopy); - copied += bytesToCopy; - if (remaining == 0) { - break; - } - md5.update(tempDigest, 0, 16); // use previous round digest as IV - } - Arrays.fill(passwordBytes, (byte) 0); - return key; - } - - /** - * Converts a hexadecimal string to a byte array - */ - private static byte[] hexStringToByteArray(String hexString) { - int len = hexString.length(); - if (len % 2 == 0) { - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - final int k = Character.digit(hexString.charAt(i), 16); - final int l = Character.digit(hexString.charAt(i + 1), 16); - if (k == -1 || l == -1) { - throw new IllegalStateException("String [" + hexString + "] is not hexadecimal"); - } - data[i / 2] = (byte) ((k << 4) + l); - } - return data; - } else { - throw new IllegalStateException( - "Hexadecimal string [" + hexString + "] has odd length and cannot be converted to a byte array" - ); - } - } - - /** - * Parses a DER encoded EC key to an {@link ECPrivateKeySpec} using a minimal {@link DerParser} - * - * @param keyBytes the private key raw bytes - * @return {@link ECPrivateKeySpec} - * @throws IOException if the DER encoded key can't be parsed - */ - private static ECPrivateKeySpec parseEcDer(byte[] keyBytes) throws IOException, GeneralSecurityException { - DerParser parser = new DerParser(keyBytes); - DerParser.Asn1Object sequence = parser.readAsn1Object(); - parser = sequence.getParser(); - parser.readAsn1Object().getInteger(); // version - String keyHex = parser.readAsn1Object().getString(); - BigInteger privateKeyInt = new BigInteger(keyHex, 16); - DerParser.Asn1Object choice = parser.readAsn1Object(); - parser = choice.getParser(); - String namedCurve = getEcCurveNameFromOid(parser.readAsn1Object().getOid()); - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); - AlgorithmParameterSpec algorithmParameterSpec = new ECGenParameterSpec(namedCurve); - keyPairGenerator.initialize(algorithmParameterSpec); - ECParameterSpec parameterSpec = ((ECKey) keyPairGenerator.generateKeyPair().getPrivate()).getParams(); - return new ECPrivateKeySpec(privateKeyInt, parameterSpec); - } - - /** - * Parses a DER encoded RSA key to a {@link RSAPrivateCrtKeySpec} using a minimal {@link DerParser} - * - * @param keyBytes the private key raw bytes - * @return {@link RSAPrivateCrtKeySpec} - * @throws IOException if the DER encoded key can't be parsed - */ - private static RSAPrivateCrtKeySpec parseRsaDer(byte[] keyBytes) throws IOException { - DerParser parser = new DerParser(keyBytes); - DerParser.Asn1Object sequence = parser.readAsn1Object(); - parser = sequence.getParser(); - parser.readAsn1Object().getInteger(); // (version) We don't need it but must read to get to modulus - BigInteger modulus = parser.readAsn1Object().getInteger(); - BigInteger publicExponent = parser.readAsn1Object().getInteger(); - BigInteger privateExponent = parser.readAsn1Object().getInteger(); - BigInteger prime1 = parser.readAsn1Object().getInteger(); - BigInteger prime2 = parser.readAsn1Object().getInteger(); - BigInteger exponent1 = parser.readAsn1Object().getInteger(); - BigInteger exponent2 = parser.readAsn1Object().getInteger(); - BigInteger coefficient = parser.readAsn1Object().getInteger(); - return new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, prime1, prime2, exponent1, exponent2, coefficient); - } - - /** - * Parses a DER encoded DSA key to a {@link DSAPrivateKeySpec} using a minimal {@link DerParser} - * - * @param keyBytes the private key raw bytes - * @return {@link DSAPrivateKeySpec} - * @throws IOException if the DER encoded key can't be parsed - */ - private static DSAPrivateKeySpec parseDsaDer(byte[] keyBytes) throws IOException { - DerParser parser = new DerParser(keyBytes); - DerParser.Asn1Object sequence = parser.readAsn1Object(); - parser = sequence.getParser(); - parser.readAsn1Object().getInteger(); // (version) We don't need it but must read to get to p - BigInteger p = parser.readAsn1Object().getInteger(); - BigInteger q = parser.readAsn1Object().getInteger(); - BigInteger g = parser.readAsn1Object().getInteger(); - parser.readAsn1Object().getInteger(); // we don't need x - BigInteger x = parser.readAsn1Object().getInteger(); - return new DSAPrivateKeySpec(x, p, q, g); } /** - * Parses a DER encoded private key and reads its algorithm identifier Object OID. + * Supports PEM files that includes parameters. * - * @param keyBytes the private key raw bytes - * @return A string identifier for the key algorithm (RSA, DSA, or EC) - * @throws GeneralSecurityException if the algorithm oid that is parsed from ASN.1 is unknown - * @throws IOException if the DER encoded key can't be parsed - */ - private static String getKeyAlgorithmIdentifier(byte[] keyBytes) throws IOException, GeneralSecurityException { - DerParser parser = new DerParser(keyBytes); - DerParser.Asn1Object sequence = parser.readAsn1Object(); - parser = sequence.getParser(); - parser.readAsn1Object().getInteger(); // version - DerParser.Asn1Object algSequence = parser.readAsn1Object(); - parser = algSequence.getParser(); - String oidString = parser.readAsn1Object().getOid(); - switch (oidString) { - case "1.2.840.10040.4.1": - return "DSA"; - case "1.2.840.113549.1.1.1": - return "RSA"; - case "1.2.840.10045.2.1": - return "EC"; - } - throw new GeneralSecurityException( - "Error parsing key algorithm identifier. Algorithm with OID [" + oidString + "] is not żsupported" - ); - } - - static List readCertificates(Collection certPaths) throws CertificateException, IOException { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - List certificates = new ArrayList<>(certPaths.size()); - for (Path path : certPaths) { - try (InputStream input = Files.newInputStream(path)) { - final Collection parsed = certFactory.generateCertificates(input); - if (parsed.isEmpty()) { - throw new SslConfigException("failed to parse any certificates from [" + path.toAbsolutePath() + "]"); + * @return high-level Object from the content + */ + private static Object readObject(Path keyPath, PEMParser pemParser) throws IOException { + while (pemParser.ready()) { + try { + var object = pemParser.readObject(); + if (object instanceof ASN1ObjectIdentifier) { // handles -----BEGIN EC PARAMETERS----- + continue; } - certificates.addAll(parsed); + return object; + } catch (IOException e) { // handles -----BEGIN DSA PARAMETERS----- + // ignore } } - return certificates; - } - - private static String getEcCurveNameFromOid(String oidString) throws GeneralSecurityException { - switch (oidString) { - // see https://tools.ietf.org/html/rfc5480#section-2.1.1.1 - case "1.2.840.10045.3.1": - return "secp192r1"; - case "1.3.132.0.1": - return "sect163k1"; - case "1.3.132.0.15": - return "sect163r2"; - case "1.3.132.0.33": - return "secp224r1"; - case "1.3.132.0.26": - return "sect233k1"; - case "1.3.132.0.27": - return "sect233r1"; - case "1.2.840.10045.3.1.7": - return "secp256r1"; - case "1.3.132.0.16": - return "sect283k1"; - case "1.3.132.0.17": - return "sect283r1"; - case "1.3.132.0.34": - return "secp384r1"; - case "1.3.132.0.36": - return "sect409k1"; - case "1.3.132.0.37": - return "sect409r1"; - case "1.3.132.0.35": - return "secp521r1"; - case "1.3.132.0.38": - return "sect571k1"; - case "1.3.132.0.39": - return "sect571r1"; - } - throw new GeneralSecurityException( - "Error parsing EC named curve identifier. Named curve with OID: " + oidString + " is not supported" - ); + throw new SslConfigException("Error parsing Private Key [" + keyPath.toAbsolutePath() + "], file is empty"); } } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java index 23acb0ff269e2..d6aa106039ce3 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java @@ -32,6 +32,8 @@ package org.opensearch.common.ssl; +import org.bouncycastle.crypto.CryptoServicesRegistrar; + import javax.net.ssl.SSLContext; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; @@ -155,7 +157,12 @@ public SSLContext createSslContext() { final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(); final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); try { - SSLContext sslContext = SSLContext.getInstance(contextProtocol()); + SSLContext sslContext; + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + sslContext = SSLContext.getInstance("TLS"); + } else { + sslContext = SSLContext.getInstance(contextProtocol()); + } sslContext.init(new X509ExtendedKeyManager[] { keyManager }, new X509ExtendedTrustManager[] { trustManager }, null); return sslContext; } catch (GeneralSecurityException e) { diff --git a/plugins/identity-shiro/build.gradle b/plugins/identity-shiro/build.gradle index 436a9b3e48128..4af2eef0d0840 100644 --- a/plugins/identity-shiro/build.gradle +++ b/plugins/identity-shiro/build.gradle @@ -28,7 +28,8 @@ dependencies { implementation 'org.passay:passay:1.6.3' - implementation "org.bouncycastle:bcprov-jdk18on:${versions.bouncycastle}" + // Bcrypt hash matching + implementation 'com.password4j:password4j:1.8.2' testImplementation project(path: ':modules:transport-netty4') // for http testImplementation "org.mockito:mockito-core:${versions.mockito}" diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java index f8113101deb70..9c900836d3a66 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java @@ -12,7 +12,16 @@ import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.CredentialsMatcher; -import org.bouncycastle.crypto.generators.OpenBSDBCrypt; +import com.password4j.BcryptFunction; +import com.password4j.Password; +import org.opensearch.SpecialPermission; + +import java.nio.CharBuffer; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; + +import static org.opensearch.core.common.Strings.isNullOrEmpty; /** * Password matcher for BCrypt @@ -28,7 +37,35 @@ public class BCryptPasswordMatcher implements CredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { final UsernamePasswordToken userToken = (UsernamePasswordToken) token; - return OpenBSDBCrypt.checkPassword((String) info.getCredentials(), userToken.getPassword()); + return check(userToken.getPassword(), (String) info.getCredentials()); + + } + + private boolean check(char[] password, String hash) { + if (password == null || password.length == 0) { + throw new IllegalStateException("Password cannot be empty or null"); + } + if (isNullOrEmpty(hash)) { + throw new IllegalStateException("Hash cannot be empty or null"); + } + CharBuffer passwordBuffer = CharBuffer.wrap(password); + try { + SecurityManager securityManager = System.getSecurityManager(); + if (securityManager != null) { + securityManager.checkPermission(new SpecialPermission()); + } + return AccessController.doPrivileged((PrivilegedAction) () -> Password.check(passwordBuffer, hash) + .with(BcryptFunction.getInstanceFromHash(hash))); + } finally { + cleanup(passwordBuffer); + } + } + + private void cleanup(CharBuffer password) { + password.clear(); + char[] passwordOverwrite = new char[password.capacity()]; + Arrays.fill(passwordOverwrite, '\0'); + password.put(passwordOverwrite); } } diff --git a/plugins/ingest-attachment/build.gradle b/plugins/ingest-attachment/build.gradle index e0ad602266602..47a15b75234dc 100644 --- a/plugins/ingest-attachment/build.gradle +++ b/plugins/ingest-attachment/build.gradle @@ -81,9 +81,6 @@ dependencies { api "org.apache.pdfbox:fontbox:${versions.pdfbox}" api "org.apache.pdfbox:jempbox:1.8.17" api "commons-logging:commons-logging:${versions.commonslogging}" - api "org.bouncycastle:bcmail-jdk18on:${versions.bouncycastle}" - api "org.bouncycastle:bcprov-jdk18on:${versions.bouncycastle}" - api "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}" // OpenOffice api "org.apache.poi:poi-ooxml:${versions.poi}" api "org.apache.poi:poi:${versions.poi}" diff --git a/plugins/ingest-attachment/licenses/bcmail-jdk18on-1.78.jar.sha1 b/plugins/ingest-attachment/licenses/bcmail-jdk18on-1.78.jar.sha1 deleted file mode 100644 index eb7e650306f73..0000000000000 --- a/plugins/ingest-attachment/licenses/bcmail-jdk18on-1.78.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -d26f5514b8c54f2878f8d49e0bc8e2acaab3c8bd \ No newline at end of file diff --git a/plugins/ingest-attachment/licenses/bcmail-jdk18on-LICENSE.txt b/plugins/ingest-attachment/licenses/bcmail-jdk18on-LICENSE.txt deleted file mode 100644 index dbba1dd7829c7..0000000000000 --- a/plugins/ingest-attachment/licenses/bcmail-jdk18on-LICENSE.txt +++ /dev/null @@ -1,23 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. - (http://www.bouncycastle.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/plugins/ingest-attachment/licenses/bcmail-jdk18on-NOTICE.txt b/plugins/ingest-attachment/licenses/bcmail-jdk18on-NOTICE.txt deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/plugins/ingest-attachment/licenses/bcpkix-jdk18on-1.78.jar.sha1 b/plugins/ingest-attachment/licenses/bcpkix-jdk18on-1.78.jar.sha1 deleted file mode 100644 index 385a9d930eede..0000000000000 --- a/plugins/ingest-attachment/licenses/bcpkix-jdk18on-1.78.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -dd61bcdb87678451dd42d42e267979bd4b4451a1 \ No newline at end of file diff --git a/plugins/ingest-attachment/licenses/bcpkix-jdk18on-LICENSE.txt b/plugins/ingest-attachment/licenses/bcpkix-jdk18on-LICENSE.txt deleted file mode 100644 index e1fc4a1506db5..0000000000000 --- a/plugins/ingest-attachment/licenses/bcpkix-jdk18on-LICENSE.txt +++ /dev/null @@ -1,23 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. - (http://www.bouncycastle.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/plugins/ingest-attachment/licenses/bcpkix-jdk18on-NOTICE.txt b/plugins/ingest-attachment/licenses/bcpkix-jdk18on-NOTICE.txt deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/plugins/ingest-attachment/licenses/bcprov-jdk18on-1.78.jar.sha1 b/plugins/ingest-attachment/licenses/bcprov-jdk18on-1.78.jar.sha1 deleted file mode 100644 index 47fb5fd5e5f5d..0000000000000 --- a/plugins/ingest-attachment/licenses/bcprov-jdk18on-1.78.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -619aafb92dc0b4c6cc4cf86c487ca48ee2d67a8e \ No newline at end of file diff --git a/plugins/ingest-attachment/licenses/bcprov-jdk18on-LICENSE.txt b/plugins/ingest-attachment/licenses/bcprov-jdk18on-LICENSE.txt deleted file mode 100644 index 9f27bafe96885..0000000000000 --- a/plugins/ingest-attachment/licenses/bcprov-jdk18on-LICENSE.txt +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. - (http://www.bouncycastle.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/plugins/ingest-attachment/licenses/bcprov-jdk18on-NOTICE.txt b/plugins/ingest-attachment/licenses/bcprov-jdk18on-NOTICE.txt deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/server/build.gradle b/server/build.gradle index 5d98874cbef23..e9bc4be170feb 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -113,6 +113,11 @@ dependencies { // https://mvnrepository.com/artifact/org.roaringbitmap/RoaringBitmap api libs.roaringbitmap + + // bouncyCastle + api 'org.bouncycastle:bc-fips:1.0.2.5' + api 'org.bouncycastle:bctls-fips:1.0.19' + testImplementation 'org.awaitility:awaitility:4.2.2' testImplementation(project(":test:framework")) { // tests use the locally compiled version of server diff --git a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java index b40ac67b04917..66cc85ee33756 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -40,6 +40,7 @@ import org.apache.logging.log4j.core.config.Configurator; import org.apache.lucene.util.Constants; import org.apache.lucene.util.StringHelper; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.OpenSearchException; import org.opensearch.Version; import org.opensearch.cli.Terminal; @@ -52,6 +53,7 @@ import org.opensearch.common.logging.LogConfigurator; import org.opensearch.common.logging.Loggers; import org.opensearch.common.network.IfConfig; +import org.opensearch.common.settings.FipsSettings; import org.opensearch.common.settings.KeyStoreWrapper; import org.opensearch.common.settings.SecureSettings; import org.opensearch.common.settings.Settings; @@ -196,6 +198,17 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot BootstrapSettings.CTRLHANDLER_SETTING.get(settings) ); + var isFipsEnabled = FipsSettings.FIPS_ENABLED.get(settings); + var isRunningInFipsMode = CryptoServicesRegistrar.setApprovedOnlyMode(isFipsEnabled); + + if (!isRunningInFipsMode && isFipsEnabled){ + throw new BootstrapException(new RuntimeException("cannot enable FIPS mode")); + } + + if (isRunningInFipsMode) { + LogManager.getLogger(Bootstrap.class).info("running in FIPS mode"); + } + // initialize probes before the security manager is installed initializeProbes(); diff --git a/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java b/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java index f8c5d8e3b2480..cf2d459d7a1c8 100644 --- a/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java @@ -673,6 +673,7 @@ public void apply(Settings value, Settings current, Settings previous) { ClusterManagerTaskThrottler.THRESHOLD_SETTINGS, ClusterManagerTaskThrottler.BASE_DELAY_SETTINGS, ClusterManagerTaskThrottler.MAX_DELAY_SETTINGS, + FipsSettings.FIPS_ENABLED, // Settings related to search backpressure SearchBackpressureSettings.SETTING_MODE, diff --git a/server/src/main/java/org/opensearch/common/settings/FipsSettings.java b/server/src/main/java/org/opensearch/common/settings/FipsSettings.java new file mode 100644 index 0000000000000..958a1440ec29f --- /dev/null +++ b/server/src/main/java/org/opensearch/common/settings/FipsSettings.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.settings; + +import org.opensearch.common.settings.Setting.Property; + +/** + * Settings used for NIST FIPS 140-2 compliance + */ +public class FipsSettings { + + public static final Setting FIPS_ENABLED = Setting.boolSetting( + "fips.approved", + false, + Property.NodeScope); + +} diff --git a/server/src/main/resources/org/opensearch/bootstrap/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index a6d6014b26bfb..ee75ad91904a4 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -93,6 +93,28 @@ grant codeBase "${codebase.reactor-core}" { permission java.net.SocketPermission "*", "connect,resolve"; }; +// security +grant { + permission java.security.SecurityPermission "getProperty.jdk.tls.disabledAlgorithms"; + permission java.security.SecurityPermission "getProperty.jdk.certpath.disabledAlgorithms"; + permission java.security.SecurityPermission "getProperty.keystore.type.compat"; + permission java.security.SecurityPermission "getProperty.org.bouncycastle.*"; + permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; + permission java.security.SecurityPermission "putProviderProperty.BCJSSE"; + permission java.lang.RuntimePermission "getProtectionDomain"; + permission java.util.PropertyPermission "java.runtime.name", "read"; + permission org.bouncycastle.crypto.CryptoServicesPermission "changeToApprovedModeEnabled"; + permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled"; + //io.netty.handler.codec.DecoderException + permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; + //java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec + permission java.lang.RuntimePermission "accessDeclaredMembers"; + permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; + permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; + permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read"; + permission java.io.FilePermission "${javaHome}/lib/security/jssecacerts", "read"; +}; + //// Everything else: grant { From 2c6e14b994ef66cf66bf478e8ba9c30c5cf8396d Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Wed, 7 Aug 2024 18:52:46 +0200 Subject: [PATCH 02/21] make tests run without BC (not BCFIPS) libraries. Signed-off-by: Iwan Igonin --- buildSrc/build.gradle | 2 +- .../gradle/OpenSearchTestBasePlugin.java | 5 + .../opensearch/gradle/info/BuildParams.java | 1 - client/rest/build.gradle | 18 ++ client/rest/licenses/bc-fips-1.0.2.4.jar.sha1 | 1 + .../rest/licenses/bctls-fips-1.0.19.jar.sha1 | 1 + client/rest/licenses/bouncycastle-LICENSE.txt | 14 ++ client/rest/licenses/bouncycastle-NOTICE.txt | 1 + .../client/RestClientBuilderIntegTests.java | 7 +- distribution/src/config/fips_java.security | 6 +- .../keystore/AddFileKeyStoreCommandTests.java | 18 +- .../AddStringKeyStoreCommandTests.java | 18 +- .../ChangeKeyStorePasswordCommandTests.java | 18 +- .../cli/keystore/KeyStoreWrapperTests.java | 26 +-- .../keystore/ListKeyStoreCommandTests.java | 18 +- .../RemoveSettingKeyStoreCommandTests.java | 19 +- .../tools/launchers/SystemJvmOptions.java | 4 +- distribution/tools/plugin-cli/build.gradle | 3 +- gradle/libs.versions.toml | 5 +- libs/ssl-config/build.gradle | 4 +- .../licenses/bc-fips-1.0.2.5.jar.sha1 | 1 - .../licenses/bouncycastle-LICENSE.txt | 14 ++ .../licenses/bouncycastle-NOTICE.txt | 1 + .../common/ssl/DefaultJdkTrustConfig.java | 22 +- .../org/opensearch/common/ssl/PemUtils.java | 43 ++-- .../common/ssl/PemKeyConfigTests.java | 13 +- .../common/ssl/PemTrustConfigTests.java | 17 +- .../opensearch/common/ssl/PemUtilsTests.java | 10 +- .../reindex/ReindexRestClientSslTests.java | 11 +- modules/transport-netty4/build.gradle | 4 + .../SecureNetty4HttpServerTransportTests.java | 4 +- .../ssl/SimpleSecureNetty4TransportTests.java | 4 +- .../src/test/resources/netty4-secure.jks | Bin 2790 -> 0 bytes .../src/test/resources/netty4-secure.p12 | Bin 0 -> 2790 bytes .../AzureDiscoveryClusterFormationTests.java | 4 +- .../licenses/bcprov-jdk18on-1.78.jar.sha1 | 1 - .../licenses/bcprov-jdk18on-LICENSE.txt | 22 -- .../licenses/password4j-1.8.2.jar.sha1 | 1 + .../licenses/password4j-LICENSE.txt | 201 ++++++++++++++++++ ...k18on-NOTICE.txt => password4j-NOTICE.txt} | 0 .../shiro/realm/BCryptPasswordMatcher.java | 28 +-- plugins/repository-azure/build.gradle | 3 - plugins/telemetry-otel/build.gradle | 16 +- server/build.gradle | 20 +- server/licenses/bc-fips-1.0.2.4.jar.sha1 | 1 + server/licenses/bctls-fips-1.0.19.jar.sha1 | 1 + server/licenses/bouncycastle-LICENSE.txt | 14 ++ server/licenses/bouncycastle-NOTICE.txt | 1 + .../org/opensearch/bootstrap/Bootstrap.java | 15 +- .../common/settings/FipsSettings.java | 5 +- .../common/settings/KeyStoreWrapper.java | 4 +- .../org/opensearch/bootstrap/security.policy | 15 +- .../bootstrap/test-framework.policy | 2 +- 53 files changed, 455 insertions(+), 232 deletions(-) create mode 100644 client/rest/licenses/bc-fips-1.0.2.4.jar.sha1 create mode 100644 client/rest/licenses/bctls-fips-1.0.19.jar.sha1 create mode 100644 client/rest/licenses/bouncycastle-LICENSE.txt create mode 100644 client/rest/licenses/bouncycastle-NOTICE.txt delete mode 100644 libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 create mode 100644 libs/ssl-config/licenses/bouncycastle-LICENSE.txt create mode 100644 libs/ssl-config/licenses/bouncycastle-NOTICE.txt delete mode 100644 modules/transport-netty4/src/test/resources/netty4-secure.jks create mode 100644 modules/transport-netty4/src/test/resources/netty4-secure.p12 delete mode 100644 plugins/identity-shiro/licenses/bcprov-jdk18on-1.78.jar.sha1 delete mode 100644 plugins/identity-shiro/licenses/bcprov-jdk18on-LICENSE.txt create mode 100644 plugins/identity-shiro/licenses/password4j-1.8.2.jar.sha1 create mode 100644 plugins/identity-shiro/licenses/password4j-LICENSE.txt rename plugins/identity-shiro/licenses/{bcprov-jdk18on-NOTICE.txt => password4j-NOTICE.txt} (100%) create mode 100644 server/licenses/bc-fips-1.0.2.4.jar.sha1 create mode 100644 server/licenses/bctls-fips-1.0.19.jar.sha1 create mode 100644 server/licenses/bouncycastle-LICENSE.txt create mode 100644 server/licenses/bouncycastle-NOTICE.txt diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index c227d890984f7..38e4f6afb1cf5 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -123,7 +123,7 @@ dependencies { api 'org.jruby.joni:joni:2.2.1' api "com.fasterxml.jackson.core:jackson-databind:${props.getProperty('jackson_databind')}" api "org.ajoberstar.grgit:grgit-core:5.2.1" - api "org.bouncycastle:bc-fips:1.0.2.5" + api "org.bouncycastle:bc-fips:${props.getProperty('bouncycastle_jce')}" testFixturesApi "junit:junit:${props.getProperty('junit')}" diff --git a/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java index d0cb2da9c1dd3..3161343ee1148 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java @@ -164,6 +164,11 @@ public void execute(Task t) { test.systemProperty("tests.seed", BuildParams.getTestSeed()); } + test.systemProperty( + "java.security.properties", + project.getRootProject().getLayout().getProjectDirectory() + "/distribution/src/config/fips_java.security" + ); + // don't track these as inputs since they contain absolute paths and break cache relocatability File gradleHome = project.getGradle().getGradleUserHomeDir(); String gradleVersion = project.getGradle().getGradleVersion(); diff --git a/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java b/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java index bbd06c84b30c9..a9ccb75569002 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/BuildParams.java @@ -52,7 +52,6 @@ public class BuildParams { private static JavaVersion gradleJavaVersion; private static JavaVersion runtimeJavaVersion; private static String runtimeJavaDetails; - @Deprecated private static Boolean inFipsJvm; private static String gitRevision; private static String gitOrigin; diff --git a/client/rest/build.gradle b/client/rest/build.gradle index 93faf0024b51e..8e3d82c9a95b8 100644 --- a/client/rest/build.gradle +++ b/client/rest/build.gradle @@ -51,6 +51,8 @@ dependencies { api "commons-codec:commons-codec:${versions.commonscodec}" api "commons-logging:commons-logging:${versions.commonslogging}" api "org.slf4j:slf4j-api:${versions.slf4j}" + api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" + api "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" // reactor api "io.projectreactor:reactor-core:${versions.reactor}" @@ -70,6 +72,10 @@ dependencies { testImplementation "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}" } +tasks.named("dependencyLicenses").configure { + mapping from: /bc.*/, to: 'bouncycastle' +} + tasks.withType(CheckForbiddenApis).configureEach { //client does not depend on server, so only jdk and http signatures should be checked replaceSignatureFiles('jdk-signatures', 'http-signatures') @@ -141,6 +147,18 @@ thirdPartyAudit { 'reactor.blockhound.integration.BlockHoundIntegration' ) ignoreViolations( + 'org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$CoreSecureRandom', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$BaseTLSKeyGeneratorSpi', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSKeyMaterialGenerator', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSKeyMaterialGenerator$2', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSMasterSecretGenerator', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSMasterSecretGenerator$2', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSPRFKeyGenerator', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSRsaPreMasterSecretGenerator', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSRsaPreMasterSecretGenerator$2', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator$2', 'reactor.core.publisher.Traces$SharedSecretsCallSiteSupplierFactory$TracingException' ) } diff --git a/client/rest/licenses/bc-fips-1.0.2.4.jar.sha1 b/client/rest/licenses/bc-fips-1.0.2.4.jar.sha1 new file mode 100644 index 0000000000000..da37449f80d7e --- /dev/null +++ b/client/rest/licenses/bc-fips-1.0.2.4.jar.sha1 @@ -0,0 +1 @@ +9008d04fc13da6455e6a792935b93b629757335d \ No newline at end of file diff --git a/client/rest/licenses/bctls-fips-1.0.19.jar.sha1 b/client/rest/licenses/bctls-fips-1.0.19.jar.sha1 new file mode 100644 index 0000000000000..7f0eb6e1c6bea --- /dev/null +++ b/client/rest/licenses/bctls-fips-1.0.19.jar.sha1 @@ -0,0 +1 @@ +b15d650f6e2a9de08d5569e25a642b6a384dbfd2 \ No newline at end of file diff --git a/client/rest/licenses/bouncycastle-LICENSE.txt b/client/rest/licenses/bouncycastle-LICENSE.txt new file mode 100644 index 0000000000000..5c7c14696849d --- /dev/null +++ b/client/rest/licenses/bouncycastle-LICENSE.txt @@ -0,0 +1,14 @@ +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/client/rest/licenses/bouncycastle-NOTICE.txt b/client/rest/licenses/bouncycastle-NOTICE.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/client/rest/licenses/bouncycastle-NOTICE.txt @@ -0,0 +1 @@ + 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 0b7cf6e8bb5fe..46f6cfa22096b 100644 --- a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java +++ b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java @@ -43,7 +43,7 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; import java.io.IOException; @@ -95,15 +95,14 @@ public static void stopHttpServers() throws IOException { } public void testBuilderUsesDefaultSSLContext() throws Exception { - assumeFalse("https://github.com/elastic/elasticsearch/issues/49094", inFipsJvm()); final SSLContext defaultSSLContext = SSLContext.getDefault(); try { try (RestClient client = buildRestClient()) { try { client.performRequest(new Request("GET", "/")); - fail("connection should have been rejected due to SSL handshake"); + fail("connection should have been rejected due to SSL failure"); } catch (Exception e) { - assertThat(e, instanceOf(SSLHandshakeException.class)); + assertThat(e.getCause(), instanceOf(SSLException.class)); } } diff --git a/distribution/src/config/fips_java.security b/distribution/src/config/fips_java.security index e54e471c89e71..fc1608c8df1bd 100644 --- a/distribution/src/config/fips_java.security +++ b/distribution/src/config/fips_java.security @@ -22,9 +22,9 @@ ssl.KeyManagerFactory.algorithm=PKIX ssl.TrustManagerFactory.algorithm=PKIX networkaddress.cache.negative.ttl=10 krb5.kdc.bad.policy = tryLast -jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1, jdkCA&usageTLSServer, RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224 -jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySize < 1024 -jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, MD5withRSA, DH keySize < 1024, EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC +jdk.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, \ 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 3d188590d5c47..db6bb2d5473f4 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 @@ -211,17 +211,13 @@ public void testIncorrectPassword() throws Exception { terminal.addSecretInput("thewrongkeystorepassword"); UserException e = expectThrows(UserException.class, () -> execute("foo", file.toString())); assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); - if (inFipsJvm()) { - assertThat( - e.getMessage(), - anyOf( - containsString("Provided keystore password was incorrect"), - containsString("Keystore has been corrupted or tampered with") - ) - ); - } else { - assertThat(e.getMessage(), containsString("Provided keystore password was incorrect")); - } + assertThat( + e.getMessage(), + anyOf( + containsString("Provided keystore password was incorrect"), + containsString("Keystore has been corrupted or tampered with") + ) + ); } public void testAddToUnprotectedKeystore() throws Exception { 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 22012d1f44986..05ce0bb61a4fd 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 @@ -72,17 +72,13 @@ public void testInvalidPassphrease() throws Exception { terminal.addSecretInput("thewrongpassword"); UserException e = expectThrows(UserException.class, () -> execute("foo2")); assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); - if (inFipsJvm()) { - assertThat( - e.getMessage(), - anyOf( - containsString("Provided keystore password was incorrect"), - containsString("Keystore has been corrupted or tampered with") - ) - ); - } else { - assertThat(e.getMessage(), containsString("Provided keystore password was incorrect")); - } + assertThat( + e.getMessage(), + anyOf( + containsString("Provided keystore password was incorrect"), + containsString("Keystore has been corrupted or tampered with") + ) + ); } 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 1ce57332a9a31..1aa62cf71ed65 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 @@ -104,16 +104,12 @@ public void testChangeKeyStorePasswordWrongExistingPassword() throws Exception { // We'll only be prompted once (for the old password) UserException e = expectThrows(UserException.class, this::execute); assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); - if (inFipsJvm()) { - assertThat( - e.getMessage(), - anyOf( - containsString("Provided keystore password was incorrect"), - containsString("Keystore has been corrupted or tampered with") - ) - ); - } else { - assertThat(e.getMessage(), containsString("Provided keystore password was incorrect")); - } + assertThat( + e.getMessage(), + anyOf( + containsString("Provided keystore password was incorrect"), + containsString("Keystore has been corrupted or tampered with") + ) + ); } } 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 efb833e8fd94a..19bc06f7c64bc 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 @@ -133,17 +133,13 @@ public void testDecryptKeyStoreWithWrongPassword() throws Exception { SecurityException.class, () -> loadedKeystore.decrypt(new char[] { 'i', 'n', 'v', 'a', 'l', 'i', 'd' }) ); - if (inFipsJvm()) { - assertThat( - exception.getMessage(), - anyOf( - containsString("Provided keystore password was incorrect"), - containsString("Keystore has been corrupted or tampered with") - ) - ); - } else { - assertThat(exception.getMessage(), containsString("Provided keystore password was incorrect")); - } + assertThat( + exception.getMessage(), + anyOf( + containsString("Provided keystore password was incorrect"), + containsString("Keystore has been corrupted or tampered with") + ) + ); } public void testCannotReadStringFromClosedKeystore() throws Exception { @@ -374,8 +370,8 @@ public void testBackcompatV1() throws Exception { output.writeString("PKCS12"); output.writeString("PBE"); - SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE"); - KeyStore keystore = KeyStore.getInstance("PKCS12"); + SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); + KeyStore keystore = KeyStore.getInstance("PKCS12", "SUN"); keystore.load(null, null); SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); @@ -415,8 +411,8 @@ public void testBackcompatV2() throws Exception { output.writeString("file_setting"); output.writeString("FILE"); - SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE"); - KeyStore keystore = KeyStore.getInstance("PKCS12"); + SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); + KeyStore keystore = KeyStore.getInstance("PKCS12", "SUN"); keystore.load(null, null); SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); 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 0846e28fb42af..36bef3a82281d 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 @@ -90,17 +90,13 @@ public void testListWithIncorrectPassword() throws Exception { terminal.addSecretInput("thewrongkeystorepassword"); UserException e = expectThrows(UserException.class, this::execute); assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); - if (inFipsJvm()) { - assertThat( - e.getMessage(), - anyOf( - containsString("Provided keystore password was incorrect"), - containsString("Keystore has been corrupted or tampered with") - ) - ); - } else { - assertThat(e.getMessage(), containsString("Provided keystore password was incorrect")); - } + assertThat( + e.getMessage(), + anyOf( + containsString("Provided keystore password was incorrect"), + containsString("Keystore has been corrupted or tampered with") + ) + ); } public void testListWithUnprotectedKeystore() throws Exception { 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 66d448400d4e3..276af6cfa659f 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 @@ -107,18 +107,13 @@ public void testRemoveWithIncorrectPassword() throws Exception { terminal.addSecretInput("thewrongpassword"); UserException e = expectThrows(UserException.class, () -> execute("foo")); assertEquals(e.getMessage(), ExitCodes.DATA_ERROR, e.exitCode); - if (inFipsJvm()) { - assertThat( - e.getMessage(), - anyOf( - containsString("Provided keystore password was incorrect"), - containsString("Keystore has been corrupted or tampered with") - ) - ); - } else { - assertThat(e.getMessage(), containsString("Provided keystore password was incorrect")); - } - + assertThat( + e.getMessage(), + anyOf( + containsString("Provided keystore password was incorrect"), + containsString("Keystore has been corrupted or tampered with") + ) + ); } public void testRemoveFromUnprotectedKeystore() throws Exception { 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 5098d0e343f5f..030dc15aa67fe 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 @@ -87,8 +87,8 @@ static List systemJvmOptions(final Path config) { } private static String loadJavaSecurityProperties(final Path config) { - var securityFile = config.resolve("fips_java.security").toFile(); - return "-Djava.security.properties=" + securityFile.getAbsolutePath(); + var securityFile = config.resolve("fips_java.security"); + return "-Djava.security.properties=" + securityFile.toAbsolutePath(); } private static String allowSecurityManagerOption() { diff --git a/distribution/tools/plugin-cli/build.gradle b/distribution/tools/plugin-cli/build.gradle index 784cdc457a1a9..34a895538a0ae 100644 --- a/distribution/tools/plugin-cli/build.gradle +++ b/distribution/tools/plugin-cli/build.gradle @@ -37,8 +37,7 @@ base { dependencies { compileOnly project(":server") compileOnly project(":libs:opensearch-cli") - api "org.bouncycastle:bcpg-fips:2.0.9" - api "org.bouncycastle:bc-fips:2.0.0" + api "org.bouncycastle:bcpg-fips:${versions.bouncycastle_pg}" testImplementation project(":test:framework") testImplementation 'com.google.jimfs:jimfs:1.3.0' testRuntimeOnly("com.google.guava:guava:${versions.guava}") { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0b6886ce011b2..07928ca3a4942 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -58,7 +58,10 @@ reactivestreams = "1.0.4" # when updating this version, you need to ensure compatibility with: # - plugins/ingest-attachment (transitive dependency, check the upstream POM) # - distribution/tools/plugin-cli -bouncycastle="1.78" +bouncycastle_jce = "1.0.2.4" +bouncycastle_tls = "1.0.19" +bouncycastle_pkix = "1.0.7" +bouncycastle_pg = "1.0.7.1" # test dependencies randomizedrunner = "2.7.1" junit = "4.13.2" diff --git a/libs/ssl-config/build.gradle b/libs/ssl-config/build.gradle index 889c278ffbfcb..f2da097a7fe09 100644 --- a/libs/ssl-config/build.gradle +++ b/libs/ssl-config/build.gradle @@ -36,8 +36,8 @@ dependencies { api project(':libs:opensearch-common') // bouncyCastle - implementation 'org.bouncycastle:bcpkix-fips:1.0.7' - compileOnly 'org.bouncycastle:bc-fips:1.0.2.5' + implementation "org.bouncycastle:bcpkix-fips:${versions.bouncycastle_pkix}" + compileOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" testImplementation(project(":test:framework")) { exclude group: 'org.opensearch', module: 'opensearch-ssl-config' diff --git a/libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 b/libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 deleted file mode 100644 index cc29a787cee56..0000000000000 --- a/libs/ssl-config/licenses/bc-fips-1.0.2.5.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -704e65f7e4fe679e5ab2aa8a840f27f8ced4c522 diff --git a/libs/ssl-config/licenses/bouncycastle-LICENSE.txt b/libs/ssl-config/licenses/bouncycastle-LICENSE.txt new file mode 100644 index 0000000000000..5c7c14696849d --- /dev/null +++ b/libs/ssl-config/licenses/bouncycastle-LICENSE.txt @@ -0,0 +1,14 @@ +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/libs/ssl-config/licenses/bouncycastle-NOTICE.txt b/libs/ssl-config/licenses/bouncycastle-NOTICE.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/libs/ssl-config/licenses/bouncycastle-NOTICE.txt @@ -0,0 +1 @@ + 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 6d5b1b02c67ca..859b74b200dc6 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java @@ -65,7 +65,7 @@ final class DefaultJdkTrustConfig implements SslTrustConfig { * Create a trust config that uses supplied {@link BiFunction} to determine the TrustStore type, and the relevant password. */ DefaultJdkTrustConfig(BiFunction systemProperties) { - this(systemProperties, getSystemTrustStorePassword(systemProperties)); + this(systemProperties, isPkcs11Truststore(systemProperties) ? getSystemTrustStorePassword(systemProperties) : null); } /** @@ -93,16 +93,11 @@ public X509ExtendedTrustManager createTrustManager() { * @return the KeyStore used as truststore for PKCS#11 initialized with the password, null otherwise */ private KeyStore getSystemTrustStore() { - if (trustStorePassword != null) { + if (isPkcs11Truststore(systemProperties) && trustStorePassword != null) { try { - if (isBcfksTruststore(systemProperties)) { - var path = Path.of(System.getProperty("javax.net.ssl.trustStore", "")); - KeyStoreUtil.readKeyStore(path, "BCFKS", trustStorePassword); - } else if (isPkcs11Truststore(systemProperties)) { - KeyStore keyStore = KeyStore.getInstance("PKCS11"); - keyStore.load(null, trustStorePassword); - return keyStore; - } + KeyStore keyStore = KeyStore.getInstance("PKCS11"); + keyStore.load(null, trustStorePassword); + return keyStore; } catch (GeneralSecurityException | IOException e) { throw new SslConfigException("failed to load the system PKCS#11 truststore", e); } @@ -110,17 +105,12 @@ private KeyStore getSystemTrustStore() { return null; } - private static boolean isBcfksTruststore(BiFunction systemProperties) { - return systemProperties.apply("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("BCFKS"); - } - private static boolean isPkcs11Truststore(BiFunction systemProperties) { return systemProperties.apply("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11"); } private static char[] getSystemTrustStorePassword(BiFunction systemProperties) { - var password = systemProperties.apply("javax.net.ssl.trustStorePassword", ""); - return password.isEmpty() ? null : password.toCharArray(); + return systemProperties.apply("javax.net.ssl.trustStorePassword", "").toCharArray(); } @Override diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java index df842650d65ca..821c38c8d2f46 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java @@ -43,21 +43,25 @@ import org.bouncycastle.pkcs.PKCSException; import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder; -import java.security.PrivateKey; - -import java.io.*; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.function.Supplier; final class PemUtils { + protected static final String BCFIPS = "BCFIPS"; + private PemUtils() { throw new IllegalStateException("Utility class should not be instantiated"); } @@ -71,9 +75,9 @@ private PemUtils() { * @return a private key from the contents of the file */ public static PrivateKey readPrivateKey(Path keyPath, Supplier passwordSupplier) throws IOException, PKCSException { - PrivateKeyInfo pki = loadPrivateKeyFromFile(keyPath, passwordSupplier); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); - return converter.getPrivateKey(pki); + PrivateKeyInfo pki = loadPrivateKeyFromFile(keyPath, passwordSupplier); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + return converter.getPrivateKey(pki); } static List readCertificates(Collection certPaths) throws CertificateException, IOException { @@ -102,22 +106,20 @@ static List readCertificates(Collection certPaths) throws Cer * @return {@link PrivateKey} * @throws IOException If the file can't be read */ - private static PrivateKeyInfo loadPrivateKeyFromFile(Path keyPath, Supplier passwordSupplier) - throws IOException, PKCSException { - try (PEMParser pemParser = new PEMParser(new FileReader(keyPath.toFile()))) { + private static PrivateKeyInfo loadPrivateKeyFromFile(Path keyPath, Supplier passwordSupplier) throws IOException, + PKCSException { + + try (PEMParser pemParser = new PEMParser(Files.newBufferedReader(keyPath, StandardCharsets.UTF_8))) { Object object = readObject(keyPath, pemParser); if (object instanceof PKCS8EncryptedPrivateKeyInfo) { // encrypted private key in pkcs8-format var privateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) object; - var inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder() - .setProvider("BCFIPS") + var inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder().setProvider(BCFIPS) .build(passwordSupplier.get()); return privateKeyInfo.decryptPrivateKeyInfo(inputDecryptorProvider); } else if (object instanceof PEMEncryptedKeyPair) { // encrypted private key var encryptedKeyPair = (PEMEncryptedKeyPair) object; - var decryptorProvider = new JcePEMDecryptorProviderBuilder() - .setProvider("BCFIPS") - .build(passwordSupplier.get()); + var decryptorProvider = new JcePEMDecryptorProviderBuilder().setProvider(BCFIPS).build(passwordSupplier.get()); var keyPair = encryptedKeyPair.decryptKeyPair(decryptorProvider); return keyPair.getPrivateKeyInfo(); } else if (object instanceof PEMKeyPair) { // unencrypted private key @@ -125,11 +127,14 @@ private static PrivateKeyInfo loadPrivateKeyFromFile(Path keyPath, Supplier PemUtils.readPrivateKey(path, TESTNODE_PASSWORD)); - assertThat(e.getMessage(), containsString("file does not contain a supported key format")); + assertThat(e.getMessage(), containsString("Error parsing Private Key")); assertThat(e.getMessage(), containsString(path.toAbsolutePath().toString())); + assertThat(e.getMessage(), containsString("file is empty")); } public void testReadPemCertificateAsKey() { final Path path = getDataPath("/certs/pem-utils/testnode.crt"); SslConfigException e = expectThrows(SslConfigException.class, () -> PemUtils.readPrivateKey(path, TESTNODE_PASSWORD)); - assertThat(e.getMessage(), containsString("file does not contain a supported key format")); + assertThat(e.getMessage(), containsString("invalid encrypted private key class")); assertThat(e.getMessage(), containsString(path.toAbsolutePath().toString())); } public void testReadCorruptedKey() { final Path path = getDataPath("/certs/pem-utils/corrupted_key_pkcs8_plain.pem"); SslConfigException e = expectThrows(SslConfigException.class, () -> PemUtils.readPrivateKey(path, TESTNODE_PASSWORD)); - assertThat(e.getMessage(), containsString("private key")); - assertThat(e.getMessage(), containsString("cannot be parsed")); + assertThat(e.getMessage(), containsString("Error parsing Private Key")); assertThat(e.getMessage(), containsString(path.toAbsolutePath().toString())); - assertThat(e.getCause().getMessage(), containsString("PEM footer is invalid or missing")); + assertThat(e.getMessage(), containsString("file is empty")); } public void testReadEmptyFile() { 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 1123ae4623300..4fa84f88af35f 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 @@ -56,7 +56,6 @@ import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.TrustManager; import javax.net.ssl.X509ExtendedKeyManager; @@ -65,6 +64,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.nio.file.Path; +import java.security.cert.CertPathBuilderException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -129,7 +129,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()) @@ -138,7 +137,13 @@ public void testClientFailsWithUntrustedCertificate() throws IOException { 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)) { - expectThrows(SSLHandshakeException.class, () -> client.performRequest(new Request("GET", "/"))); + var exception = expectThrows(Exception.class, () -> client.performRequest(new Request("GET", "/"))); + var rootCause = exception.getCause().getCause().getCause().getCause(); + assertThat(rootCause, Matchers.instanceOf(CertPathBuilderException.class)); + assertThat( + rootCause.getMessage(), + Matchers.containsString("No issuer certificate for certificate in certification path found") + ); } } diff --git a/modules/transport-netty4/build.gradle b/modules/transport-netty4/build.gradle index 4e68a4ce17f73..89d3f03032d9f 100644 --- a/modules/transport-netty4/build.gradle +++ b/modules/transport-netty4/build.gradle @@ -77,6 +77,10 @@ tasks.named("dependencyLicenses").configure { mapping from: /netty-.*/, to: 'netty' } +forbiddenPatterns { + exclude '**/*.p12' +} + test { /* * We have to disable setting the number of available processors as tests in the same JVM randomize processors and will step on each 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 f80ad901ce765..d57252950620a 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 @@ -123,11 +123,11 @@ public Optional buildSecureHttpServerEngine(Settings settings, HttpSe try { final KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load( - SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-secure.jks"), + SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-secure.p12"), "password".toCharArray() ); - final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, "password".toCharArray()); SSLEngine engine = SslContextBuilder.forServer(keyManagerFactory) 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 e0600aebd90e5..ff52e6e178692 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 @@ -79,11 +79,11 @@ public Optional buildSecureServerTransportEngine(Settings settings, T try { final KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load( - SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-secure.jks"), + SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-secure.p12"), "password".toCharArray() ); - final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, "password".toCharArray()); SSLEngine engine = SslContextBuilder.forServer(keyManagerFactory) diff --git a/modules/transport-netty4/src/test/resources/netty4-secure.jks b/modules/transport-netty4/src/test/resources/netty4-secure.jks deleted file mode 100644 index 59dfd31c2a1567c6fbae386aa8f15c563bc66ae0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2790 zcma)8XEYm(8crgiLaZ7k_^3S_Tdh)5I#8=SvDFOy$b^} z0rT-tEEo?BJ)r{;cu4DiNstOK9`fphE~rNH!5#!!9K_}1H^u_V z=usLU2dZ9W+tu&&HP&rr0Gj`32Xv({(IBcgf--PNjPP?Wl@c*PRlG~?w#y{$$E7Ac zx~e{WW#twe_l9Rk?!G}q_9k~-h|^Nd;KI<0tW*OJCTydW5K?zK18ZR;_&+8)QRWs&D)HUeQu z9uJg@R_z>@?>MI)N$iZs?C70IBKa$HMTmwtRZM2#Eoh>qRjH=pY|48!KPy$z*ryQVZ6bbD&&t4TF&?ZKHwA0q_P!85hGFhoG?V+nrEvu|YdG zNrL!x5hXK9XG!dHbKAhpa*yK2X+&`U9}I8`ON-SR2AaO}$md&6HEJY?sp&0?74^a& zVGN_jRIazY8Xhs~^9fS?Fi-gwcl8p=<-=HX>-`)aSrru|SJ3LA(S5lX(T8(;a;TEk zjAO5JZ;+6Ri7Fk+ojzl_j>FsNxien-@nif$*x?i|H~&{et(-zmOA(y|b9$qWo>F~6 zki+c@3Ez0iXk}m9oTR;sSPhbZ=SK~bRJ`f>5rk8+I0fjlFBn~o1e<4(6(c5WFg`wy)X+ZN{sOnlx=ETJarFP_~xRzcNqSZ7%QHR!VzITeKkh$31~ zImEggf>lp$FXnU=Ts#bWExW*Y9;7%4Oe?((yn8vIeg2W0PfQBbZg?H7S6jR7_Ab`i zFt+ORFZV!dxP5(el{8;w<@pHgN@T-AM|1c0R#EBHJ|EF9tDR?*`HCSSS-NChYvG5g86>yVnk$g?6B#Mb(2mG-cf!r7rvHyVnf8E8TJzgSB$KPWTl~g|#-Rw(h>E!kcXTQ3Q6z`cP z)fS*)cgRrG{5|EtiN~1M7vJ;J2n&CXaT*Pb--Bc$Zi!bnJlbwCvj3*7ebDIBSRV6) zJ(Pz1F*1RFZ#H99b! zFAr=1?f}O-Ds0l4sX~>=qET8%wZFXa-H{oyX`N12eqs0c0QyH-0D8xlm&3z#uIe`1 zrI9EslWc5kB75)nwQj9;_~TDgCgpG=C=?yr`DQL%YWUdLLKt~BF_>(ozPEDYfmq5| z0JoEQ)AV!wN+o_w*8sM82I;8gUaTdYv=QY$QCfcLv(IxGSQ+S> z%RxCJI*K1%Di#OIIA!I`gNrV;_(=luy&a6iR5jIAl$8;9aP{vcCdPa`xZs4!1pxskBjXCrY< zOcTKv`EhagL&xGTrIIUt2v?a2%l?bgMOV&&+|WU;e$=RpZpz1|dNV>*m~d#i)d2TYZVO)~XMlNxBrxoWuJ<66oc1(`c8LIHSqMFI@oVB5 zCTi-Btx2^*IwaoUOI*HGf^JQ#GU_^GnY6KL3-W;<6D`7ahRD>wHVTL3(9$baE*+Uh zI?Qh*6kS`O(EC~T8Xj=&-O3)}wcfX~t$yft z0#%1s3udIR?zCsDgD^((_g@(9316i|*L)iFc0tc}Oh8!c)=u|})&8|LYjG)q`SIr(O$~<-27o6EP z#<~$zeByPj(`59rPL~;qQy3J_7#W=XF0H@rb!E#Ub}O0BW;u}u1Ml#*1v|V#XY%_PSfaE#-vVR6s9p13SJS(vq?@w&x z1io}4NWAA0VtOK@D-a#+({tI}OZlv_g9^ZdL~a$%7K051T_}A1FpPpLUh51%m?D%A zu;0HQ2nYdyMN)q`vbBY0Z04-Vi-TU!{Ws&)J05FHCn{A|`7rGNj0AIUHubc-71}?2 T^$whsN!%rkGx+QOE291eC9)6< diff --git a/modules/transport-netty4/src/test/resources/netty4-secure.p12 b/modules/transport-netty4/src/test/resources/netty4-secure.p12 new file mode 100644 index 0000000000000000000000000000000000000000..822d7ff8236f3dccd5693f1dcd7e39dbd0ca0f26 GIT binary patch literal 2790 zcma)8cQhLe_f8^-SJ0?Usl=X@gyN-krF4)YMyb(+4vkgRiV-VnbkI^GrM9B9Mr+Ti z1_@eJrA?_-HA-t$`TWlBJAKdh*Y}-!?z!iAp8MQCpL0HGtEP%2W1{=gGb>DV(5!@AmxDgO;&T*6xdkhK>_?vBRbRC@E)X6$G{!s6CPLxV{$o`sV=~!EoWGu=nTC?Rh#N`U-puvz zLSvd`pYX?tERoss3zIabhY^YvBkx^^ZO>(ma+s;zrT$}vp)q7~x^TtW%wO0*)SLbuhY&`-Xq?-|G;P;*Tot}vK5czFRS`!DCzk;6S>*tB;V&2Td5jKRYdx8wc zM%$X~bAtI?rT5US-nl-V{qs6!+H4En2c7|#644UYFe-RZT7ln zQJ0xXrmIAWgJO-)7~xh7_%gq5MN)$2G!{}gW&P!n@Q*wC5fhj`#PwT8bbekNS-@slxl* z-qUT2H+8Q$X5^_((SJG3O0&>>GjS68uREx9g;mP7t)w>YC|2)4`wPO_py!W8WR!6| z(Z!55XY@D|;|ptak9Ey{YiU}kb6@FRK}S+%a-|1p=Hj7PKR(f=S6fak48jm)K*;R= zqrq!!u!cQO*~Yfs`tZ7%s7SKoE%UBV5T$|F;c!Xbh=D=fN2YZRSw7FL>HAV>dUg1I z)4LzPgCnuNuc)hUMZx3ej0Z|@l{BQBT*9d|sKl+sMrm8Dx=j1PAH$Q+cL%3sL`>2R z^b2xdn$JECm}3ebIj^G>e*rn-TqIk;h zm}v^s8LVfP*3XTZJoz3UlHXM&Bu?$HM%4F-sT<-*{6iwhr3s=lw{M;;{L}qGDLp;4 zi@@TG^9qdM*cMX1mAqNYb$lNd)z-a`AQlioyAzX`1Zu?G9G^GKHTk`UFTU%$-1D~t z9a;o~+`Q zs#A=)KtUkc3+1X9JGivQk0T|aUtf7^nZPq&4nmvj?~J5-DTf9=p?U)XI9`T5G?39k z~7JSWAa$s}dWJoO(qZhy2 zW|v+gOX$1VPd_8?aaOntJzad`Ws$%;a?X4wRIzhm!cQO!5UcwPVfWN^aCd?u_6meX za8O%;mYdb!ac`LcEIbi*Nn z$Z9S@e`hp5aKb*e)E|SOpB4JnsxTtlsA~4Nl3ZV%DL^x`{6rvYg}FR*k<-Y;0DE@} z#3(t98(=v;4g`mMti-iS1>F^h=!6TJUSPpDd^; z4=>FyAL;qIo_0>I%9BBBJX$ZcS+wr3LINU|0b((Efpt^9r|M05Ud^fLjrwGF(;=AA zQu$*$u1hX#^D)w6x?7?+P7$fRh2SI7SO>aNzQW zoBcYVH_KSps%LlKsd7?(3I^b5Ud>|DA){NrXnzHN<6C$4sOsn8ou03Sy(8u((5^J5 ztUI3UN(%@(8Bu|~ozmjpa@Jr|kKDL2cN<675UQRI%{w-|#-erxx*ulSFror-Br#eS z$#?wBEGjyzV%^2aYm4C-ai-ozTIZhtI$Ln0&e~HJN5j=3A@J%M!^GnIS zj#kIri|f+gbE?~c>t%Fen^`BDS1+B=o<<3iUwR#;A7hxkx-xWQ_^@Vp3BIDvCMs9I z#lToMM|B)F5MoE#Jdwq4;I5%)UOju|T=d$Ry6d9#>3nIU@1xWSL8PW4CU5%$*@nNrc!H7xDJPa zZ=&*(?WSq-Z4bpqs)909IIW&F< z5$VTtvMw)Qe(knHJUlFdgZTQ?{Owptksuzym^&glW%4O5`yta(Q#;q~bM;3C-0WID zWscpTVs08zdo~jC$1f>l9j^%NDz+Bb|ET^>EEvyDd!*Jgk%|P%W2>EMV&*T zQM`YCKM)WC03&3!eU%ip;M|@a7S@R&`86xqY05<34@) () -> Password.check(passwordBuffer, hash) - .with(BcryptFunction.getInstanceFromHash(hash))); - } finally { - cleanup(passwordBuffer); + SecurityManager securityManager = System.getSecurityManager(); + if (securityManager != null) { + securityManager.checkPermission(new SpecialPermission()); } - } - - private void cleanup(CharBuffer password) { - password.clear(); - char[] passwordOverwrite = new char[password.capacity()]; - Arrays.fill(passwordOverwrite, '\0'); - password.put(passwordOverwrite); + return AccessController.doPrivileged( + (PrivilegedAction) () -> Password.check(passwordBuffer, hash).with(BcryptFunction.getInstanceFromHash(hash)) + ); } } diff --git a/plugins/repository-azure/build.gradle b/plugins/repository-azure/build.gradle index 332651e37cfa4..5dae37d80ca6a 100644 --- a/plugins/repository-azure/build.gradle +++ b/plugins/repository-azure/build.gradle @@ -213,9 +213,6 @@ thirdPartyAudit { // Worth nothing that, the latest dependency "net.shibboleth.utilities:java-support:8.0.0" has many vulnerabilities. // Hence ignored. 'net.shibboleth.utilities.java.support.xml.SerializeSupport', - 'org.bouncycastle.asn1.pkcs.PrivateKeyInfo', - 'org.bouncycastle.asn1.x509.AlgorithmIdentifier', - 'org.bouncycastle.asn1.x509.SubjectPublicKeyInfo', 'org.bouncycastle.cert.X509CertificateHolder', 'org.bouncycastle.cert.jcajce.JcaX509CertificateHolder', 'org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder', diff --git a/plugins/telemetry-otel/build.gradle b/plugins/telemetry-otel/build.gradle index 54f4f2f897562..3c8aedb4a12d2 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( @@ -73,8 +73,6 @@ thirdPartyAudit { 'io.grpc.stub.AbstractFutureStub', 'io.grpc.stub.AbstractStub', 'io.grpc.stub.ClientCalls', - 'org.bouncycastle.jsse.BCSSLParameters', - 'org.bouncycastle.jsse.BCSSLSocket', 'org.conscrypt.Conscrypt', 'org.conscrypt.Conscrypt$Version', 'org.conscrypt.ConscryptHostnameVerifier', diff --git a/server/build.gradle b/server/build.gradle index e9bc4be170feb..875b25a671081 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -115,8 +115,8 @@ dependencies { api libs.roaringbitmap // bouncyCastle - api 'org.bouncycastle:bc-fips:1.0.2.5' - api 'org.bouncycastle:bctls-fips:1.0.19' + api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" + api "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" testImplementation 'org.awaitility:awaitility:4.2.2' testImplementation(project(":test:framework")) { @@ -154,6 +154,10 @@ tasks.named("forbiddenPatterns").configure { exclude '**/*.meta' } +tasks.named("dependencyLicenses").configure { + mapping from: /bc.*/, to: 'bouncycastle' +} + tasks.named("testingConventions").configure { naming.clear() naming { @@ -345,6 +349,18 @@ tasks.named("thirdPartyAudit").configure { 'com.google.protobuf.UnsafeUtil$MemoryAccessor', 'org.apache.logging.log4j.core.util.internal.UnsafeUtil', 'org.apache.logging.log4j.core.util.internal.UnsafeUtil$1', + 'org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$CoreSecureRandom', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$BaseTLSKeyGeneratorSpi', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSKeyMaterialGenerator', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSKeyMaterialGenerator$2', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSMasterSecretGenerator', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSMasterSecretGenerator$2', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSPRFKeyGenerator', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSRsaPreMasterSecretGenerator', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSRsaPreMasterSecretGenerator$2', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator', + 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator$2', 'reactor.core.publisher.Traces$SharedSecretsCallSiteSupplierFactory$TracingException' ) } diff --git a/server/licenses/bc-fips-1.0.2.4.jar.sha1 b/server/licenses/bc-fips-1.0.2.4.jar.sha1 new file mode 100644 index 0000000000000..da37449f80d7e --- /dev/null +++ b/server/licenses/bc-fips-1.0.2.4.jar.sha1 @@ -0,0 +1 @@ +9008d04fc13da6455e6a792935b93b629757335d \ No newline at end of file diff --git a/server/licenses/bctls-fips-1.0.19.jar.sha1 b/server/licenses/bctls-fips-1.0.19.jar.sha1 new file mode 100644 index 0000000000000..7f0eb6e1c6bea --- /dev/null +++ b/server/licenses/bctls-fips-1.0.19.jar.sha1 @@ -0,0 +1 @@ +b15d650f6e2a9de08d5569e25a642b6a384dbfd2 \ No newline at end of file diff --git a/server/licenses/bouncycastle-LICENSE.txt b/server/licenses/bouncycastle-LICENSE.txt new file mode 100644 index 0000000000000..5c7c14696849d --- /dev/null +++ b/server/licenses/bouncycastle-LICENSE.txt @@ -0,0 +1,14 @@ +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/server/licenses/bouncycastle-NOTICE.txt b/server/licenses/bouncycastle-NOTICE.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/server/licenses/bouncycastle-NOTICE.txt @@ -0,0 +1 @@ + diff --git a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java index 66cc85ee33756..69816e435c48f 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -199,14 +199,13 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot ); var isFipsEnabled = FipsSettings.FIPS_ENABLED.get(settings); - var isRunningInFipsMode = CryptoServicesRegistrar.setApprovedOnlyMode(isFipsEnabled); - - if (!isRunningInFipsMode && isFipsEnabled){ - throw new BootstrapException(new RuntimeException("cannot enable FIPS mode")); - } - - if (isRunningInFipsMode) { - LogManager.getLogger(Bootstrap.class).info("running in FIPS mode"); + try { + var isRunningInFipsMode = CryptoServicesRegistrar.setApprovedOnlyMode(isFipsEnabled); + if (isRunningInFipsMode) { + LogManager.getLogger(Bootstrap.class).info("running in FIPS mode"); + } + } catch (Exception e) { + throw new BootstrapException(e); } // initialize probes before the security manager is installed diff --git a/server/src/main/java/org/opensearch/common/settings/FipsSettings.java b/server/src/main/java/org/opensearch/common/settings/FipsSettings.java index 958a1440ec29f..9f667b1497a2e 100644 --- a/server/src/main/java/org/opensearch/common/settings/FipsSettings.java +++ b/server/src/main/java/org/opensearch/common/settings/FipsSettings.java @@ -15,9 +15,6 @@ */ public class FipsSettings { - public static final Setting FIPS_ENABLED = Setting.boolSetting( - "fips.approved", - false, - Property.NodeScope); + public static final Setting FIPS_ENABLED = Setting.boolSetting("fips.approved", false, Property.NodeScope); } 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 ed58e6b21e165..81fb1309df310 100644 --- a/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java +++ b/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java @@ -449,7 +449,7 @@ private byte[] encrypt(char[] password, byte[] salt, byte[] iv) throws GeneralSe private void decryptLegacyEntries() throws GeneralSecurityException, IOException { // v1 and v2 keystores never had passwords actually used, so we always use an empty password - KeyStore keystore = KeyStore.getInstance("PKCS12"); + KeyStore keystore = KeyStore.getInstance("PKCS12", "SUN"); Map settingTypes = new HashMap<>(); ByteArrayInputStream inputBytes = new ByteArrayInputStream(dataBytes); try (DataInputStream input = new DataInputStream(inputBytes)) { @@ -488,7 +488,7 @@ private void decryptLegacyEntries() throws GeneralSecurityException, IOException // fill in the entries now that we know all the types to expect this.entries.set(new HashMap<>()); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBE"); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); KeyStore.PasswordProtection password = new KeyStore.PasswordProtection("".toCharArray()); for (Map.Entry settingEntry : settingTypes.entrySet()) { diff --git a/server/src/main/resources/org/opensearch/bootstrap/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index ee75ad91904a4..fb3d62ab432db 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -95,24 +95,21 @@ grant codeBase "${codebase.reactor-core}" { // security grant { + permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; + permission java.lang.RuntimePermission "accessDeclaredMembers"; + permission java.lang.RuntimePermission "getProtectionDomain"; + permission java.io.FilePermission "${java.home}/lib/security/jssecacerts", "read"; + permission java.io.FilePermission "${java.home}/lib/security/cacerts", "read"; permission java.security.SecurityPermission "getProperty.jdk.tls.disabledAlgorithms"; permission java.security.SecurityPermission "getProperty.jdk.certpath.disabledAlgorithms"; permission java.security.SecurityPermission "getProperty.keystore.type.compat"; permission java.security.SecurityPermission "getProperty.org.bouncycastle.*"; permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; permission java.security.SecurityPermission "putProviderProperty.BCJSSE"; - permission java.lang.RuntimePermission "getProtectionDomain"; permission java.util.PropertyPermission "java.runtime.name", "read"; - permission org.bouncycastle.crypto.CryptoServicesPermission "changeToApprovedModeEnabled"; - permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled"; - //io.netty.handler.codec.DecoderException - permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; - //java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec - permission java.lang.RuntimePermission "accessDeclaredMembers"; permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; - permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read"; - permission java.io.FilePermission "${javaHome}/lib/security/jssecacerts", "read"; + permission java.security.SecurityPermission "getProperty.jdk.tls.server.defaultDHEParameters"; }; //// Everything else: diff --git a/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy b/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy index 78f302e9b23db..8492c9f12fdd8 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy @@ -167,5 +167,5 @@ grant { permission org.opensearch.secure_sm.ThreadContextPermission "markAsSystemContext"; permission org.opensearch.secure_sm.ThreadContextPermission "stashAndMergeHeaders"; permission org.opensearch.secure_sm.ThreadContextPermission "stashWithOrigin"; - permission java.lang.RuntimePermission "setDefaultUncaughtExceptionHandler"; + permission org.bouncycastle.crypto.CryptoServicesPermission "changeToApprovedModeEnabled"; }; From 30c0b323913a1f01764fbe50afad6abc5039cc85 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Thu, 8 Aug 2024 09:48:15 +0200 Subject: [PATCH 03/21] disable approved-only mode for launch configuration of testcluster Signed-off-by: Iwan Igonin # Conflicts: # buildSrc/version.properties --- .../java/org/opensearch/gradle/testclusters/OpenSearchNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 974ef2c89efae..1f4f4a447a1fd 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java @@ -1187,7 +1187,7 @@ private void createConfiguration() { baseConfig.put("indices.breaker.total.use_real_memory", "false"); // Don't wait for state, just start up quickly. This will also allow new and old nodes in the BWC case to become the master baseConfig.put("discovery.initial_state_timeout", "0s"); - baseConfig.put("fips.approved", "true"); + baseConfig.put("fips.approved", "false"); HashSet overriden = new HashSet<>(baseConfig.keySet()); overriden.retainAll(settings.keySet()); From 0ef725d2b91d448f3a3b80aa8143d74b0661800d Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Thu, 8 Aug 2024 13:09:40 +0200 Subject: [PATCH 04/21] update all BC libraries to support JAVA 21 Signed-off-by: Iwan Igonin --- client/rest/build.gradle | 13 +------------ client/rest/licenses/bc-fips-1.0.2.4.jar.sha1 | 1 - client/rest/licenses/bc-fips-2.0.0.jar.sha1 | 1 + client/rest/licenses/bctls-fips-1.0.19.jar.sha1 | 1 - client/rest/licenses/bctls-fips-2.0.19.jar.sha1 | 1 + client/rest/licenses/bcutil-fips-2.0.3.jar.sha1 | 1 + .../plugin-cli/licenses/bc-fips-2.0.0.jar.sha1 | 1 - gradle/libs.versions.toml | 9 +++++---- libs/ssl-config/build.gradle | 1 + libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 | 1 - libs/ssl-config/licenses/bcpkix-fips-2.0.7.jar.sha1 | 1 + server/build.gradle | 13 +------------ server/licenses/bc-fips-1.0.2.4.jar.sha1 | 1 - server/licenses/bc-fips-2.0.0.jar.sha1 | 1 + server/licenses/bctls-fips-1.0.19.jar.sha1 | 1 - server/licenses/bctls-fips-2.0.19.jar.sha1 | 1 + server/licenses/bcutil-fips-2.0.3.jar.sha1 | 1 + 17 files changed, 15 insertions(+), 34 deletions(-) delete mode 100644 client/rest/licenses/bc-fips-1.0.2.4.jar.sha1 create mode 100644 client/rest/licenses/bc-fips-2.0.0.jar.sha1 delete mode 100644 client/rest/licenses/bctls-fips-1.0.19.jar.sha1 create mode 100644 client/rest/licenses/bctls-fips-2.0.19.jar.sha1 create mode 100644 client/rest/licenses/bcutil-fips-2.0.3.jar.sha1 delete mode 100644 distribution/tools/plugin-cli/licenses/bc-fips-2.0.0.jar.sha1 delete mode 100644 libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 create mode 100644 libs/ssl-config/licenses/bcpkix-fips-2.0.7.jar.sha1 delete mode 100644 server/licenses/bc-fips-1.0.2.4.jar.sha1 create mode 100644 server/licenses/bc-fips-2.0.0.jar.sha1 delete mode 100644 server/licenses/bctls-fips-1.0.19.jar.sha1 create mode 100644 server/licenses/bctls-fips-2.0.19.jar.sha1 create mode 100644 server/licenses/bcutil-fips-2.0.3.jar.sha1 diff --git a/client/rest/build.gradle b/client/rest/build.gradle index 8e3d82c9a95b8..049d66eb99ddb 100644 --- a/client/rest/build.gradle +++ b/client/rest/build.gradle @@ -53,6 +53,7 @@ dependencies { api "org.slf4j:slf4j-api:${versions.slf4j}" api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" api "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" + api "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" // reactor api "io.projectreactor:reactor-core:${versions.reactor}" @@ -147,18 +148,6 @@ thirdPartyAudit { 'reactor.blockhound.integration.BlockHoundIntegration' ) ignoreViolations( - 'org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$CoreSecureRandom', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$BaseTLSKeyGeneratorSpi', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSKeyMaterialGenerator', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSKeyMaterialGenerator$2', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSMasterSecretGenerator', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSMasterSecretGenerator$2', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSPRFKeyGenerator', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSRsaPreMasterSecretGenerator', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSRsaPreMasterSecretGenerator$2', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator$2', 'reactor.core.publisher.Traces$SharedSecretsCallSiteSupplierFactory$TracingException' ) } diff --git a/client/rest/licenses/bc-fips-1.0.2.4.jar.sha1 b/client/rest/licenses/bc-fips-1.0.2.4.jar.sha1 deleted file mode 100644 index da37449f80d7e..0000000000000 --- a/client/rest/licenses/bc-fips-1.0.2.4.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -9008d04fc13da6455e6a792935b93b629757335d \ No newline at end of file diff --git a/client/rest/licenses/bc-fips-2.0.0.jar.sha1 b/client/rest/licenses/bc-fips-2.0.0.jar.sha1 new file mode 100644 index 0000000000000..d635985983ebc --- /dev/null +++ b/client/rest/licenses/bc-fips-2.0.0.jar.sha1 @@ -0,0 +1 @@ +ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab diff --git a/client/rest/licenses/bctls-fips-1.0.19.jar.sha1 b/client/rest/licenses/bctls-fips-1.0.19.jar.sha1 deleted file mode 100644 index 7f0eb6e1c6bea..0000000000000 --- a/client/rest/licenses/bctls-fips-1.0.19.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -b15d650f6e2a9de08d5569e25a642b6a384dbfd2 \ No newline at end of file diff --git a/client/rest/licenses/bctls-fips-2.0.19.jar.sha1 b/client/rest/licenses/bctls-fips-2.0.19.jar.sha1 new file mode 100644 index 0000000000000..2c7aff0e46a13 --- /dev/null +++ b/client/rest/licenses/bctls-fips-2.0.19.jar.sha1 @@ -0,0 +1 @@ +9cc33650ede63bc1a8281ed5c8e1da314d50bc76 diff --git a/client/rest/licenses/bcutil-fips-2.0.3.jar.sha1 b/client/rest/licenses/bcutil-fips-2.0.3.jar.sha1 new file mode 100644 index 0000000000000..d553536576656 --- /dev/null +++ b/client/rest/licenses/bcutil-fips-2.0.3.jar.sha1 @@ -0,0 +1 @@ +a1857cd639295b10cc90e6d31ecbc523cdafcc19 \ No newline at end of file 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 deleted file mode 100644 index 79f0e3e9930bb..0000000000000 --- a/distribution/tools/plugin-cli/licenses/bc-fips-2.0.0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 07928ca3a4942..9dc0f5ad58d69 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -58,10 +58,11 @@ reactivestreams = "1.0.4" # when updating this version, you need to ensure compatibility with: # - plugins/ingest-attachment (transitive dependency, check the upstream POM) # - distribution/tools/plugin-cli -bouncycastle_jce = "1.0.2.4" -bouncycastle_tls = "1.0.19" -bouncycastle_pkix = "1.0.7" -bouncycastle_pg = "1.0.7.1" +bouncycastle_jce = "2.0.0" +bouncycastle_tls = "2.0.19" +bouncycastle_pkix = "2.0.7" +bouncycastle_pg = "2.0.8" +bouncycastle_util = "2.0.3" # test dependencies randomizedrunner = "2.7.1" junit = "4.13.2" diff --git a/libs/ssl-config/build.gradle b/libs/ssl-config/build.gradle index f2da097a7fe09..68f3cae7a0ac2 100644 --- a/libs/ssl-config/build.gradle +++ b/libs/ssl-config/build.gradle @@ -38,6 +38,7 @@ dependencies { // bouncyCastle implementation "org.bouncycastle:bcpkix-fips:${versions.bouncycastle_pkix}" compileOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" + compileOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" testImplementation(project(":test:framework")) { exclude group: 'org.opensearch', module: 'opensearch-ssl-config' diff --git a/libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 b/libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 deleted file mode 100644 index 57cf4cfe10b5f..0000000000000 --- a/libs/ssl-config/licenses/bcpkix-fips-1.0.7.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -fe07959721cfa2156be9722ba20fdfee2b5441b0 diff --git a/libs/ssl-config/licenses/bcpkix-fips-2.0.7.jar.sha1 b/libs/ssl-config/licenses/bcpkix-fips-2.0.7.jar.sha1 new file mode 100644 index 0000000000000..ff463e602ad88 --- /dev/null +++ b/libs/ssl-config/licenses/bcpkix-fips-2.0.7.jar.sha1 @@ -0,0 +1 @@ +01eea0f325315ca6295b0a6926ff862d8001cdf9 diff --git a/server/build.gradle b/server/build.gradle index 875b25a671081..f12dfaaba91de 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -117,6 +117,7 @@ dependencies { // bouncyCastle api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" api "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" + api "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" testImplementation 'org.awaitility:awaitility:4.2.2' testImplementation(project(":test:framework")) { @@ -349,18 +350,6 @@ tasks.named("thirdPartyAudit").configure { 'com.google.protobuf.UnsafeUtil$MemoryAccessor', 'org.apache.logging.log4j.core.util.internal.UnsafeUtil', 'org.apache.logging.log4j.core.util.internal.UnsafeUtil$1', - 'org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$CoreSecureRandom', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$BaseTLSKeyGeneratorSpi', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSKeyMaterialGenerator', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSKeyMaterialGenerator$2', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSMasterSecretGenerator', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSMasterSecretGenerator$2', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSPRFKeyGenerator', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSRsaPreMasterSecretGenerator', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSRsaPreMasterSecretGenerator$2', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator', - 'org.bouncycastle.jcajce.provider.ProvSunTLSKDF$TLSExtendedMasterSecretGenerator$2', 'reactor.core.publisher.Traces$SharedSecretsCallSiteSupplierFactory$TracingException' ) } diff --git a/server/licenses/bc-fips-1.0.2.4.jar.sha1 b/server/licenses/bc-fips-1.0.2.4.jar.sha1 deleted file mode 100644 index da37449f80d7e..0000000000000 --- a/server/licenses/bc-fips-1.0.2.4.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -9008d04fc13da6455e6a792935b93b629757335d \ No newline at end of file diff --git a/server/licenses/bc-fips-2.0.0.jar.sha1 b/server/licenses/bc-fips-2.0.0.jar.sha1 new file mode 100644 index 0000000000000..d635985983ebc --- /dev/null +++ b/server/licenses/bc-fips-2.0.0.jar.sha1 @@ -0,0 +1 @@ +ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab diff --git a/server/licenses/bctls-fips-1.0.19.jar.sha1 b/server/licenses/bctls-fips-1.0.19.jar.sha1 deleted file mode 100644 index 7f0eb6e1c6bea..0000000000000 --- a/server/licenses/bctls-fips-1.0.19.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -b15d650f6e2a9de08d5569e25a642b6a384dbfd2 \ No newline at end of file diff --git a/server/licenses/bctls-fips-2.0.19.jar.sha1 b/server/licenses/bctls-fips-2.0.19.jar.sha1 new file mode 100644 index 0000000000000..2c7aff0e46a13 --- /dev/null +++ b/server/licenses/bctls-fips-2.0.19.jar.sha1 @@ -0,0 +1 @@ +9cc33650ede63bc1a8281ed5c8e1da314d50bc76 diff --git a/server/licenses/bcutil-fips-2.0.3.jar.sha1 b/server/licenses/bcutil-fips-2.0.3.jar.sha1 new file mode 100644 index 0000000000000..d553536576656 --- /dev/null +++ b/server/licenses/bcutil-fips-2.0.3.jar.sha1 @@ -0,0 +1 @@ +a1857cd639295b10cc90e6d31ecbc523cdafcc19 \ No newline at end of file From a33a10f678fc889bb00b1ba779dc22d17000b420 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Thu, 8 Aug 2024 17:35:13 +0200 Subject: [PATCH 05/21] try to pass bwc staged build by adding expected version Signed-off-by: Iwan Igonin --- .../java/org/opensearch/gradle/testclusters/OpenSearchNode.java | 1 - 1 file changed, 1 deletion(-) 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 1f4f4a447a1fd..aaa2daef2a158 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java @@ -1187,7 +1187,6 @@ private void createConfiguration() { baseConfig.put("indices.breaker.total.use_real_memory", "false"); // Don't wait for state, just start up quickly. This will also allow new and old nodes in the BWC case to become the master baseConfig.put("discovery.initial_state_timeout", "0s"); - baseConfig.put("fips.approved", "false"); HashSet overriden = new HashSet<>(baseConfig.keySet()); overriden.retainAll(settings.keySet()); From 54e31bedf63d2d0a9dbbc23d2b5f55c7cc980139 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Fri, 30 Aug 2024 19:03:16 +0200 Subject: [PATCH 06/21] ensure FIPS-mode can be enabled by fixing existent and creating additional tests. Signed-off-by: Iwan Igonin --- .../main/resources/test/ssl/test-node.bcfks | Bin 0 -> 3775 bytes client/rest/build.gradle | 7 +- .../client/FipsEnabledThreadFactory.java | 36 ++++ .../opensearch/client/RestClientBuilder.java | 2 + .../client/RestClientBuilderIntegTests.java | 90 ++++++---- .../RestClientDocumentation.java | 11 +- .../HeapBufferedAsyncEntityConsumerTests.java | 18 +- .../src/test/resources/test_truststore.bcfks | Bin 0 -> 1502 bytes .../src/test/resources/test_truststore.jks | Bin 1097 -> 0 bytes client/rest/src/test/resources/testks.bcfks | Bin 0 -> 2889 bytes client/rest/src/test/resources/testks.jks | Bin 2381 -> 0 bytes client/test/build.gradle | 6 + .../opensearch/client/RestClientTestCase.java | 11 +- .../opensearch/bootstrap/BootstrapTests.java | 1 + .../keystore/AddFileKeyStoreCommandTests.java | 7 +- .../AddStringKeyStoreCommandTests.java | 7 +- .../ChangeKeyStorePasswordCommandTests.java | 8 +- .../keystore/CreateKeyStoreCommandTests.java | 16 +- .../HasPasswordKeyStoreCommandTests.java | 5 +- .../cli/keystore/KeyStoreWrapperTests.java | 36 ++-- .../keystore/ListKeyStoreCommandTests.java | 4 + .../RemoveSettingKeyStoreCommandTests.java | 1 + libs/common/build.gradle | 2 + .../common/crypto/KeyStoreFactory.java | 56 +++++++ .../common/crypto/KeyStoreType.java | 68 ++++++++ libs/ssl-config/build.gradle | 1 + .../common/ssl/DefaultJdkTrustConfig.java | 4 +- .../opensearch/common/ssl/KeyStoreUtil.java | 24 +-- .../org/opensearch/common/ssl/PemUtils.java | 13 +- .../common/ssl/SslConfiguration.java | 22 +-- .../common/ssl/SslConfigurationLoader.java | 40 +++-- .../opensearch/common/ssl/StoreKeyConfig.java | 8 +- .../common/ssl/StoreTrustConfig.java | 8 +- .../common/ssl/TrustEverythingConfig.java | 12 ++ .../common/ssl/PemKeyConfigTests.java | 14 +- .../opensearch/common/ssl/PemUtilsTests.java | 91 +++++++--- .../ssl/SslConfigurationLoaderTests.java | 53 +++++- .../common/ssl/SslConfigurationTests.java | 4 +- .../common/ssl/SslDiagnosticsTests.java | 18 +- .../common/ssl/StoreKeyConfigTests.java | 93 +++++++---- .../common/ssl/StoreTrustConfigTests.java | 52 +++--- .../src/test/resources/certs/README.md | 155 ++++++++++++++++++ .../src/test/resources/certs/README.txt | 85 ---------- .../src/test/resources/certs/ca-all/ca.bcfks | Bin 0 -> 2909 bytes .../test/resources/certs/cert-all/certs.bcfks | Bin 0 -> 4901 bytes .../test/resources/certs/cert-all/certs.p12 | Bin 4757 -> 4895 bytes .../resources/certs/cert1/cert1-pkcs1.crt | 19 +++ .../resources/certs/cert1/cert1-pkcs1.key | 27 +++ .../resources/certs/cert1/cert1-pkcs8.key | 28 ---- .../src/test/resources/certs/cert1/cert1.crt | 34 ++-- .../src/test/resources/certs/cert1/cert1.key | 55 ++++--- .../src/test/resources/certs/cert1/cert1.p12 | Bin 2456 -> 2606 bytes .../resources/certs/cert2/cert2-pkcs1.crt | 19 +++ .../resources/certs/cert2/cert2-pkcs1.key | 30 ++++ .../resources/certs/cert2/cert2-pkcs8.key | 29 ---- .../src/test/resources/certs/cert2/cert2.crt | 34 ++-- .../src/test/resources/certs/cert2/cert2.key | 60 +++---- .../src/test/resources/certs/cert2/cert2.p12 | Bin 2456 -> 2606 bytes .../test/resources/certs/pem-utils/README.md | 70 +++++++- .../certs/pem-utils/key_DSA_enc_pbkdf2.pem | 18 ++ .../certs/pem-utils/key_EC_enc_pbkdf2.pem | 6 + .../certs/pem-utils/key_PKCS8_enc_pbkdf2.pem | 30 ++++ .../resources/certs/pem-utils/testnode.bcfks | Bin 0 -> 6009 bytes .../resources/certs/pem-utils/testnode.jks | Bin 9360 -> 14572 bytes .../ingest/common/DateProcessorTests.java | 1 - .../opensearch/index/reindex/Reindexer.java | 37 ++++- ...ReindexFromRemoteBuildRestClientTests.java | 2 +- .../reindex/ReindexRestClientSslTests.java | 58 +++++-- .../org/opensearch/index/reindex/README.md | 48 ++++++ .../org/opensearch/index/reindex/README.txt | 16 -- .../org/opensearch/index/reindex/ca.key | 30 ++++ .../org/opensearch/index/reindex/ca.pem | 43 +++-- .../index/reindex/client/client.crt | 35 ++-- .../index/reindex/client/client.key | 60 +++---- .../opensearch/index/reindex/http/http.crt | 38 ++--- .../opensearch/index/reindex/http/http.key | 60 +++---- .../SecureNetty4HttpServerTransportTests.java | 4 +- .../ssl/SimpleSecureNetty4TransportTests.java | 4 +- .../AzureDiscoveryClusterFormationTests.java | 9 +- plugins/repository-gcs/build.gradle | 2 +- .../gcs/GoogleCloudStorageService.java | 13 +- .../gcs/GoogleCloudStorageServiceTests.java | 2 +- .../repositories/gcs/TestUtils.java | 2 +- .../packaging/util/ServerUtils.java | 4 +- .../common/settings/KeyStoreWrapper.java | 8 +- .../org/opensearch/bootstrap/security.policy | 5 +- .../test/OpenSearchIntegTestCase.java | 4 - .../opensearch/test/OpenSearchTestCase.java | 9 +- .../test/rest/OpenSearchRestTestCase.java | 4 +- 89 files changed, 1404 insertions(+), 642 deletions(-) create mode 100644 buildSrc/src/main/resources/test/ssl/test-node.bcfks create mode 100644 client/rest/src/main/java/org/opensearch/client/FipsEnabledThreadFactory.java create mode 100644 client/rest/src/test/resources/test_truststore.bcfks delete mode 100644 client/rest/src/test/resources/test_truststore.jks create mode 100644 client/rest/src/test/resources/testks.bcfks delete mode 100644 client/rest/src/test/resources/testks.jks create mode 100644 libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java create mode 100644 libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java create mode 100644 libs/ssl-config/src/test/resources/certs/README.md delete mode 100644 libs/ssl-config/src/test/resources/certs/README.txt create mode 100644 libs/ssl-config/src/test/resources/certs/ca-all/ca.bcfks create mode 100644 libs/ssl-config/src/test/resources/certs/cert-all/certs.bcfks create mode 100644 libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs1.crt create mode 100644 libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs1.key delete mode 100644 libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs8.key create mode 100644 libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs1.crt create mode 100644 libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs1.key delete mode 100644 libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs8.key create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/key_DSA_enc_pbkdf2.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/key_EC_enc_pbkdf2.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/key_PKCS8_enc_pbkdf2.pem create mode 100644 libs/ssl-config/src/test/resources/certs/pem-utils/testnode.bcfks create mode 100644 modules/reindex/src/test/resources/org/opensearch/index/reindex/README.md delete mode 100644 modules/reindex/src/test/resources/org/opensearch/index/reindex/README.txt create mode 100644 modules/reindex/src/test/resources/org/opensearch/index/reindex/ca.key 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 0000000000000000000000000000000000000000..141eec9d66ded9d995087bcf2391631ce799b2b3 GIT binary patch literal 3775 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 literal 0 HcmV?d00001 diff --git a/client/rest/build.gradle b/client/rest/build.gradle index 049d66eb99ddb..87cf3d460752f 100644 --- a/client/rest/build.gradle +++ b/client/rest/build.gradle @@ -34,8 +34,8 @@ apply plugin: 'opensearch.build' apply plugin: 'opensearch.publish' java { - targetCompatibility = JavaVersion.VERSION_1_8 - sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_11 } base { @@ -60,6 +60,7 @@ dependencies { api "org.reactivestreams:reactive-streams:${versions.reactivestreams}" testImplementation project(":client:test") + testImplementation project(':libs:opensearch-common') testImplementation "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" testImplementation "junit:junit:${versions.junit}" testImplementation "org.hamcrest:hamcrest:${versions.hamcrest}" @@ -71,6 +72,7 @@ dependencies { testImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" testImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}" testImplementation "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}" + testImplementation "commons-io:commons-io:${versions.commonsio}" } tasks.named("dependencyLicenses").configure { @@ -83,6 +85,7 @@ tasks.withType(CheckForbiddenApis).configureEach { } forbiddenPatterns { + exclude '**/*.bcfks' exclude '**/*.der' } diff --git a/client/rest/src/main/java/org/opensearch/client/FipsEnabledThreadFactory.java b/client/rest/src/main/java/org/opensearch/client/FipsEnabledThreadFactory.java new file mode 100644 index 0000000000000..2efdc48c70f59 --- /dev/null +++ b/client/rest/src/main/java/org/opensearch/client/FipsEnabledThreadFactory.java @@ -0,0 +1,36 @@ +/* + * 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.apache.hc.core5.concurrent.DefaultThreadFactory; +import org.bouncycastle.crypto.CryptoServicesRegistrar; + +import java.util.concurrent.ThreadFactory; + +public class FipsEnabledThreadFactory implements ThreadFactory { + + private final ThreadFactory defaultFactory; + private final boolean isFipsEnabled; + + public FipsEnabledThreadFactory(String namePrefix, boolean isFipsEnabled) { + this.defaultFactory = new DefaultThreadFactory(namePrefix); + this.isFipsEnabled = isFipsEnabled; + } + + @Override + public Thread newThread(final Runnable target) { + return defaultFactory.newThread(() -> { + if (isFipsEnabled) { + CryptoServicesRegistrar.setApprovedOnlyMode(true); + } + target.run(); + }); + } + +} diff --git a/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java b/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java index 3e38f9ae95dec..0a46da6e1e8b0 100644 --- a/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java +++ b/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java @@ -48,6 +48,7 @@ import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Timeout; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -334,6 +335,7 @@ public TlsDetails create(final SSLEngine sslEngine) { HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClientBuilder.create() .setDefaultRequestConfig(requestConfigBuilder.build()) .setConnectionManager(connectionManager) + .setThreadFactory(new FipsEnabledThreadFactory("os-client-dispatcher", CryptoServicesRegistrar.isInApprovedOnlyMode())) .setTargetAuthenticationStrategy(DefaultAuthenticationStrategy.INSTANCE) .disableAutomaticRetries(); if (httpClientConfigCallback != null) { 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 46f6cfa22096b..db578ebb196ac 100644 --- a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java +++ b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java @@ -38,6 +38,9 @@ import com.sun.net.httpserver.HttpsServer; import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.KeyStoreType; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -50,19 +53,18 @@ import java.io.InputStream; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.nio.file.Files; -import java.nio.file.Paths; import java.security.AccessController; -import java.security.KeyFactory; import java.security.KeyStore; import java.security.PrivilegedAction; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.spec.PKCS8EncodedKeySpec; +import java.security.SecureRandom; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +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; /** @@ -75,8 +77,11 @@ public class RestClientBuilderIntegTests extends RestClientTestCase { @BeforeClass public static void startHttpServer() throws Exception { httpsServer = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); - httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext())); + httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext(true))); httpsServer.createContext("/", new ResponseHandler()); + var threadFactory = new FipsEnabledThreadFactory("test-httpserver-dispatch", inFipsJvm()); + Executor executor = Executors.newFixedThreadPool(1, threadFactory); + httpsServer.setExecutor(executor); httpsServer.start(); } @@ -90,8 +95,22 @@ public void handle(HttpExchange httpExchange) throws IOException { @AfterClass public static void stopHttpServers() throws IOException { - httpsServer.stop(0); - httpsServer = null; + if (httpsServer != null) { + httpsServer.stop(0); // Stop the server + Executor executor = httpsServer.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(); + } + } + httpsServer = null; + } } public void testBuilderUsesDefaultSSLContext() throws Exception { @@ -106,7 +125,7 @@ public void testBuilderUsesDefaultSSLContext() throws Exception { } } - SSLContext.setDefault(getSslContext()); + SSLContext.setDefault(getSslContext(false)); try (RestClient client = buildRestClient()) { Response response = client.performRequest(new Request("GET", "/")); assertEquals(200, response.getStatusLine().getStatusCode()); @@ -121,34 +140,37 @@ private RestClient buildRestClient() { return RestClient.builder(new HttpHost("https", address.getHostString(), address.getPort())).build(); } - private static SSLContext getSslContext() throws Exception { - SSLContext sslContext = SSLContext.getInstance(getProtocol()); + private static SSLContext getSslContext(boolean server) throws Exception { + SSLContext sslContext; + char[] password = "password".toCharArray(); + SecureRandom secureRandom = SecureRandom.getInstance("DEFAULT", "BCFIPS"); + try ( - InputStream certFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test.crt"); - InputStream keyStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test_truststore.jks") + InputStream trustStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test_truststore.bcfks"); + InputStream keyStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/testks.bcfks") ) { - // Build a keystore of default type programmatically since we can't use JKS keystores to - // init a KeyManagerFactory in FIPS 140 JVMs. - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null, "password".toCharArray()); - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec( - Files.readAllBytes(Paths.get(RestClientBuilderIntegTests.class.getResource("/test.der").toURI())) - ); - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - keyStore.setKeyEntry( - "mykey", - keyFactory.generatePrivate(privateKeySpec), - "password".toCharArray(), - new Certificate[] { certFactory.generateCertificate(certFile) } - ); + KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); + keyStore.load(keyStoreFile, password); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(keyStore, "password".toCharArray()); - KeyStore trustStore = KeyStore.getInstance("JKS"); - trustStore.load(keyStoreFile, "password".toCharArray()); + kmf.init(keyStore, password); + + KeyStore trustStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); + trustStore.load(trustStoreFile, password); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + var sslContextBuilder = SSLContextBuilder.create() + .setProvider("BCJSSE") + .setProtocol(getProtocol()) + .setSecureRandom(secureRandom); + + if (server) { + sslContextBuilder.loadKeyMaterial(keyStore, password).build(); + } else { + sslContextBuilder.loadTrustMaterial(trustStore, null); + } + sslContext = sslContextBuilder.build(); + } return sslContext; } 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..60cbf689d8853 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.crypto.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.crypto.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/java/org/opensearch/client/nio/HeapBufferedAsyncEntityConsumerTests.java b/client/rest/src/test/java/org/opensearch/client/nio/HeapBufferedAsyncEntityConsumerTests.java index fdfe49ca901c9..6a4b176edd011 100644 --- a/client/rest/src/test/java/org/opensearch/client/nio/HeapBufferedAsyncEntityConsumerTests.java +++ b/client/rest/src/test/java/org/opensearch/client/nio/HeapBufferedAsyncEntityConsumerTests.java @@ -35,34 +35,34 @@ public void tearDown() { } public void testConsumerAllocatesBufferLimit() throws IOException { - consumer.consume((ByteBuffer) randomByteBufferOfLength(1000).flip()); + consumer.consume(randomByteBufferOfLength(1000).flip()); assertThat(consumer.getBuffer().capacity(), equalTo(1000)); } public void testConsumerAllocatesEmptyBuffer() throws IOException { - consumer.consume((ByteBuffer) ByteBuffer.allocate(0).flip()); + consumer.consume(ByteBuffer.allocate(0).flip()); assertThat(consumer.getBuffer().capacity(), equalTo(0)); } public void testConsumerExpandsBufferLimits() throws IOException { - consumer.consume((ByteBuffer) randomByteBufferOfLength(1000).flip()); - consumer.consume((ByteBuffer) randomByteBufferOfLength(2000).flip()); - consumer.consume((ByteBuffer) randomByteBufferOfLength(3000).flip()); + consumer.consume(randomByteBufferOfLength(1000).flip()); + consumer.consume(randomByteBufferOfLength(2000).flip()); + consumer.consume(randomByteBufferOfLength(3000).flip()); assertThat(consumer.getBuffer().capacity(), equalTo(6000)); } public void testConsumerAllocatesLimit() throws IOException { - consumer.consume((ByteBuffer) randomByteBufferOfLength(BUFFER_LIMIT).flip()); + consumer.consume(randomByteBufferOfLength(BUFFER_LIMIT).flip()); assertThat(consumer.getBuffer().capacity(), equalTo(BUFFER_LIMIT)); } public void testConsumerFailsToAllocateOverLimit() throws IOException { - assertThrows(ContentTooLongException.class, () -> consumer.consume((ByteBuffer) randomByteBufferOfLength(BUFFER_LIMIT + 1).flip())); + assertThrows(ContentTooLongException.class, () -> consumer.consume(randomByteBufferOfLength(BUFFER_LIMIT + 1).flip())); } public void testConsumerFailsToExpandOverLimit() throws IOException { - consumer.consume((ByteBuffer) randomByteBufferOfLength(BUFFER_LIMIT).flip()); - assertThrows(ContentTooLongException.class, () -> consumer.consume((ByteBuffer) randomByteBufferOfLength(1).flip())); + consumer.consume(randomByteBufferOfLength(BUFFER_LIMIT).flip()); + assertThrows(ContentTooLongException.class, () -> consumer.consume(randomByteBufferOfLength(1).flip())); } private static ByteBuffer randomByteBufferOfLength(int length) { 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 0000000000000000000000000000000000000000..dfa168caf70ef59a030b72e7993bb35927f3ce10 GIT binary patch literal 1502 zcmV<41tIz{f(6~*CNl$vc#K4f8~DJ;uHAH8H2tmRKlybR%~MlHuuZ#P4h zur-)dAbuj~x&i_~00IFZFboC=Duzgg_YDFI1pqJ}1_@w>NC9O71OYEF5d;i9<6?-e zDu)k!w?be70SE+w1cHi`ZlDi6NkPK~s2gZVgo1UR>sPQ0`{ zzd6*kKlu5!)I1W@YFzoTz;RGk8%BW{DdfSE!(y|?i;^F!`2dBYAzO-7NCHoS#9p)VkZ z*Bha^9ofRL_-bTRH#^~5B44iq+w#^rfzbnRvFNM1{PgNDON~;n*xX?zjC!>i-X>sq zRTpztnGc%94m;{{%3io>ni{0o5Z%Z>qf)Y=u0Fb%s!P(CY0x-Af+d=Sp9^&{$OPKH zmn&)dx%FHLso=}rwynAvO!~1>S?c!ZOp?6XYr%fO12%00tN*E~ht6UuLb*ooIrIyx ze;JAk*v2v9V$DzrEzqy$QJDO zxAXL>qBP$x@;&81m-Dnk<-VTEN8R^{Taw_EMwVqarK zE*I!Pz0FMUh-Qz1N;qG2f5m`lqNl|5W72a)JHCNfjEpw@;V3FMdZ2B?=PD-tMOS*T_D9HW=YbLNgDeOS?W5 zs}I>~^an-xvvDDrpYTFV6w>9;BwPn67mg8}q{E)X1=Fn86J=6e|7)FJS7W8;ZQ7e5 zrIP5s-HM8-jeDBTFoCu(3%Btc9zF2597a@-!C53d6M2NX(sO{ z0n@L`@GBXwuX)Fw^zC?BFbe_#KmY;(Krjpj2r7n1hW8Bu3k3iKKz4T*oY$ E?DD(L)c^nh literal 0 HcmV?d00001 diff --git a/client/rest/src/test/resources/test_truststore.jks b/client/rest/src/test/resources/test_truststore.jks deleted file mode 100644 index d27635fec5040d63969f7d124d499fa1b4788d1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1097 zcmezO_TO6u1_mY|W(3pRB}JvhC8;UNsYN9~vAkwc`ArP05qhQumJAFmtOiXij0R0i zFBdQ~F)}f+SnhC6G2mt6)N1o+`_9YA$j!=NkZ#Crz{$oO%EBhh6dDZUa0oLwI~s}^ zh=63+g?WAROY=$+GxHR}GE>V91q}E=;#|V)sX2+oC7H>FyawDL5pH3YlGNf7Lm>kJ z5SLk)6U0@>%TGx)kQ3)MG&e9eG&eLeG%_`d66ZAnaZRCI`Z=VDQ3*K^7+D#Zn;7{S z44N3Zn3@9EiBm+{dKY&Mx?Dh3UI&DWy9-kNdy3Y1&zCVq0?3+gn8M zW^BmU&9hebbM`WL%SM>)H`p6)xX$#V^e3^_c|yKH+g&^_eww-4(bN0WeUHXpj<24| z8#s44{mgM{<-Ekjav*FHySkd1LILvkfpKRps}T$r5>2Z+690q z4;e5vapvSFC+1}27nd}N!kKztR!V*@FxfP5!G%ql#1U*{H6}=k%#akBAuBR7kOeu2 zk420{B!;(W5m!xu6HAtD?Gnzr*6g$93L!@ePE8)StWekcf6T@8>!Hn2u@bR@rlREmQe3 z*E!m4Dbw#AUFZ2b>f&VICND7KS>ox+=yy2j@n+^nCE4?htut-6e$*Az+UNcJvug@p zxy3{0kBJ}Or$o&-*t0S9IK#8deQaWSimx8m{Sg0s=1fG3q~-m^ZtGbxL!O1KZ8*=P zSUgEB^1BHS@5>phh28)F diff --git a/client/rest/src/test/resources/testks.bcfks b/client/rest/src/test/resources/testks.bcfks new file mode 100644 index 0000000000000000000000000000000000000000..988242f0db6b8296ef66da25e1d7d4ced35b246b GIT binary patch literal 2889 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 literal 0 HcmV?d00001 diff --git a/client/rest/src/test/resources/testks.jks b/client/rest/src/test/resources/testks.jks deleted file mode 100644 index cd706db5e3834f4b44493530bca17697f32d8de2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2381 zcmcgt`9IYA8vlOB80%QF%P^K4yYI*ll4T;A?8cHMVh&T*Y{!}zYjfo2&U*Xwz`-tYJ4{loKmp6B&EKURON0ssIz4)Cv__YA&G z3_fNKX6Ang002DBTG4J7uREh5mJ7*;DV;dAlA=s5+KPl4wULQJdoL8~Pe7D3^*Cpn+O9sr03 zISo5KN{!m#CIL0)yz>t}H?Dji7ewQeMTpah_;lIM9lfx^{MI46EY=H2Gb87zSV!L9 zBNwGc)hcRNP-{ZbigjTAb+b#asOW1;eDvzR6aB?nZ0b51o)HL=(P6?em8T=Dtimf@ zkw!zNEH-cfeLsno#O=RbxSvAmmq_5eGP;=CTVxI0y~COhkJiND1UdLS2Ol7#CcE^d zL>{Nth-L14Z4j+X`yfibe=ag4rVlxJp#US7Q|y@QX^G~`3fl?pyl5Y^R8rIQWW4Id zQ{z=r`vr-qyIpkx)5AVH*xUo_`4Nx4b(N?wT{OYbRG2-rcbCh$ubPg4`&+@r5D zzPUX@zCiejz)?}CTe3&^u7rVdbjjY6C5l@V4|cw6NVPj+>;MVdCH6G z;*UbVqziZ{8{q|$yRDA6AbU?YM#cWH7~gF>>!8r(I*ID&@IdyK9Vh)mhm9(9QprF* zTcmmB5ofTGhE^wgc6e}~gaOj0+F~@7M8SRe&>AqUy`aoNh?gL%tOLG=Z`F!b~nE6fm|g zn~sp%GU*wT?a;|Lx{14y5rz@Zi*Ei`1^Asfnk+f1QoG!rBKWDm!N)n4tjm$w;#(Ms z*y4S9lR%xR>-qfN9B<|yeOK)-bH747=ZNpA3?IQ6RWFHIE8SgO`efbu{fcV~?x(fP{fMBSHJgUi@HE~B!hGqp zOO=HE;j=bw>XQc_Lz$N7+B!oz5O}9z4>`!sl^^yX__= zt-G_fDHBHnrXd?sZVgtfT~YRfOVPRZGOfh@@i4+lcQw=8yf9tQpirKyrJ1>K}mXUYH+n6o>({TY+BG7mQ%>U&6=* z!|7mnA?}kO1}>zLcYv3_Gsz2WMIr`aIF6U>vChoJK=g3-^C!7rkPyp>!omgjC;Itg z&OjU|l#Yw(grdE?U5St|9}=SqsbW+yDi|fK%5^@Z(h0@>hyMQ(p@3qii*S4+=qMmN z08v0@7zG4@>`*WMc%Za&v|oF7I$+-EMoqJWYD_z76t$P8`b?SG)ttMjYum12Jk&V7 zsHmP6>E*Mnu22@bNiF7pFuY}Fi}583GKns-grfA-Wjf!$M+>b?2e(*eKQE{W(|_ zqzG@m_}u1gR1!=yj)8OGH|*MNL9vesbIH50(wEe#5QVStcy5LF4wl>1k4ux85&=BQ z2HOs`T4SDJht6*D#{Bb)k$L@P!=cU0W*tNP=9B^XyUy|_5eP@s-Sg0mVfsMlT$VO- z8v~;Q0kD8v1Cc8sa`JH>QJipc97K+egWo;Aws9QCLw5Rs6ebUE7iSN5Z$Ezu&)-nt zFXZa&c|0~0=D%fS3g7?mX^-+LlgcTR%4w4dggQxx4bBVawnyH{XAX1Hh2PQ)FJzj~ zV0fH;=5%9^r~A0EAmDBKU?@>M=V)d1wfsQ-HBPDx*3~(vs9PE7?jqurC(74^6*Ip;MfXd)H`2nLpdhX1nEE+M3Y~js@@+eX5olR{E5MTxf=!t-%Qh!Tb~aJm hJ4YDTZ?{bYj5Ah4y>@bn_^A>sIjjET5{u3>b{aXM4 diff --git a/client/test/build.gradle b/client/test/build.gradle index b77865df6decf..4c59fc1a07d99 100644 --- a/client/test/build.gradle +++ b/client/test/build.gradle @@ -43,6 +43,12 @@ 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}" + api "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" +} + +tasks.named("dependencyLicenses").configure { + mapping from: /bc.*/, to: 'bouncycastle' } 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..e7c17e81dabed 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,8 @@ import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite; import org.apache.hc.core5.http.Header; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.junit.BeforeClass; import java.util.ArrayList; import java.util.HashMap; @@ -125,7 +127,14 @@ private static void addValueToListEntry(final Map> map, fin values.add(value); } + @BeforeClass + public static void setFipsJvm() { + boolean isFipsEnabled = Boolean.parseBoolean(System.getProperty("tests.fips.enabled", "false")); + CryptoServicesRegistrar.setApprovedOnlyMode(isFipsEnabled); + } + public static boolean inFipsJvm() { - return Boolean.parseBoolean(System.getProperty("tests.fips.enabled")); + return CryptoServicesRegistrar.isInApprovedOnlyMode(); } + } 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..b09e2a921f5fc 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,9 @@ public void testMissingCreateWithEmptyPasswordWithoutPromptIfForced() throws Exc } public void testMissingNoCreate() throws Exception { - terminal.addSecretInput(randomFrom("", "keystorepassword")); + var password = randomFrom("", "keystorepassword"); + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); + terminal.addSecretInput(password); terminal.addTextInput("n"); // explicit no execute("foo"); assertNull(KeyStoreWrapper.load(env.configDir())); @@ -221,6 +225,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 05ce0bb61a4fd..2f284c622f3b4 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 @@ -83,6 +83,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"); @@ -90,6 +91,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", ""); @@ -261,7 +263,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)); @@ -270,6 +274,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..11755400cb16b 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 @@ -59,6 +59,7 @@ protected Environment createEnv(Map settings) throws UserExcepti public void testNotMatchingPasswords() throws Exception { String password = randomFrom("", "keystorepassword"); + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); terminal.addSecretInput(password); terminal.addSecretInput("notthekeystorepasswordyouarelookingfor"); UserException e = expectThrows(UserException.class, () -> execute(randomFrom("-p", "--password"))); @@ -67,6 +68,7 @@ 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)); @@ -74,25 +76,29 @@ public void testDefaultNotPromptForPassword() throws Exception { public void testPosix() throws Exception { String password = randomFrom("", "keystorepassword"); + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); 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"); + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); 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"); + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); + Path keystoreFile = KeyStoreWrapper.keystorePath(env.configDir()); byte[] content = "not a keystore".getBytes(StandardCharsets.UTF_8); Files.write(keystoreFile, content); @@ -106,9 +112,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 19bc06f7c64bc..c8c0b8f8b3928 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 @@ -40,6 +40,8 @@ import org.apache.lucene.store.NIOFSDirectory; import org.opensearch.common.Randomness; import org.opensearch.common.settings.KeyStoreWrapper; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.KeyStoreType; import org.opensearch.common.util.io.IOUtils; import org.opensearch.core.common.settings.SecureString; import org.opensearch.env.Environment; @@ -76,6 +78,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; @@ -85,6 +88,7 @@ public class KeyStoreWrapperTests extends OpenSearchTestCase { + Supplier passphraseSupplier = () -> inFipsJvm() ? "6!6428DQXwPpi7@$ggeg/=".toCharArray() : new char[0]; Environment env; List fileSystems = new ArrayList<>(); @@ -105,9 +109,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(); @@ -127,11 +131,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(), @@ -181,12 +185,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()); } @@ -213,7 +217,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)); } @@ -270,7 +274,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")); } @@ -301,7 +305,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"); @@ -341,12 +345,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()); } @@ -371,7 +375,7 @@ public void testBackcompatV1() throws Exception { output.writeString("PBE"); SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); - KeyStore keystore = KeyStore.getInstance("PKCS12", "SUN"); + KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); keystore.load(null, null); SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); @@ -412,7 +416,7 @@ public void testBackcompatV2() throws Exception { output.writeString("FILE"); SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); - KeyStore keystore = KeyStore.getInstance("PKCS12", "SUN"); + KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); keystore.load(null, null); SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); @@ -455,12 +459,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..63e9bbbdf7849 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 @@ -62,6 +62,7 @@ public void testMissing() throws Exception { public void testEmpty() throws Exception { String password = randomFrom("", "keystorepassword"); + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); createKeystore(password); terminal.addSecretInput(password); execute(); @@ -70,6 +71,7 @@ public void testEmpty() throws Exception { public void testOne() throws Exception { String password = randomFrom("", "keystorepassword"); + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); createKeystore(password, "foo", "bar"); terminal.addSecretInput(password); execute(); @@ -78,6 +80,7 @@ public void testOne() throws Exception { public void testMultiple() throws Exception { String password = randomFrom("", "keystorepassword"); + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); createKeystore(password, "foo", "1", "baz", "2", "bar", "3"); terminal.addSecretInput(password); execute(); @@ -100,6 +103,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/libs/common/build.gradle b/libs/common/build.gradle index 576e78bbe19f4..909d7c5ebe209 100644 --- a/libs/common/build.gradle +++ b/libs/common/build.gradle @@ -25,6 +25,8 @@ 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" + compileOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" + compileOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" /******* * !!!! NO THIRD PARTY DEPENDENCIES !!!! diff --git a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java new file mode 100644 index 0000000000000..ee2d1b92c855a --- /dev/null +++ b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java @@ -0,0 +1,56 @@ +/* + * 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.crypto; + +import org.bouncycastle.crypto.CryptoServicesRegistrar; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchProviderException; +import java.util.stream.Collectors; + +import static org.opensearch.common.crypto.KeyStoreType.SECURE_KEYSTORE_TYPES; +import static org.opensearch.common.crypto.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 { + + /** + * 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. + */ + public static KeyStore getInstanceBasedOnFileExtension(String filePath) { + return getInstance(inferStoreType(filePath)); + } + + public static KeyStore getInstance(KeyStoreType type) { + return getInstance(type, null); + } + + public static KeyStore getInstance(KeyStoreType type, String provider) { + if (CryptoServicesRegistrar.isInApprovedOnlyMode() && !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"); + } + + 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/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java new file mode 100644 index 0000000000000..d92b8067d3368 --- /dev/null +++ b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java @@ -0,0 +1,68 @@ +/* + * 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.crypto; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +public enum KeyStoreType { + + JKS("JKS"), + PKCS_12("PKCS12"), + PKCS_11("PKCS11"), + BKS("BKS"), + BCFKS("BCFKS"); + + private 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(BKS, List.of(".bks")); // Bouncy Castle Keystore + 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. + * - JKS can be used under FIPS with restrictions: it may contain only certificates, and it will be readable but not writable. + */ + public static final List SECURE_KEYSTORE_TYPES = List.of(PKCS_11, BCFKS, JKS); + + 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); + } + + public static String getExtensionsByType(KeyStoreType type) { + return TYPE_TO_EXTENSION_MAP.get(type).get(0); + } + +} diff --git a/libs/ssl-config/build.gradle b/libs/ssl-config/build.gradle index 68f3cae7a0ac2..8d09782b04b89 100644 --- a/libs/ssl-config/build.gradle +++ b/libs/ssl-config/build.gradle @@ -63,6 +63,7 @@ forbiddenPatterns { exclude '**/*.pem' exclude '**/*.p12' exclude '**/*.jks' + exclude '**/*.bcfks' } tasks.test { 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..d82716085b504 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 @@ -33,6 +33,8 @@ package org.opensearch.common.ssl; import org.opensearch.common.Nullable; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.KeyStoreType; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; @@ -95,7 +97,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/KeyStoreUtil.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java index 3fca84e303e27..e0bef21ed4242 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 @@ -33,6 +33,8 @@ package org.opensearch.common.ssl; import org.opensearch.common.Nullable; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.KeyStoreType; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -52,7 +54,6 @@ import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.Collection; -import java.util.Locale; /** * A variety of utility methods for working with or constructing {@link KeyStore} instances. @@ -63,35 +64,20 @@ 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 if (name.endsWith(".bks")) { - return "BCFKS"; - } 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); } @@ -135,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/PemUtils.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java index 821c38c8d2f46..3a1b369c64902 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java @@ -60,7 +60,7 @@ final class PemUtils { - protected static final String BCFIPS = "BCFIPS"; + private static final String BCFIPS = "BCFIPS"; private PemUtils() { throw new IllegalStateException("Utility class should not be instantiated"); @@ -148,15 +148,20 @@ private static Object readObject(Path keyPath, PEMParser pemParser) throws IOExc while (pemParser.ready()) { try { var object = pemParser.readObject(); - if (object instanceof ASN1ObjectIdentifier) { // handles -----BEGIN EC PARAMETERS----- + if (object == null) { // ignore unknown objects; + continue; + } + if (object instanceof ASN1ObjectIdentifier) { // ignore -----BEGIN EC PARAMETERS----- continue; } return object; - } catch (IOException e) { // handles -----BEGIN DSA PARAMETERS----- + } catch (IOException e) { // ignore -----BEGIN DSA PARAMETERS----- // ignore } } - throw new SslConfigException("Error parsing Private Key [" + keyPath.toAbsolutePath() + "], file is empty"); + throw new SslConfigException( + "Error parsing Private Key [" + keyPath.toAbsolutePath() + "]. The file is empty, or does not contain expected key format." + ); } } 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 d6aa106039ce3..909b8facd68b5 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 @@ -40,7 +40,6 @@ import java.nio.file.Path; import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -68,12 +67,7 @@ public class SslConfiguration { static final Map ORDERED_PROTOCOL_ALGORITHM_MAP; static { LinkedHashMap protocolAlgorithmMap = new LinkedHashMap<>(); - try { - SSLContext.getInstance("TLSv1.3"); - protocolAlgorithmMap.put("TLSv1.3", "TLSv1.3"); - } catch (NoSuchAlgorithmException e) { - // ignore since we support JVMs (and BC JSSE in FIPS mode) that do not support TLSv1.3 - } + protocolAlgorithmMap.put("TLSv1.3", "TLSv1.3"); protocolAlgorithmMap.put("TLSv1.2", "TLSv1.2"); protocolAlgorithmMap.put("TLSv1.1", "TLSv1.1"); protocolAlgorithmMap.put("TLSv1", "TLSv1"); @@ -157,12 +151,7 @@ public SSLContext createSslContext() { final X509ExtendedKeyManager keyManager = keyConfig.createKeyManager(); final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); try { - SSLContext sslContext; - if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { - sslContext = SSLContext.getInstance("TLS"); - } else { - sslContext = SSLContext.getInstance(contextProtocol()); - } + SSLContext sslContext = SSLContext.getInstance(contextProtocol()); sslContext.init(new X509ExtendedKeyManager[] { keyManager }, new X509ExtendedTrustManager[] { trustManager }, null); return sslContext; } catch (GeneralSecurityException e) { @@ -178,6 +167,13 @@ 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())) { return entry.getValue(); 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 0b06a0692197e..4deeed2bbc45e 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 @@ -32,6 +32,8 @@ package org.opensearch.common.ssl; +import org.opensearch.common.crypto.KeyStoreType; + import javax.crypto.Cipher; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; @@ -40,14 +42,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.crypto.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 +86,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 +117,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 +165,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 +246,11 @@ 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 +289,12 @@ 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); } @@ -360,14 +367,11 @@ private List resolveListSetting(String key, Function parser, L private static List loadDefaultCiphers() { final boolean has256BitAES = has256BitAES(); - final boolean tlsV13Supported = DEFAULT_PROTOCOLS.contains("TLSv1.3"); List ciphers = new ArrayList<>(); - if (tlsV13Supported) { // TLSv1.3 cipher has PFS, AEAD, hardware support - if (has256BitAES) { - ciphers.add("TLS_AES_256_GCM_SHA384"); - } - ciphers.add("TLS_AES_128_GCM_SHA256"); + if (has256BitAES) { + ciphers.add("TLS_AES_256_GCM_SHA384"); } + ciphers.add("TLS_AES_128_GCM_SHA256"); // use GCM: PFS, AEAD, hardware support if (has256BitAES) { ciphers.addAll( 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..c60e4d1b0f65a 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 @@ -32,6 +32,8 @@ package org.opensearch.common.ssl; +import org.opensearch.common.crypto.KeyStoreType; + import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; @@ -51,7 +53,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 +61,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..138fff71b8a3d 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 @@ -32,6 +32,8 @@ package org.opensearch.common.ssl; +import org.opensearch.common.crypto.KeyStoreType; + import javax.net.ssl.X509ExtendedTrustManager; import java.nio.file.Path; @@ -47,17 +49,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/PemKeyConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java index a4cbea14ff261..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 @@ -45,6 +45,7 @@ import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.function.Supplier; import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -57,6 +58,7 @@ public class PemKeyConfigTests extends OpenSearchTestCase { private static final int IP_NAME = 7; private static final int DNS_NAME = 2; + private static final Supplier STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="::toCharArray; public void testBuildKeyConfigFromPkcs1PemFilesWithoutPassword() throws Exception { final Path cert = getDataPath("/certs/cert1/cert1.crt"); @@ -67,8 +69,9 @@ public void testBuildKeyConfigFromPkcs1PemFilesWithoutPassword() throws Exceptio } public void testBuildKeyConfigFromPkcs1PemFilesWithPassword() throws Exception { - final Path cert = getDataPath("/certs/cert2/cert2.crt"); - final Path key = getDataPath("/certs/cert2/cert2.key"); + 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()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key)); assertCertificateAndKey(keyConfig, "CN=cert2"); @@ -76,17 +79,16 @@ public void testBuildKeyConfigFromPkcs1PemFilesWithPassword() throws Exception { public void testBuildKeyConfigFromPkcs8PemFilesWithoutPassword() throws Exception { final Path cert = getDataPath("/certs/cert1/cert1.crt"); - final Path key = getDataPath("/certs/cert1/cert1-pkcs8.key"); + final Path key = getDataPath("/certs/cert1/cert1.key"); final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, new char[0]); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key)); assertCertificateAndKey(keyConfig, "CN=cert1"); } 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-pkcs8.key"); - final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, "c2-pass".toCharArray()); + final Path key = getDataPath("/certs/cert2/cert2.key"); + final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, STRONG_PRIVATE_SECRET.get()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key)); assertCertificateAndKey(keyConfig, "CN=cert2"); } 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 85d5172b87d0b..7ab684fea0670 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,18 +32,20 @@ package org.opensearch.common.ssl; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.KeyStoreType; import org.opensearch.test.OpenSearchTestCase; -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; import java.security.spec.ECParameterSpec; +import java.util.Locale; import java.util.function.Supplier; import static org.hamcrest.Matchers.equalTo; @@ -55,9 +57,11 @@ 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; // has to be at least 112 bit long. 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); @@ -66,7 +70,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); @@ -74,7 +79,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); @@ -94,7 +100,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); @@ -104,7 +111,7 @@ public void testReadPKCS8EcKey() 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); @@ -113,7 +120,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); @@ -122,7 +130,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"); @@ -133,7 +142,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); @@ -143,7 +153,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); @@ -153,7 +164,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( @@ -166,7 +178,8 @@ public void testReadOpenSslDsaKeyWithParams() throws Exception { } public void testReadEncryptedOpenSslDsaKey() 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_encrypted.pem"), TESTNODE_PASSWORD); @@ -176,7 +189,8 @@ public void testReadEncryptedOpenSslDsaKey() throws Exception { } public void testReadOpenSslEcKey() 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_openssl_plain.pem"), EMPTY_PASSWORD); @@ -186,7 +200,8 @@ public void testReadOpenSslEcKey() throws Exception { } 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( @@ -199,7 +214,8 @@ public void testReadOpenSslEcKeyWithParams() throws Exception { } public void testReadEncryptedOpenSslEcKey() throws Exception { - Key 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)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/ec_key_openssl_encrypted.pem"), TESTNODE_PASSWORD); @@ -208,6 +224,33 @@ public void testReadEncryptedOpenSslEcKey() throws Exception { assertThat(privateKey, equalTo(key)); } + public void testReadEncryptedPKCS8KeyWithPBKDF2() throws Exception { + Key key = getKeyFromKeystore("PKCS8_PBKDF2"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/key_PKCS8_enc_pbkdf2.pem"), STRONG_PRIVATE_SECRET); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + public void testReadEncryptedDsaKeyWithPBKDF2() throws Exception { + Key key = getKeyFromKeystore("DSA_PBKDF2"); + assertThat(key, notNullValue()); + assertThat(key, instanceOf(PrivateKey.class)); + PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/key_DSA_enc_pbkdf2.pem"), STRONG_PRIVATE_SECRET); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + + 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"), STRONG_PRIVATE_SECRET); + assertThat(privateKey, notNullValue()); + assertThat(privateKey, equalTo(key)); + } + public void testReadUnsupportedKey() { final Path path = getDataPath("/certs/pem-utils/key_unsupported.pem"); SslConfigException e = expectThrows(SslConfigException.class, () -> PemUtils.readPrivateKey(path, TESTNODE_PASSWORD)); @@ -239,11 +282,17 @@ public void testReadEmptyFile() { } private Key getKeyFromKeystore(String algo) throws Exception { - Path keystorePath = getDataPath("/certs/pem-utils/testnode.jks"); - try (InputStream in = Files.newInputStream(keystorePath)) { - KeyStore keyStore = KeyStore.getInstance("jks"); - keyStore.load(in, "testnode".toCharArray()); - return keyStore.getKey("testnode_" + algo, "testnode".toCharArray()); + return getKeyFromKeystore(algo, CryptoServicesRegistrar.isInApprovedOnlyMode() ? KeyStoreType.BCFKS : KeyStoreType.JKS); + } + + private Key getKeyFromKeystore(String algo, KeyStoreType keyStoreType) throws Exception { + var keystorePath = getDataPath("/certs/pem-utils/testnode" + KeyStoreType.getExtensionsByType(keyStoreType)); + var alias = "testnode_" + algo.toLowerCase(Locale.ROOT); + var password = "testnode".toCharArray(); + try (var in = Files.newInputStream(keystorePath)) { + var keyStore = KeyStoreFactory.getInstance(keyStoreType); + keyStore.load(in, password); + return keyStore.getKey(alias, password); } } } 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 5af7ddc73e680..bb46c086fbedc 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,8 @@ 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(); private Settings settings; @@ -113,7 +115,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"); @@ -136,6 +161,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"); @@ -166,9 +192,9 @@ public void testLoadKeysFromPemFiles() { .put("test.ssl.key", certName + "/" + certName + ".key"); if (usePassword) { if (useLegacyPassword) { - builder.put("test.ssl.key_passphrase", "c2-pass"); + builder.put("test.ssl.key_passphrase", STRONG_PRIVATE_SECRET); } else { - secureSettings.setString("test.ssl.secure_key_passphrase", "c2-pass"); + secureSettings.setString("test.ssl.secure_key_passphrase", STRONG_PRIVATE_SECRET); } } settings = builder.build(); @@ -185,7 +211,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 JKS/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..9281d9612d806 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 @@ -166,7 +166,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 +184,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, diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java index c966b4259219f..b733b6536f5cb 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java @@ -85,7 +85,7 @@ public void testDiagnosticMessageWhenServerProvidesAFullCertChainThatIsTrusted() message, Matchers.equalTo( "failed to establish trust with server at [192.168.1.1];" - + " the server provided a certificate with subject name [CN=cert1] and fingerprint [3bebe388a66362784afd6c51a9000961a4e10050];" + + " the server provided a certificate with subject name [CN=cert1] and fingerprint [7e0919348e566651a136f2a1d5974585d5b3712e];" + " the certificate has subject alternative names [DNS:localhost,IP:127.0.0.1];" + " the certificate is issued by [CN=Test CA 1];" + " the certificate is signed by" @@ -110,7 +110,7 @@ public void testDiagnosticMessageWhenServerProvidesAFullCertChainThatIsntTrusted message, Matchers.equalTo( "failed to establish trust with server at [192.168.1.1];" - + " the server provided a certificate with subject name [CN=cert1] and fingerprint [3bebe388a66362784afd6c51a9000961a4e10050];" + + " the server provided a certificate with subject name [CN=cert1] and fingerprint [7e0919348e566651a136f2a1d5974585d5b3712e];" + " the certificate has subject alternative names [DNS:localhost,IP:127.0.0.1];" + " the certificate is issued by [CN=Test CA 1];" + " the certificate is signed by (subject [CN=Test CA 1] fingerprint [2b7b0416391bdf86502505c23149022d2213dadc])" @@ -134,7 +134,7 @@ public void testDiagnosticMessageWhenServerFullCertChainIsntTrustedButMimicIssue message, Matchers.equalTo( "failed to establish trust with server at [192.168.1.1];" - + " the server provided a certificate with subject name [CN=cert1] and fingerprint [3bebe388a66362784afd6c51a9000961a4e10050];" + + " the server provided a certificate with subject name [CN=cert1] and fingerprint [7e0919348e566651a136f2a1d5974585d5b3712e];" + " the certificate has subject alternative names [DNS:localhost,IP:127.0.0.1];" + " the certificate is issued by [CN=Test CA 1];" + " the certificate is signed by (subject [CN=Test CA 1] fingerprint [2b7b0416391bdf86502505c23149022d2213dadc])" @@ -160,7 +160,7 @@ public void testDiagnosticMessageWhenServerProvidesEndCertificateOnlyAndTheCertA message, Matchers.equalTo( "failed to establish trust with server at [192.168.1.1];" - + " the server provided a certificate with subject name [CN=cert1] and fingerprint [3bebe388a66362784afd6c51a9000961a4e10050];" + + " the server provided a certificate with subject name [CN=cert1] and fingerprint [7e0919348e566651a136f2a1d5974585d5b3712e];" + " the certificate has subject alternative names [DNS:localhost,IP:127.0.0.1];" + " the certificate is issued by [CN=Test CA 1]" + " but the server did not provide a copy of the issuing certificate in the certificate chain;" @@ -185,7 +185,7 @@ public void testDiagnosticMessageWhenServerProvidesEndCertificateOnlyButTheCertA message, Matchers.equalTo( "failed to establish trust with server at [192.168.1.1];" - + " the server provided a certificate with subject name [CN=cert1] and fingerprint [3bebe388a66362784afd6c51a9000961a4e10050];" + + " the server provided a certificate with subject name [CN=cert1] and fingerprint [7e0919348e566651a136f2a1d5974585d5b3712e];" + " the certificate has subject alternative names [DNS:localhost,IP:127.0.0.1];" + " the certificate is issued by [CN=Test CA 1]" + " but the server did not provide a copy of the issuing certificate in the certificate chain;" @@ -209,7 +209,7 @@ public void testDiagnosticMessageWhenServerProvidesEndCertificateOnlyWithMimicIs message, Matchers.equalTo( "failed to establish trust with server at [192.168.1.1];" - + " the server provided a certificate with subject name [CN=cert1] and fingerprint [3bebe388a66362784afd6c51a9000961a4e10050];" + + " the server provided a certificate with subject name [CN=cert1] and fingerprint [7e0919348e566651a136f2a1d5974585d5b3712e];" + " the certificate has subject alternative names [DNS:localhost,IP:127.0.0.1];" + " the certificate is issued by [CN=Test CA 1]" + " but the server did not provide a copy of the issuing certificate in the certificate chain;" @@ -235,7 +235,7 @@ public void testDiagnosticMessageWhenServerProvidesEndCertificateWithMultipleMim message, Matchers.equalTo( "failed to establish trust with server at [192.168.1.9];" - + " the server provided a certificate with subject name [CN=cert1] and fingerprint [3bebe388a66362784afd6c51a9000961a4e10050];" + + " the server provided a certificate with subject name [CN=cert1] and fingerprint [7e0919348e566651a136f2a1d5974585d5b3712e];" + " the certificate has subject alternative names [DNS:localhost,IP:127.0.0.1];" + " the certificate is issued by [CN=Test CA 1]" + " but the server did not provide a copy of the issuing certificate in the certificate chain;" @@ -538,7 +538,7 @@ public void testDiagnosticMessageForClientCertificate() throws Exception { Matchers.equalTo( "failed to establish trust with client at [192.168.1.7];" + " the client provided a certificate with subject name [CN=cert1]" - + " and fingerprint [3bebe388a66362784afd6c51a9000961a4e10050];" + + " and fingerprint [7e0919348e566651a136f2a1d5974585d5b3712e];" + " the certificate is issued by [CN=Test CA 1]" + " but the client did not provide a copy of the issuing certificate in the certificate chain;" + " the issuing certificate with fingerprint [2b7b0416391bdf86502505c23149022d2213dadc]" @@ -571,7 +571,7 @@ public void testDiagnosticMessageWhenCaHasNewIssuingCertificate() throws Excepti message, Matchers.equalTo( "failed to establish trust with server at [192.168.1.4];" - + " the server provided a certificate with subject name [CN=cert1] and fingerprint [3bebe388a66362784afd6c51a9000961a4e10050];" + + " the server provided a certificate with subject name [CN=cert1] and fingerprint [7e0919348e566651a136f2a1d5974585d5b3712e];" + " the certificate has subject alternative names [DNS:localhost,IP:127.0.0.1];" + " the certificate is issued by [CN=Test CA 1];" + " the certificate is signed by (subject [CN=Test CA 1]" 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 7806671d02793..5c7a23445a877 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,10 @@ import java.security.cert.X509Certificate; import java.util.Arrays; +import static org.opensearch.common.crypto.KeyStoreType.BCFKS; +import static org.opensearch.common.crypto.KeyStoreType.JKS; +import static org.opensearch.common.crypto.KeyStoreType.PKCS_12; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; @@ -64,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()); 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"); } @@ -76,18 +81,17 @@ 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"); } public void testLoadMultipleKeyJksWithSeparateKeyPassword() 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, "key-pass".toCharArray(), KeyManagerFactory.getDefaultAlgorithm() ); @@ -95,13 +99,19 @@ public void testLoadMultipleKeyJksWithSeparateKeyPassword() throws Exception { assertKeysLoaded(keyConfig, "cert1", "cert2"); } - public void testKeyManagerFailsWithIncorrectStorePassword() throws Exception { - assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + 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 { final Path jks = getDataPath("/certs/cert-all/certs.jks"); final StoreKeyConfig keyConfig = new StoreKeyConfig( jks, P12_PASS, - "jks", + JKS, "key-pass".toCharArray(), KeyManagerFactory.getDefaultAlgorithm() ); @@ -109,37 +119,61 @@ public void testKeyManagerFailsWithIncorrectStorePassword() throws Exception { assertPasswordIsIncorrect(keyConfig, jks); } - public void testKeyManagerFailsWithIncorrectKeyPassword() throws Exception { - assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + 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 { 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); } - public void testMissingKeyEntriesFailsWithMeaningfulMessage() throws Exception { + public void testMissingKeyEntriesFailsForJksWithMeaningfulMessage() throws Exception { + 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()); + 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()); - final Path ks; - final char[] password; - final String type; - if (randomBoolean()) { - type = "PKCS12"; - ks = getDataPath("/certs/ca-all/ca.p12"); - password = P12_PASS; - } else { - type = "jks"; - ks = getDataPath("/certs/ca-all/ca.jks"); - password = JKS_PASS; - } - final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, type, password, KeyManagerFactory.getDefaultAlgorithm()); + final Path ks = getDataPath("/certs/ca-all/ca.p12"); + final char[] password = P12_PASS; + 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 +186,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"); @@ -211,7 +245,10 @@ private void assertPasswordIsIncorrect(StoreKeyConfig keyConfig, Path key) { assertThat(exception.getMessage(), containsString("password")); } else { assertThat(exception.getCause(), instanceOf(IOException.class)); - assertThat(exception.getCause().getMessage(), containsString("password")); + assertThat( + exception.getCause().getMessage(), + anyOf(containsString("Keystore was tampered with, or password was incorrect"), containsString("BCFKS KeyStore corrupted")) + ); } } 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 5609f0fa2c877..5bceb5aef80cf 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.crypto.KeyStoreType.BCFKS; +import static org.opensearch.common.crypto.KeyStoreType.JKS; +import static org.opensearch.common.crypto.KeyStoreType.PKCS_12; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.nullValue; @@ -56,20 +59,20 @@ 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 testBuildTrustConfigFromPKCS12() 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"); } 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"); } @@ -78,7 +81,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,33 +89,40 @@ 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 testIncorrectPasswordFailsWithMeaningfulMessage() 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 testMissingTrustEntriesFailsWithMeaningfulMessage() throws Exception { + public void testMissingTrustEntriesFailsForJksKeystoreWithMeaningfulMessage() throws Exception { + final char[] password = JKS_PASS; + final Path ks = getDataPath("/certs/cert-all/certs.jks"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, password, JKS, DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertNoCertificateEntries(trustConfig, ks); + } + + public void testMissingTrustEntriesFailsForP12KeystoreWithMeaningfulMessage() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); - final Path ks; - final char[] password; - final String type; - if (randomBoolean()) { - type = "PKCS12"; - ks = getDataPath("/certs/cert-all/certs.p12"); - password = P12_PASS; - } else { - type = "jks"; - ks = getDataPath("/certs/cert-all/certs.jks"); - password = JKS_PASS; - } - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, password, type, DEFAULT_ALGORITHM); + final Path ks = getDataPath("/certs/cert-all/certs.p12"); + final char[] password = P12_PASS; + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, password, 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 char[] password = BCFKS_PASS; + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, password, BCFKS, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertNoCertificateEntries(trustConfig, ks); } @@ -124,7 +134,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/resources/certs/README.md b/libs/ssl-config/src/test/resources/certs/README.md new file mode 100644 index 0000000000000..dbbe840dd5431 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/README.md @@ -0,0 +1,155 @@ +#!/usr/bin/env bash +# +# This is README describes how the certificates in this directory were created. +# This file can also be executed as a script +# + +# 1. Create first CA PEM ("ca1") + +opensearch-certutil ca --pem --out ca1.zip --days 9999 --ca-dn "CN=Test CA 1" +unzip ca1.zip +mv ca ca1 + +# 2. Create first CA PEM ("ca2") + +opensearch-certutil ca --pem --out ca2.zip --days 9999 --ca-dn "CN=Test CA 2" +unzip ca2.zip +mv ca ca2 + +# 3. Create first CA PEM ("ca3") + +opensearch-certutil ca --pem --out ca3.zip --days 9999 --ca-dn "CN=Test CA 3" +unzip ca3.zip +mv ca ca3 + +# 4. Create "cert1-pkcs1" PEM + +opensearch-certutil cert --pem --out cert1-pkcs1.zip --name cert1 --ip 127.0.0.1 --dns localhost --days 9999 --ca-key ca1/ca.key --ca-cert ca1/ca.crt +unzip cert1.zip + +# 5. Create "cert2-pkcs1" PEM (same as cert1, but with a password) + +opensearch-certutil cert --pem --out cert2-pkcs1.zip --name cert2 --ip 127.0.0.1 --dns localhost --days 9999 --ca-key ca1/ca.key --ca-cert ca1/ca.crt --pass "c2-pass" +unzip cert2.zip + +# 6. Create "cert1" PEM + +```bash +openssl genpkey -algorithm RSA -out cert1/cert1.key +openssl req -new \ + -key cert1/cert1.key \ + -subj "/CN=cert1" \ + -out cert1/cert1.csr +openssl x509 -req \ + -in cert1/cert1.csr \ + -CA ca1/ca.crt \ + -CAkey ca1/ca.key \ + -CAcreateserial \ + -out cert1/cert1.crt \ + -days 3650 \ + -sha256 \ + -extfile <(printf "subjectAltName=DNS:localhost,IP:127.0.0.1") +rm cert1/cert1.csr +``` + +# 7. Create "cert2" PEM (same as cert1, but with a password) + +```bash +openssl genpkey -algorithm RSA -out cert2/cert2.key -aes256 -pass pass:"$KEY_PW" +openssl req -new \ +-key cert2/cert2.key \ +-subj "/CN=cert2" \ +-out cert2/cert2.csr \ +-passin pass:"$KEY_PW" +openssl x509 -req \ +-in cert2/cert2.csr \ +-CA ca1/ca.crt \ +-CAkey ca1/ca.key \ +-CAcreateserial \ +-out cert2/cert2.crt \ +-days 3650 \ +-sha256 \ +-extfile <(printf "subjectAltName=DNS:localhost,IP:127.0.0.1") \ +-passin pass:"$KEY_PW" +rm cert2/cert2.csr +``` + +# 8. Convert CAs to PKCS#12 + +for n in 1 2 3 +do + keytool -importcert -file ca${n}/ca.crt -alias ca -keystore ca${n}/ca.p12 -storetype PKCS12 -storepass p12-pass -v + keytool -importcert -file ca${n}/ca.crt -alias ca${n} -keystore ca-all/ca.p12 -storetype PKCS12 -storepass p12-pass -v +done + +# 9. Convert CAs to JKS + +for n in 1 2 3 +do + keytool -importcert -file ca${n}/ca.crt -alias ca${n} -keystore ca-all/ca.jks -storetype jks -storepass jks-pass -v +done + +# 10. Convert CAs to BCFKS + +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 + +# 11. Convert Certs to PKCS#12 + +for Cert in cert1 cert2 +do + openssl pkcs12 -export -out $Cert/$Cert.p12 -inkey $Cert/$Cert.key -in $Cert/$Cert.crt -name $Cert -passout pass:p12-pass +done + +# 12. Import Certs into single PKCS#12 keystore + +for Cert in cert1 cert2 +do + keytool -importkeystore -noprompt \ + -srckeystore $Cert/$Cert.p12 -srcstoretype PKCS12 -srcstorepass p12-pass \ + -destkeystore cert-all/certs.p12 -deststoretype PKCS12 -deststorepass p12-pass +done + +# 13. Import Certs into single JKS keystore with separate key-password + +for Cert in cert1 cert2 +do + keytool -importkeystore -noprompt \ + -srckeystore $Cert/$Cert.p12 -srcstoretype PKCS12 -srcstorepass p12-pass \ + -destkeystore cert-all/certs.jks -deststoretype jks -deststorepass jks-pass + keytool -keypasswd -keystore cert-all/certs.jks -alias $Cert -keypass p12-pass -new key-pass -storepass jks-pass +done + +# 14. Import Certs into single BCFKS keystore with separate key-password + +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 + +# 15. Create a mimic of the first CA ("ca1b") for testing certificates with the same name but different keys + +opensearch-certutil ca --pem --out ${PWD}/ca1-b.zip --days 9999 --ca-dn "CN=Test CA 1" +unzip ca1-b.zip +mv ca ca1-b diff --git a/libs/ssl-config/src/test/resources/certs/README.txt b/libs/ssl-config/src/test/resources/certs/README.txt deleted file mode 100644 index 09910e99a132e..0000000000000 --- a/libs/ssl-config/src/test/resources/certs/README.txt +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env bash -# -# This is README describes how the certificates in this directory were created. -# This file can also be executed as a script -# - -# 1. Create first CA PEM ("ca1") - -opensearch-certutil ca --pem --out ca1.zip --days 9999 --ca-dn "CN=Test CA 1" -unzip ca1.zip -mv ca ca1 - -# 2. Create first CA PEM ("ca2") - -opensearch-certutil ca --pem --out ca2.zip --days 9999 --ca-dn "CN=Test CA 2" -unzip ca2.zip -mv ca ca2 - -# 3. Create first CA PEM ("ca3") - -opensearch-certutil ca --pem --out ca3.zip --days 9999 --ca-dn "CN=Test CA 3" -unzip ca3.zip -mv ca ca3 - -# 4. Create "cert1" PEM - -opensearch-certutil cert --pem --out cert1.zip --name cert1 --ip 127.0.0.1 --dns localhost --days 9999 --ca-key ca1/ca.key --ca-cert ca1/ca.crt -unzip cert1.zip - -# 5. Create "cert2" PEM (same as cert1, but with a password) - -opensearch-certutil cert --pem --out cert2.zip --name cert2 --ip 127.0.0.1 --dns localhost --days 9999 --ca-key ca1/ca.key --ca-cert ca1/ca.crt --pass "c2-pass" -unzip cert2.zip - -# 6. Convert CAs to PKCS#12 - -for n in 1 2 3 -do - keytool -importcert -file ca${n}/ca.crt -alias ca -keystore ca${n}/ca.p12 -storetype PKCS12 -storepass p12-pass -v - keytool -importcert -file ca${n}/ca.crt -alias ca${n} -keystore ca-all/ca.p12 -storetype PKCS12 -storepass p12-pass -v -done - -# 7. Convert CAs to JKS - -for n in 1 2 3 -do - keytool -importcert -file ca${n}/ca.crt -alias ca${n} -keystore ca-all/ca.jks -storetype jks -storepass jks-pass -v -done - -# 8. Convert Certs to PKCS#12 - -for Cert in cert1 cert2 -do - openssl pkcs12 -export -out $Cert/$Cert.p12 -inkey $Cert/$Cert.key -in $Cert/$Cert.crt -name $Cert -passout pass:p12-pass -done - -# 9. Import Certs into single PKCS#12 keystore - -for Cert in cert1 cert2 -do - keytool -importkeystore -noprompt \ - -srckeystore $Cert/$Cert.p12 -srcstoretype PKCS12 -srcstorepass p12-pass \ - -destkeystore cert-all/certs.p12 -deststoretype PKCS12 -deststorepass p12-pass -done - -# 10. Import Certs into single JKS keystore with separate key-password - -for Cert in cert1 cert2 -do - keytool -importkeystore -noprompt \ - -srckeystore $Cert/$Cert.p12 -srcstoretype PKCS12 -srcstorepass p12-pass \ - -destkeystore cert-all/certs.jks -deststoretype jks -deststorepass jks-pass - keytool -keypasswd -keystore cert-all/certs.jks -alias $Cert -keypass p12-pass -new key-pass -storepass jks-pass -done - -# 11. Create a mimic of the first CA ("ca1b") for testing certificates with the same name but different keys - -opensearch-certutil ca --pem --out ${PWD}/ca1-b.zip --days 9999 --ca-dn "CN=Test CA 1" -unzip ca1-b.zip -mv ca ca1-b - -# 12. Convert certifcate keys to pkcs8 - -openssl pkcs8 -topk8 -inform PEM -in cert1/cert1.key -outform PEM -out cert1/cert1-pkcs8.key -nocrypt -openssl pkcs8 -topk8 -inform PEM -in cert2/cert2.key -outform PEM -out cert2/cert2-pkcs8.key -passin pass:"c2-pass" -passout pass:"c2-pass" 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 0000000000000000000000000000000000000000..f27717d0ce67aa8d1beb0c87410c9201633c1f95 GIT binary patch literal 2909 zcmV-j3!?Nef(uzNf(o24fs_UbDuzgg_YDCB4KRU*Fk}V^Duzgg_YDCB3@}#&K(eq? zjmcH3l^NC9O71OYEF5d;iO;wj=< z)nG*}|8K|w0SE+w3ITPGJDz1zW|9h%)JHj^_6tQkz&Lh4#&g?u_`}s^2H7KMdVzc_ z$n~BVc?buaD4Ybjn@ORANinu}qYsd@GM9u69~=N(x-?v@K!Iz}1e@;GA_Zc1xxp1C zH)c(HU;V;ej-RyAR*3=!@f|UMHzmskQxk^qK3hbWt?d>N5pANnZ%Sfso#m^c5Nhu8W0BYf4XU%mgP!ypJSf_xyrb*I2_XU+0!x z;X&@s;tBA-`7}B4bFCsQs}jhQArpt00J~|qK3VQ-y?e(hEOZ~4zs7+U0hFWs+$3#( z&Q;&x`8XDyCuFA2HW-lE3L-7`$C*E-`PaS$jEUVVy#?~7(GxU69vmiT%$j{sgAa45 zVi>O!9=`Tf{tsJ1Ffj1rbaibDRxLHVOZVW(?JnJ;k%)LcQ+G*ia@woH2i=S&kV@ZrFdLAx7(2!G zB6F8eclS-<{BCx#^J&&gQ|Xy^%#sE`Py#^5N_JvuOx=?f{OMVKY^J(9VQ?(?5@Xn( z?gdz`FB7Q++z89f4<)I^7HV{;Y4hYpj-M0TSW=ecfHcHcnqRk?4IH3>?y!S4l2hpQbCy$;+xf`sIglb+r8L3mot5p*kpGhE1Q;EF3@Hrf9>$y*mS91u ztKmAbAiL_$*5u0K$*HN-gNV>Ieb#d&V(G{i-TH84??6qAB}==CV4QwiR1>}imzqoL z$0eoW=*5yO^s9+vI2M*3@_Q20bNJg$CZgZ@AEF+St%YpV2LX!L-zwWI&yGta93KGu z>nO0CUJY$-$Isk75!8hD11n47b}Hwy{Jbmu!+IUHGhJ4fw@ZxN9U+;S+z>nTuKyHq zY+pFi=Q`wriT;Gd-F!uiwzQ|k{#obN8K9+~!1qexT2thy492PYhehfdZOs1q65I#j zsM8&vTpV)XXNk)rSwtvOR8haygG_nUXiCg`3W-39i^YG+Jv3QXWi&wJhPDoi0@6mj zUe)0D{cnBa**Q!Av|(p3$J*&{`V&_)3{sk>B+ea9{gzDCsRNF~x2)HC7_ z8re;JRitCKk!QsRI%Z^vrpFiXRDLUTg@%(pZOAED;sgYvD1mhnozhtw^L(WK`8?fQ zeRth}3?$_d2A1lUFhQ>Wb1*Fi^S+Q2mbL})mXN4}J9V%#-EVg?_@MVrXQDSquFh=r zt5IpMydbX{Adj>|yfB`H@!0h%vNm%bT)mG&lY6c-ZiZ|&YEJ;>fYE+lsifQy>Y4l-<+%3ueM151)+76CW_2U)9Md=B$Rk zb?JX+aLVzxvatGQd`0}@r@yh=&t!`%I$yj>c5p48?!*kmQBx}z+-V|~l06g{k(c_t zo{E9Ye{M6Gf9VGEv0hERB7PjYJj{htVXX>S6we{3^oyc10~w!=&{aiFfi6)tH4Qb? zWPU;n)Xzmdg+69fp(l4R7H`znV^!Gbrim79G(F-7B^!UQt#GLdV8*DKxZ;^zT3~!M z?2oxPM5ZCO+|t)PlA1MK`Uu`&1#r%fxn4gSe|(@=p`1X?%paX$CVBL8{G(M~Ui)x% zPAUT-qRds1EiCY11%>pg{cTo8AT$}v?4j2SgV!05HWg+w3ovFkAa4d`gob==@78Vn zU7WN7$Q=k3ph(h(j!sz2YfT%kAxQAX=DR3Q?!JglX~OA$1wR(hcaCz=U#NLpaeU^O z3hj>@D4>`_C{K?3nHPpp4L3J?e`M(TR+{P5Tb)L8s9j8~? zh0Dgv76i9;^_=`}!5&t}MJD=-B|Le)P!ph>JC3h;vpjG@eFBllH83{0_M~B{?Qap_ zYD|Ns+(y1^e-bfZT2MJ)`>0FyuMNs$DsI+o+>ru1rZ~7W6xa_^cA%w)zeU=iLIA;i zluC=0k02Ocb$lzi$Hk%xCB)_aye!|#2sl`5fY++TdS<`}D5X z9~X59dk%UyU>N<#a5{(7_JrvE#d8BE&&($;6Ph4A1U6|Qyz-ZJaugs7O}M#D^EL&K z?orbQErex zl}e`px1Hu5{~fjorf>nrsaw1hz|OC!26Jr>lcW?Ny&^^~zUqjZj?qQ_A^VUjh&e7E zKP66UAXKwZfc~0bNkgio?p!s^9+Iao$z-%7E#nprOR43-s5WPw-E!iiyL0N=g&l_A`Ee zO8TU_qD;+zqB|}^O{rQ$E=Foiwm>X>I&4nzyGQj@-@;|neY;3`NP_xoGFna*7=AV& z`Z1}zeOBFxSz%Z)SUq$prp$lAdsh?WHdBn|!SmmNHETgHF1rf;@{*dR#KqW~W10fC zy4bIvn#=JxvrRa|K1b%JKtQGpcf}Evyv>sEpK3?VC&`-hBH?Xa@F!(=1^p=Nt8l@- z`;Fys@%(_@o zE5Rg;3XX#}$m^fvlKb=oW-D^OKmr0l00IF(FboC=Duzgg_YDFI1pove9GTc+ge$wCNt?J-G|wBgVNn z7yt90%kS#De}#g{S%iYg?1Z4nIQTr_Z^EyMfM6maXt2*nSQoaem zI%nFUB;&pm=j1p*u!h?Au6ATkeOuF^LLyd*nzeN_8=6+u+EZRBDL18g5{xtYOm-8i z3+g}BGJqf9!XwfBxTb*PhwY$ zkfHTMa`?QRpHlrkZkmML=p?_+8VYCtcUSy z2BtZiNzUnJRc_4q>Ez^ZxPfY4a+^MSRq+_x6|Ih#)z5WhcAEEP9wy zlw~(r(sa<_yg?Q~s9 z&$H>9@0IXJ8g{LEJJjrX-4H+04MqYfaC{b)yX6ZADP;0lWY*VlWUC^sq|&CvnEk=1 zQ^7lb6Wz6(OwsffRoui9f>w$O{U8O^Fv^wAwLi&DD`~{KE`GsM96m>W1^9&CvFd~y z%E#!y_rB+~kI^!&!g*10lVa4TV?W4Y#m=gBkmpVlEY2iq`7=1N5+4w7IzMDXYe#bK@TKYUINHg`@Do!gEM z)Le{7@&?ij5ZCMO!%^K_VXZ8uwxeRoPdt6osrJNbD*G$lQSBME5+i`tSLR20k&D>= z;#~hK592+b-U*;$l{2!C9J9F zH`pIo)Ke4zyg*!cn%3pkIaNjrK;|p8KAI&h%nKyxgmm5CZ+al&yXi|!V$ z!Z+!_zM|4W|MM^zAbih%_Sb!#F9-d5Nfx-vLGq9bus?*UJHE0vU%9jdmFhFyUaa9l z$34+d8?+-ee7Paxpjmsj_@{ShJUYHm*L{`$WvQun9IbC4jODI!;m#VESn#K{PLgh| zYpvJxNhA-IqZX1hC{+m|VCE`)KPCzmA@i0br%Km~GjX6UMLdVS)W_;jk`2BU)w%5f zJE@@Tk`}()OJpFC2*K2EMXp~V@O_l4|Em(!RZ;?_YB==wCs-#Whgng>)S>TN;}rg* z%P8W}tGKt8D6YnP5P)O@5K12ky^D!?*SpUe3EqrsBf*JJImYc_LEy ztPI;PRI)==4od*@3&2(5KH-Vy0c?4!py*M_x|ek?BsB&a{;LN~yR{=%kupWym*;G1 zruSI?UM)d@_kvSwk%L*}aCOW=`w^K{H_ER+Df98o-J4?=N2bSQEuSvSmaDz9bazaw z%(3V99Xvx`4CHGp>h3yk^1YG_&`*gB*9mDxV~mQa40GB@xqFjreX<<8 z^5t^r%zln4zG>&2!D42hV%O_<=Xw%C#-`nFouftIVjx?@CEjoyQ31C9F__J|EZ+Mq zqF-3hB019O6gRynN$K}nz5Bn1Qz@7jJHz6~jYdtp`s3?gZFqI>!b1i)>3G7@gkEad#3%#7Vb~ zS~aAQN`#2^pp{@fBw((^#nY5!rBr51Cgu)+$_7T$ik{J_P}BwBJ&ks|X6&aAos*_x z3HNdD<)E6?O6uo{4Me%d3`AW80NqaKNm#FzB#s#hiycc-jRMLY4H@! zB^nj49o6y(>3DA`E(fy5CM{DGJZrmKSzdk2@BC0yd~a;1WQzZn?h&WUI@X4QlE3(b z+w<&MtM5|Sd1>D!VM7bA3!nPkynw;=`rq%v<5?1}L$5>HZ}7qMdh@yS3=fbNtYH(* zZlw82D8TwqSakGvaWB7o#X%ksIoGBRk({;;au2*T2kv+%-z&y?Gt#iM8UZ^Q-J;&R z;Anag2rR?wAk}6H!@Mngn&Q8Y(uu5K6miHgnon9n0sS$5}n zuwB`nolRPQTnxFc;T*3A=DQXx(a#~`^-P#qZ`(`r`;P;TJ2fIn)Yh>4{wmAn`*_2I zcXo_YDWqS{Noyf&OC@ZqucNFwIdP1>xJ)QR}Br#^i8D!S!b zkR1Da1Jf}ltFJtDiqMj^PnCNZu8Su!gf`0eBV4E58mR@0@`JJZ`9sA|AzZpIn25g| z26plCP5VX(#!2E8{NT+Z%Bk-|_h(*l6|D3jClvSxJEzh@kPBt3!8T~SnXhfn6NL3l zm}U-ZVHIQrK{5f`CJci*FHd;~zMxi^lvQXd;AFf3?&Jg7A3L12y%>SJN6AxE9j4$> zXF;@;v^tZQdrID|><<>O!lW z;^k^7r_o4EY)BFzmhIEF{IczIrwdwlB$E{$B0-T!Pe)&cksSPrx+23mJLl%Dw$lr; zofQtw<$V3iZ@WdGEFy7~yB{q|I&F>RahGMM=84A(S;sqz^`6aw5yU|&1%JEPwuWUn zLt{Ou>E8tlC6&tkmIJ|uF{3Bi4Kbmdh zV8-WN)5ppS{LMfFLMYXgSHWP`-<>(7J#{TjuMznNRQ9tRWs(a>-=Tn0XBI%7pwG2O zhJ`!CQPgV1G_ztV{k1^+?D*TTA=<$E2==(0qtoB{dqpUvj=k`py-*qqc#A($lS(dv?JkTW&BXkBTT6jz+5vvk@4FaFR$>Qz>;MSWd?k8;`KU# z@`%6>n_7^%gU3eR#^5BjJd+Q(Lz$mPZYQMZ9D`zd&jwS@ESnUCFv1#9A!|=*|9KS7 zmfY^XOaEq=^-sz1*%QvEh5(TRMN8O7rK;n-f#RLOt+PRfDJ3*m9sJ8BRLOZm7SzV#p8u_ocXnUb`>U^?Mc%#Us5s<&oYb0i=sBzp zRO^$Vi}fKw=&)_J`l>P>NWhW6fY9*j?cPy~mR;)iB~+bqwpS*E!z;^*WZ=W`&WD!C zewj2sM%}$CyX(7v#?KtlW-ju_BPxLFvb%hz46~Ri>@I0!bol zwfc++&A8L(=QQ^Q=F}m?6a6+}I;o&&`kay%e-&B|w{#_7(qt(sl6yvQ6 zb_1GXs%54gvl6f3+si;=3x}8T@nx<@L%UrsPndS>@BYwX3j0+#7NWEA#ap*?Uk3kik zzE>*)s{rf^R}<<9skg-vHhKaN44udB6w6i4L4Sreih1R!Da-9TRayGT%L-` zPrnDuv{yZ?KMtB5^mYvj9_+}m0y(J+*jLo^Z!3JqCdFS38#T*RNf7;A3b~clNCzBV zd`fu0vOTBjjp!&iIW%?_3BN)gO3{2CXE|*fYDWe<2oH+AZTmhQ7Cf<85Wn*rWSo#m zt>c6`;F01+kxK?!WC+T5$-=zTIL&`^!?wls=YDFVEm;hfTP zFLAYV)L9iy0g)^rtIKywVblrNv=nEBJ1NBI=%96bI{i)++Q3BfCdMc9hb_LkkV|;% zE|xhfGpj9B!VY0jZNFf%+lE|5ed_v_AjEW+u>E7TB{aKoZsF(gP^}DNyNjAG_VVca z8zXXhq*8w)_#t#aS8=Nz7^=sv-7oG2`}YhLyV&^`1;uWp6c8iG5`Gxx=<$BOmdxy< z6>2GV8^|ieJr7BgbiF2|7cw$^7B|1qL77Fdc~P~<&%RG$=B0v}UT2=c4uQ!&jdfAEOd111ae`$vGqXb{o{%9c$~sUMgPhgb$nWQJ5E>o`Z$o z^jQLV1FRuiAqg+*l`F2Z#Yd^7wC5kE-*P%0CP5H(FEIF}v!?M8HD>Y}Q`+<6zx1aG z*_sRGN=`^_g{yz60C3T3^+!-W2I$!uWgU*Odgu$r9fe&u?UMarzMHa_#jPp(4XKO- zL%BN4`QbUnA9|#`yna z!-G(nWc^%N_GdF&SeCV!ZYfN4d8w#p*@ozQVWh;5P8Q!$m((vy(-!AA%wN#Sl^DMY z3aN?d&rsjHG@e@20$hRL%OI=ea``!Dm{}Zjd};9XvH$8aF+alJrg+2d@VXP8H@wsw zy;LovX~O<|34sn6u7qaBRJ{=VE_`8h6kzFavTlk0jr$alek>Vebk62a1|s4X&$`rQ zZ>e3_+1gP!jq$~Dj2frV%^g6GB$aP=tbmqN5Y0ixakjJ^*@xFO;E)TFQu?ALxUxgu zx7Q%`!Y_Hmt6uj%W%UC1(Gw;5)Ny)%D;kKK-EuH+4v?biMBVn3C9?SQJrkD71^_~a@4YM8sRUllOj z!Q=Lr`CUXIcXgoGTfr+zHwr4pT~VZ3A*0ajCuF@-nAMIZW3SR9Yc)3^5$fqG>Gg-I zQ;@omj!q{sjkwS}q(tG!jxcKosKdigjaPtX3!J9T$tr_0niZOAOvmxiV9dp|+G1*o zbjAdAEzV^1ck$q{km?+Bo|%*#^C`y|bn`Gj;?hxgdtaeU)>qo-AWwaSYT6US?kZ;l zH6}ZCg>O(D9o#xc^qj%a;?(!$Uo8gjpAhxPQZBrQsQU9lj2joDLmIc(J5mf=<3txp z?TM|oZbk*1KfH2m$9dAGu+7mUO$U$#ZHwHfr6(#bdx5P2aJiQ;k&^e+xvk7Dwi zvRFv|<m}fqgUE7syNK^xDVnx=G=PS zV=pJBoQzu|YrEeG8mD|Dsh3jB_H_cGW`&@Q|D!+uYfOub`w|E2w`2I>k}$l|1o?VI zmxW?TQ`pP_XWZn+$g4;wCO`Pxzn^>q?*lxo4ywso&OOdYbKR|Fb|1$G&;PG6$^HKe zNY2`C7%E4tGVa#;kJZ?1OBMg-+gF6LTVA{O;HKOqvG|M2VCZY__1GOt9wpq$sAMar TnX~Eyn=bsqtlq$;INbjLB$s?i literal 0 HcmV?d00001 diff --git a/libs/ssl-config/src/test/resources/certs/cert-all/certs.p12 b/libs/ssl-config/src/test/resources/certs/cert-all/certs.p12 index b971a1e39c83baeea8e4fab3cf6b76804047ee48..73912976ca7cc61d310d02f1f8805d35ea75f612 100644 GIT binary patch literal 4895 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 delta 2438 zcmV;133>Le6qpl#FoFq`0s#Xsf(cp%2`Yw2hW8Bt2LYgh2}=Zm2}dx32}3Y~1K$P- zDuzgg_YDCD2B3li&@h4n%mM)bFoFZc1_>&LNQU{4$JmO{9AnAxe z%pE9Du}mEHoN27Q=W*6s6Ro{so@{L#X}!H4N;*$z_hJAXG!zyA@0GM4$^|As$Ba^! z;XSa!%p$x+$x0J^$|SYjEn5BlS5W6Ibtw7Ok~>U*Iewul+*yTwq+)-Ot$S$rQzT2W z701@Qzw1GNSYyMHY=J@{3u%8ZuNI>%*65q#tD!VMqfj%w;ro)L+Gn8R?ye&)tsK|_ zy=t>GI}}%Rq{_x-`1rrkVSk7{n5=Q9eSEs}EL=|M<11ILj1-1u>KAY>#2Ap4Y!6%I z%V0xkxPC(9VOs9HDHGaDktpKK=DZeuT^$OBDZ5dB`j%-X(~EV@mj!hj3n#21)H0A1 znRnXL|LitM%vVg~R7o;k1E76rL@xvVA{A>ZquL$WENfz0c?``|y=RiACPC5i zKVk-dm&Xe*EGAvC3eJkEAn`{d-q7(=Kw<#f8wm?lnE19;?y2@o*_8>zTsE?CVcIOx zC>qd#F%Y*^{nYOMLT^`9maETFAz7|ML3&2I7-%>IxP(Bvg|`lWfY5dFNQNs@Dw)w> ze#3gH&U;Yz+hfH8$JT<7Kqpv8H5O+#kDw)$7LqFltH8Pn~Jd~N4}(3mI2a*oF#tp(v=g;n!4;Ew=PriKU& zb0{20!r08ZwA_{tI!@S9ycsk2XlL)v(W}_ZJ~t1cgT#}OkAHLkULrdU*1eWjebgy` z5LLb;y7L9I2jo4tN(7YdnCJxVL=B>4X+z?a-iN?Ajfj7AOa7 z*6#rD#-HuK&|l$33)p@*?60<6#lQ+N&8%E-j{*tA?tON3N&ku<738Grt^wSc$mC4G z$cD;p@mWytimb?L$Mam^s0A!jC(~PhRLuq)dZ1Sl9be$C6b6Z&zzw;mNmO{8RhQ@I z>h>*t{n|$s5}tXeH5&+Vui}_dVe)WLT`ijAJ#EatZwKdQ!IuV2%p+fcga28mcn|1c zyPb@{!B0)C;2C%Ix7$(E6`1EtlK$|fO<#p=NGTXuCPTDfy$zID@gx3eZI8HrxS97o zobON$DDHXY)i8nuTm}g$hDe6@4FLxMpn?TW1cC)gFoFd|FoFd^1`8^NNQUM$Gz3Mz(3hW8Bt3;_c$4g?6XOYnNQTJuE$0tf&Ef&|DK32U6pmoc+) z3GEu~2Kp!%Y?s5n$^hS7=@sy3N5NC(DIt;j(9g}Ylg={C1fRmvr~%${VYBaZo_MbVfD99!bE7NzR% z5Kd#Wou{d&?>gMzVCJS?oH=@0c6`BV_q@Uvq`8)!<=KaC;HD-_2qpG#Dw)(C zA9;5urF@31Xv9%=8qQ5M7I1`N1*Xt@qpY(S(kt3EXW>@8oz@G|29aC;3T5RJdB($h z5KaloUq8Ia!F}8BkRq&qYmN>7hdRM3xDl4jC?(q_5ofs8^rk_W5SI4E?ODyXzG?3fn0_m2KfskgZe~v_Qa5#s>aQVAu@m z6Jj?F5LVIZxHZxE1dc^+MlznCvV@a{L*3<(B*DG#kQOmhXiU(%39 zWcN1AX%|d9m~IT1-tz^m0>~f64u^_YFW?g|<;1;YZ|P7c;5EIXAg#*yxT_g3M5z~S zvq}PEn<&N$crVW7dhKjtO~hGWx67u?7Jvry@81Y%rf8xiC+#V$%mmUn4-Q|lFmutB zla#6i#+pZmJyCCep)E~0d)yzfW2alUl}en_4k}PZSCM>{OTtwN5~ZVj*_Lp;_`0z2 z(Xh>7U*KIFU0un1F0N!EAml{ z?|#Off5UFyQr);NU~pE!y%9H(KmZnfe61!fjb5C=BWY)Ud=_c2cOgzjDx89elDYdy zfy6zX6p$*%%8j`1`gN%gS^+fVvuguBpZi#T7DDwn;ZA~NL$h9r3zV-t9&`rB9=SwA zTjgXpFZ-ugz!n$oz-!UpChPrPIT0ruJN78EZAUME=a8`@!`~k&M95{U!0QmhfaDOL z1YuV)^Oj7|uA?|4TCt`M0J6hG+z3gOgxv!^ZzItdP5OxphuAJdyDH@@PqoRB<UAr>3A& zo{q{TC6&iE%c02Z_#^Uy#e!gUDl}S5KG@rlTa&2?E=L($fBS6PWQ#G?>=~AZ65$LL z|2{A=Fd;Ar1_dh)0|FWa00b20bFT$JkH{yYoH6LjuRN%@20#4-2;8v43FM87R00AB E018}lU;qFB 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 28602ac097f78..f116410329c81 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 @@ -98,7 +98,7 @@ Create a `CA` keypair for testing Generate Certificates signed with our CA for testing -  openssl req -new -newkey rsa:2048 -keyout n2.c2.key -reqexts SAN -extensions SAN \ + openssl req -new -newkey rsa:2048 -keyout n2.c2.key -reqexts SAN -extensions SAN \ -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=otherName.1:2.5.4.3;UTF8:node2.cluster2"))\ -out n2.c2.csr @@ -123,3 +123,71 @@ and the respective certificates openssl req -x509 -extensions v3_req -key private_secp256r1.pem -out certificate_secp256r1.pem -days 1460 -config openssl_config.cnf openssl req -x509 -extensions v3_req -key private_secp384r1.pem -out certificate_secp384r1.pem -days 1460 -config openssl_config.cnf openssl req -x509 -extensions v3_req -key private_secp521r1.pem -out certificate_secp521r1.pem -days 1460 -config openssl_config.cnf + +# Generate encrypted keys with PBKDF2 standard (OpenSSL v3.3.1) +In approved-only mode BC-FIPS supports PBKDF2 standard only. +PKCS#12 (requires PBE), JKS or JCEKS keystores are supported in general operation mode. + +## RSA PKCS#8 +openssl genrsa -out key-temp.pem 2048 +openssl pkcs8 -in key-temp.pem -topk8 -v2 aes-256-cbc -v2prf hmacWithSHA512 -out key_PKCS8_enc_pbkdf2.pem + +## DSA +openssl genpkey -genparam -algorithm DSA -out param_temp.pem -pkeyopt pbits:2048 -pkeyopt qbits:224 -pkeyopt digest:SHA256 -pkeyopt gindex:1 -text +openssl genpkey -paramfile param_temp.pem -out key_DSA_enc_pbkdf2.pem -aes256 -pass stdin + +## EC +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/key_DSA_enc_pbkdf2.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/key_DSA_enc_pbkdf2.pem new file mode 100644 index 0000000000000..bb1655d2e9548 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/key_DSA_enc_pbkdf2.pem @@ -0,0 +1,18 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIC1TBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ9WLcmXfK4mQgb8z0 +VEFgnAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEGUh9m77oFyis8j5 +VedmDqIEggJwymDZJmHaNgIiJAI/psd+hR4n03oMwUaV72DmQewEdMhI2sEy36WU +Pup7X8VmRLb4tyiSiEUlh8FIX3cMpQ11e1j/lwW7wF+W3Qb6CHcMu8FCz3LN/CS4 +M+sQttfXiHh70qZvRx0SNaJo8A+e8HRGmYrbz6VqdlslSdB4fDT8Igls45rDZbch +LJlHQfy9XQSgCFR6J+6/6Q8GyW07+WnkuYnbixN8ZdZ4jPE5mrZYMMQrQY0l4ThG +vpb7U6VnWepDnXgeNWZTjHVLSAx3bbLUpbwotJnZISyTlRCxFSnunrRIkgaWPNMr +qE78FfE8I8Y/3Ft3AURgM+o/AvgyNCNM9g6DCqjaYpuaK0aJpdvaez9BiiANosBq +Powto+vuaDyYVIEhZ+GbokkvXx9muzvyA3KpqN1dg18au7Mqpkrenrw7Z5J8TnS2 +Pv686vSxCmisInC7c7uQYVxhze7fYMDUsyvWNPNUUrYnqrVtZtjD+VjkuZHJrBnL +haz5xQ0cw7pPY9r8R1y5jxMCVKxMBvbOsQJ+MBqGXseYmeB8qBBMYVdC+bNdEzga +rWD6FCX/k0PH2nP6KaU3qWLh3ueEtwTh0KO4yXgKyiLzF1KXoF93+4i9hX2w+t/W +Y5jgNErriqrW5WOQFDrSlVmMx1dLNFzM1cB7TKygZrzytULAYAg/0el8Gjbw7nKP +HInVUFKWhpNipEhDCGnGKoBvSz88AYAHS2I4fnFg3AfZCWEkkKJg++Y4Wip4+KTC +XjECqMqv6hwNbvMf3JkmqTPZVh8MtLIAiR1rUIWdZMq18+4vnHtW0FXzLb2nYn3u +ZrtXtOGxpBUY +-----END ENCRYPTED PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/key_EC_enc_pbkdf2.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/key_EC_enc_pbkdf2.pem new file mode 100644 index 0000000000000..b851058d17217 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/key_EC_enc_pbkdf2.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDA0+sj4ekT4h5OgmLaj +idCmLthqOUDdUNf67bBLjRSapUedsBIqSCx2u5E9ca2uGXKhZANiAAS6mhP+8zyk +CYIaOgF35O1KeRxrPsvWfm8tb5+KjuepPI+WR33xiBQcnYfeNrYMgP000Ifk8gfS +mv5aCHa5dBdgTzixsupMng0R8/jLPtS73Fzhi6G+KlRIe58c0xcVB5o= +-----END PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/key_PKCS8_enc_pbkdf2.pem b/libs/ssl-config/src/test/resources/certs/pem-utils/key_PKCS8_enc_pbkdf2.pem new file mode 100644 index 0000000000000..445d50f1cafe2 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/key_PKCS8_enc_pbkdf2.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ86A6gbRa4DZIX+cz +TSf/DAICCAAwDAYIKoZIhvcNAgsFADAdBglghkgBZQMEASoEEABJ5byRdWBpd1Ho +U/5ukYAEggTQoJkyyzwsns3QvYy4hIuwge7G867QPSCnHXhKInOYNDgbTnf36ia/ +eO5PELfEW0sW6ZZt/D9h28vssT0RI4PTyCQCv3DaVym6f9JbmnfvJePlaWkheieN +j2Y1gth4fEFWKQK6Px3hkZCCjc1LGrSSKoqy3YhWlxbjrj0UfCpF60MY0TLcegZ1 +Zdl4HVjROcDpSBC/OyWb9LXtyUM5NJVEjHqr138iP/S/qtkn7kovJEVqUSIZd2T9 +BQwzCDzZD8Rl3W/ivZnCn/3lHkDl2JgQ9gVXrk1QhtKy0XF8z1lrKbYPkCL4nXR7 +2qOScFSvF/JjbmhxlnfjyrpCv4ckcvT/+KFvbNQP1p8/OFfIsapG6wTz2XGcwgA/ +c4uxrnB/110KO2m1zexsasxRTfvyHaTIPHl6NNh565cjieqdvp5KbzZBs9eJA19e +NTeLVbXYZA5Ols0FF9cG5eeU7NPVFVMS7UILHnq9v+i1eKO1VPUWmCZhR8Sje0M2 +DpzSnQmrErVaH/lbZ9ZOklFhpL+UvW+g8IBSLLdCo+MlyOr/Ydr0HiADBb5zSiUo +iWOrzgA9lLDG7VSHrpTU0I+PE+QctLVTPX2f+S+/pErnQ7Y+DE+OOsM37jGt8Zsi +r+XcxxTZUmiakr6fUDEVG0NxbErTRgpHdSoT3RFgcs37MlrC88JbOs1cOiwma7/e +56gqx/3uHJWyPKjVC/RUfIqsSpTx1EjqHeGYnJ9DTW+Yft+d7/HEZOr0Nl+3Qmoa +b6Bxw+5c6Of7HYhEKoi37l7O1//bmrs0pURPWPmawPZtlfwd03ifFTZDOvn8cKEL +TUFHBYd3V8kNmqRI/oUq28gk7uFd0Wby4epXcSVgSX2hAloSMYGfzcUU5u38v18J +JxYgg3DyJAMH3V/GHV98XU0zscbaTKreKMUaXduDS3ktk9maq6Mne/fpI/ZZP7pr +C7c1RJWKbSSdwAchQCMcIHUSZjA0iI7dIde9VP8e1DlErdWch3i7wdJQV4YqMM8v +3sR3fV31vkZcSUDRCcBJPlNd/j6+AaIU0zVt3yWUUSCExSAOCybrlu0JPCSjjyOu +Kkp0xEa6xt240QA+PyeUl7aov1wKZ1P95aek0y1AJy9SmcBUwBBVaeG+ETO/C5gv +g6VqjG18BX6ulzJsOsLnQCxvbQajB/eF7dvex2OzU+jPUPuvZ1IRu4SHw88eyGz6 +r8RzQ1d7sCr+kV6pWXrEaNnwyFhOhwdNMxaYwSUItrfb3+4jPDoHa/di0sJ4Dkr/ +UVuqnc7TAdW9x+PTtUWMQfaX7S8o6XDNXkhcWznhNP7OmkQpT2K5kkaGfLeHKRbz +7NHCwRXEm49ZPfDCnI9kddnejU60vDHW1uBGH2S5kn71noAe7R07s9qeKW50eLOf +Pe9BlOPb205gnibRYjjj0pUZ1YJwD4rkiXaX/fXHkPpgpyUEbw3tAZW+FqXUZSaW +TwAj41oXms0VoaUi/TcvsIDjnldVvZ0MkHUwMtfOvHb/lbrafHKoTHIuBbRAHSNf +uQXvBDwiq2uv3v0EZdz4mouqcp8aNZmunVHu7c22HCaf4s608BIq4FBqrq6XRdqo +cAOcq+WGk+F/helMKaRWo737062tq3dlhtRpGLXbZUcYThUNY4SjR6Q= +-----END ENCRYPTED PRIVATE KEY----- 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 0000000000000000000000000000000000000000..8d1a016d790b10dfe792cbaa4abf1582116aebec GIT binary patch literal 6009 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 literal 0 HcmV?d00001 diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.jks b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.jks index ebe6146124e8fd607e46a1a3129bdf9b4de0370d..25363b6d8a5a4b3d791e1216c32b169ff39a1bf9 100644 GIT binary patch delta 4840 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 delta 77 zcmV-T0J8t=aga$2{_Xzl0000200008vmY_Q1GBFJ{R6Y#H2DLwrv_jIu@RgCvv(W^ j3A11=!UMB^7xe@b>~lBQV-e5ldhPAzVKh9cG#>Gr$^{`1 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/main/java/org/opensearch/index/reindex/Reindexer.java b/modules/reindex/src/main/java/org/opensearch/index/reindex/Reindexer.java index c553effc65ab5..92313e27f0544 100644 --- a/modules/reindex/src/main/java/org/opensearch/index/reindex/Reindexer.java +++ b/modules/reindex/src/main/java/org/opensearch/index/reindex/Reindexer.java @@ -45,6 +45,7 @@ import org.apache.hc.core5.util.Timeout; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.action.DocWriteRequest; import org.opensearch.action.bulk.BackoffPolicy; import org.opensearch.action.bulk.BulkItemResponse; @@ -182,7 +183,14 @@ private ActionListener getRemoteReindexWrapperListener( } static RestClient buildRestClient(RemoteInfo remoteInfo, ReindexSslConfig sslConfig, long taskId, List threadCollector) { - return buildRestClient(remoteInfo, sslConfig, taskId, threadCollector, Optional.empty()); + return buildRestClient( + remoteInfo, + sslConfig, + taskId, + threadCollector, + Optional.empty(), + CryptoServicesRegistrar.isInApprovedOnlyMode() + ); } /** @@ -199,7 +207,8 @@ static RestClient buildRestClient( ReindexSslConfig sslConfig, long taskId, List threadCollector, - Optional restInterceptor + Optional restInterceptor, + boolean isFipsEnabled ) { Header[] clientHeaders = new Header[remoteInfo.getHeaders().size()]; int i = 0; @@ -226,11 +235,16 @@ static RestClient buildRestClient( } // Stick the task id in the thread name so we can track down tasks from stack traces AtomicInteger threads = new AtomicInteger(); - c.setThreadFactory(r -> { - String name = "es-client-" + taskId + "-" + threads.getAndIncrement(); - Thread t = new Thread(r, name); - threadCollector.add(t); - return t; + c.setThreadFactory((Runnable r) -> { + Runnable runnable = () -> { + if (isFipsEnabled) { + CryptoServicesRegistrar.setApprovedOnlyMode(true); + } + r.run(); + }; + var thread = new Thread(runnable, "os-client-" + taskId + "-" + threads.getAndIncrement()); + threadCollector.add(thread); + return thread; }); // Limit ourselves to one reactor thread because for now the search process is single threaded. c.setIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(1).build()); @@ -313,7 +327,14 @@ protected ScrollableHitSource buildScrollableResultSource(BackoffPolicy backoffP RemoteInfo remoteInfo = mainRequest.getRemoteInfo(); createdThreads = synchronizedList(new ArrayList<>()); assert sslConfig != null : "Reindex ssl config must be set"; - RestClient restClient = buildRestClient(remoteInfo, sslConfig, task.getId(), createdThreads, this.interceptor); + RestClient restClient = buildRestClient( + remoteInfo, + sslConfig, + task.getId(), + createdThreads, + this.interceptor, + CryptoServicesRegistrar.isInApprovedOnlyMode() + ); return new RemoteScrollableHitSource( logger, backoffPolicy, diff --git a/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteBuildRestClientTests.java b/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteBuildRestClientTests.java index 2e14df4628283..7fbd6044d9b18 100644 --- a/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteBuildRestClientTests.java +++ b/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteBuildRestClientTests.java @@ -77,7 +77,7 @@ public void testBuildRestClient() throws Exception { assertBusy(() -> assertThat(threads, hasSize(2))); int i = 0; for (Thread thread : threads) { - assertEquals("es-client-" + taskId + "-" + i, thread.getName()); + assertEquals("os-client-" + taskId + "-" + i, thread.getName()); i++; } } finally { 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 4fa84f88af35f..36b6edaa9c703 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 @@ -37,6 +37,7 @@ import com.sun.net.httpserver.HttpsParameters; import com.sun.net.httpserver.HttpsServer; +import org.opensearch.client.FipsEnabledThreadFactory; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.RestClient; @@ -70,6 +71,10 @@ 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.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -84,6 +89,7 @@ @SuppressForbidden(reason = "use http server") public class ReindexRestClientSslTests extends OpenSearchTestCase { + 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 -> {}; @@ -93,6 +99,9 @@ public static void setupHttpServer() throws Exception { SSLContext sslContext = buildServerSslContext(); server = HttpsServer.create(address, 0); server.setHttpsConfigurator(new ClientAuthHttpsConfigurator(sslContext)); + var threadFactory = new FipsEnabledThreadFactory("test-httpserver-dispatcher", inFipsJvm()); + Executor executor = Executors.newFixedThreadPool(1, threadFactory); + server.setExecutor(executor); server.start(); server.createContext("/", http -> { assert http instanceof HttpsExchange; @@ -108,18 +117,31 @@ 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 { final SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - final char[] password = "http-password".toCharArray(); final Path cert = PathUtils.get(ReindexRestClientSslTests.class.getResource("http/http.crt").toURI()); final Path key = PathUtils.get(ReindexRestClientSslTests.class.getResource("http/http.key").toURI()); - final X509ExtendedKeyManager keyManager = new PemKeyConfig(cert, key, password).createKeyManager(); + final X509ExtendedKeyManager keyManager = new PemKeyConfig(cert, key, STRONG_PRIVATE_SECRET.toCharArray()).createKeyManager(); final Path ca = PathUtils.get(ReindexRestClientSslTests.class.getResource("ca.pem").toURI()); final X509ExtendedTrustManager trustManager = new PemTrustConfig(Collections.singletonList(ca)).createTrustManager(); @@ -164,7 +186,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()) @@ -172,10 +193,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)); + } } } @@ -189,7 +221,7 @@ public void testClientPassesClientCertificate() throws IOException { .putList("reindex.ssl.certificate_authorities", ca.toString()) .put("reindex.ssl.certificate", cert) .put("reindex.ssl.key", key) - .put("reindex.ssl.key_passphrase", "client-password") + .put("reindex.ssl.key_passphrase", STRONG_PRIVATE_SECRET) .put("reindex.ssl.supported_protocols", "TLSv1.2") .build(); AtomicReference clientCertificates = new AtomicReference<>(); @@ -211,8 +243,8 @@ public void testClientPassesClientCertificate() throws IOException { assertThat(certs, Matchers.arrayWithSize(1)); assertThat(certs[0], Matchers.instanceOf(X509Certificate.class)); final X509Certificate clientCert = (X509Certificate) certs[0]; - assertThat(clientCert.getSubjectDN().getName(), Matchers.is("CN=client")); - assertThat(clientCert.getIssuerDN().getName(), Matchers.is("CN=Elastic Certificate Tool Autogenerated CA")); + assertThat(clientCert.getSubjectDN().getName(), Matchers.is("CN=localhost,OU=UNIT,O=ORG,L=TORONTO,ST=ONTARIO,C=CA")); + assertThat(clientCert.getIssuerDN().getName(), Matchers.is("CN=OpenSearch Test Node")); } } diff --git a/modules/reindex/src/test/resources/org/opensearch/index/reindex/README.md b/modules/reindex/src/test/resources/org/opensearch/index/reindex/README.md new file mode 100644 index 0000000000000..f2ff25d41a890 --- /dev/null +++ b/modules/reindex/src/test/resources/org/opensearch/index/reindex/README.md @@ -0,0 +1,48 @@ +# generate self-signed CA key + cert +```bash +export KEY_PW='6!6428DQXwPpi7@$ggeg/=' +openssl genpkey -algorithm RSA -out ca.key -aes256 -pass pass:"$KEY_PW" +openssl req -x509 -key ca.key -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.pem +``` +# generate client key + cert +```bash +export NAME='client' +openssl genpkey -algorithm RSA -out "$NAME".key -aes256 -pass pass:"$KEY_PW" +openssl req -new \ + -key "$NAME".key \ + -subj "/C=CA/ST=ONTARIO/L=TORONTO/O=ORG/OU=UNIT/CN=localhost" \ + -out "$NAME".csr \ + -passin pass:"$KEY_PW" +openssl x509 -req \ + -in "$NAME".csr \ + -CA ../ca.pem \ + -CAkey ../ca.key \ + -CAcreateserial \ + -out "$NAME".crt \ + -days 3650 \ + -sha256 \ + -passin pass:"$KEY_PW" +rm "$NAME".csr +``` +# repeat the same for server key + cert +```bash +export NAME='http' +openssl genpkey -algorithm RSA -out "$NAME".key -aes256 -pass pass:"$KEY_PW" +openssl req -new \ + -key "$NAME".key \ + -subj "/C=CA/ST=ONTARIO/L=TORONTO/O=ORG/OU=UNIT/CN=localhost" \ + -out "$NAME".csr \ + -passin pass:"$KEY_PW" +openssl x509 -req \ + -in "$NAME".csr \ + -CA ../ca.pem \ + -CAkey ../ca.key \ + -CAcreateserial \ + -out "$NAME".crt \ + -days 3650 \ + -sha256 \ + -passin pass:"$KEY_PW" +rm "$NAME".csr +``` diff --git a/modules/reindex/src/test/resources/org/opensearch/index/reindex/README.txt b/modules/reindex/src/test/resources/org/opensearch/index/reindex/README.txt deleted file mode 100644 index efd5e4c20ffd3..0000000000000 --- a/modules/reindex/src/test/resources/org/opensearch/index/reindex/README.txt +++ /dev/null @@ -1,16 +0,0 @@ -# ca.p12 - - -# ca.pem - -openssl pkcs12 -info -in ./ca.p12 -nokeys -out ca.pem -passin "pass:ca-password" - -# http.p12 - -unzip http.zip -rm http.zip - -# client.p12 - -unzip client.zip -rm client.zip diff --git a/modules/reindex/src/test/resources/org/opensearch/index/reindex/ca.key b/modules/reindex/src/test/resources/org/opensearch/index/reindex/ca.key new file mode 100644 index 0000000000000..a04c18c994359 --- /dev/null +++ b/modules/reindex/src/test/resources/org/opensearch/index/reindex/ca.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ8TSOq343U8BV3rEt +vOpSPQICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEFXKi3C3VJzsGiCw +Lh2zY40EggTQwtBoa+e+J/UAA/mVv50rVH7oqvs5t9wRfznrldPtUgTR7r06TxNB +DXN1spBSmJjrohC3RbEO4169YqCwAk2HsptENM3MV5A9EwTuXPVBW/ic2SDOwmiP +wvRRKUujjaYZTfVeVJi0LqnCtyv7/hc33MJ3IMeNefEwmYRH3u/ktp+NBXZPEp1G +sdbPLpCxUqtq8zE84ev+RyURbErWVvjI8ma20Hn2gACkQazYTSVMVMxvj4+m0oBd +hzQ54GjRypm6Tc+CkJXGbCp+3sCONUqKARZYo+oiL5wEdGTLOcCwaCZxVkftDZ4V +oGrHVlgFrYgADaOuokjMf178ymMJX1+kTYze/k/ajXHd8qBKRD1X49dDhrHjnlhV +2sGOTKk16fBXSoM/q4vfmBKkd+BxDcdbsDkLDdT266XBy9hdRnL6e3Qk6ag6i0dB +faJwyXHIhiS87nFLpYeXY47DABBvmKVqafdHJDab7GYmLb+2J33EbmQX+tMgKrI+ +l5FjPX0Lz6/c74M6jYGHhbii3fZKGzb9BwWCEG7eIMONfv7IoaP2HI/P5G1WheQ+ +Ocd4lsb+pCmy+tzQcB7+GtWX0sG4ugCTsKIofN9ZmkvdQsvQvjT/oubDtBXUMgIL +/6GpYr7f535wD8jp4qHjSNyiNf93XiepxUsKBh0xvcGRRfhEjrZhnDm8DYP014bL +HhWzPVUgQwDJMa92wzsqFpXCujhLDb3BzLZLCGWDUkDsPjX2hUzNRWw+nN0FEwkD +ezxZOpK7m/ZfZi0rI94oYpmanwLNH5tvwr7pKLJ2SAP2WTNYRtff7vgeKOmgDG97 +pSm49phrSdM/VbwWgoPHpGxn6De5mfp+52dz5sCZMP0tsYMa947z2VDAU9f7+AQL +V73HGQKu8eny2ofOvQiKMK7sVo9dDvf6O4fGUCZh55YmQYzNq1cYh5lgQgPJ/CDb +c2mUVhwPfd4gvmKzBQ+nxjo5Jbh0vJwqOxk0SMCwWqQW5+Y9mdcDseyJwL7iyiTd +xyN9rUdro86foF85Xja+MZ0hVW/q1xwrZSiunWuvg0uaGMdSuknn7skLnKrdbfIU +RocweZPetFxzCm7XeikCaKucoNLNSPjAKW13doZSOc4OxS4hXep211dGVvK43XwX +B6xp8WtquZaGk01J789H1XU/sz6AssuCrMvql0Gd/GeFz+Ql9dMd4bH2ZzjpRcWL +FMZvsxXzqp5zodsn/j26h+WKZYmLSnxvE+WjQHyECt1JgSyYD2I84CxKj9I5ezX7 +1PIc3/OPl14p+ni/lfx6UM5WmbrHcuLM5a2ml/9e+HQci2xDNflkCiRQ1jcXYSB4 +p5mAaxSPbC33mi7jvBtUF1Yk9CiIRW941pKhn5YSj4bEMs6h8tB4M9wfXn9HPe/X +0KdYFMzf5sc9nmDZt2A1EoZexYwMk56wVQ7gnekw9ECCs6OLUmXkAmKojvbNXG0C ++t0W3LSoFsMM6vnINVooK+dQgRLqXFe57HY8j7zTmFh69Kh3/Cv24gQ21xwPYB6y +A9AVrrxRUV4Nlqkw5A4kVKXRry9/xj5DGgZ4SI2rJZ3vhfD2jiLFnl+JBT/Cw2xL +NL32subXNGqY4ymnq1HSG3SO/Jgh21XZL8rl2kZ+QiT7QvRVFWefRdA= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/modules/reindex/src/test/resources/org/opensearch/index/reindex/ca.pem b/modules/reindex/src/test/resources/org/opensearch/index/reindex/ca.pem index ee758ca3e6370..615f00e468ae6 100644 --- a/modules/reindex/src/test/resources/org/opensearch/index/reindex/ca.pem +++ b/modules/reindex/src/test/resources/org/opensearch/index/reindex/ca.pem @@ -1,25 +1,22 @@ -Bag Attributes - friendlyName: ca - localKeyID: 54 69 6D 65 20 31 35 34 37 30 38 36 32 32 39 31 30 37 -subject=/CN=Elastic Certificate Tool Autogenerated CA -issuer=/CN=Elastic Certificate Tool Autogenerated CA -----BEGIN CERTIFICATE----- -MIIDSTCCAjGgAwIBAgIUacmv5ElKJ1cs9n61tEpy5KM3Dv0wDQYJKoZIhvcNAQEL -BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l -cmF0ZWQgQ0EwHhcNMTkwMTEwMDIxMDI5WhcNNDYwNTI3MDIxMDI5WjA0MTIwMAYD -VQQDEylFbGFzdGljIENlcnRpZmljYXRlIFRvb2wgQXV0b2dlbmVyYXRlZCBDQTCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ0rA35tPl0FN+BPk2YfmET9 -MvDWFLvfL2Z1aw1q1vnd12K9zumjN6veilHA2Iw/P4LG/mkQZvY4bDPgibRD7hbE -vwPoju4vr614tw60+FlkpO6HezYo2I3cni1//Gehhs5EW2P3g7Lw7UNCOAfcR2QQ -p/dtwXYWzXHY9jTevQSv2q/x5jWKZT4ltaQExzvXAcxRGqyWV6d5vol3KH/GpCSI -SQvRmRVNQGXhxi66MjCglGAM2oicd1qCUDCrljdFD/RQ1UzqIJRTXZQKOno1/Em9 -xR0Cd5KQapqttPusAO6uZblMO2Ru+XjCD6Y0o41eCDbkd0xA3/wgP3MD5n41yncC -AwEAAaNTMFEwHQYDVR0OBBYEFJTry9da5RZbbELYCaWVVFllSm8DMB8GA1UdIwQY -MBaAFJTry9da5RZbbELYCaWVVFllSm8DMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI -hvcNAQELBQADggEBADA6qhC35PwuL7LRddbhjjW8U/cCmG9m7AIvH6N+Mw/k76gt -tJkEDxztMHUG+A2IPyEcYm7MLr1D8xEQYsq0x4pzFcQnMSQDv4WTK35vRxMtaqwA -WZTyA+DibBknbaP1z3gNhR9A0TKx4cPagN3OYFvAi/24abf8qS6D/bcOiPDQ4oPb -DVhmhqt5zduDM+Xsf6d4nsA6sf9+4AzneaZKGAMgCXgo4mYeP7M4nMQk0L3ao9Ts -+Usr8WRxc4xHGyb09fsXWSz7ZmiJ6iXK2NvRUq46WCINLONLzNkx29WEKQpI3wh4 -kyx6wF9lwBF06P1raFIBMeMOCkqDc+nj7A91PEA= +MIIDszCCApugAwIBAgIUOpUOL6Dz5+T+y+SIDknp8nOB2x4wDQYJKoZIhvcNAQEL +BQAwHzEdMBsGA1UEAwwUT3BlblNlYXJjaCBUZXN0IE5vZGUwHhcNMjQwODI3MTgy +MDE2WhcNMzQwODI1MTgyMDE2WjAfMR0wGwYDVQQDDBRPcGVuU2VhcmNoIFRlc3Qg +Tm9kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK2bmzHyMB705hS2 +Vu02WaTz7iWU11aVlNwAEVWIpjarDsk1IeICYe2vtv7e9qAp5IAMC6y9Db4XAx6A +PKJHZ5XcrWKpJqanMUwMi7dJ7wLWauMlx4WdyWSdJ3KRVO0Xzdr6My6dV+LCiiYX +cQCFYzEQYX02kU8M8NZ3J9t5OK3MF8/f0gta5vMs/1akPJzTMYyLva+hcNyGC9pW +Ly0w2kWxqze00KjT8wnmUz3h6gxxRwwdocsyZ1AE635anRu2MuAo94sA8kwQdl6z +cKtTzlzbLmrBQzusnuQtJCKGzvH+uBGodFpQhi5JpYVbuSvqI1Lumg7RA524cb0t +OKnijBECAwEAAaOB5jCB4zAdBgNVHQ4EFgQU41fNVZMW0Kc5nmv53kKTINZT0CMw +HwYDVR0jBBgwFoAU41fNVZMW0Kc5nmv53kKTINZT0CMwDwYDVR0TAQH/BAUwAwEB +/zCBjwYDVR0RBIGHMIGEgglsb2NhbGhvc3SCFWxvY2FsaG9zdC5sb2NhbGRvbWFp +boIKbG9jYWxob3N0NIIXbG9jYWxob3N0NC5sb2NhbGRvbWFpbjSCCmxvY2FsaG9z +dDaCF2xvY2FsaG9zdDYubG9jYWxkb21haW42hwR/AAABhxAAAAAAAAAAAAAAAAAA +AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBObbHtMsaa0XTJAlJk4DE9kHgZoxF8ImFI +c1huhnCr2X+XkKxYDF/QUA1XRDWI9S4/6xBDKZdD+RhZ6ds3CbG4JVtoJa1Vvjla +dk11uirkKCqbYrdyc/+KeLS4ruYhG/JoqycTp/G5aCrThZgIgf0jm4peJwd9nqaz ++yjP4L4sDR4rfdLIsk96hPKDImD+5uuJ9KqMj8DO589uqJwhTehfPcNfL4hVdQ66 +IEKK6HM5DMXYzRFr7yAseKZbXngn5QJ+ZBldikP0hgGFYbT1kbNtFOqwpYNvgGvr +ptei46poM3WCB04puszm62E4Jora6rxaLwWGp+6TWELLwUUs9so7 -----END CERTIFICATE----- diff --git a/modules/reindex/src/test/resources/org/opensearch/index/reindex/client/client.crt b/modules/reindex/src/test/resources/org/opensearch/index/reindex/client/client.crt index 337d24e2493ac..9111fb215a448 100644 --- a/modules/reindex/src/test/resources/org/opensearch/index/reindex/client/client.crt +++ b/modules/reindex/src/test/resources/org/opensearch/index/reindex/client/client.crt @@ -1,19 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDIDCCAgigAwIBAgIUNOREYZadZ2EVkJ1m8Y9jnVmWmtAwDQYJKoZIhvcNAQEL -BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l -cmF0ZWQgQ0EwHhcNMTkwMTEwMDIxMDMyWhcNNDYwNTI3MDIxMDMyWjARMQ8wDQYD -VQQDEwZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCCP2LE -nws2+ZIwSQ3IvIhVfrueUmNt7Y5TdhhwO32p2wC4ZA62J9L8klAzt7R+izcL/qbF -65inbXM0A7ge/2wZ09kbqBk5uS8jDetJS8lQmWVZDHfVi8g/yDMWklz2mQYleYmU -HPyIplai3P3KBoT8HurzHw2C953EZ2HiANFnGoEPZZ5ytcT2WenxuU5kSXSxuDyn -8/dCVHEQL1Yipr2LQKYQAHotjo56OhyL9KS5YPjzSFREeyRfQinssTmpGFsua/PK -Vqj+hRdkaqRfiqPq3wxn8oOSpZLQe58O1e7OlqgjkPuZdjZ0pQ7KJj7N3fUQNSeg -2VC2tk8zv/C/Qr2bAgMBAAGjTTBLMB0GA1UdDgQWBBQziDNuD83ZLwEt1e1txYJu -oSseEDAfBgNVHSMEGDAWgBSU68vXWuUWW2xC2AmllVRZZUpvAzAJBgNVHRMEAjAA -MA0GCSqGSIb3DQEBCwUAA4IBAQAPpyWyR4w6GvfvPmA1nk1qd7fsQ1AucrYweIJx -dTeXg3Ps1bcgNq9Us9xtsKmsoKD8UhtPN6e8W8MkMmri+MSzlEemE+pJZrjHEudi -Sj0AFVOK6jaE0lerbCnTQZvYH+J9Eb1i9RP7XHRShkR4MWgy2BzlENk9/LRbr84W -Yf5TuM9+ApiiiOoX9UfSGBzNnqwhJNpG9yJ+HnQSqTnJJc/wL0211zLme9I/nhf0 -kQx6mPedJ3gGoJ8gqz38djIrhJDxq+0Bd9SsdlR6yT+1+bY7hinYx2eLV91AybZ4 -x07Kyl174DD41PYaE1AtoLlrMrQ5BG7Md50Am+XXOR1X1dkZ +MIIDUTCCAjmgAwIBAgIURxNp9ImDloxqOPNAP0ySBZN/BDQwDQYJKoZIhvcNAQEL +BQAwHzEdMBsGA1UEAwwUT3BlblNlYXJjaCBUZXN0IE5vZGUwHhcNMjQwODI4MTA0 +MzUwWhcNMzQwODI2MTA0MzUwWjBiMQswCQYDVQQGEwJDQTEQMA4GA1UECAwHT05U +QVJJTzEQMA4GA1UEBwwHVE9ST05UTzEMMAoGA1UECgwDT1JHMQ0wCwYDVQQLDARV +TklUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCp7qyGufu1cQYWJJGZ04XulVdwsKytMeLNSDHT90ratfsAy5WP3CRy +fug0E6nB7eykSHnE8aYomrghJIL0oP3v7b7vV/iasZ17Q2uiY67fQb4s6Rvrcov5 +R7ak5/B22uslDrDY0BaSWKCxHREb55rMhVWlVTXpm91kdGvo4Q61Gcxe45mweKR8 +UMbUlNuXrW/xwTwYI4pdDxha2ZXgTBrBJXppEh/KQp0rdy4Be3KG5IbqrH/Bh6cG +4CZ/di0i6xWxAhQOlOKlcTHpMAtXx0eBjha/Y9+p3/7z9fmE/JsYozw56r75CPDG +VpNiSDoPMPed4uhpbXQVYeCTUe3Hh8WRAgMBAAGjQjBAMB0GA1UdDgQWBBTm5Cel +/aWnBGFDUnZKNYs+BVFHFzAfBgNVHSMEGDAWgBTjV81VkxbQpzmea/neQpMg1lPQ +IzANBgkqhkiG9w0BAQsFAAOCAQEAjaXJN+NyS74cDTAtjVqo4e+h2K/LfYyIpdYp +mTDi+wRBlprJUDl18TK26c0hV6T4MN8QxqoqCXoEVJZWDjBYOUsl3OfSgPpT0aww +3Z/mIPOLb9mR1zOO9tXZhgNdFCLRRepiLyPRsRVQ3K3klle42DHaEIOUlwtqAArF +d9MKg9PShrRjqJwlm8vL3E8KjNeC8gAvebF3e7ADIatXjRK5Rc/LQhgPCaCZKSDF +w36AhGBnXsCgi3IR00E9CWOsC2UVeAhgHHaN1oJjuLfFupG/2Vx6Ii+PAgueE7ec +VWQeasxHihc0VjEYtSiNlYO6A8rcH7lg+0OCzGr97DC+zfFZwQ== -----END CERTIFICATE----- diff --git a/modules/reindex/src/test/resources/org/opensearch/index/reindex/client/client.key b/modules/reindex/src/test/resources/org/opensearch/index/reindex/client/client.key index 95e11f79cea24..ca0c6ba868047 100644 --- a/modules/reindex/src/test/resources/org/opensearch/index/reindex/client/client.key +++ b/modules/reindex/src/test/resources/org/opensearch/index/reindex/client/client.key @@ -1,30 +1,30 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,81AB10154C04B38F - -0L6Buvpeg6QHh/mbYp/3bXDCsu0k0j5xPdIGWd6NCOdb24OQFsOjeA2WuPqs0WWF -gzVrjh984biS3IqeglEr6X6PfVJ0QOgBkq0XgSBXhuoRJL/302N9oPGsf8T8oW9t -pqR/JIB2L7lMbJlJYSjMl0YQT3hWpo2BlrtSIc/GWOKfjDNWc9BL+oHvKJwql1lb -n4yMvYFYJDqgzgxa3r4IIQNsCn3SP+gqbTx9vF6StOIroV51BdSL4IGWRvqnMJrh -ybk1EHSLR1oGcONLU4Ksi33UxdImG70SsnoH/NnInDvV2bxmxmgf5SfYKtxFhoxz -0hISKTMTerPGtRQ5p8wtEi/ULKyInK+qF3tLgZa+S5VbByjDnUo2dCcbDDSkH5pO -uczJ2bs1kJegpCrUueJdbi9OX2upmF+tJb9+5hzFTvey8dUWTEpdiN0xbp4BLfNd -Yp4sMHZovsDJKIjDb0NbXRgLeFh1ijlLPhKwIXWTF3BaCKcSw34Qv22YPwn3qNuw -0KuUPAo0B65R/hoJguvtks8QAXe0S1jZS/fAlQCoIB0TIduy1qkyje+AnSW+1RL0 -ysBxLqbvRUqWlgnu7/28V4FD8JNu3O+UGBEelXlfokLgCBZ6lSys2d3Zy/XVBnG0 -cPl59if+fxKaMWlhFvMLFBup1Y4a/1zA7Sx6kkhvawekHr40NcG4kLHJ+O6UoM4d -/ibnbfIksLNkuo/nwoEcKp7W6SxafV0hROdxClkGKild66rnHtk4IGATjaBqt9nr -FuO3vRtLuUMS+/4kpvhMwl0RhX2/i6xgV+klWNYNu1JTGDFvdG3qfiY2w88EIbGe -rn8JEvRtaH/XNeGdhBwbuObvTifiHyYzA1i5Zh8zvE2+Dthlk19jbBoOUx//LOi2 -JrNkAsqQCF4HXh7n9HWA/ZrKTP7Xvkig6Vf7M2Y/tO361LSJfzKcRFLpl0P2ntEv -XwFOqTvOURERTVr4sBLOVPRAhIs3yvkI5xfurXzbRWtSeLgrMoDgJlXIQbuXd8sq -zIBLqvYf2bcroB66XJqX1IFWEstym/NHGcbrwjR5Fn2p3YAtXnIbw8VhHwV+LIOl -ky/wH9vbnML/DE81qFqRe8vNZw2sGn9skOyU/QvKeV1NRHYZSV3hMx82bPnjgFeB -ilzkb8FEPOAOJ0m44Q3C9eUoazJT8aCuRIAgSL43se1E2pFlIXQTfYRARaWEkSf9 -0hXqQJc17b+Hj0ire3PUqbG3+/l1qMhhIHwq7Kuyy2neTuW/DXbXp2AMv/bLcnHH -apVeRZaYXVSnGXJNk2CeRnCs8OGir8g5zkH+fmVb9knt6TL2oFIsQqULyrLolhfe -6Q8mLzq/sd+w+VuN1n/5+RQqOJZWEkLFzQPx8wTqeTB19OE0gjncrqzCHq7INqRe -tGClWOj/yL0Sciu3ctVGz1VAbgeBKnLdKm2TX4oFB4OG4E7GMXIL7hGxjtjLAVMW -XNc3ZYNQra+iPqJtFxnmbrF2Sn0Wr0hcAT1V0A0TRKe/n0lpUrfhTy/q4DUlOVKG -qdCsTGoYXObpUWU5G9GyCVWWRJyrTxJcBZ9KWJu9Y/aMFzoa2n0HQw== ------END RSA PRIVATE KEY----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQO04hOVF1REJsgAkP +xkFZ/gICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEENoXPnjByIDKjwqz +3+WRgNsEggTQuv3EOfjFwF8f0fac2GjJJxN3L2b88CeKxbjTL/6kQ1bvWSI1+L45 +0zP6CQ+5lI3N9/0YFoCWX5y57e+OXafAWivkUp/LiGkYWcRnqGVhZgSQTFQP9rly ++3PUDLlM5FuGylKvoqYmTIBud1puBiChYj0FKImOyHgPH3/GEGbTSrtvCSZkCw72 +XkkF32/OtSbqTuGlGgl+pGLTtnS2+RhgiCzXMCtvHJqjhAh22J7uoYYqk02QKEme +GMWM4anxmLPBr/Rw04NrlEfgRl8mTIhgrgwKV/mwfK++kqboWpzfXPs/S4KHJxmv +WvVcxHovoyovBA87C8cY4Qz/PZzm9vZr/+hQCF0OJgvZejWiUiuRJ9HgeteKTEMo +CrOlyZXcaMHPCa8CK6U+lUBwTZbAAzMYSazfaf8524yDGksOA4J/KGC3uvviYW09 +hTaqhq0yGqBUe5mrgEEhSV2vIpjK6MKxMtvjKvc1fjfrYIL9BGiiHOCGaljQTQAA +yLZqQwlj//v4om3onR6HOfZeYsQxzH5zNFSIJa96/kBBWG9Q0ZMmqEqB52rNUT28 +ZapjaqqRkos/rBdvzDQzlyx+NjZnOsueEkC+cX/1psIoE+6vLbonMrlzl+SSqtxB +EuSD7dekZ7o3eQLzRI13ohRtzMv4ojWMpr769WsQ4KKflK7pLVdIYFZbL0Q44s/w +Bc9ByiwSGymhEO6uqqfBT1baj19yTrc3FU/jaJyIsRNs/EAc7c6nPejiiwxtE7Ex +oVSwbKoD2CXB/DYlenenBGvuP1jyHSkQqv2YWdL1bm9Rp8DNJ+HG0OP913fTuE3V +7ScOt2ZnR2B+VWN3Eu8MdiX16vi/ub/4H1HihANw/W5HSwuW88V7fGcbSzRWxyCN +5Od7b5y2zAD/tl+x4GXFZ9k+di2sZc7W6zzVqHr55nfxvsFvHt5dWipTxZFdVhRh +tXhGnYCfr1gKN4FdTW/MuYa3otHL4gVpnVdQ10C48bCljCaVdep/AhC5dj0GaTyx +VJBzzD5vp6zt6jsfjI059+zVyR5zxhEKeotURVTqzhz08TOHCkyQP0KRQ+U5ve80 +9cj1odt43JBXFq5w9/aUQWG6ZnBJQup/zlDdGncPd0+3Eh0WoQyDh/XlFosrxt7L +QF9SqN9oTIp9Fgr6yOFrDOamQAb6f+5Ms5XNegHmlqSkGcpJxf2JBNinrY4drrQ8 +GuVCQ94GhjdGMdSM8Vv8Yi+8RHyqn6R2hjiY4PX+86J+xFNOGr5RiXk8NUp5kM5s +ZfffpB0ELlgBQzEv2PV9hdh66M8EGjyQl4ItzXg3JhbiXOKAQLbpPOD22zcZsmm2 +r5E4vgRwYfHnmwqJsrIcvMK1m4USlGuwJYP5ExuwE4xdsaUNwKEd3gZAXzhV1YKn +HyBfJFwYJsBR+l9G9kt/ZWpEd2DNnfss7ujQYTHGQ6WT1zbKbCsb8aE1CNXXs93C +DtuMUvG+BRTwuSAtvWTf+XPcTjgTrrAKQq2tmsbDe3CEgW5r/4+OL6s3nxI/mVVg +4jOcUZ0bePBvu+4/jIRqlx2MZIFRp+vvR4RiQ0wYBcihW7Wed8y+ZWdHxg6eUlJP +WXwdmXsz+NFMXpJvBX0OgntVzxEdJAyGEeBArBJPAKmcbR3JfDWMQ8M= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/modules/reindex/src/test/resources/org/opensearch/index/reindex/http/http.crt b/modules/reindex/src/test/resources/org/opensearch/index/reindex/http/http.crt index 309ade87fbd78..317991a707a16 100644 --- a/modules/reindex/src/test/resources/org/opensearch/index/reindex/http/http.crt +++ b/modules/reindex/src/test/resources/org/opensearch/index/reindex/http/http.crt @@ -1,22 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDsjCCApqgAwIBAgIUXxlg/0/g3UYekXWBRpkHM84EYfIwDQYJKoZIhvcNAQEL -BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l -cmF0ZWQgQ0EwHhcNMTkwMTEwMDIxMDMwWhcNNDYwNTI3MDIxMDMwWjAPMQ0wCwYD -VQQDEwRodHRwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi8VQaSR6 -uqgT1Rkw+a39OSXcXuhJBVdoO+AyYPK7hdUTxj1aqnXkKeAiNGpe/J+uXZ837Spy -rmBZS3k6S5hLEceF2xug8yrR7RYEZ+JvGlRgg/jj+61gGbHAD314+vvu0YUo06YG -wbz9AnjJA/sMbsCp3iSzWIkwZBZcCoZ/YsG4I89LSjYL3YmRi2193WMX6/OfQYMN -Fkv61r/iwBEkgJ14cUSYe3norGuQfZuXSh5kI5D5R7q7Bmb0um+jzY/l62kj3oR1 -YWo3g6DdU/Bc/3/KmEEVXIfdTonMBMyL8PvYORoMKrYdph3E8e39ZQhPeBJNJKw0 -XzsZFzIUlTw0kQIDAQABo4HgMIHdMB0GA1UdDgQWBBTiqknjZLa5E1BneHRvTkNa -Bm4nNTAfBgNVHSMEGDAWgBSU68vXWuUWW2xC2AmllVRZZUpvAzCBjwYDVR0RBIGH -MIGEgglsb2NhbGhvc3SCF2xvY2FsaG9zdDYubG9jYWxkb21haW42hwR/AAABhxAA -AAAAAAAAAAAAAAAAAAABggpsb2NhbGhvc3Q0ggpsb2NhbGhvc3Q2ghVsb2NhbGhv -c3QubG9jYWxkb21haW6CF2xvY2FsaG9zdDQubG9jYWxkb21haW40MAkGA1UdEwQC -MAAwDQYJKoZIhvcNAQELBQADggEBAIZr8EhhCbNyc6iHzUJ/NrUGht5RDHUKN9WU -2fd+SJlWijQYGoFW6LfabmYxIVPAFtYzUiA378NFoOZZ4kdC3gQng8izvS2UDcO6 -cAG5q/dxop3VXqcLeK3NpH2jd83M8VZaOThPj/F07eTkVX+sGu+7VL5Lc/XPe8JS -HhH2QtcTPGPpzPnWOUMLpRy4mh5sDyeftWr2PTFgMXFD6dtzDvaklGJvr1TmcOVb -BFYyVyXRq6v8YsrRPp0GIl+X3zd3KgwUMuEzRKkJgeI1lZRjmHMIyFcqxlwMaHpv -r1XUmz02ycy6t3n+2kCgfU6HnjbeFh55KzNCEv8TXQFg8Z8YpDA= +MIIDUTCCAjmgAwIBAgIURxNp9ImDloxqOPNAP0ySBZN/BDUwDQYJKoZIhvcNAQEL +BQAwHzEdMBsGA1UEAwwUT3BlblNlYXJjaCBUZXN0IE5vZGUwHhcNMjQwODI4MTA0 +NDE1WhcNMzQwODI2MTA0NDE1WjBiMQswCQYDVQQGEwJDQTEQMA4GA1UECAwHT05U +QVJJTzEQMA4GA1UEBwwHVE9ST05UTzEMMAoGA1UECgwDT1JHMQ0wCwYDVQQLDARV +TklUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCk1Ot2RGbUS3yJchvdtrcGJPoR8cTTUfVVMMRT+btXayllbLQd/cHV +jP1DxauXiLQs77R3NGfPs/Sk7fGQh6p/4F52F5wlNqG/Hq0MquqjXEo/ey8i+p5Y +zTB8v2Hv6RwN0HLB2uiAUOWjHvddiz36nfPmQ5jlF+IsR36KMb6AWHaB60kUabZL +vPOrtw7KZMkHRC+3tXvvepNe3uAKTIOEeHJneNNc76ShPnjANev7ONpNHgvMTJDY +nbNtDL2WnHvnyEwIgWLOnJ1WgOAsiSpebPqibi+25FirFKGTB2qp2NfU+tCoK7hG +1nPfPSCxBEqhwoJOywft2AxhDoicvo+HAgMBAAGjQjBAMB0GA1UdDgQWBBQ2Dr4v +2/aWi1JSmXfRITKOTlwa+DAfBgNVHSMEGDAWgBTjV81VkxbQpzmea/neQpMg1lPQ +IzANBgkqhkiG9w0BAQsFAAOCAQEAXEmxgNViixLWVQx9EgWscxaiI4d4OFd7Dfb/ +11qRtKoobEuSK5lOhDim8hZfs+iueKHuT/bRJ59Yu/p4GS+ZeJRgEXfCdY9S3Zeb +qGCi/IBRT1oq4vD3OSWA88C3I+pGXRb7R3fvtIcfy42o1FdHAg3MOlRx7fZHtAdE +GJ4SRsKTex7phWvKZ14R+wj45B8dA8Ty6/6nzPqb5+SLa5w37jU/gdew2cW2lEaN +tZb/aj1l5LmxXje3mvVag5SR2ussDrARcRu+uW7qYq0IzzQDxyzwpEWPC/QsgEme +9GFPd3xNu4tSoM0arrK8xjNtEh4P2gokhNJwy+vDGvKMrrWjVg== -----END CERTIFICATE----- diff --git a/modules/reindex/src/test/resources/org/opensearch/index/reindex/http/http.key b/modules/reindex/src/test/resources/org/opensearch/index/reindex/http/http.key index 8b8d3b4083c67..68b61c6d6e03e 100644 --- a/modules/reindex/src/test/resources/org/opensearch/index/reindex/http/http.key +++ b/modules/reindex/src/test/resources/org/opensearch/index/reindex/http/http.key @@ -1,30 +1,30 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,127A4142FA81C5A1 - -dP6oSAUl47KCnP0YZSX108qcX5s2nVGpD0qtnVQg89mLVFd7IxpKQaIuODSadRTo -AD0KINITy3ZwUr/TTJgERu88baBsTHv3PLEe7TpQI2DGGDz3aZfO9e6Jvglbdi5b -CBLaxRXGGhhH9YH0E87Lp3JEwg4udWmlNahGIhbqNheZNTtDKt+Lx80TyyIml2r/ -GAhjT4UPvIRrATFAcL/3EKOjRqvb6SeGnZu21n2TSmsBEr02gC0Ox3qmsnRM3kvU -jCuUzWTzJSQLXZwZuMtv5srOSFAbU8EklFXNhWJU/7GBy215aAAW48hCzkPMVEbg -oeH4nuze/Uulih9UxJGCBIpvfTnksyMRGP/zdy1mnKuqQk+yI0n7JWMJL8QoDQc8 -XvzqOmKLdBVezmzOVP/PyMAhYWetILh/1UesjyJot2hwSXPAxqBHPVA9bnmel6CQ -VccNSwaK120yT5YhkUMFc0AmUpztzNMQzJ10g1dW+Qsr+n4vtFmAuTvBgogNNVXn -eX1hbbiXGO1Fw4OMu6qTJ4T/P+VFb0CxoxETWeqdjcs4LGbeqF68nayEsW0ZzhbI -W5c+JAbW18Kb+k/KzKZTtJEXBw6B/2FMe9x9z3BIpVhplM2KsNk7joWnumD8LfUT -ORRHUPV7bkdiDsn2CRaevubDQiChcjsdLWhG7JLm54ttyif7/X7htGOXPZLDLK8B -Vxe09B006f7lM0tXEx8BLFDNroMLlrxB4K5MlwWpS3LLqy4zDbHka2I3s/ST/BD4 -0EURHefiXJkR6bRsfGCl3JDk0EakcUXM+Ob5/2rC/rPXO2pC0ksiQ2DSBm7ak9om -vlC7dIzVipL0LZTd4SUDJyvmK4Ws6V98O5b+79To6oZnVs5CjvcmpSFVePZa5gm/ -DB8LOpW4jklz+ybJtHJRbEIzmpfwpizThto/zLbhPRyvJkagJfWgXI0j+jjKZj+w -sy1V8S44aXJ3GX9p4d/Grnx6WGvEJSV0na7m3YQCPEi5sUgr+EMizGUYstSSUPtU -XhxQRZ95K2cKORul9vzG3zZqqvi73Ju5vu9DLmmlI00sLzyVGFtvkuhrF2p7XclM -GU/rMOeMClMb6qyCzldSs84Anhlh/6mYri6uYPhIGvxqtH44FTbu1APvZp0s2rVm -ueClHG78lat+oqWFpbA8+peT0dMPdSKDAFDiHsGoeWCIoCF44a84bJX35OZk+Y4a -+fDFuSiKYBMfAgqf/ZNzV4+ySka7dWdRQ2TDgIuxnvFV1NgC/ir3/mPgkf0xZU5d -w8T+TW6T8PmJfHnW4nxgHaqgxMoEoPm8zn0HNpRFKwsDYRFfobpCXnoyx50JXxa4 -jg095zlp8X0JwconlGJB1gfeqvS2I50WEDR+2ZtDf7fUEnQ3LYJzP4lSwiSKiQsQ -MPjy0SMQnqmWijylLYKunTl3Uh2DdYg4MOON662H3TxQW8TCYwK2maKujwS9VFLN -GtRGlLrOtrOfHBSwDCujFjqEmQBsF/y2C6XfMoNq6xi5NzREGmNXYrHbLvl2Njwm -WB1ouB4JzmEmb1QNwxkllBAaUp1SJGhW2+fYOe0zjWOP9R4sUq4rRw== ------END RSA PRIVATE KEY----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQprhRDFFTnmWmHgAB +ULpI4wICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEEuzT8itQgHZfKb/ +ReywEdIEggTQD117YFYRhSSivErIhTKQSuofhH/ZgW6nYnKlcDT08bgNQjbEg94a +QZqsPl9D6tfcmg7XlNTEiQpnSnsh6LrrhQbNkt3PvJxfUUy0ATVXXdH538RcPLAC +K2NHi1iwSbnqdcBU+/Be8M1F9e9P5hx6HbJGEF/JIkpWDDmOoCGvlwfH0PSiliY4 +uqxsmekvNgz2GBhELZj4sEJ7C7/I26vOuzS6suDn6xGF8JZIg8i7upamUgLoBtG/ +waxlmfTx+hkYFDQGcy9jvkV043sK/hLTOycUGhmS1ybQSf9ANbsM8RjOIq6QxpIZ +wtV/7EzqDWYradQBRrhAP24yzEj6H1cTr8yMmD6JuxvGZ7uQpTCRiFopB6TgK+x+ +2HqEgeRyBz4hU0i22kyGHC9sSG9WwKhmXhfcBtzJi3JABbkeg9LarwOzbh51DaxN +/gTop4UYRTYbJB9bhcIU0Y5xPSSphphCWmGuBU6CinsBj1w+UBP137GzgnXvV6PL +S8tai963P38Oafw/T2IyFTyAkuHJJ5MjVc71Q+vYLzfu4SfBdSIb1oFPT4otNwHP +NbPvTYq0DWnHFNeIc5vmLJJTWVemBTkxvHr+WfU8meFsjxZT05gzgOk+5BZFya5h +oV53mYQYPSyJiBUz0icHyyzUWaEHQLXHrmE6i+kW7+b4lrhi7KV1AMGRSJXUS9/Q +I7NuCQG3+iCyMd+CupvsiK7xjOytgCstwWIGeHlSmYwS+txi1wpbBJ4X6NQLlHyy +KZoFxyWTKtEdX1QKioBxeoKVy5G5LOh7S/jd9jEsZ2C8snFnDbNHALBmXIH3fshA +bo4keel427V6W3f9/u0nT1RWrYiBK12XJiS3/kXg8krln1Xb/MkgTKmLEZF+VDXO +Y3QwAICNM6/235siHuQG+uJ/WoL9xd1R22/+2mxNy1Rdhd49n8GFg0Kjsbmd+hL9 +aMwRU09SNNPCwdAIHmoMCIYS6uTX1bcGSzMir16JepmIYQllwdOoLk2nxtBCaHwj +ZLYO21W+iFgo4TwXzkuaI2q3Ll0n79BJUVdOnz8hBCq0Ox7sTEY7g1vQGHIsBx98 +PYZmaaXVh+u2chHKrwp6L9mRikXQiNWwtqTH/kp7BydRnYIcaP27SCM8HbaYfV/x +02FjBbpZ7u1PwS3jlGmcxE/qTd+cLkk3pm7WPPMlOnMh/X5N3/OpznUgJnVRtGqk +uDy4HSE5vEhHDp0F67R0ph8/HfIBamvJIoonYzoC2iEMgL4yqL0x44SOCioXScgz +hluYX1kQRfyXWjoP+vBBOUapwYDwk1gGXap5iQjtiVq6FN8DspckHRVI5B1voVIC +37Mn2OXH9JloObouLYMRa1dDm7h+/3Cb9UAhKpOjpLc1apA49+Rjtq1gBExhac74 +9SwrcQJdRx0NDJjoMHKrGUFkg/W+R7OTad7+l98M473nWuV3mzJDXcuxmam9llRI +2O+1QsV5hjd4/zCtIka+pOALp+cVSmktTjKNh105asX7d4XIxtg3M+FJWTEODZfy +VulvKri/rkrbCBwMQyj3TpF4AkVjhSM2P5j7LRsivfGc8VL00OqYJp9pYfav38gs +EpYOmaDEV/Ls744WSJJo5Qq0EpDclBTFjky6kZx7RDfySUzfN/Nhv6A= +-----END ENCRYPTED PRIVATE KEY----- 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 d57252950620a..ffcc982c758b9 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 @@ -10,6 +10,8 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.OpenSearchException; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.KeyStoreType; import org.opensearch.common.network.NetworkAddress; import org.opensearch.common.network.NetworkService; import org.opensearch.common.settings.ClusterSettings; @@ -121,7 +123,7 @@ 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.PKCS_12); keyStore.load( SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-secure.p12"), "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 ff52e6e178692..2008c6a2e9a3a 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 @@ -10,6 +10,8 @@ import org.opensearch.Version; import org.opensearch.cluster.node.DiscoveryNode; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.KeyStoreType; import org.opensearch.common.network.NetworkService; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; @@ -77,7 +79,7 @@ public Optional buildServerTransportExceptionHandler( @Override public Optional buildSecureServerTransportEngine(Settings settings, Transport transport) throws SSLException { try { - final KeyStore keyStore = KeyStore.getInstance("PKCS12"); + final KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12); keyStore.load( SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-secure.p12"), "password".toCharArray() 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..3105967cd9001 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 @@ -41,6 +41,8 @@ import org.apache.logging.log4j.LogManager; import org.opensearch.cloud.azure.classic.management.AzureComputeService; import org.opensearch.common.SuppressForbidden; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.KeyStoreType; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; import org.opensearch.core.util.FileSystemUtils; @@ -109,7 +111,10 @@ protected Collection> nodePlugins() { public static final ExternalResource MUTE_IN_FIPS_JVM = new ExternalResource() { @Override protected void before() { - assumeFalse("Can't run in a FIPS JVM because none of the supported Keystore types can be used", inFipsJvm()); + assumeFalse( + "Can't run in a FIPS JVM because none of the supported Keystore types can be used", + Boolean.parseBoolean(System.getProperty(FIPS_SYSPROP)) + ); } }; @@ -278,7 +283,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/repository-gcs/build.gradle b/plugins/repository-gcs/build.gradle index d4c870e1ca2b2..58bfe4a3dde51 100644 --- a/plugins/repository-gcs/build.gradle +++ b/plugins/repository-gcs/build.gradle @@ -246,7 +246,7 @@ def encodedCredentials = { task createServiceAccountFile() { doLast { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA") - keyPairGenerator.initialize(1024) + keyPairGenerator.initialize(2048) KeyPair keyPair = keyPairGenerator.generateKeyPair() String encodedKey = Base64.getEncoder().encodeToString(keyPair.private.getEncoded()) 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..b39e0de08eba7 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 @@ -36,6 +36,7 @@ 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 +47,13 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.common.collect.MapBuilder; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.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 +189,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 JKS trustStore format instead of PKCS#12 to ensure compatibility with BC-FIPS + var certTrustStore = KeyStoreFactory.getInstance(KeyStoreType.JKS); + InputStream keyStoreStream = GoogleUtils.class.getResourceAsStream("google.jks"); + 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/test/java/org/opensearch/repositories/gcs/GoogleCloudStorageServiceTests.java b/plugins/repository-gcs/src/test/java/org/opensearch/repositories/gcs/GoogleCloudStorageServiceTests.java index b620f212df413..5406e41e5f03d 100644 --- a/plugins/repository-gcs/src/test/java/org/opensearch/repositories/gcs/GoogleCloudStorageServiceTests.java +++ b/plugins/repository-gcs/src/test/java/org/opensearch/repositories/gcs/GoogleCloudStorageServiceTests.java @@ -188,7 +188,7 @@ public void testClientsAreNotSharedAcrossRepositories() throws Exception { private byte[] serviceAccountFileContent(String projectId) throws Exception { final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(1024); + keyPairGenerator.initialize(2048); final KeyPair keyPair = keyPairGenerator.generateKeyPair(); final String encodedKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()); final XContentBuilder serviceAccountBuilder = jsonBuilder().startObject() diff --git a/plugins/repository-gcs/src/test/java/org/opensearch/repositories/gcs/TestUtils.java b/plugins/repository-gcs/src/test/java/org/opensearch/repositories/gcs/TestUtils.java index 648955c079b3e..ba7422e6cf811 100644 --- a/plugins/repository-gcs/src/test/java/org/opensearch/repositories/gcs/TestUtils.java +++ b/plugins/repository-gcs/src/test/java/org/opensearch/repositories/gcs/TestUtils.java @@ -50,7 +50,7 @@ private TestUtils() {} static byte[] createServiceAccount(final Random random) { try { final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(1024); + keyPairGenerator.initialize(2048); final String privateKey = Base64.getEncoder().encodeToString(keyPairGenerator.generateKeyPair().getPrivate().getEncoded()); final ByteArrayOutputStream out = new ByteArrayOutputStream(); 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..04ef48f61c95c 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.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.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/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java b/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java index 81fb1309df310..a151864610521 100644 --- a/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java +++ b/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java @@ -40,10 +40,13 @@ 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.cli.ExitCodes; import org.opensearch.cli.UserException; import org.opensearch.common.Randomness; import org.opensearch.common.SetOnce; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.KeyStoreType; import org.opensearch.common.hash.MessageDigests; import org.opensearch.core.common.settings.SecureString; @@ -448,8 +451,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)) { diff --git a/server/src/main/resources/org/opensearch/bootstrap/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index fb3d62ab432db..04982aa8986d7 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -97,11 +97,13 @@ grant codeBase "${codebase.reactor-core}" { grant { permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; permission java.lang.RuntimePermission "accessDeclaredMembers"; + permission java.lang.RuntimePermission "closeClassLoader"; permission java.lang.RuntimePermission "getProtectionDomain"; - permission java.io.FilePermission "${java.home}/lib/security/jssecacerts", "read"; permission java.io.FilePermission "${java.home}/lib/security/cacerts", "read"; + permission java.io.FilePermission "${java.home}/lib/security/jssecacerts", "read"; permission java.security.SecurityPermission "getProperty.jdk.tls.disabledAlgorithms"; permission java.security.SecurityPermission "getProperty.jdk.certpath.disabledAlgorithms"; + permission java.security.SecurityPermission "getProperty.jdk.tls.server.defaultDHEParameters"; permission java.security.SecurityPermission "getProperty.keystore.type.compat"; permission java.security.SecurityPermission "getProperty.org.bouncycastle.*"; permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; @@ -109,7 +111,6 @@ grant { permission java.util.PropertyPermission "java.runtime.name", "read"; permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; - permission java.security.SecurityPermission "getProperty.jdk.tls.server.defaultDHEParameters"; }; //// Everything else: 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 318549f676edf..e2dbec151742d 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 b180187303a60..4572e3c59b1de 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.client.Client; @@ -362,6 +363,12 @@ protected void afterIfSuccessful() throws Exception {} // setup mock filesystems for this test run. we change PathUtils // so that all accesses are plumbed thru any mock wrappers + @BeforeClass + public static void setFipsJvm() throws Exception { + var runInApprovedMode = Boolean.parseBoolean(System.getProperty(FIPS_SYSPROP)); + CryptoServicesRegistrar.setApprovedOnlyMode(runInApprovedMode); + } + @BeforeClass public static void setFileSystem() throws Exception { PathUtilsForTesting.setup(); @@ -1758,7 +1765,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/rest/OpenSearchRestTestCase.java b/test/framework/src/main/java/org/opensearch/test/rest/OpenSearchRestTestCase.java index 8c612d258f183..d7b29b57f3934 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 @@ -60,6 +60,7 @@ import org.opensearch.client.WarningsHandler; import org.opensearch.common.CheckedRunnable; import org.opensearch.common.SetOnce; +import org.opensearch.common.crypto.KeyStoreFactory; import org.opensearch.common.io.PathUtils; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; @@ -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()); } From 32586d41c8a7e6329b285fc35941510690ec9d16 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Mon, 23 Sep 2024 11:12:07 +0200 Subject: [PATCH 07/21] fixing additional tests. Signed-off-by: Iwan Igonin --- .../client/FipsEnabledThreadFactory.java | 36 ------------------- .../opensearch/client/RestClientBuilder.java | 12 ++++++- .../client/RestClientBuilderIntegTests.java | 13 +++++-- .../licenses/bcpg-fips-2.0.8.jar.sha1 | 1 + .../licenses/bcpg-fips-2.0.9.jar.sha1 | 1 - .../common/crypto/KeyStoreType.java | 3 ++ .../common/ssl/SslConfigurationLoader.java | 6 ++-- .../reindex/ReindexRestClientSslTests.java | 14 ++++++-- .../netty4/Netty4ClientYamlTestSuiteIT.java | 5 --- .../reactor/netty4/ReactorHttpClient.java | 13 ++++++- .../nodes.reload_secure_settings/10_basic.yml | 3 ++ .../action/admin/ReloadSecureSettingsIT.java | 14 +++++--- .../org/opensearch/bootstrap/security.policy | 1 + .../junit/listeners/ReproduceInfoPrinter.java | 2 ++ 14 files changed, 67 insertions(+), 57 deletions(-) delete mode 100644 client/rest/src/main/java/org/opensearch/client/FipsEnabledThreadFactory.java create mode 100644 distribution/tools/plugin-cli/licenses/bcpg-fips-2.0.8.jar.sha1 delete mode 100644 distribution/tools/plugin-cli/licenses/bcpg-fips-2.0.9.jar.sha1 diff --git a/client/rest/src/main/java/org/opensearch/client/FipsEnabledThreadFactory.java b/client/rest/src/main/java/org/opensearch/client/FipsEnabledThreadFactory.java deleted file mode 100644 index 2efdc48c70f59..0000000000000 --- a/client/rest/src/main/java/org/opensearch/client/FipsEnabledThreadFactory.java +++ /dev/null @@ -1,36 +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. - */ - -package org.opensearch.client; - -import org.apache.hc.core5.concurrent.DefaultThreadFactory; -import org.bouncycastle.crypto.CryptoServicesRegistrar; - -import java.util.concurrent.ThreadFactory; - -public class FipsEnabledThreadFactory implements ThreadFactory { - - private final ThreadFactory defaultFactory; - private final boolean isFipsEnabled; - - public FipsEnabledThreadFactory(String namePrefix, boolean isFipsEnabled) { - this.defaultFactory = new DefaultThreadFactory(namePrefix); - this.isFipsEnabled = isFipsEnabled; - } - - @Override - public Thread newThread(final Runnable target) { - return defaultFactory.newThread(() -> { - if (isFipsEnabled) { - CryptoServicesRegistrar.setApprovedOnlyMode(true); - } - target.run(); - }); - } - -} diff --git a/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java b/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java index 0a46da6e1e8b0..325c7b0c0fbb8 100644 --- a/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java +++ b/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java @@ -332,10 +332,20 @@ public TlsDetails create(final SSLEngine sslEngine) { .setTlsStrategy(tlsStrategy) .build(); + var inFipsJvm = CryptoServicesRegistrar.isInApprovedOnlyMode(); + HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClientBuilder.create() .setDefaultRequestConfig(requestConfigBuilder.build()) .setConnectionManager(connectionManager) - .setThreadFactory(new FipsEnabledThreadFactory("os-client-dispatcher", CryptoServicesRegistrar.isInApprovedOnlyMode())) + .setThreadFactory((Runnable r) -> { + Runnable runnable = () -> { + if (inFipsJvm) { + CryptoServicesRegistrar.setApprovedOnlyMode(true); + } + r.run(); + }; + return new Thread(runnable, "os-client-dispatcher"); + }) .setTargetAuthenticationStrategy(DefaultAuthenticationStrategy.INSTANCE) .disableAutomaticRetries(); if (httpClientConfigCallback != null) { 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 db578ebb196ac..aab9e008ca708 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,7 @@ import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.crypto.KeyStoreFactory; import org.opensearch.common.crypto.KeyStoreType; import org.junit.AfterClass; @@ -79,8 +80,16 @@ public static void startHttpServer() throws Exception { httpsServer = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext(true))); httpsServer.createContext("/", new ResponseHandler()); - var threadFactory = new FipsEnabledThreadFactory("test-httpserver-dispatch", inFipsJvm()); - Executor executor = Executors.newFixedThreadPool(1, threadFactory); + var inFipsJvm = inFipsJvm(); + Executor executor = Executors.newFixedThreadPool(1, (Runnable r) -> { + Runnable runnable = () -> { + if (inFipsJvm) { + CryptoServicesRegistrar.setApprovedOnlyMode(true); + } + r.run(); + }; + return new Thread(runnable, "test-httpserver-dispatcher"); + }); httpsServer.setExecutor(executor); httpsServer.start(); } 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/bcpg-fips-2.0.9.jar.sha1 b/distribution/tools/plugin-cli/licenses/bcpg-fips-2.0.9.jar.sha1 deleted file mode 100644 index 20cdbf6dc8aa8..0000000000000 --- a/distribution/tools/plugin-cli/licenses/bcpg-fips-2.0.9.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -f69719ef8dbf34d5f906ce480496446b2fd2ae27 \ No newline at end of file diff --git a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java index d92b8067d3368..2b458d431215f 100644 --- a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java +++ b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java @@ -13,6 +13,9 @@ import java.util.Map; import java.util.stream.Stream; +/** + * Enum representing the types of KeyStores supported by {@link KeyStoreFactory}. + */ public enum KeyStoreType { JKS("JKS"), 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 4deeed2bbc45e..98bc97c1c8787 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 @@ -247,8 +247,7 @@ private SslTrustConfig buildTrustConfig(Path basePath, SslVerificationMode verif if (trustStorePath != null) { final char[] password = resolvePasswordSetting(TRUSTSTORE_SECURE_PASSWORD, TRUSTSTORE_LEGACY_PASSWORD); final Optional maybeStoreType = Optional.ofNullable(resolveSetting(TRUSTSTORE_TYPE, Function.identity(), null)); - final KeyStoreType storeType = maybeStoreType - .map(KeyStoreType::getByJcaName) + final KeyStoreType storeType = maybeStoreType.map(KeyStoreType::getByJcaName) .orElse(inferStoreType(trustStorePath.toString().toLowerCase(Locale.ROOT))); final String algorithm = resolveSetting(TRUSTSTORE_ALGORITHM, Function.identity(), TrustManagerFactory.getDefaultAlgorithm()); @@ -291,8 +290,7 @@ private SslKeyConfig buildKeyConfig(Path basePath) { } final Optional maybeStoreType = Optional.ofNullable(resolveSetting(KEYSTORE_TYPE, Function.identity(), null)); - final KeyStoreType storeType = maybeStoreType - .map(KeyStoreType::getByJcaName) + final KeyStoreType storeType = maybeStoreType.map(KeyStoreType::getByJcaName) .orElse(inferStoreType(keyStorePath.toString().toLowerCase(Locale.ROOT))); final String algorithm = resolveSetting(KEYSTORE_ALGORITHM, Function.identity(), KeyManagerFactory.getDefaultAlgorithm()); 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 36b6edaa9c703..2fc7ab1654d41 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 @@ -37,7 +37,7 @@ import com.sun.net.httpserver.HttpsParameters; import com.sun.net.httpserver.HttpsServer; -import org.opensearch.client.FipsEnabledThreadFactory; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.RestClient; @@ -99,8 +99,16 @@ public static void setupHttpServer() throws Exception { SSLContext sslContext = buildServerSslContext(); server = HttpsServer.create(address, 0); server.setHttpsConfigurator(new ClientAuthHttpsConfigurator(sslContext)); - var threadFactory = new FipsEnabledThreadFactory("test-httpserver-dispatcher", inFipsJvm()); - Executor executor = Executors.newFixedThreadPool(1, threadFactory); + var inFipsJvm = inFipsJvm(); + Executor executor = Executors.newFixedThreadPool(1, (Runnable r) -> { + Runnable runnable = () -> { + if (inFipsJvm) { + CryptoServicesRegistrar.setApprovedOnlyMode(true); + } + r.run(); + }; + return new Thread(runnable, "test-httpserver-dispatcher"); + }); server.setExecutor(executor); server.start(); server.createContext("/", http -> { 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/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ReactorHttpClient.java b/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ReactorHttpClient.java index 8d20650d76583..33ba9168e7d6a 100644 --- a/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ReactorHttpClient.java +++ b/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ReactorHttpClient.java @@ -13,6 +13,7 @@ package org.opensearch.http.reactor.netty4; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.collect.Tuple; import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.xcontent.ToXContent; @@ -65,6 +66,7 @@ public class ReactorHttpClient implements Closeable { private final boolean compression; private final boolean secure; + private final boolean fipsEnabled; static Collection returnHttpResponseBodies(Collection responses) { List list = new ArrayList<>(responses.size()); @@ -85,6 +87,7 @@ static Collection returnOpaqueIds(Collection responses public ReactorHttpClient(boolean compression, boolean secure) { this.compression = compression; this.secure = secure; + this.fipsEnabled = CryptoServicesRegistrar.isInApprovedOnlyMode(); } public static ReactorHttpClient create() { @@ -183,7 +186,15 @@ private List sendRequests( final Collection requests, boolean ordered ) { - final NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(1); + final NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(1, (Runnable r) -> { + Runnable runnable = () -> { + if (fipsEnabled) { + CryptoServicesRegistrar.setApprovedOnlyMode(true); + } + r.run(); + }; + return new Thread(runnable); + }); try { final HttpClient client = createClient(remoteAddress, eventLoopGroup); 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/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/resources/org/opensearch/bootstrap/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index 04982aa8986d7..d6251acf07d4e 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -109,6 +109,7 @@ grant { permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; permission java.security.SecurityPermission "putProviderProperty.BCJSSE"; permission java.util.PropertyPermission "java.runtime.name", "read"; + permission org.bouncycastle.crypto.CryptoServicesPermission "changeToApprovedModeEnabled"; permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; }; 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..2b5f3fdc3e6b4 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 @@ -194,6 +194,8 @@ private ReproduceErrorMessageBuilder appendESProperties() { 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)); + appendOpt("org.bouncycastle.jca.enable_jks", "true"); + appendOpt("org.bouncycastle.rsa.allow_multi_use", "true"); return this; } From 24510e789dbf7b8dabee96c357174b01a71e8944 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Fri, 18 Oct 2024 14:54:57 +0200 Subject: [PATCH 08/21] Configure FIPS mode through global ENV VAR instead of thread-based. Signed-off-by: Iwan Igonin --- .../gradle/info/GlobalBuildInfoPlugin.java | 7 +++- .../gradle/testclusters/OpenSearchNode.java | 9 ++++- .../opensearch/client/RestClientBuilder.java | 12 ------ .../client/RestClientBuilderIntegTests.java | 13 ------ .../opensearch/client/RestClientTestCase.java | 12 ------ .../tools/launchers/SystemJvmOptions.java | 11 +++++- .../opensearch/index/reindex/Reindexer.java | 37 ++++-------------- ...ReindexFromRemoteBuildRestClientTests.java | 2 +- .../reindex/ReindexRestClientSslTests.java | 13 ------ .../SecureNetty4HttpServerTransportTests.java | 4 +- .../ssl/SimpleSecureNetty4TransportTests.java | 6 ++- .../src/test/resources/README.txt | 8 ++-- .../src/test/resources/netty4-secure.jks | Bin 0 -> 2790 bytes .../AzureDiscoveryClusterFormationTests.java | 13 ------ plugins/repository-s3/build.gradle | 14 ++++++- .../reactor/netty4/ReactorHttpClient.java | 13 +----- .../org/opensearch/bootstrap/Bootstrap.java | 13 ++---- .../common/settings/ClusterSettings.java | 1 - .../common/settings/FipsSettings.java | 20 ---------- .../java/fixture/s3/S3HttpFixtureWithEC2.java | 4 +- .../opensearch/test/OpenSearchTestCase.java | 8 ---- .../junit/listeners/ReproduceInfoPrinter.java | 3 -- 22 files changed, 60 insertions(+), 163 deletions(-) create mode 100644 modules/transport-netty4/src/test/resources/netty4-secure.jks delete mode 100644 server/src/main/java/org/opensearch/common/settings/FipsSettings.java 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..adf6f6878d322 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java @@ -77,6 +77,7 @@ public class GlobalBuildInfoPlugin implements Plugin { private static final Logger LOGGER = Logging.getLogger(GlobalBuildInfoPlugin.class); private static final String DEFAULT_LEGACY_VERSION_JAVA_FILE_PATH = "libs/core/src/main/java/org/opensearch/LegacyESVersion.java"; private static final String DEFAULT_VERSION_JAVA_FILE_PATH = "libs/core/src/main/java/org/opensearch/Version.java"; + protected static final String OPENSEARCH_CRYPTO_STANDARD = "OPENSEARCH_CRYPTO_STANDARD"; private static Integer _defaultParallel = null; private final JvmMetadataDetector jvmMetadataDetector; @@ -112,6 +113,8 @@ public void apply(Project project) { BuildParams.init(params -> { // Initialize global build parameters boolean isInternal = GlobalBuildInfoPlugin.class.getResource("/buildSrc.marker") != null; + var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); + var inFipsJvm = cryptoStandard != null && cryptoStandard.equals("FIPS-140-2"); params.reset(); params.setRuntimeJavaHome(runtimeJavaHome); @@ -129,7 +132,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(inFipsJvm); params.setIsSnapshotBuild(Util.getBooleanProperty("build.snapshot", true)); if (isInternal) { params.setBwcVersions(resolveBwcVersions(rootDir)); @@ -179,7 +182,7 @@ private void logGlobalBuildInfo() { LOGGER.quiet(" JAVA_HOME : " + gradleJvm.getJavaHome()); } LOGGER.quiet(" Random Testing Seed : " + BuildParams.getTestSeed()); - LOGGER.quiet(" In FIPS 140 mode : " + BuildParams.isInFipsJvm()); + LOGGER.quiet(" Crypto Standard : " + Optional.ofNullable(System.getenv(OPENSEARCH_CRYPTO_STANDARD)).orElse("any-supported")); LOGGER.quiet("======================================="); } 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 aaa2daef2a158..254cf345f50fd 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java @@ -556,7 +556,7 @@ public synchronized void start() { if (keystoreSettings.isEmpty() == false || keystoreFiles.isEmpty() == false) { logToProcessStdout("Adding " + keystoreSettings.size() + " keystore settings and " + keystoreFiles.size() + " keystore files"); - keystoreSettings.forEach((key, value) -> runKeystoreCommandWithPassword(keystorePassword, value.toString(), "add", "-x", key)); + keystoreSettings.forEach((key, value) -> runKeystoreCommandWithPassword(keystorePassword, value.toString(), "add", key)); for (Map.Entry entry : keystoreFiles.entrySet()) { File file = entry.getValue(); @@ -738,7 +738,12 @@ private void runOpenSearchBinScriptWithInput(String input, String tool, CharSequ } private void runKeystoreCommandWithPassword(String keystorePassword, String input, CharSequence... args) { - final String actualInput = keystorePassword.length() > 0 ? keystorePassword + "\n" + input : input; + final String actualInput; + if (keystorePassword.length() > 0) { + actualInput = keystorePassword + "\n" + input + "\n" + input; + } else { + actualInput = input + "\n" + input; + } runOpenSearchBinScriptWithInput(actualInput, "opensearch-keystore", args); } diff --git a/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java b/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java index 325c7b0c0fbb8..3e38f9ae95dec 100644 --- a/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java +++ b/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java @@ -48,7 +48,6 @@ import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Timeout; -import org.bouncycastle.crypto.CryptoServicesRegistrar; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -332,20 +331,9 @@ public TlsDetails create(final SSLEngine sslEngine) { .setTlsStrategy(tlsStrategy) .build(); - var inFipsJvm = CryptoServicesRegistrar.isInApprovedOnlyMode(); - HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClientBuilder.create() .setDefaultRequestConfig(requestConfigBuilder.build()) .setConnectionManager(connectionManager) - .setThreadFactory((Runnable r) -> { - Runnable runnable = () -> { - if (inFipsJvm) { - CryptoServicesRegistrar.setApprovedOnlyMode(true); - } - r.run(); - }; - return new Thread(runnable, "os-client-dispatcher"); - }) .setTargetAuthenticationStrategy(DefaultAuthenticationStrategy.INSTANCE) .disableAutomaticRetries(); if (httpClientConfigCallback != null) { 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 aab9e008ca708..c1e89664dc405 100644 --- a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java +++ b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java @@ -39,7 +39,6 @@ import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.ssl.SSLContextBuilder; -import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.crypto.KeyStoreFactory; import org.opensearch.common.crypto.KeyStoreType; import org.junit.AfterClass; @@ -60,7 +59,6 @@ import java.security.SecureRandom; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import static org.hamcrest.MatcherAssert.assertThat; @@ -80,17 +78,6 @@ public static void startHttpServer() throws Exception { httpsServer = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext(true))); httpsServer.createContext("/", new ResponseHandler()); - var inFipsJvm = inFipsJvm(); - Executor executor = Executors.newFixedThreadPool(1, (Runnable r) -> { - Runnable runnable = () -> { - if (inFipsJvm) { - CryptoServicesRegistrar.setApprovedOnlyMode(true); - } - r.run(); - }; - return new Thread(runnable, "test-httpserver-dispatcher"); - }); - httpsServer.setExecutor(executor); httpsServer.start(); } 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 e7c17e81dabed..49ee3373a071e 100644 --- a/client/test/src/main/java/org/opensearch/client/RestClientTestCase.java +++ b/client/test/src/main/java/org/opensearch/client/RestClientTestCase.java @@ -45,8 +45,6 @@ import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite; import org.apache.hc.core5.http.Header; -import org.bouncycastle.crypto.CryptoServicesRegistrar; -import org.junit.BeforeClass; import java.util.ArrayList; import java.util.HashMap; @@ -127,14 +125,4 @@ private static void addValueToListEntry(final Map> map, fin values.add(value); } - @BeforeClass - public static void setFipsJvm() { - boolean isFipsEnabled = Boolean.parseBoolean(System.getProperty("tests.fips.enabled", "false")); - CryptoServicesRegistrar.setApprovedOnlyMode(isFipsEnabled); - } - - public static boolean inFipsJvm() { - return CryptoServicesRegistrar.isInApprovedOnlyMode(); - } - } 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 030dc15aa67fe..2d10fac45efda 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 @@ -78,7 +78,8 @@ static List systemJvmOptions(final Path config) { // log4j 2 "-Dlog4j.shutdownHookEnabled=false", "-Dlog4j2.disable.jmx=true", - // security manager + // security settings + enableFips(), allowSecurityManagerOption(), loadJavaSecurityProperties(config), javaLocaleProviders() @@ -86,6 +87,14 @@ static List systemJvmOptions(final Path config) { ).stream().filter(e -> e.isEmpty() == false).collect(Collectors.toList()); } + private static String enableFips() { + var cryptoStandard = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); + if (cryptoStandard != null && cryptoStandard.equals("FIPS-140-2")) { + return "-Dorg.bouncycastle.fips.approved_only=true"; + } + return ""; + } + private static String loadJavaSecurityProperties(final Path config) { var securityFile = config.resolve("fips_java.security"); return "-Djava.security.properties=" + securityFile.toAbsolutePath(); diff --git a/modules/reindex/src/main/java/org/opensearch/index/reindex/Reindexer.java b/modules/reindex/src/main/java/org/opensearch/index/reindex/Reindexer.java index 92313e27f0544..c553effc65ab5 100644 --- a/modules/reindex/src/main/java/org/opensearch/index/reindex/Reindexer.java +++ b/modules/reindex/src/main/java/org/opensearch/index/reindex/Reindexer.java @@ -45,7 +45,6 @@ import org.apache.hc.core5.util.Timeout; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.action.DocWriteRequest; import org.opensearch.action.bulk.BackoffPolicy; import org.opensearch.action.bulk.BulkItemResponse; @@ -183,14 +182,7 @@ private ActionListener getRemoteReindexWrapperListener( } static RestClient buildRestClient(RemoteInfo remoteInfo, ReindexSslConfig sslConfig, long taskId, List threadCollector) { - return buildRestClient( - remoteInfo, - sslConfig, - taskId, - threadCollector, - Optional.empty(), - CryptoServicesRegistrar.isInApprovedOnlyMode() - ); + return buildRestClient(remoteInfo, sslConfig, taskId, threadCollector, Optional.empty()); } /** @@ -207,8 +199,7 @@ static RestClient buildRestClient( ReindexSslConfig sslConfig, long taskId, List threadCollector, - Optional restInterceptor, - boolean isFipsEnabled + Optional restInterceptor ) { Header[] clientHeaders = new Header[remoteInfo.getHeaders().size()]; int i = 0; @@ -235,16 +226,11 @@ static RestClient buildRestClient( } // Stick the task id in the thread name so we can track down tasks from stack traces AtomicInteger threads = new AtomicInteger(); - c.setThreadFactory((Runnable r) -> { - Runnable runnable = () -> { - if (isFipsEnabled) { - CryptoServicesRegistrar.setApprovedOnlyMode(true); - } - r.run(); - }; - var thread = new Thread(runnable, "os-client-" + taskId + "-" + threads.getAndIncrement()); - threadCollector.add(thread); - return thread; + c.setThreadFactory(r -> { + String name = "es-client-" + taskId + "-" + threads.getAndIncrement(); + Thread t = new Thread(r, name); + threadCollector.add(t); + return t; }); // Limit ourselves to one reactor thread because for now the search process is single threaded. c.setIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(1).build()); @@ -327,14 +313,7 @@ protected ScrollableHitSource buildScrollableResultSource(BackoffPolicy backoffP RemoteInfo remoteInfo = mainRequest.getRemoteInfo(); createdThreads = synchronizedList(new ArrayList<>()); assert sslConfig != null : "Reindex ssl config must be set"; - RestClient restClient = buildRestClient( - remoteInfo, - sslConfig, - task.getId(), - createdThreads, - this.interceptor, - CryptoServicesRegistrar.isInApprovedOnlyMode() - ); + RestClient restClient = buildRestClient(remoteInfo, sslConfig, task.getId(), createdThreads, this.interceptor); return new RemoteScrollableHitSource( logger, backoffPolicy, diff --git a/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteBuildRestClientTests.java b/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteBuildRestClientTests.java index 7fbd6044d9b18..2e14df4628283 100644 --- a/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteBuildRestClientTests.java +++ b/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexFromRemoteBuildRestClientTests.java @@ -77,7 +77,7 @@ public void testBuildRestClient() throws Exception { assertBusy(() -> assertThat(threads, hasSize(2))); int i = 0; for (Thread thread : threads) { - assertEquals("os-client-" + taskId + "-" + i, thread.getName()); + assertEquals("es-client-" + taskId + "-" + i, thread.getName()); i++; } } finally { 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 2fc7ab1654d41..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 @@ -37,7 +37,6 @@ import com.sun.net.httpserver.HttpsParameters; import com.sun.net.httpserver.HttpsServer; -import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.RestClient; @@ -73,7 +72,6 @@ import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -99,17 +97,6 @@ public static void setupHttpServer() throws Exception { SSLContext sslContext = buildServerSslContext(); server = HttpsServer.create(address, 0); server.setHttpsConfigurator(new ClientAuthHttpsConfigurator(sslContext)); - var inFipsJvm = inFipsJvm(); - Executor executor = Executors.newFixedThreadPool(1, (Runnable r) -> { - Runnable runnable = () -> { - if (inFipsJvm) { - CryptoServicesRegistrar.setApprovedOnlyMode(true); - } - r.run(); - }; - return new Thread(runnable, "test-httpserver-dispatcher"); - }); - server.setExecutor(executor); server.start(); server.createContext("/", http -> { assert http instanceof HttpsExchange; 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 ffcc982c758b9..6383705149521 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 @@ -123,9 +123,9 @@ public Optional buildHttpServerExceptionHandler(Setti @Override public Optional buildSecureHttpServerEngine(Settings settings, HttpServerTransport transport) throws SSLException { try { - final KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12); + final KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.JKS); keyStore.load( - SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-secure.p12"), + SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-secure.jks"), "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 2008c6a2e9a3a..2502a77af50e0 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 @@ -8,6 +8,7 @@ package org.opensearch.transport.netty4.ssl; +import org.apache.lucene.tests.util.LuceneTestCase; import org.opensearch.Version; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.common.crypto.KeyStoreFactory; @@ -66,6 +67,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.lessThanOrEqualTo; +@LuceneTestCase.AwaitsFix(bugUrl = "") public class SimpleSecureNetty4TransportTests extends AbstractSimpleTransportTestCase { @Override protected Transport build(Settings settings, final Version version, ClusterSettings clusterSettings, boolean doHandshake) { @@ -79,9 +81,9 @@ public Optional buildServerTransportExceptionHandler( @Override public Optional buildSecureServerTransportEngine(Settings settings, Transport transport) throws SSLException { try { - final KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12); + final KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.JKS); keyStore.load( - SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-secure.p12"), + SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-secure.jks"), "password".toCharArray() ); diff --git a/modules/transport-netty4/src/test/resources/README.txt b/modules/transport-netty4/src/test/resources/README.txt index c8cec5d3803a4..a1a226c5ba674 100644 --- a/modules/transport-netty4/src/test/resources/README.txt +++ b/modules/transport-netty4/src/test/resources/README.txt @@ -6,12 +6,12 @@ # 1. Create certificate key -openssl req -x509 -sha256 -newkey rsa:2048 -keyout certificate.key -out certificate.crt -days 1024 -nodes +openssl req -x509 -sha256 -newkey rsa:2048 -keyout certificate.key -out certificate.crt -days 1024 -nodes # 2. Export the certificate in pkcs12 format -openssl pkcs12 -export -in certificate.crt -inkey certificate.key -out server.p12 -name netty4-secure -password pass:password +openssl pkcs12 -export -in certificate.crt -inkey certificate.key -out netty4-secure.p12 -name netty4-secure -password pass:password -# 3. Import the certificate into JDK keystore (PKCS12 type) +# 3. Migrate from P12 to JKS keystore -keytool -importkeystore -srcstorepass password -destkeystore netty4-secure.jks -srckeystore server.p12 -srcstoretype PKCS12 -alias netty4-secure -deststorepass password \ No newline at end of file +keytool -importkeystore -srcstorepass password -destkeystore netty4-secure.jks -srckeystore server.p12 -srcstoretype PKCS12 -alias netty4-secure -deststorepass password diff --git a/modules/transport-netty4/src/test/resources/netty4-secure.jks b/modules/transport-netty4/src/test/resources/netty4-secure.jks new file mode 100644 index 0000000000000000000000000000000000000000..d158f1fe60ef73fdaa610ce8e92a398e4212f20a GIT binary patch literal 2790 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 Sr$&KCjn%F1%tQWVQGWyU;r0Fi literal 0 HcmV?d00001 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 3105967cd9001..771de6de486d6 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 @@ -55,8 +55,6 @@ import org.opensearch.transport.TransportSettings; import org.junit.AfterClass; import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.rules.ExternalResource; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -107,17 +105,6 @@ protected Collection> nodePlugins() { private static Path keyStoreFile; - @ClassRule - public static final ExternalResource MUTE_IN_FIPS_JVM = new ExternalResource() { - @Override - protected void before() { - assumeFalse( - "Can't run in a FIPS JVM because none of the supported Keystore types can be used", - Boolean.parseBoolean(System.getProperty(FIPS_SYSPROP)) - ); - } - }; - @BeforeClass public static void setupKeyStore() throws IOException { Path tempDir = createTempDir(); diff --git a/plugins/repository-s3/build.gradle b/plugins/repository-s3/build.gradle index 6e84edddcc252..dc1e9c99ce2ed 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 @@ -143,6 +144,13 @@ def fixtureAddress = { fixture, name, port -> 'http://127.0.0.1:' + ephemeralPort } +def applyFipsConfig(OpenSearchCluster cluster) { + if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-2') { + cluster.keystorePassword 'notarealpasswordphrase' + cluster.environment 'OPENSEARCH_CRYPTO_STANDARD', 'FIPS-140-2' + } +} + // We test against two repositories, one which uses the usual two-part "permanent" credentials and // the other which uses three-part "temporary" or "session" credentials. @@ -260,6 +268,7 @@ yamlRestTest { } testClusters.yamlRestTest { + applyFipsConfig(delegate) keystore 's3.client.integration_test_permanent.access_key', s3PermanentAccessKey keystore 's3.client.integration_test_permanent.secret_key', s3PermanentSecretKey @@ -292,7 +301,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 { @@ -323,6 +332,7 @@ if (useFixture) { check.dependsOn(yamlRestTestMinio) testClusters.yamlRestTestMinio { + applyFipsConfig(delegate) keystore 's3.client.integration_test_permanent.access_key', s3PermanentAccessKey keystore 's3.client.integration_test_permanent.secret_key', s3PermanentSecretKey setting 's3.client.integration_test_permanent.endpoint', { "${-> fixtureAddress('minio-fixture', 'minio-fixture', '9000')}" }, IGNORE_VALUE @@ -351,6 +361,7 @@ if (useFixture) { check.dependsOn(yamlRestTestECS) testClusters.yamlRestTestECS { + applyFipsConfig(delegate) setting 's3.client.integration_test_ecs.endpoint', { "${-> fixtureAddress('s3-fixture', 's3-fixture-with-ecs', '80')}" }, IGNORE_VALUE plugin tasks.bundlePlugin.archiveFile environment 'AWS_CONTAINER_CREDENTIALS_FULL_URI', { "${-> fixtureAddress('s3-fixture', 's3-fixture-with-ecs', '80')}/ecs_credentials_endpoint" }, IGNORE_VALUE @@ -378,6 +389,7 @@ if (useFixture) { check.dependsOn(yamlRestTestEKS) testClusters.yamlRestTestEKS { + applyFipsConfig(delegate) keystore 's3.client.integration_test_eks.role_arn', "arn:aws:iam::000000000000:role/test" keystore 's3.client.integration_test_eks.role_session_name', "s3-test" keystore 's3.client.integration_test_eks.access_key', "access_key" diff --git a/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ReactorHttpClient.java b/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ReactorHttpClient.java index 33ba9168e7d6a..8d20650d76583 100644 --- a/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ReactorHttpClient.java +++ b/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ReactorHttpClient.java @@ -13,7 +13,6 @@ package org.opensearch.http.reactor.netty4; -import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.collect.Tuple; import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.xcontent.ToXContent; @@ -66,7 +65,6 @@ public class ReactorHttpClient implements Closeable { private final boolean compression; private final boolean secure; - private final boolean fipsEnabled; static Collection returnHttpResponseBodies(Collection responses) { List list = new ArrayList<>(responses.size()); @@ -87,7 +85,6 @@ static Collection returnOpaqueIds(Collection responses public ReactorHttpClient(boolean compression, boolean secure) { this.compression = compression; this.secure = secure; - this.fipsEnabled = CryptoServicesRegistrar.isInApprovedOnlyMode(); } public static ReactorHttpClient create() { @@ -186,15 +183,7 @@ private List sendRequests( final Collection requests, boolean ordered ) { - final NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(1, (Runnable r) -> { - Runnable runnable = () -> { - if (fipsEnabled) { - CryptoServicesRegistrar.setApprovedOnlyMode(true); - } - r.run(); - }; - return new Thread(runnable); - }); + final NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(1); try { final HttpClient client = createClient(remoteAddress, eventLoopGroup); diff --git a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java index 69816e435c48f..f0f2d72e6f2fa 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -40,7 +40,6 @@ import org.apache.logging.log4j.core.config.Configurator; import org.apache.lucene.util.Constants; import org.apache.lucene.util.StringHelper; -import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.OpenSearchException; import org.opensearch.Version; import org.opensearch.cli.Terminal; @@ -53,7 +52,6 @@ import org.opensearch.common.logging.LogConfigurator; import org.opensearch.common.logging.Loggers; import org.opensearch.common.network.IfConfig; -import org.opensearch.common.settings.FipsSettings; import org.opensearch.common.settings.KeyStoreWrapper; import org.opensearch.common.settings.SecureSettings; import org.opensearch.common.settings.Settings; @@ -198,14 +196,9 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot BootstrapSettings.CTRLHANDLER_SETTING.get(settings) ); - var isFipsEnabled = FipsSettings.FIPS_ENABLED.get(settings); - try { - var isRunningInFipsMode = CryptoServicesRegistrar.setApprovedOnlyMode(isFipsEnabled); - if (isRunningInFipsMode) { - LogManager.getLogger(Bootstrap.class).info("running in FIPS mode"); - } - } catch (Exception e) { - throw new BootstrapException(e); + var cryptoStandard = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); + if (cryptoStandard != null && cryptoStandard.equals("FIPS-140-2")) { + LogManager.getLogger(Bootstrap.class).info("running in FIPS-140-2 mode"); } // initialize probes before the security manager is installed diff --git a/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java b/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java index cf2d459d7a1c8..f8c5d8e3b2480 100644 --- a/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/opensearch/common/settings/ClusterSettings.java @@ -673,7 +673,6 @@ public void apply(Settings value, Settings current, Settings previous) { ClusterManagerTaskThrottler.THRESHOLD_SETTINGS, ClusterManagerTaskThrottler.BASE_DELAY_SETTINGS, ClusterManagerTaskThrottler.MAX_DELAY_SETTINGS, - FipsSettings.FIPS_ENABLED, // Settings related to search backpressure SearchBackpressureSettings.SETTING_MODE, diff --git a/server/src/main/java/org/opensearch/common/settings/FipsSettings.java b/server/src/main/java/org/opensearch/common/settings/FipsSettings.java deleted file mode 100644 index 9f667b1497a2e..0000000000000 --- a/server/src/main/java/org/opensearch/common/settings/FipsSettings.java +++ /dev/null @@ -1,20 +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. - */ - -package org.opensearch.common.settings; - -import org.opensearch.common.settings.Setting.Property; - -/** - * Settings used for NIST FIPS 140-2 compliance - */ -public class FipsSettings { - - public static final Setting FIPS_ENABLED = Setting.boolSetting("fips.approved", false, Property.NodeScope); - -} diff --git a/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..cf6ce7d014199 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 @@ -90,9 +90,9 @@ protected HttpHandler createHandler(final String[] args) { protected String buildCredentialResponse(final String ec2AccessKey, final String ec2SessionToken) { return "{" + "\"AccessKeyId\": \"" + ec2AccessKey + "\"," - + "\"Expiration\": \"" + ZonedDateTime.now().plusDays(1L).format(DateTimeFormatter.ISO_DATE_TIME) + "\"," + + "\"Expiration\": \"" + ZonedDateTime.now().plusDays(1L).format(DateTimeFormatter.ISO_INSTANT) + "\"," + "\"RoleArn\": \"arn\"," - + "\"SecretAccessKey\": \"secret\"," + + "\"SecretAccessKey\": \"secret_key\"," + "\"Token\": \"" + ec2SessionToken + "\"" + "}"; } 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 4572e3c59b1de..153a64a23642c 100644 --- a/test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java +++ b/test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java @@ -254,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(); @@ -363,12 +361,6 @@ protected void afterIfSuccessful() throws Exception {} // setup mock filesystems for this test run. we change PathUtils // so that all accesses are plumbed thru any mock wrappers - @BeforeClass - public static void setFipsJvm() throws Exception { - var runInApprovedMode = Boolean.parseBoolean(System.getProperty(FIPS_SYSPROP)); - CryptoServicesRegistrar.setApprovedOnlyMode(runInApprovedMode); - } - @BeforeClass public static void setFileSystem() throws Exception { PathUtilsForTesting.setup(); 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 2b5f3fdc3e6b4..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,9 +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)); - appendOpt("org.bouncycastle.jca.enable_jks", "true"); - appendOpt("org.bouncycastle.rsa.allow_multi_use", "true"); return this; } From 5562d1fdb8db37d7f9a3cb63e23ca5c7d13cf037 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Thu, 24 Oct 2024 11:45:53 +0200 Subject: [PATCH 09/21] write some additional unit tests. Signed-off-by: Iwan Igonin --- build.gradle | 11 +- .../gradle/OpenSearchTestBasePlugin.java | 3 +- distribution/src/config/fips_java.security | 12 +- distribution/src/config/java.security | 58 +++++++ .../cli/keystore/KeyStoreWrapperTests.java | 154 +++++++++++------- .../tools/launchers/SystemJvmOptions.java | 17 +- .../common/crypto/KeyStoreType.java | 4 +- .../common/crypto/KeyStoreFactoryTests.java | 77 +++++++++ .../org/opensearch/common/ssl/PemUtils.java | 4 +- .../common/ssl/SslConfiguration.java | 3 - .../common/ssl/PemTrustConfigTests.java | 4 +- .../opensearch/common/ssl/PemUtilsTests.java | 4 + .../ssl/SslConfigurationLoaderTests.java | 8 + .../common/ssl/SslConfigurationTests.java | 78 +++++++++ .../common/ssl/SslDiagnosticsTests.java | 12 ++ .../src/test/resources/certs/README.md | 17 ++ .../test/resources/certs/cert-all/empty.jks | Bin 0 -> 32 bytes .../shiro/realm/BCryptPasswordMatcher.java | 1 - .../realm/BCryptPasswordMatcherTests.java | 19 +++ 19 files changed, 406 insertions(+), 80 deletions(-) create mode 100644 distribution/src/config/java.security create mode 100644 libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java create mode 100644 libs/ssl-config/src/test/resources/certs/cert-all/empty.jks diff --git a/build.gradle b/build.gradle index 80b60306a0917..e6416fbbde5aa 100644 --- a/build.gradle +++ b/build.gradle @@ -471,8 +471,8 @@ gradle.projectsEvaluated { } } -// test retry configuration subprojects { + // test retry configuration tasks.withType(Test).configureEach { develocity.testRetry { if (BuildParams.isCi()) { @@ -558,6 +558,15 @@ subprojects { } } } + + // test with FIPS-140-2 enabled + plugins.withType(JavaPlugin).configureEach { + tasks.withType(Test).configureEach { testTask -> + if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-2') { + testTask.jvmArgs += "-Dorg.bouncycastle.fips.approved_only=true" + } + } + } } // 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 3161343ee1148..cf2cad686f3ac 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java @@ -164,9 +164,10 @@ public void execute(Task t) { test.systemProperty("tests.seed", BuildParams.getTestSeed()); } + var securityFile = BuildParams.isInFipsJvm() ? "fips_java.security" : "java.security"; test.systemProperty( "java.security.properties", - project.getRootProject().getLayout().getProjectDirectory() + "/distribution/src/config/fips_java.security" + project.getRootProject().getLayout().getProjectDirectory() + "/distribution/src/config/" + securityFile ); // don't track these as inputs since they contain absolute paths and break cache relocatability diff --git a/distribution/src/config/fips_java.security b/distribution/src/config/fips_java.security index fc1608c8df1bd..bf160a0055a59 100644 --- a/distribution/src/config/fips_java.security +++ b/distribution/src/config/fips_java.security @@ -1,27 +1,34 @@ -# 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.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&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 @@ -31,7 +38,9 @@ jdk.tls.legacyAlgorithms= \ 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,\ @@ -45,5 +54,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 new file mode 100644 index 0000000000000..5ddbbde240f8f --- /dev/null +++ b/distribution/src/config/java.security @@ -0,0 +1,58 @@ +# Security Properties for JDK 11 and higher, with BouncyCastle FIPS provider and BouncyCastleJSSEProvider in non-approved mode + +security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider +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= + +login.configuration.provider=sun.security.provider.ConfigFile +policy.provider=sun.security.provider.PolicyFile +policy.expandProperties=true +policy.allowSystemProperty=true +policy.ignoreIdentityScope=false +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, RSA keySize < 1024, DSA keySize < 1024 +jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySize < 1024 +jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 1024, DES40_CBC, RC4_40 +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/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.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/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 c8c0b8f8b3928..9f20dec124549 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 @@ -70,8 +70,13 @@ import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -364,31 +369,24 @@ public void testIllegalSettingName() throws Exception { assertTrue(e.getMessage().contains("does not match the allowed setting name pattern")); } - public void testBackcompatV1() throws Exception { - assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); - Path configDir = env.configDir(); - NIOFSDirectory directory = new NIOFSDirectory(configDir); - try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) { - CodecUtil.writeHeader(output, "opensearch.keystore", 1); - output.writeByte((byte) 0); // hasPassword = false - output.writeString("PKCS12"); - output.writeString("PBE"); + public void testFailLoadV1KeystoresInFipsJvm() throws Exception { + assumeTrue("Test in FIPS JVM", inFipsJvm()); - SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); - KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); - keystore.load(null, null); - SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); - KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); - keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); + Exception e = assertThrows(SecurityException.class, () -> generateV1()); + assertThat(e.getMessage(), containsString("Only PKCS_11, BCFKS, JKS keystores are allowed in FIPS JVM")); + } - ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream(); - keystore.store(keystoreBytesStream, new char[0]); - byte[] keystoreBytes = keystoreBytesStream.toByteArray(); - output.writeInt(keystoreBytes.length); - output.writeBytes(keystoreBytes, keystoreBytes.length); - CodecUtil.writeFooter(output); - } + public void testFailLoadV2KeystoresInFipsJvm() throws Exception { + assumeTrue("Test in FIPS JVM", inFipsJvm()); + + Exception e = assertThrows(SecurityException.class, () -> generateV2()); + assertThat(e.getMessage(), containsString("Only PKCS_11, BCFKS, JKS keystores are allowed in FIPS JVM")); + } + public void testBackcompatV1() throws Exception { + assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); + generateV1(); + Path configDir = env.configDir(); KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir); keystore.decrypt(new char[0]); SecureString testValue = keystore.getString("string_setting"); @@ -397,47 +395,8 @@ public void testBackcompatV1() throws Exception { public void testBackcompatV2() throws Exception { assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); + byte[] fileBytes = generateV2(); Path configDir = env.configDir(); - NIOFSDirectory directory = new NIOFSDirectory(configDir); - byte[] fileBytes = new byte[20]; - random().nextBytes(fileBytes); - try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) { - - CodecUtil.writeHeader(output, "opensearch.keystore", 2); - output.writeByte((byte) 0); // hasPassword = false - output.writeString("PKCS12"); - output.writeString("PBE"); // string algo - output.writeString("PBE"); // file algo - - output.writeVInt(2); // num settings - output.writeString("string_setting"); - output.writeString("STRING"); - output.writeString("file_setting"); - output.writeString("FILE"); - - SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); - KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); - keystore.load(null, null); - SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); - KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); - keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); - - byte[] base64Bytes = Base64.getEncoder().encode(fileBytes); - char[] chars = new char[base64Bytes.length]; - for (int i = 0; i < chars.length; ++i) { - chars[i] = (char) base64Bytes[i]; // PBE only stores the lower 8 bits, so this narrowing is ok - } - secretKey = secretFactory.generateSecret(new PBEKeySpec(chars)); - keystore.setEntry("file_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); - - ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream(); - keystore.store(keystoreBytesStream, new char[0]); - byte[] keystoreBytes = keystoreBytesStream.toByteArray(); - output.writeInt(keystoreBytes.length); - output.writeBytes(keystoreBytes, keystoreBytes.length); - CodecUtil.writeFooter(output); - } - KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir); keystore.decrypt(new char[0]); SecureString testValue = keystore.getString("string_setting"); @@ -497,6 +456,77 @@ public void testLegacyV3() throws GeneralSecurityException, IOException { assertThat(toByteArray(wrapper.getFile("file_setting")), equalTo("file_value".getBytes(StandardCharsets.UTF_8))); } + private void generateV1() throws IOException, NoSuchAlgorithmException, NoSuchProviderException, CertificateException, + InvalidKeySpecException, KeyStoreException { + Path configDir = env.configDir(); + NIOFSDirectory directory = new NIOFSDirectory(configDir); + try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) { + CodecUtil.writeHeader(output, "opensearch.keystore", 1); + output.writeByte((byte) 0); // hasPassword = false + output.writeString("PKCS12"); + output.writeString("PBE"); + + SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); + KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); + keystore.load(null, null); + SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); + KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); + keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); + + ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream(); + keystore.store(keystoreBytesStream, new char[0]); + byte[] keystoreBytes = keystoreBytesStream.toByteArray(); + output.writeInt(keystoreBytes.length); + output.writeBytes(keystoreBytes, keystoreBytes.length); + CodecUtil.writeFooter(output); + } + } + + private byte[] generateV2() throws Exception { + Path configDir = env.configDir(); + NIOFSDirectory directory = new NIOFSDirectory(configDir); + byte[] fileBytes = new byte[20]; + random().nextBytes(fileBytes); + try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) { + + CodecUtil.writeHeader(output, "opensearch.keystore", 2); + output.writeByte((byte) 0); // hasPassword = false + output.writeString("PKCS12"); + output.writeString("PBE"); // string algo + output.writeString("PBE"); // file algo + + output.writeVInt(2); // num settings + output.writeString("string_setting"); + output.writeString("STRING"); + output.writeString("file_setting"); + output.writeString("FILE"); + + SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); + KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); + keystore.load(null, null); + SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); + KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); + keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); + + byte[] base64Bytes = Base64.getEncoder().encode(fileBytes); + char[] chars = new char[base64Bytes.length]; + for (int i = 0; i < chars.length; ++i) { + chars[i] = (char) base64Bytes[i]; // PBE only stores the lower 8 bits, so this narrowing is ok + } + secretKey = secretFactory.generateSecret(new PBEKeySpec(chars)); + keystore.setEntry("file_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); + + ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream(); + keystore.store(keystoreBytesStream, new char[0]); + byte[] keystoreBytes = keystoreBytesStream.toByteArray(); + output.writeInt(keystoreBytes.length); + output.writeBytes(keystoreBytes, keystoreBytes.length); + CodecUtil.writeFooter(output); + } + + return fileBytes; + } + private byte[] toByteArray(final InputStream is) throws IOException { final ByteArrayOutputStream os = new ByteArrayOutputStream(); final byte[] buffer = new byte[1024]; 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 2d10fac45efda..45e10b461d1db 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 @@ -40,6 +40,9 @@ final class SystemJvmOptions { + protected static final String OPENSEARCH_CRYPTO_STANDARD = "OPENSEARCH_CRYPTO_STANDARD"; + protected static final String FIPS_140_2 = "FIPS-140-2"; + static List systemJvmOptions(final Path config) { return Collections.unmodifiableList( Arrays.asList( @@ -88,16 +91,22 @@ static List systemJvmOptions(final Path config) { } private static String enableFips() { - var cryptoStandard = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); - if (cryptoStandard != null && cryptoStandard.equals("FIPS-140-2")) { + var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); + if (cryptoStandard != null && cryptoStandard.equals(FIPS_140_2)) { return "-Dorg.bouncycastle.fips.approved_only=true"; } return ""; } private static String loadJavaSecurityProperties(final Path config) { - var securityFile = config.resolve("fips_java.security"); - return "-Djava.security.properties=" + securityFile.toAbsolutePath(); + String securityFile; + var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); + if (cryptoStandard != null && cryptoStandard.equals(FIPS_140_2)) { + securityFile = "fips_java.security"; + } else { + securityFile = "java.security"; + } + return "-Djava.security.properties=" + config.resolve(securityFile).toAbsolutePath(); } private static String allowSecurityManagerOption() { diff --git a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java index 2b458d431215f..2c1ba85e8235d 100644 --- a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java +++ b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java @@ -21,15 +21,13 @@ public enum KeyStoreType { JKS("JKS"), PKCS_12("PKCS12"), PKCS_11("PKCS11"), - BKS("BKS"), BCFKS("BCFKS"); - private static final Map> TYPE_TO_EXTENSION_MAP = new HashMap<>(); + 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(BKS, List.of(".bks")); // Bouncy Castle Keystore TYPE_TO_EXTENSION_MAP.put(BCFKS, List.of(".bcfks")); // Bouncy Castle FIPS Keystore } diff --git a/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java b/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java new file mode 100644 index 0000000000000..2ed3a2fafa867 --- /dev/null +++ b/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java @@ -0,0 +1,77 @@ +/* + * 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.crypto; + +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 run in a FIPS JVM as PBE is not available", 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() { + 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")); + + assertThrows("BCFKS not found", SecurityException.class, () -> KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "SUN")); + + 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/main/java/org/opensearch/common/ssl/PemUtils.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java index 3a1b369c64902..dd5b67cbcbbb8 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java @@ -62,7 +62,7 @@ final class PemUtils { private static final String BCFIPS = "BCFIPS"; - private PemUtils() { + PemUtils() { throw new IllegalStateException("Utility class should not be instantiated"); } @@ -87,7 +87,7 @@ static List readCertificates(Collection certPaths) throws Cer try (InputStream input = Files.newInputStream(path)) { final Collection parsed = certFactory.generateCertificates(input); if (parsed.isEmpty()) { - throw new SslConfigException("failed to parse any certificates from [" + path.toAbsolutePath() + "]"); + throw new SslConfigException("Failed to parse any certificate from [" + path.toAbsolutePath() + "]"); } certificates.addAll(parsed); } 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 909b8facd68b5..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 @@ -164,9 +164,6 @@ 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( diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemTrustConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemTrustConfigTests.java index 543021e0b1252..3d66c2157b754 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemTrustConfigTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemTrustConfigTests.java @@ -138,14 +138,14 @@ private void assertEmptyFile(PemTrustConfig trustConfig, Path file) { final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); logger.info("failure", exception); assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); - assertThat(exception.getMessage(), Matchers.containsString("failed to parse any certificates")); + assertThat(exception.getMessage(), Matchers.containsString("Failed to parse any certificate from")); } private void assertFailedToParse(PemTrustConfig trustConfig, Path file) { final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); logger.info("failure", exception); assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); - assertThat(exception.getMessage(), Matchers.containsString("failed to parse any certificates")); + assertThat(exception.getMessage(), Matchers.containsString("Failed to parse any certificate from")); } private void assertFileNotFound(PemTrustConfig trustConfig, Path 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 7ab684fea0670..0aa0213d54111 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 @@ -59,6 +59,10 @@ public class PemUtilsTests extends OpenSearchTestCase { private static final Supplier TESTNODE_PASSWORD = "testnode"::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 { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); Key key = getKeyFromKeystore("RSA", KeyStoreType.JKS); 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 bb46c086fbedc..f4c42231ab96a 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 @@ -100,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 + ); + } } } 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 9281d9612d806..ce846381c6e5e 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; @@ -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/SslDiagnosticsTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java index b733b6536f5cb..31a4082f0609a 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java @@ -52,6 +52,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -70,6 +71,17 @@ public class SslDiagnosticsTests extends OpenSearchTestCase { private static final byte[] MOCK_ENCODING_4 = { 0x64, 0x65, 0x66, 0x67, 0x68, 0x69 }; private static final String MOCK_FINGERPRINT_4 = "5d96965bfae50bf2be0d6259eb87a6cc9f5d0b26"; + public void testTrustEmptyStore() { + var fileName = "cert-all/empty.jks"; + var exception = assertThrows(SslConfigException.class, () -> loadCertificate(fileName)); + assertThat( + exception.getMessage(), + Matchers.equalTo( + String.format(Locale.ROOT, "Failed to parse any certificate from [%s]", getDataPath("/certs/" + fileName).toAbsolutePath()) + ) + ); + } + public void testDiagnosticMessageWhenServerProvidesAFullCertChainThatIsTrusted() throws Exception { X509Certificate[] chain = loadCertChain("cert1/cert1.crt", "ca1/ca.crt"); final SSLSession session = session("192.168.1.1"); diff --git a/libs/ssl-config/src/test/resources/certs/README.md b/libs/ssl-config/src/test/resources/certs/README.md index dbbe840dd5431..e363e287c9521 100644 --- a/libs/ssl-config/src/test/resources/certs/README.md +++ b/libs/ssl-config/src/test/resources/certs/README.md @@ -153,3 +153,20 @@ done opensearch-certutil ca --pem --out ${PWD}/ca1-b.zip --days 9999 --ca-dn "CN=Test CA 1" unzip ca1-b.zip mv ca ca1-b + +# 16. Create empty KeyStore + +```bash +keytool -genkeypair \ + -alias temp \ + -storetype JKS \ + -keyalg rsa \ + -storepass storePassword \ + -keypass secretPassword \ + -keystore cert-all/empty.jks \ + -dname "CN=foo,DC=example,DC=com" +keytool -delete \ + -alias temp \ + -storepass storePassword \ + -keystore cert-all/empty.jks +``` diff --git a/libs/ssl-config/src/test/resources/certs/cert-all/empty.jks b/libs/ssl-config/src/test/resources/certs/cert-all/empty.jks new file mode 100644 index 0000000000000000000000000000000000000000..1327d550f61fd92422fecc4cb63ad35cdd2bc6d4 GIT binary patch literal 32 mcmezO_TO6u1_mY|W{?e7XzyD5dU}}5KBM|+P4StVO0xjX8Vs@k literal 0 HcmV?d00001 diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java index a1945bab6a4ed..45bf634de1ec1 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java @@ -38,7 +38,6 @@ public class BCryptPasswordMatcher implements CredentialsMatcher { public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { final UsernamePasswordToken userToken = (UsernamePasswordToken) token; return check(userToken.getPassword(), (String) info.getCredentials()); - } private boolean check(char[] password, String hash) { diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java index 91e88ed1bf701..304903e27c953 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java @@ -41,4 +41,23 @@ public void testCredentialDoNotMatch() { assertThat(result, equalTo(false)); } + + public void testEmptyPassword() { + final UsernamePasswordToken token = mock(UsernamePasswordToken.class); + when(token.getPassword()).thenReturn(randomFrom("".toCharArray(), null)); + final AuthenticationInfo info = mock(AuthenticationInfo.class); + + Exception e = assertThrows(IllegalStateException.class, () -> new BCryptPasswordMatcher().doCredentialsMatch(token, info)); + assertThat(e.getMessage(), equalTo("Password cannot be empty or null")); + } + + public void testEmptyHash() { + final UsernamePasswordToken token = mock(UsernamePasswordToken.class); + when(token.getPassword()).thenReturn("HashedPassword".toCharArray()); + final AuthenticationInfo info = mock(AuthenticationInfo.class); + when(info.getCredentials()).thenReturn(randomFrom("", null)); + + Exception e = assertThrows(IllegalStateException.class, () -> new BCryptPasswordMatcher().doCredentialsMatch(token, info)); + assertThat(e.getMessage(), equalTo("Hash cannot be empty or null")); + } } From 71ef8d93f82b837ed2db6a4269a1bb1d2a2a9d24 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Mon, 28 Oct 2024 17:07:48 +0100 Subject: [PATCH 10/21] test SystemJvmOptions.java & replace FIPS-140-2 by FIPS-140-3 Signed-off-by: Iwan Igonin --- build.gradle | 4 +- .../gradle/info/GlobalBuildInfoPlugin.java | 9 +- distribution/tools/launchers/build.gradle | 1 + .../tools/launchers/JvmOptionsParser.java | 2 +- .../tools/launchers/SystemJvmOptions.java | 33 ++++--- .../launchers/SystemJvmOptionsTests.java | 86 +++++++++++++++++++ plugins/repository-s3/build.gradle | 4 +- .../org/opensearch/bootstrap/Bootstrap.java | 4 +- 8 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 distribution/tools/launchers/src/test/java/org/opensearch/tools/launchers/SystemJvmOptionsTests.java diff --git a/build.gradle b/build.gradle index e6416fbbde5aa..27e1bde68b4f6 100644 --- a/build.gradle +++ b/build.gradle @@ -559,10 +559,10 @@ subprojects { } } - // test with FIPS-140-2 enabled + // test with FIPS-140-3 enabled plugins.withType(JavaPlugin).configureEach { tasks.withType(Test).configureEach { testTask -> - if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-2') { + if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-3') { testTask.jvmArgs += "-Dorg.bouncycastle.fips.approved_only=true" } } 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 adf6f6878d322..41ef9d11d7062 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java @@ -114,7 +114,7 @@ public void apply(Project project) { // Initialize global build parameters boolean isInternal = GlobalBuildInfoPlugin.class.getResource("/buildSrc.marker") != null; var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); - var inFipsJvm = cryptoStandard != null && cryptoStandard.equals("FIPS-140-2"); + var inFipsJvm = cryptoStandard != null && cryptoStandard.equals("FIPS-140-3"); params.reset(); params.setRuntimeJavaHome(runtimeJavaHome); @@ -166,6 +166,7 @@ private void logGlobalBuildInfo() { final String osArch = System.getProperty("os.arch"); final Jvm gradleJvm = Jvm.current(); final String gradleJvmDetails = getJavaInstallation(gradleJvm.getJavaHome()).getDisplayName(); + final String cryptStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); LOGGER.quiet("======================================="); LOGGER.quiet("OpenSearch Build Hamster says Hello!"); @@ -182,7 +183,11 @@ private void logGlobalBuildInfo() { LOGGER.quiet(" JAVA_HOME : " + gradleJvm.getJavaHome()); } LOGGER.quiet(" Random Testing Seed : " + BuildParams.getTestSeed()); - LOGGER.quiet(" Crypto Standard : " + Optional.ofNullable(System.getenv(OPENSEARCH_CRYPTO_STANDARD)).orElse("any-supported")); + if (cryptStandard != null && cryptStandard.equals("FIPS-140-3")) { + LOGGER.quiet(" Crypto Standard : FIPS-140-3"); + } else { + LOGGER.quiet(" Crypto Standard : any-supported"); + } LOGGER.quiet("======================================="); } 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 8ab3a65090b2b..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(config); + 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 45e10b461d1db..15b38e50fee4e 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,8 @@ 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; @@ -40,10 +42,10 @@ final class SystemJvmOptions { - protected static final String OPENSEARCH_CRYPTO_STANDARD = "OPENSEARCH_CRYPTO_STANDARD"; - protected static final String FIPS_140_2 = "FIPS-140-2"; + static final String OPENSEARCH_CRYPTO_STANDARD = "OPENSEARCH_CRYPTO_STANDARD"; + static final String FIPS_140_3 = "FIPS-140-3"; - static List systemJvmOptions(final Path config) { + static List systemJvmOptions(final Path config, Runtime.Version runtimeVersion) throws FileNotFoundException { return Collections.unmodifiableList( Arrays.asList( /* @@ -72,7 +74,7 @@ static List systemJvmOptions(final Path config) { */ "-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", @@ -83,7 +85,7 @@ static List systemJvmOptions(final Path config) { "-Dlog4j2.disable.jmx=true", // security settings enableFips(), - allowSecurityManagerOption(), + allowSecurityManagerOption(runtimeVersion), loadJavaSecurityProperties(config), javaLocaleProviders() ) @@ -92,33 +94,38 @@ static List systemJvmOptions(final Path config) { private static String enableFips() { var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); - if (cryptoStandard != null && cryptoStandard.equals(FIPS_140_2)) { + if (cryptoStandard != null && cryptoStandard.equals(FIPS_140_3)) { return "-Dorg.bouncycastle.fips.approved_only=true"; } return ""; } - private static String loadJavaSecurityProperties(final Path config) { + private static String loadJavaSecurityProperties(final Path config) throws FileNotFoundException { String securityFile; var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); - if (cryptoStandard != null && cryptoStandard.equals(FIPS_140_2)) { + if (cryptoStandard != null && cryptoStandard.equals(FIPS_140_3)) { securityFile = "fips_java.security"; } else { securityFile = "java.security"; } - return "-Djava.security.properties=" + config.resolve(securityFile).toAbsolutePath(); + 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() { - if (Runtime.version().feature() > 17) { + 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..1ef0ddc810276 --- /dev/null +++ b/distribution/tools/launchers/src/test/java/org/opensearch/tools/launchers/SystemJvmOptionsTests.java @@ -0,0 +1,86 @@ +/* + * 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()); + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + assertThat(jvmOptions, hasItem("-Dorg.bouncycastle.fips.approved_only=true")); + } else { + assertThat(jvmOptions, not(hasItem("-Dorg.bouncycastle.fips.approved_only=true"))); + } + } + + 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/plugins/repository-s3/build.gradle b/plugins/repository-s3/build.gradle index dc1e9c99ce2ed..2a303f9a01656 100644 --- a/plugins/repository-s3/build.gradle +++ b/plugins/repository-s3/build.gradle @@ -145,9 +145,9 @@ def fixtureAddress = { fixture, name, port -> } def applyFipsConfig(OpenSearchCluster cluster) { - if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-2') { + if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-3') { cluster.keystorePassword 'notarealpasswordphrase' - cluster.environment 'OPENSEARCH_CRYPTO_STANDARD', 'FIPS-140-2' + cluster.environment 'OPENSEARCH_CRYPTO_STANDARD', 'FIPS-140-3' } } diff --git a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java index f0f2d72e6f2fa..424677ce96066 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -197,8 +197,8 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot ); var cryptoStandard = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); - if (cryptoStandard != null && cryptoStandard.equals("FIPS-140-2")) { - LogManager.getLogger(Bootstrap.class).info("running in FIPS-140-2 mode"); + if (cryptoStandard != null && cryptoStandard.equals("FIPS-140-3")) { + LogManager.getLogger(Bootstrap.class).info("running in FIPS-140-3 mode"); } // initialize probes before the security manager is installed From 48325537cee24c7848e89ed393a853f4c4dd37cf Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Fri, 8 Nov 2024 09:22:55 +0100 Subject: [PATCH 11/21] categorize JKS keystore as untrusted Signed-off-by: Iwan Igonin --- .../client/RestClientBuilderIntegTests.java | 21 +-------- .../cli/keystore/KeyStoreWrapperTests.java | 7 +-- .../launchers/SystemJvmOptionsTests.java | 5 ++- .../common/crypto/KeyStoreFactory.java | 21 +++++++-- .../common/crypto/KeyStoreType.java | 9 +--- .../common/crypto/KeyStoreFactoryTests.java | 5 +-- .../opensearch/common/ssl/KeyStoreUtil.java | 10 +++-- .../org/opensearch/common/ssl/PemUtils.java | 2 +- .../opensearch/common/ssl/PemUtilsTests.java | 7 ++- .../ssl/SslConfigurationLoaderTests.java | 2 +- .../common/ssl/SslConfigurationTests.java | 2 +- .../common/ssl/StoreKeyConfigTests.java | 10 +++-- .../common/ssl/StoreTrustConfigTests.java | 36 +++++++++++---- modules/transport-netty4/build.gradle | 2 + .../SecureNetty4HttpServerTransportTests.java | 4 +- .../src/test/resources/README.md | 42 ++++++++++++++++++ .../src/test/resources/README.txt | 17 ------- .../src/test/resources/netty4-secure.bcfks | Bin 0 -> 2787 bytes .../AzureDiscoveryClusterFormationTests.java | 10 +++++ plugins/repository-gcs/build.gradle | 4 ++ .../gcs/GoogleCloudStorageService.java | 7 ++- .../src/main/resources/README.md | 19 ++++++++ .../src/main/resources/google.bcfks | Bin 0 -> 73692 bytes .../org/opensearch/bootstrap/security.policy | 1 - .../bootstrap/test-framework.policy | 1 - .../java/fixture/s3/S3HttpFixtureWithEC2.java | 2 +- 26 files changed, 159 insertions(+), 87 deletions(-) create mode 100644 modules/transport-netty4/src/test/resources/README.md delete mode 100644 modules/transport-netty4/src/test/resources/README.txt create mode 100644 modules/transport-netty4/src/test/resources/netty4-secure.bcfks create mode 100644 plugins/repository-gcs/src/main/resources/README.md create mode 100644 plugins/repository-gcs/src/main/resources/google.bcfks 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 c1e89664dc405..e48648964f3f6 100644 --- a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java +++ b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java @@ -57,9 +57,6 @@ import java.security.KeyStore; import java.security.PrivilegedAction; import java.security.SecureRandom; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; @@ -91,22 +88,8 @@ public void handle(HttpExchange httpExchange) throws IOException { @AfterClass public static void stopHttpServers() throws IOException { - if (httpsServer != null) { - httpsServer.stop(0); // Stop the server - Executor executor = httpsServer.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(); - } - } - httpsServer = null; - } + httpsServer.stop(0); + httpsServer = null; } public void testBuilderUsesDefaultSSLContext() throws Exception { 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 9f20dec124549..a8962b06317f2 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 @@ -93,7 +93,8 @@ public class KeyStoreWrapperTests extends OpenSearchTestCase { - Supplier passphraseSupplier = () -> inFipsJvm() ? "6!6428DQXwPpi7@$ggeg/=".toCharArray() : new char[0]; + 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<>(); @@ -373,14 +374,14 @@ public void testFailLoadV1KeystoresInFipsJvm() throws Exception { assumeTrue("Test in FIPS JVM", inFipsJvm()); Exception e = assertThrows(SecurityException.class, () -> generateV1()); - assertThat(e.getMessage(), containsString("Only PKCS_11, BCFKS, JKS keystores are allowed in FIPS JVM")); + assertThat(e.getMessage(), containsString("Only PKCS_11, BCFKS keystores are allowed in FIPS JVM")); } public void testFailLoadV2KeystoresInFipsJvm() throws Exception { assumeTrue("Test in FIPS JVM", inFipsJvm()); Exception e = assertThrows(SecurityException.class, () -> generateV2()); - assertThat(e.getMessage(), containsString("Only PKCS_11, BCFKS, JKS keystores are allowed in FIPS JVM")); + assertThat(e.getMessage(), containsString("Only PKCS_11, BCFKS keystores are allowed in FIPS JVM")); } public void testBackcompatV1() throws Exception { 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 index 1ef0ddc810276..fb9f0bcbd5936 100644 --- 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 @@ -51,10 +51,11 @@ public void testFailSetJavaSecurityProperties() throws Exception { 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("-Dorg.bouncycastle.fips.approved_only=true")); + assertThat(jvmOptions, hasItem(fipsProperty)); } else { - assertThat(jvmOptions, not(hasItem("-Dorg.bouncycastle.fips.approved_only=true"))); + assertThat(jvmOptions, not(hasItem(fipsProperty))); } } diff --git a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java index ee2d1b92c855a..4cd6e9922ab27 100644 --- a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java +++ b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java @@ -13,6 +13,7 @@ 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.crypto.KeyStoreType.SECURE_KEYSTORE_TYPES; @@ -25,8 +26,10 @@ */ public final class KeyStoreFactory { + private static final String FIPS_PROVIDER = "BCFIPS"; + /** - * Make a best guess about the "type" (see {@link KeyStore#getType()}) of the keystore file located at the given {@code Path}. + * 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) { @@ -37,10 +40,20 @@ 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() && !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 (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; } try { diff --git a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java index 2c1ba85e8235d..6654ee9cef40f 100644 --- a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java +++ b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java @@ -23,7 +23,7 @@ public enum KeyStoreType { PKCS_11("PKCS11"), BCFKS("BCFKS"); - static final Map> TYPE_TO_EXTENSION_MAP = new HashMap<>(); + public static final Map> TYPE_TO_EXTENSION_MAP = new HashMap<>(); static { TYPE_TO_EXTENSION_MAP.put(JKS, List.of(".jks", ".ks")); @@ -35,9 +35,8 @@ public enum KeyStoreType { * 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. - * - JKS can be used under FIPS with restrictions: it may contain only certificates, and it will be readable but not writable. */ - public static final List SECURE_KEYSTORE_TYPES = List.of(PKCS_11, BCFKS, JKS); + public static final List SECURE_KEYSTORE_TYPES = List.of(PKCS_11, BCFKS); private final String jcaName; @@ -62,8 +61,4 @@ public static KeyStoreType getByJcaName(String value) { return Stream.of(KeyStoreType.values()).filter(type -> type.getJcaName().equals(value)).findFirst().orElse(null); } - public static String getExtensionsByType(KeyStoreType type) { - return TYPE_TO_EXTENSION_MAP.get(type).get(0); - } - } diff --git a/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java b/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java index 2ed3a2fafa867..32b5ecfbcd23c 100644 --- a/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java +++ b/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java @@ -17,7 +17,7 @@ public class KeyStoreFactoryTests extends OpenSearchTestCase { public void testPKCS12KeyStore() { - assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); + 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")); @@ -34,6 +34,7 @@ public void testPKCS12KeyStore() { } 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")); @@ -55,8 +56,6 @@ public void testBCFIPSKeyStore() { assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "BCFIPS").getType(), equalTo("BCFKS")); assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "BCFIPS").getProvider().getName(), equalTo("BCFIPS")); - assertThrows("BCFKS not found", SecurityException.class, () -> KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "SUN")); - KeyStoreType.TYPE_TO_EXTENSION_MAP.get(KeyStoreType.BCFKS).forEach(extension -> { var keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName(extension)); assertThat(keyStore.getType(), equalTo(KeyStoreType.BCFKS.getJcaName())); 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 e0bef21ed4242..4d45e8df14130 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 @@ -54,6 +54,7 @@ import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.Collection; +import java.util.Objects; /** * A variety of utility methods for working with or constructing {@link KeyStore} instances. @@ -83,10 +84,11 @@ static KeyStore readKeyStore(Path path, KeyStoreType type, char[] password) thro } 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); } } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java index dd5b67cbcbbb8..9dddac3b88ed1 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java @@ -68,7 +68,7 @@ final class PemUtils { /** * Creates a {@link PrivateKey} from the contents of a file. Supports PKCS#1, PKCS#8 - * encoded formats of encrypted and plaintext RSA, DSA and EC(secp256r1) keys + * encoded formats of encrypted and plaintext RSA, DSA and EC(secp256r1) keys. * * @param keyPath the path for the key file * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key 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 0aa0213d54111..a0d15258ed3ca 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,7 +32,6 @@ package org.opensearch.common.ssl; -import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.crypto.KeyStoreFactory; import org.opensearch.common.crypto.KeyStoreType; import org.opensearch.test.OpenSearchTestCase; @@ -182,7 +181,7 @@ public void testReadOpenSslDsaKeyWithParams() throws Exception { } public void testReadEncryptedOpenSslDsaKey() throws Exception { - assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + 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)); @@ -286,11 +285,11 @@ public void testReadEmptyFile() { } private Key getKeyFromKeystore(String algo) throws Exception { - return getKeyFromKeystore(algo, CryptoServicesRegistrar.isInApprovedOnlyMode() ? KeyStoreType.BCFKS : KeyStoreType.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.getExtensionsByType(keyStoreType)); + 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 (var in = Files.newInputStream(keystorePath)) { 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 f4c42231ab96a..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 @@ -242,7 +242,7 @@ public void testLoadKeysFromBCFKS() { } public void testLoadKeysFromPKCS12() { - assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + 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 ce846381c6e5e..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 @@ -246,7 +246,7 @@ public void testNotSupportedProtocolsInFipsJvm() { assertThat( exception.getMessage(), equalTo( - String.format(Locale.ROOT, "in FIPS mode only the following SSL/TLS protocols are allowed: [%s]", FIPS_APPROVED_PROTOCOLS) + String.format(Locale.ROOT, "in FIPS mode only the following SSL/TLS protocols are allowed: %s", FIPS_APPROVED_PROTOCOLS) ) ); } 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 5c7a23445a877..6df03f72a0be8 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 @@ -71,7 +71,7 @@ public class StoreKeyConfigTests extends OpenSearchTestCase { 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, PKCS_12, P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(p12)); @@ -87,6 +87,7 @@ public void testLoadMultipleKeyPKCS12() throws Exception { } public void testLoadMultipleKeyJksWithSeparateKeyPassword() 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, @@ -107,6 +108,7 @@ public void testLoadMultipleKeyBcfks() throws CertificateParsingException { } 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, @@ -127,6 +129,7 @@ public void testKeyManagerFailsWithIncorrectBcfksStorePassword() throws Exceptio } 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()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(jks)); @@ -154,6 +157,7 @@ public void testKeyManagerFailsWithMissingKeystoreFile() throws Exception { } public void testMissingKeyEntriesFailsForJksWithMeaningfulMessage() throws Exception { + 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()); @@ -162,7 +166,7 @@ public void testMissingKeyEntriesFailsForJksWithMeaningfulMessage() throws Excep } 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, PKCS_12, password, KeyManagerFactory.getDefaultAlgorithm()); @@ -179,7 +183,7 @@ public void testMissingKeyEntriesFailsForBcfksWithMeaningfulMessage() throws Exc } public void testKeyConfigReloadsFileContents() 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 cert1 = getDataPath("/certs/cert1/cert1.p12"); final Path cert2 = getDataPath("/certs/cert2/cert2.p12"); final Path jks = getDataPath("/certs/cert-all/certs.jks"); 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 5bceb5aef80cf..3ba0c39e9d7e6 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 @@ -62,7 +62,7 @@ public class StoreTrustConfigTests extends OpenSearchTestCase { private static final char[] BCFKS_PASS = "bcfks-pass".toCharArray(); private static final String DEFAULT_ALGORITHM = TrustManagerFactory.getDefaultAlgorithm(); - public void testBuildTrustConfigFromPKCS12() throws Exception { + 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, PKCS_12, DEFAULT_ALGORITHM); @@ -70,13 +70,21 @@ public void testBuildTrustConfigFromPKCS12() throws Exception { assertCertificateChain(trustConfig, "CN=Test CA 1"); } - public void testBuildTrustConfigFromJKS() 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); 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"); + } + public void testBadKeyStoreFormatFails() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = createTempFile("ca", ".p12"); @@ -94,7 +102,7 @@ public void testMissingKeyStoreFailsWithMeaningfulMessage() throws Exception { assertFileNotFound(trustConfig, ks); } - public void testIncorrectPasswordFailsWithMeaningfulMessage() throws Exception { + 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], PKCS_12, DEFAULT_ALGORITHM); @@ -102,10 +110,22 @@ public void testIncorrectPasswordFailsWithMeaningfulMessage() throws Exception { 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); + } + public void testMissingTrustEntriesFailsForJksKeystoreWithMeaningfulMessage() throws Exception { - final char[] password = JKS_PASS; + 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, password, JKS, DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, JKS, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertNoCertificateEntries(trustConfig, ks); } @@ -113,16 +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 char[] password = P12_PASS; - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, password, PKCS_12, 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 char[] password = BCFKS_PASS; - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, password, BCFKS, DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, BCFKS_PASS, BCFKS, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertNoCertificateEntries(trustConfig, ks); } diff --git a/modules/transport-netty4/build.gradle b/modules/transport-netty4/build.gradle index 89d3f03032d9f..25f713f19758d 100644 --- a/modules/transport-netty4/build.gradle +++ b/modules/transport-netty4/build.gradle @@ -79,6 +79,8 @@ tasks.named("dependencyLicenses").configure { forbiddenPatterns { exclude '**/*.p12' + exclude '**/*.jks' + exclude '**/*.bcfks' } test { 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 6383705149521..0a801a0f58f02 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 @@ -123,9 +123,9 @@ public Optional buildHttpServerExceptionHandler(Setti @Override public Optional buildSecureHttpServerEngine(Settings settings, HttpServerTransport transport) throws SSLException { try { - final KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.JKS); + 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/resources/README.md b/modules/transport-netty4/src/test/resources/README.md new file mode 100644 index 0000000000000..5b74bd85e7c4a --- /dev/null +++ b/modules/transport-netty4/src/test/resources/README.md @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# This is README describes how the certificates in this directory were created. +# This file can also be executed as a script +# + +# 1. Create certificate key + +`openssl req -x509 -sha256 -newkey rsa:2048 -keyout certificate.key -out certificate.crt -days 1024 -nodes` + +# 2. Export the certificate in pkcs12 format + +`openssl pkcs12 -export -in certificate.crt -inkey certificate.key -out netty4-secure.p12 -name netty4-secure -password pass:password` + +# 3. Migrate from P12 to JKS keystore + +``` +keytool -importkeystore -noprompt \ + -srckeystore netty4-secure.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass password \ + -alias netty4-secure \ + -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/README.txt b/modules/transport-netty4/src/test/resources/README.txt deleted file mode 100644 index a1a226c5ba674..0000000000000 --- a/modules/transport-netty4/src/test/resources/README.txt +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# -# This is README describes how the certificates in this directory were created. -# This file can also be executed as a script -# - -# 1. Create certificate key - -openssl req -x509 -sha256 -newkey rsa:2048 -keyout certificate.key -out certificate.crt -days 1024 -nodes - -# 2. Export the certificate in pkcs12 format - -openssl pkcs12 -export -in certificate.crt -inkey certificate.key -out netty4-secure.p12 -name netty4-secure -password pass:password - -# 3. Migrate from P12 to JKS keystore - -keytool -importkeystore -srcstorepass password -destkeystore netty4-secure.jks -srckeystore server.p12 -srcstoretype PKCS12 -alias netty4-secure -deststorepass password 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 0000000000000000000000000000000000000000..034f2a133172943d9a698aaa9215418285413d5e GIT binary patch literal 2787 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 literal 0 HcmV?d00001 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 771de6de486d6..f48b20d52f601 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 @@ -55,6 +55,8 @@ import org.opensearch.transport.TransportSettings; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.rules.ExternalResource; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -105,6 +107,14 @@ protected Collection> nodePlugins() { private static Path keyStoreFile; + @ClassRule + public static final ExternalResource MUTE_IN_FIPS_JVM = new ExternalResource() { + @Override + protected void before() { + assumeFalse("Can't run in a FIPS JVM because none of the supported Keystore types can be used", inFipsJvm()); + } + }; + @BeforeClass public static void setupKeyStore() throws IOException { Path tempDir = createTempDir(); diff --git a/plugins/repository-gcs/build.gradle b/plugins/repository-gcs/build.gradle index 58bfe4a3dde51..7b8ce9d709685 100644 --- a/plugins/repository-gcs/build.gradle +++ b/plugins/repository-gcs/build.gradle @@ -242,6 +242,10 @@ def encodedCredentials = { Base64.encoder.encodeToString(Files.readAllBytes(serviceAccountFile.toPath())) } +forbiddenPatterns { + exclude '**/*.bcfks' +} + /** A service account file that points to the Google Cloud Storage service emulated by the fixture **/ task createServiceAccountFile() { doLast { 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 b39e0de08eba7..80d3f70614ea1 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,7 +32,6 @@ 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; @@ -189,9 +188,9 @@ public HttpRequestInitializer getHttpRequestInitializer(ServiceOptions ser private HttpTransport createHttpTransport(final GoogleCloudStorageClientSettings clientSettings) throws IOException { return SocketAccess.doPrivilegedIOException(() -> { final NetHttpTransport.Builder builder = new NetHttpTransport.Builder(); - // use the JKS trustStore format instead of PKCS#12 to ensure compatibility with BC-FIPS - var certTrustStore = KeyStoreFactory.getInstance(KeyStoreType.JKS); - InputStream keyStoreStream = GoogleUtils.class.getResourceAsStream("google.jks"); + // 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); 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 0000000000000000000000000000000000000000..13202c5c21eb3f53a5e50b3e4c1c71c0aeb09823 GIT binary patch literal 73692 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

49~SY6zGHoulYZ^^KWV4$gn8n}bTBDU2f}wj>hR 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 Date: Thu, 14 Nov 2024 10:51:32 +0100 Subject: [PATCH 12/21] general exclusion of keystore binaries and certificates from fobiddenPattern Signed-off-by: Iwan Igonin # Conflicts: # CHANGELOG-3.0.md --- CHANGELOG-3.0.md | 1 + buildSrc/build.gradle | 4 ---- .../gradle/precommit/ForbiddenPatternsTask.java | 5 +++++ client/rest/build.gradle | 5 ----- libs/ssl-config/build.gradle | 8 -------- modules/reindex/build.gradle | 5 ----- modules/transport-netty4/build.gradle | 6 ------ plugins/ingest-attachment/build.gradle | 9 --------- plugins/repository-gcs/build.gradle | 4 ---- qa/smoke-test-plugins/build.gradle | 5 ----- 10 files changed, 6 insertions(+), 46 deletions(-) diff --git a/CHANGELOG-3.0.md b/CHANGELOG-3.0.md index 14874ceb7c393..bfeb71d401514 100644 --- a/CHANGELOG-3.0.md +++ b/CHANGELOG-3.0.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Views, simplify data access and manipulation by providing a virtual layer over one or more indices ([#11957](https://github.com/opensearch-project/OpenSearch/pull/11957)) - Added pull-based Ingestion (APIs, for ingestion source, a Kafka plugin, and IngestionEngine that pulls data from the ingestion source) ([#16958](https://github.com/opensearch-project/OpenSearch/pull/16958)) - Added ConfigurationUtils to core for the ease of configuration parsing [#17223](https://github.com/opensearch-project/OpenSearch/pull/17223) +- Support for FIPS-140-3 compliance through environment variable ([#3420](https://github.com/opensearch-project/OpenSearch/pull/14912)) ### Dependencies - Update Apache Lucene to 10.1.0 ([#16366](https://github.com/opensearch-project/OpenSearch/pull/16366)) diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 38e4f6afb1cf5..7b22d612bc267 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -231,12 +231,8 @@ if (project != rootProject) { forbiddenPatterns { exclude '**/*.wav' - exclude '**/*.p12' - exclude '**/*.jks' - exclude '**/*.crt' // the file that actually defines nocommit exclude '**/ForbiddenPatternsTask.java' - exclude '**/*.bcfks' } testingConventions { diff --git a/buildSrc/src/main/java/org/opensearch/gradle/precommit/ForbiddenPatternsTask.java b/buildSrc/src/main/java/org/opensearch/gradle/precommit/ForbiddenPatternsTask.java index 1790b32fb2f36..fbf96483443ee 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/precommit/ForbiddenPatternsTask.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/precommit/ForbiddenPatternsTask.java @@ -83,8 +83,13 @@ public class ForbiddenPatternsTask extends DefaultTask { .exclude("**/*.ico") .exclude("**/*.jar") .exclude("**/*.zip") + .exclude("**/*.p12") .exclude("**/*.jks") .exclude("**/*.crt") + .exclude("**/*.der") + .exclude("**/*.pem") + .exclude("**/*.key") + .exclude("**/*.bcfks") .exclude("**/*.keystore") .exclude("**/*.png"); diff --git a/client/rest/build.gradle b/client/rest/build.gradle index 87cf3d460752f..3bd74d7ec7734 100644 --- a/client/rest/build.gradle +++ b/client/rest/build.gradle @@ -84,11 +84,6 @@ tasks.withType(CheckForbiddenApis).configureEach { replaceSignatureFiles('jdk-signatures', 'http-signatures') } -forbiddenPatterns { - exclude '**/*.bcfks' - exclude '**/*.der' -} - tasks.named('forbiddenApisTest').configure { //we are using jdk-internal instead of jdk-non-portable to allow for com.sun.net.httpserver.* usage bundledSignatures -= 'jdk-non-portable' diff --git a/libs/ssl-config/build.gradle b/libs/ssl-config/build.gradle index 8d09782b04b89..ee2c153690b43 100644 --- a/libs/ssl-config/build.gradle +++ b/libs/ssl-config/build.gradle @@ -58,14 +58,6 @@ tasks.named("dependencyLicenses").configure { mapping from: /bc.*/, to: 'bouncycastle' } -forbiddenPatterns { - exclude '**/*.key' - exclude '**/*.pem' - exclude '**/*.p12' - exclude '**/*.jks' - exclude '**/*.bcfks' -} - tasks.test { if (BuildParams.runtimeJavaVersion > JavaVersion.VERSION_1_8) { jvmArgs += ["--add-opens", "java.base/java.security.cert=ALL-UNNAMED"] diff --git a/modules/reindex/build.gradle b/modules/reindex/build.gradle index a44e1004d93ad..00931848d0644 100644 --- a/modules/reindex/build.gradle +++ b/modules/reindex/build.gradle @@ -87,11 +87,6 @@ thirdPartyAudit.ignoreMissingClasses( 'org.apache.log.Logger', ) -forbiddenPatterns { - // PKCS#12 file are not UTF-8 - exclude '**/*.p12' -} - tasks.named("bundlePlugin").configure { dependsOn("copyParentJoinMetadata") dependsOn("copyTransportNetty4Metadata") diff --git a/modules/transport-netty4/build.gradle b/modules/transport-netty4/build.gradle index 25f713f19758d..4e68a4ce17f73 100644 --- a/modules/transport-netty4/build.gradle +++ b/modules/transport-netty4/build.gradle @@ -77,12 +77,6 @@ tasks.named("dependencyLicenses").configure { mapping from: /netty-.*/, to: 'netty' } -forbiddenPatterns { - exclude '**/*.p12' - exclude '**/*.jks' - exclude '**/*.bcfks' -} - test { /* * We have to disable setting the number of available processors as tests in the same JVM randomize processors and will step on each 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-gcs/build.gradle b/plugins/repository-gcs/build.gradle index 7b8ce9d709685..58bfe4a3dde51 100644 --- a/plugins/repository-gcs/build.gradle +++ b/plugins/repository-gcs/build.gradle @@ -242,10 +242,6 @@ def encodedCredentials = { Base64.encoder.encodeToString(Files.readAllBytes(serviceAccountFile.toPath())) } -forbiddenPatterns { - exclude '**/*.bcfks' -} - /** A service account file that points to the Google Cloud Storage service emulated by the fixture **/ task createServiceAccountFile() { doLast { diff --git a/qa/smoke-test-plugins/build.gradle b/qa/smoke-test-plugins/build.gradle index 6abba5577d605..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' @@ -40,10 +39,6 @@ int pluginsCount = 0 testClusters.integTest { project(':plugins').getChildProjects().each { pluginName, pluginProject -> - if (BuildParams.inFipsJvm && pluginName == "ingest-attachment"){ - //Do not attempt to install ingest-attachment in FIPS 140 as it is not supported (it depends on non-FIPS BouncyCastle - return - } plugin pluginProject.path pluginsCount += 1 } From 5cc787d6e2915b93ba17e03322a8e09cdcc8c1c8 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Tue, 19 Nov 2024 10:54:58 +0100 Subject: [PATCH 13/21] Kerberos, forbiddenApis, SecureRandom, SunJCE, AzureTests Summery: - replace unsecure kerberos crypto algorithms - add 'java.security.KeyStore' to forbidden-apis - instantiate and use SecureRandom from BCFIPS library - exclude SunJCE from security providers list at runtime, when running in FIPS JVM - exclude Azure tests when running in FIPS JVM Signed-off-by: Iwan Igonin --- .../gradle/http/WaitForHttpResource.java | 14 +- .../gradle/testclusters/OpenSearchNode.java | 2 +- .../resources/forbidden/jdk-signatures.txt | 6 + distribution/src/config/fips_java.security | 4 - .../cli/keystore/KeyStoreWrapperTests.java | 20 +- libs/common/build.gradle | 5 + .../licenses/bcpkix-fips-2.0.7.jar.sha1 | 1 + libs/common/licenses/bouncycastle-LICENSE.txt | 14 ++ libs/common/licenses/bouncycastle-NOTICE.txt | 1 + .../opensearch/common/SecureRandomHolder.java | 4 +- .../common/crypto/KeyStoreFactory.java | 5 + modules/transport-netty4/build.gradle | 11 -- .../index/analysis/IcuAnalyzerTests.java | 5 + plugins/repository-azure/build.gradle | 11 +- plugins/repository-s3/build.gradle | 11 -- .../s3/S3BlobStoreRepositoryTests.java | 2 +- .../opensearch/repositories/s3/S3Service.java | 5 +- .../s3/S3BlobContainerRetriesTests.java | 5 +- plugins/transport-reactor-netty4/build.gradle | 11 -- ...ReactorNetty4HttpServerTransportTests.java | 16 +- .../src/test/resources/README.txt | 14 -- .../src/test/resources/certificate.crt | 22 --- .../src/test/resources/certificate.key | 28 --- server/licenses/bcpkix-fips-2.0.7.jar.sha1 | 1 + .../org/opensearch/bootstrap/Bootstrap.java | 3 + .../bootstrap/SecureRandomInitializer.java | 46 +++++ .../bootstrap/SecurityProviderManager.java | 48 +++++ .../org/opensearch/common/Randomness.java | 17 -- .../common/settings/KeyStoreWrapper.java | 16 +- .../org/opensearch/bootstrap/security.policy | 6 +- .../SecureRandomInitializerTests.java | 62 +++++++ .../SecurityProviderManagerTests.java | 171 ++++++++++++++++++ .../org/opensearch/bootstrap/test.policy | 1 + .../resources/provision/kdc.conf.template | 7 +- .../resources/provision/krb5.conf.template | 13 +- .../bootstrap/BootstrapForTesting.java | 19 ++ .../org/opensearch/test/KeyStoreUtils.java | 70 +++++++ 37 files changed, 525 insertions(+), 172 deletions(-) create mode 100644 libs/common/licenses/bcpkix-fips-2.0.7.jar.sha1 create mode 100644 libs/common/licenses/bouncycastle-LICENSE.txt create mode 100644 libs/common/licenses/bouncycastle-NOTICE.txt delete mode 100644 plugins/transport-reactor-netty4/src/test/resources/README.txt delete mode 100644 plugins/transport-reactor-netty4/src/test/resources/certificate.crt delete mode 100644 plugins/transport-reactor-netty4/src/test/resources/certificate.key create mode 100644 server/licenses/bcpkix-fips-2.0.7.jar.sha1 create mode 100644 server/src/main/java/org/opensearch/bootstrap/SecureRandomInitializer.java create mode 100644 server/src/main/java/org/opensearch/bootstrap/SecurityProviderManager.java create mode 100644 server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java create mode 100644 server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java create mode 100644 test/framework/src/main/java/org/opensearch/test/KeyStoreUtils.java 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..62f2621be7924 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java @@ -32,8 +32,10 @@ package org.opensearch.gradle.http; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; +import org.gradle.internal.impldep.com.jcraft.jsch.annotations.SuppressForbiddenApi; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; @@ -51,7 +53,6 @@ import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.util.Arrays; @@ -216,7 +217,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 +225,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,12 +240,17 @@ private KeyStore buildTrustStoreFromCA() throws GeneralSecurityException, IOExce return store; } + @SuppressForbiddenApi("runs exclusively in test-context without KeyStoreFactory on classpath.") + 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()); tmf.init(trustStore); SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - sslContext.init(new KeyManager[0], tmf.getTrustManagers(), new SecureRandom()); + sslContext.init(new KeyManager[0], tmf.getTrustManagers(), CryptoServicesRegistrar.getSecureRandom()); return sslContext; } 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 254cf345f50fd..c7af3d0a155f7 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java @@ -548,7 +548,7 @@ public synchronized void start() { logToProcessStdout("Creating opensearch keystore with password set to [" + keystorePassword + "]"); if (keystorePassword.length() > 0) { - runOpenSearchBinScriptWithInput(keystorePassword + "\n" + keystorePassword, "opensearch-keystore", "create", "-p"); + runOpenSearchBinScriptWithInput(keystorePassword + "\n" + keystorePassword + "\n", "opensearch-keystore", "create", "-p"); } else { runOpenSearchBinScript("opensearch-keystore", "-v", "create"); } diff --git a/buildSrc/src/main/resources/forbidden/jdk-signatures.txt b/buildSrc/src/main/resources/forbidden/jdk-signatures.txt index b2fd479dce5ff..646f9a474524a 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.crypto.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/distribution/src/config/fips_java.security b/distribution/src/config/fips_java.security index bf160a0055a59..2e5bf13cf8d0e 100644 --- a/distribution/src/config/fips_java.security +++ b/distribution/src/config/fips_java.security @@ -5,10 +5,6 @@ security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips 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 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 a8962b06317f2..43af00622954b 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,10 +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.opensearch.common.settings.KeyStoreWrapper; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.crypto.KeyStoreFactory; import org.opensearch.common.crypto.KeyStoreType; +import org.opensearch.common.settings.KeyStoreWrapper; import org.opensearch.common.util.io.IOUtils; import org.opensearch.core.common.settings.SecureString; import org.opensearch.env.Environment; @@ -206,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]; @@ -234,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]; @@ -263,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]; @@ -290,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]; @@ -373,15 +373,15 @@ public void testIllegalSettingName() throws Exception { public void testFailLoadV1KeystoresInFipsJvm() throws Exception { assumeTrue("Test in FIPS JVM", inFipsJvm()); - Exception e = assertThrows(SecurityException.class, () -> generateV1()); - assertThat(e.getMessage(), containsString("Only PKCS_11, BCFKS keystores are allowed in FIPS JVM")); + 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(SecurityException.class, () -> generateV2()); - assertThat(e.getMessage(), containsString("Only PKCS_11, BCFKS keystores are allowed in FIPS JVM")); + Exception e = assertThrows(NoSuchProviderException.class, this::generateV2); + assertThat(e.getMessage(), containsString("no such provider: SunJCE")); } public void testBackcompatV1() throws Exception { diff --git a/libs/common/build.gradle b/libs/common/build.gradle index 909d7c5ebe209..e9de44f894296 100644 --- a/libs/common/build.gradle +++ b/libs/common/build.gradle @@ -27,6 +27,7 @@ dependencies { compileOnly "com.google.code.findbugs:jsr305:3.0.2" compileOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" compileOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" + api "org.bouncycastle:bcpkix-fips:${versions.bouncycastle_pkix}" /******* * !!!! NO THIRD PARTY DEPENDENCIES !!!! @@ -53,6 +54,10 @@ tasks.named('forbiddenApisMain').configure { ignoreSignaturesOfMissingClasses = true } +tasks.named("dependencyLicenses").configure { + mapping from: /bc.*/, to: 'bouncycastle' +} + compileJava { options.compilerArgs += ['--add-modules', 'jdk.incubator.vector'] options.compilerArgs -= '-Werror' // use of incubator modules is reported as a warning diff --git a/libs/common/licenses/bcpkix-fips-2.0.7.jar.sha1 b/libs/common/licenses/bcpkix-fips-2.0.7.jar.sha1 new file mode 100644 index 0000000000000..ff463e602ad88 --- /dev/null +++ b/libs/common/licenses/bcpkix-fips-2.0.7.jar.sha1 @@ -0,0 +1 @@ +01eea0f325315ca6295b0a6926ff862d8001cdf9 diff --git a/libs/common/licenses/bouncycastle-LICENSE.txt b/libs/common/licenses/bouncycastle-LICENSE.txt new file mode 100644 index 0000000000000..5c7c14696849d --- /dev/null +++ b/libs/common/licenses/bouncycastle-LICENSE.txt @@ -0,0 +1,14 @@ +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/libs/common/licenses/bouncycastle-NOTICE.txt b/libs/common/licenses/bouncycastle-NOTICE.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/libs/common/licenses/bouncycastle-NOTICE.txt @@ -0,0 +1 @@ + diff --git a/libs/common/src/main/java/org/opensearch/common/SecureRandomHolder.java b/libs/common/src/main/java/org/opensearch/common/SecureRandomHolder.java index 14844293b3274..990f6968a57a2 100644 --- a/libs/common/src/main/java/org/opensearch/common/SecureRandomHolder.java +++ b/libs/common/src/main/java/org/opensearch/common/SecureRandomHolder.java @@ -32,6 +32,8 @@ package org.opensearch.common; +import org.bouncycastle.crypto.CryptoServicesRegistrar; + import java.security.SecureRandom; /** @@ -41,5 +43,5 @@ */ class SecureRandomHolder { // class loading is atomic - this is a lazy & safe singleton to be used by this package - public static final SecureRandom INSTANCE = new SecureRandom(); + public static final SecureRandom INSTANCE = CryptoServicesRegistrar.getSecureRandom(); } diff --git a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java index 4cd6e9922ab27..a07c4b64b1a36 100644 --- a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java +++ b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java @@ -9,6 +9,7 @@ package org.opensearch.common.crypto; import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.opensearch.common.SuppressForbidden; import java.security.KeyStore; import java.security.KeyStoreException; @@ -55,7 +56,11 @@ public static KeyStore getInstance(KeyStoreType type, String 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()); 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/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/repository-azure/build.gradle b/plugins/repository-azure/build.gradle index 5dae37d80ca6a..3e0e5e23c59f1 100644 --- a/plugins/repository-azure/build.gradle +++ b/plugins/repository-azure/build.gradle @@ -213,13 +213,6 @@ thirdPartyAudit { // Worth nothing that, the latest dependency "net.shibboleth.utilities:java-support:8.0.0" has many vulnerabilities. // Hence ignored. 'net.shibboleth.utilities.java.support.xml.SerializeSupport', - 'org.bouncycastle.cert.X509CertificateHolder', - 'org.bouncycastle.cert.jcajce.JcaX509CertificateHolder', - 'org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder', - 'org.bouncycastle.openssl.PEMKeyPair', - 'org.bouncycastle.openssl.PEMParser', - 'org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter', - 'org.bouncycastle.operator.jcajce.JcaContentSignerBuilder', 'org.cryptomator.siv.SivMode', 'org.opensaml.core.config.InitializationException', 'org.opensaml.core.config.InitializationService', @@ -310,6 +303,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-s3/build.gradle b/plugins/repository-s3/build.gradle index 2a303f9a01656..1832b6a165cc1 100644 --- a/plugins/repository-s3/build.gradle +++ b/plugins/repository-s3/build.gradle @@ -486,17 +486,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/transport-reactor-netty4/build.gradle b/plugins/transport-reactor-netty4/build.gradle index 12ae5ce99632e..8456abeb1d5e3 100644 --- a/plugins/transport-reactor-netty4/build.gradle +++ b/plugins/transport-reactor-netty4/build.gradle @@ -110,17 +110,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/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ssl/SecureReactorNetty4HttpServerTransportTests.java b/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ssl/SecureReactorNetty4HttpServerTransportTests.java index ac7687d551766..06d272e350a76 100644 --- a/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ssl/SecureReactorNetty4HttpServerTransportTests.java +++ b/plugins/transport-reactor-netty4/src/test/java/org/opensearch/http/reactor/netty4/ssl/SecureReactorNetty4HttpServerTransportTests.java @@ -35,6 +35,7 @@ import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; import org.opensearch.telemetry.tracing.noop.NoopTracer; +import org.opensearch.test.KeyStoreUtils; import org.opensearch.test.OpenSearchTestCase; import org.opensearch.test.rest.FakeRestRequest; import org.opensearch.threadpool.TestThreadPool; @@ -44,10 +45,10 @@ import org.junit.After; import org.junit.Before; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; -import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Optional; @@ -85,6 +86,7 @@ import static org.opensearch.core.rest.RestStatus.OK; import static org.opensearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN; import static org.opensearch.http.HttpTransportSettings.SETTING_CORS_ENABLED; +import static org.opensearch.test.KeyStoreUtils.KEYSTORE_PASSWORD; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -115,12 +117,14 @@ public Optional buildHttpServerExceptionHandler(Setti @Override public Optional buildSecureHttpServerEngine(Settings settings, HttpServerTransport transport) throws SSLException { try { - SSLEngine engine = SslContextBuilder.forServer( - SecureReactorNetty4HttpServerTransportTests.class.getResourceAsStream("/certificate.crt"), - SecureReactorNetty4HttpServerTransportTests.class.getResourceAsStream("/certificate.key") - ).trustManager(InsecureTrustManagerFactory.INSTANCE).build().newEngine(NettyAllocator.getAllocator()); + var keyManagerFactory = KeyManagerFactory.getInstance("PKIX"); + keyManagerFactory.init(KeyStoreUtils.createServerKeyStore(), KEYSTORE_PASSWORD); + SSLEngine engine = SslContextBuilder.forServer(keyManagerFactory) + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build() + .newEngine(NettyAllocator.getAllocator()); return Optional.of(engine); - } catch (final IOException ex) { + } catch (final Exception ex) { throw new SSLException(ex); } } diff --git a/plugins/transport-reactor-netty4/src/test/resources/README.txt b/plugins/transport-reactor-netty4/src/test/resources/README.txt deleted file mode 100644 index a4353cee45a97..0000000000000 --- a/plugins/transport-reactor-netty4/src/test/resources/README.txt +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -# -# This is README describes how the certificates in this directory were created. -# This file can also be executed as a script -# - -# 1. Create certificate key - -openssl req -x509 -sha256 -newkey rsa:2048 -keyout certificate.key -out certificate.crt -days 1024 -nodes - -# 2. Export the certificate in pkcs12 format - -openssl pkcs12 -export -in certificate.crt -inkey certificate.key -out server.p12 -name netty4-secure -password pass:password - diff --git a/plugins/transport-reactor-netty4/src/test/resources/certificate.crt b/plugins/transport-reactor-netty4/src/test/resources/certificate.crt deleted file mode 100644 index 54c78fdbcf6de..0000000000000 --- a/plugins/transport-reactor-netty4/src/test/resources/certificate.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDkzCCAnugAwIBAgIUddAawr5zygcd+Dcn9WVDpO4BJ7YwDQYJKoZIhvcNAQEL -BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X -DTI0MDMxNDE5NDQzOVoXDTI3MDEwMjE5NDQzOVowWTELMAkGA1UEBhMCQVUxEzAR -BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 -IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAzjOKkg6Iba5zfZ8b/RYw+PGmGEfbdGuuF10Wz4Jmx/Nk4VfDLxdh -TW8VllUL2JD7uPkjABj7pW3awAbvIJ+VGbKqfBr1Nsz0mPPzhT8cfuMH/FDZgQs3 -4HuqDKr0LfC1Kw5E3WF0GVMBDNu0U+nKoeqySeYjGdxDnd3W4cqK5AnUxL0RnIny -Bw7ZuhcU55XndH/Xauro/2EpvJduDsWMdqt7ZfIf1TOmaiQHK+82yb/drVaJbczK -uTpn1Kv2bnzkQEckgq+z1dLNOOyvP2xf+nsziw5ilJe92e5GJOUJYFAlEgUAGpfD -dv6j/gTRYvdJCJItOQEQtektNCAZsoc0wwIDAQABo1MwUTAdBgNVHQ4EFgQUzHts -wIt+zhB/R4U4Do2P6rr0YhkwHwYDVR0jBBgwFoAUzHtswIt+zhB/R4U4Do2P6rr0 -YhkwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAveh870jJX7vt -oLCrdugsyo79pR4f7Nr1kUy3jJrfoaoUmrjiiiHWgT22fGwp7j1GZF2mVfo8YVaK -63YNn5gB2NNZhguPOFC4AdvHRYOKRBOaOvWK8oq7BcJ//18JYI/pPnpgkYvJjqv4 -gFKaZX9qWtujHpAmKiVGs7pwYGNXfixPHRNV4owcfHMIH5dhbbqT49j94xVpjbXs -OymKtFl4kpCE/0LzKFrFcuu55Am1VLBHx2cPpHLOipgUcF5BHFlQ8AXiCMOwfPAw -d22mLB6Gt1oVEpyvQHYd3e04FetEXQ9E8T+NKWZx/8Ucf+IWBYmZBRxch6O83xgk -bAbGzqkbzQ== ------END CERTIFICATE----- diff --git a/plugins/transport-reactor-netty4/src/test/resources/certificate.key b/plugins/transport-reactor-netty4/src/test/resources/certificate.key deleted file mode 100644 index 228350180935d..0000000000000 --- a/plugins/transport-reactor-netty4/src/test/resources/certificate.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDOM4qSDohtrnN9 -nxv9FjD48aYYR9t0a64XXRbPgmbH82ThV8MvF2FNbxWWVQvYkPu4+SMAGPulbdrA -Bu8gn5UZsqp8GvU2zPSY8/OFPxx+4wf8UNmBCzfge6oMqvQt8LUrDkTdYXQZUwEM -27RT6cqh6rJJ5iMZ3EOd3dbhyorkCdTEvRGcifIHDtm6FxTnled0f9dq6uj/YSm8 -l24OxYx2q3tl8h/VM6ZqJAcr7zbJv92tVoltzMq5OmfUq/ZufORARySCr7PV0s04 -7K8/bF/6ezOLDmKUl73Z7kYk5QlgUCUSBQAal8N2/qP+BNFi90kIki05ARC16S00 -IBmyhzTDAgMBAAECggEAVOdiElvLjyX6xeoC00YU6hxOIMdNtHU2HMamwtDV01UD -38mMQ9KjrQelYt4n34drLrHe2IZw75/5J4JzagJrmUY47psHBwaDXItuZRokeJaw -zhLYTEs7OcKRtV+a5WOspUrdzi33aQoFb67zZG3qkpsZyFXrdBV+/fy/Iv+MCvLH -xR0jQ5mzE3cw20R7S4nddChBA/y8oKGOo6QRf2SznC1jL/+yolHvJPEn1v8AUxYm -BMPHxj1O0c4M4IxnJQ3Y5Jy9OaFMyMsFlF1hVhc/3LDDxDyOuBsVsFDicojyrRea -GKngIke0yezy7Wo4NUcp8YQhafonpWVsSJJdOUotcQKBgQD0rihFBXVtcG1d/Vy7 -FvLHrmccD56JNV744LSn2CDM7W1IulNbDUZINdCFqL91u5LpxozeE1FPY1nhwncJ -N7V7XYCaSLCuV1YJzRmUCjnzk2RyopGpzWog3f9uUFGgrk1HGbNAv99k/REya6Iu -IRSkuQhaJOj3bRXzonh0K4GjewKBgQDXvamtCioOUMSP8vq919YMkBw7F+z/fr0p -pamO8HL9eewAUg6N92JQ9kobSo/GptdmdHIjs8LqnS5C3H13GX5Qlf5GskOlCpla -V55ElaSp0gvKwWE168U7gQH4etPQAXXJrOGFaGbPj9W81hTUud7HVE88KYdfWTBo -I7TuE25tWQKBgBRjcr2Vn9xXsvVTCGgamG5lLPhcoNREGz7X0pXt34XT/vhBdnKu -331i5pZMom+YCrzqK5DRwUPBPpseTjb5amj2OKIijn5ojqXQbmI0m/GdBZC71TF2 -CXLlrMQvcy3VeGEFVjd+BYpvwAAYkfIQFZ1IQdbpHnSHpX2guzLK8UmDAoGBANUy -PIcf0EetUVHfkCIjNQfdMcjD8BTcLhsF9vWmcDxFTA9VB8ULf0D64mjt2f85yQsa -b+EQN8KZ6alxMxuLOeRxFYLPj0F9o+Y/R8wHBV48kCKhz2r1v0b6SfQ/jSm1B61x -BrxLW64qOdIOzS8bLyhUDKkrcPesr8V548aRtUKhAoGBAKlNJFd8BCGKD9Td+3dE -oP1iHTX5XZ+cQIqL0e+GMQlK4HnQP566DFZU5/GHNNAfmyxd5iSRwhTqPMHRAmOb -pqQwsyufx0dFeIBxeSO3Z6jW5h2sl4nBipZpw9bzv6EBL1xRr0SfMNZzdnf4JFzc -0htGo/VO93Z2pv8w7uGUz1nN ------END PRIVATE KEY----- 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/main/java/org/opensearch/bootstrap/Bootstrap.java b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java index 424677ce96066..650bca01f0359 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -196,9 +196,12 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot BootstrapSettings.CTRLHANDLER_SETTING.get(settings) ); + SecureRandomInitializer.init(); + var cryptoStandard = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); if (cryptoStandard != null && cryptoStandard.equals("FIPS-140-3")) { LogManager.getLogger(Bootstrap.class).info("running in FIPS-140-3 mode"); + SecurityProviderManager.excludeSunJCE(); } // initialize probes before the security manager is installed 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 a151864610521..de35e95090122 100644 --- a/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java +++ b/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java @@ -41,9 +41,9 @@ 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.crypto.KeyStoreFactory; import org.opensearch.common.crypto.KeyStoreType; @@ -78,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; @@ -122,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. */ @@ -221,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); @@ -538,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/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index 04982aa8986d7..9c61ec1ddad27 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -101,14 +101,14 @@ grant { permission java.lang.RuntimePermission "getProtectionDomain"; permission java.io.FilePermission "${java.home}/lib/security/cacerts", "read"; permission java.io.FilePermission "${java.home}/lib/security/jssecacerts", "read"; - permission java.security.SecurityPermission "getProperty.jdk.tls.disabledAlgorithms"; 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"; permission java.security.SecurityPermission "getProperty.keystore.type.compat"; permission java.security.SecurityPermission "getProperty.org.bouncycastle.*"; - permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; - permission java.security.SecurityPermission "putProviderProperty.BCJSSE"; + permission java.security.SecurityPermission "removeProvider.SunJCE"; permission java.util.PropertyPermission "java.runtime.name", "read"; + permission org.bouncycastle.crypto.CryptoServicesPermission "defaultRandomConfig"; permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; }; 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..865347438c8e5 --- /dev/null +++ b/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java @@ -0,0 +1,62 @@ +/* + * 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() { + // Reset the global state if your CryptoServicesRegistrar allows it. + // If there's a method to unset or reset the SecureRandom, call it here. + // If not, the following lines are hypothetical and depend on your actual implementation. + 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/krb5kdc-fixture/src/main/resources/provision/kdc.conf.template b/test/fixtures/krb5kdc-fixture/src/main/resources/provision/kdc.conf.template index 22909ddf60013..69be28f4548c3 100644 --- a/test/fixtures/krb5kdc-fixture/src/main/resources/provision/kdc.conf.template +++ b/test/fixtures/krb5kdc-fixture/src/main/resources/provision/kdc.conf.template @@ -16,8 +16,8 @@ # under the License. [kdcdefaults] - kdc_listen = 88 - kdc_tcp_listen = 88 + kdc_ports = 88 + kdc_tcp_ports = 88 [realms] ${REALM_NAME} = { @@ -25,8 +25,7 @@ max_life = 12h 0m 0s max_renewable_life = 7d 0h 0m 0s master_key_type = aes256-cts - # remove aes256-cts:normal since unlimited strength policy needs installed for java to use it. - supported_enctypes = aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal + supported_enctypes = aes256-cts-hmac-sha1-96:normal aes128-cts-hmac-sha1-96:normal } [logging] diff --git a/test/fixtures/krb5kdc-fixture/src/main/resources/provision/krb5.conf.template b/test/fixtures/krb5kdc-fixture/src/main/resources/provision/krb5.conf.template index 207fe939fb7a5..a87c5b50d5cf3 100644 --- a/test/fixtures/krb5kdc-fixture/src/main/resources/provision/krb5.conf.template +++ b/test/fixtures/krb5kdc-fixture/src/main/resources/provision/krb5.conf.template @@ -33,18 +33,15 @@ dns_canonicalize_hostname = false dns_lookup_kdc = false dns_lookup_realm = false - dns_uri_lookup = false forwardable = true ignore_acceptor_hostname = true rdns = false - default_tgs_enctypes = rc4-hmac - default_tkt_enctypes = rc4-hmac - permitted_enctypes = rc4-hmac + default_tgs_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 + default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 + permitted_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 # udp_preference_limit = 1 - kdc_timeout = 3000 canonicalize = true - # See please https://seanjmullan.org/blog/2021/09/14/jdk17 (deprecate 3DES and RC4 in Kerberos) - allow_weak_crypto = true + allow_weak_crypto = false [realms] ${REALM_NAME} = { @@ -52,6 +49,8 @@ kdc = 127.0.0.1:${MAPPED_PORT} admin_server = ${KDC_NAME}:749 default_domain = ${BUILD_ZONE} + master_key_type = aes256-cts + supported_enctypes = aes256-cts-hmac-sha1-96:normal aes128-cts-hmac-sha1-96:normal } [domain_realm] 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 new file mode 100644 index 0000000000000..90a5ce2768a30 --- /dev/null +++ b/test/framework/src/main/java/org/opensearch/test/KeyStoreUtils.java @@ -0,0 +1,70 @@ +/* + * 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.test; + +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.crypto.KeyStoreType; + +import javax.security.auth.x500.X500Principal; +import javax.security.auth.x500.X500PrivateCredential; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.X509Certificate; +import java.util.Date; + +public class KeyStoreUtils { + + public static final char[] KEYSTORE_PASSWORD = "keystore_password".toCharArray(); + + public static KeyStore createServerKeyStore() throws Exception { + var serverCred = createCredential(); + var keyStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); + keyStore.load(null, null); + keyStore.setKeyEntry( + serverCred.getAlias(), + serverCred.getPrivateKey(), + KEYSTORE_PASSWORD, + new X509Certificate[] { serverCred.getCertificate() } + ); + return keyStore; + } + + private static X500PrivateCredential createCredential() throws Exception { + var keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + var keyPair = keyPairGenerator.generateKeyPair(); + var rootCert = new JcaX509CertificateConverter().getCertificate(generateCert(keyPair)); + return new X500PrivateCredential(rootCert, keyPair.getPrivate(), "server-ca"); + } + + private static X509CertificateHolder generateCert(KeyPair pair) throws Exception { + var baseTime = System.currentTimeMillis(); + // 10 years in milliseconds + var validityPeriod = 10L * 365 * 24 * 60 * 60 * 1000; + + var certBuilder = new JcaX509v1CertificateBuilder( + new X500Principal("CN=Test CA Certificate"), + BigInteger.valueOf(1), + new Date(baseTime), + new Date(baseTime + validityPeriod), + new X500Principal("CN=Test CA Certificate"), + pair.getPublic() + ); + var signer = new JcaContentSignerBuilder("SHA256withRSA").build(pair.getPrivate()); + return certBuilder.build(signer); + } + +} From fe86126221546032fe08c24a71b2eb967db18899 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Thu, 9 Jan 2025 17:09:54 +0100 Subject: [PATCH 14/21] remove BC libs from 'libs:common'; revert SecureRandomHolder Signed-off-by: Iwan Igonin --- .../gradle/info/GlobalBuildInfoPlugin.java | 2 +- .../resources/forbidden/jdk-signatures.txt | 2 +- client/rest/build.gradle | 71 +++++++++++++++++-- client/rest/licenses/bc-fips-2.0.0.jar.sha1 | 1 - .../rest/licenses/bctls-fips-2.0.19.jar.sha1 | 2 +- .../rest/licenses/bcutil-fips-2.0.3.jar.sha1 | 1 - .../client/RestClientBuilderIntegTests.java | 4 +- .../RestClientDocumentation.java | 4 +- .../keystore/AddFileKeyStoreCommandTests.java | 3 +- .../keystore/CreateKeyStoreCommandTests.java | 12 ++-- .../cli/keystore/KeyStoreWrapperTests.java | 4 +- .../keystore/ListKeyStoreCommandTests.java | 9 +-- .../tools/launchers/SystemJvmOptions.java | 4 +- libs/common/build.gradle | 8 --- .../opensearch/common/SecureRandomHolder.java | 4 +- .../common/ssl/DefaultJdkTrustConfig.java | 2 - .../common/ssl}/KeyStoreFactory.java | 6 +- .../opensearch/common/ssl}/KeyStoreType.java | 2 +- .../opensearch/common/ssl/KeyStoreUtil.java | 2 - .../common/ssl/SslConfigurationLoader.java | 4 +- .../opensearch/common/ssl/StoreKeyConfig.java | 2 - .../common/ssl/StoreTrustConfig.java | 2 - .../common/ssl}/KeyStoreFactoryTests.java | 2 +- .../opensearch/common/ssl/PemUtilsTests.java | 2 - .../common/ssl/StoreKeyConfigTests.java | 6 +- .../common/ssl/StoreTrustConfigTests.java | 6 +- .../SecureNetty4HttpServerTransportTests.java | 4 +- .../ssl/SimpleSecureNetty4TransportTests.java | 4 +- .../AzureDiscoveryClusterFormationTests.java | 4 +- .../gcs/GoogleCloudStorageService.java | 4 +- plugins/transport-grpc/build.gradle | 11 --- qa/os/build.gradle | 1 + .../packaging/util/ServerUtils.java | 4 +- server/build.gradle | 1 + .../org/opensearch/bootstrap/Bootstrap.java | 2 +- .../common/settings/KeyStoreWrapper.java | 4 +- test/framework/build.gradle | 1 + .../licenses/bcpkix-fips-2.0.7.jar.sha1 | 0 .../licenses/bouncycastle-LICENSE.txt | 0 .../licenses/bouncycastle-NOTICE.txt | 0 .../org/opensearch/test/KeyStoreUtils.java | 4 +- .../test/rest/OpenSearchRestTestCase.java | 2 +- 42 files changed, 117 insertions(+), 96 deletions(-) delete mode 100644 client/rest/licenses/bc-fips-2.0.0.jar.sha1 delete mode 100644 client/rest/licenses/bcutil-fips-2.0.3.jar.sha1 rename libs/{common/src/main/java/org/opensearch/common/crypto => ssl-config/src/main/java/org/opensearch/common/ssl}/KeyStoreFactory.java (93%) rename libs/{common/src/main/java/org/opensearch/common/crypto => ssl-config/src/main/java/org/opensearch/common/ssl}/KeyStoreType.java (98%) rename libs/{common/src/test/java/org/opensearch/common/crypto => ssl-config/src/test/java/org/opensearch/common/ssl}/KeyStoreFactoryTests.java (98%) rename {libs/common => test/framework}/licenses/bcpkix-fips-2.0.7.jar.sha1 (100%) rename {libs/common => test/framework}/licenses/bouncycastle-LICENSE.txt (100%) rename {libs/common => test/framework}/licenses/bouncycastle-NOTICE.txt (100%) 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 41ef9d11d7062..6f55174e8d0f5 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java @@ -114,7 +114,7 @@ public void apply(Project project) { // Initialize global build parameters boolean isInternal = GlobalBuildInfoPlugin.class.getResource("/buildSrc.marker") != null; var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); - var inFipsJvm = cryptoStandard != null && cryptoStandard.equals("FIPS-140-3"); + var inFipsJvm = "FIPS-140-3".equals(cryptoStandard); params.reset(); params.setRuntimeJavaHome(runtimeJavaHome); diff --git a/buildSrc/src/main/resources/forbidden/jdk-signatures.txt b/buildSrc/src/main/resources/forbidden/jdk-signatures.txt index 646f9a474524a..1ece009c57ecd 100644 --- a/buildSrc/src/main/resources/forbidden/jdk-signatures.txt +++ b/buildSrc/src/main/resources/forbidden/jdk-signatures.txt @@ -37,7 +37,7 @@ 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.crypto.KeyStoreFactory instead of java.security.KeyStore +@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) diff --git a/client/rest/build.gradle b/client/rest/build.gradle index 3bd74d7ec7734..5ba3feb3a87e0 100644 --- a/client/rest/build.gradle +++ b/client/rest/build.gradle @@ -51,16 +51,15 @@ dependencies { api "commons-codec:commons-codec:${versions.commonscodec}" api "commons-logging:commons-logging:${versions.commonslogging}" api "org.slf4j:slf4j-api:${versions.slf4j}" - api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" api "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" - api "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" + // reactor api "io.projectreactor:reactor-core:${versions.reactor}" api "org.reactivestreams:reactive-streams:${versions.reactivestreams}" testImplementation project(":client:test") - testImplementation project(':libs:opensearch-common') + testImplementation project(':libs:opensearch-ssl-config') testImplementation "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" testImplementation "junit:junit:${versions.junit}" testImplementation "org.hamcrest:hamcrest:${versions.hamcrest}" @@ -72,7 +71,6 @@ dependencies { testImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" testImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}" testImplementation "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}" - testImplementation "commons-io:commons-io:${versions.commonsio}" } tasks.named("dependencyLicenses").configure { @@ -143,7 +141,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' diff --git a/client/rest/licenses/bc-fips-2.0.0.jar.sha1 b/client/rest/licenses/bc-fips-2.0.0.jar.sha1 deleted file mode 100644 index d635985983ebc..0000000000000 --- a/client/rest/licenses/bc-fips-2.0.0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab diff --git a/client/rest/licenses/bctls-fips-2.0.19.jar.sha1 b/client/rest/licenses/bctls-fips-2.0.19.jar.sha1 index 2c7aff0e46a13..387635e9e1594 100644 --- a/client/rest/licenses/bctls-fips-2.0.19.jar.sha1 +++ b/client/rest/licenses/bctls-fips-2.0.19.jar.sha1 @@ -1 +1 @@ -9cc33650ede63bc1a8281ed5c8e1da314d50bc76 +9cc33650ede63bc1a8281ed5c8e1da314d50bc76 \ No newline at end of file diff --git a/client/rest/licenses/bcutil-fips-2.0.3.jar.sha1 b/client/rest/licenses/bcutil-fips-2.0.3.jar.sha1 deleted file mode 100644 index d553536576656..0000000000000 --- a/client/rest/licenses/bcutil-fips-2.0.3.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -a1857cd639295b10cc90e6d31ecbc523cdafcc19 \ No newline at end of file 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 e48648964f3f6..e7c83dcf68e94 100644 --- a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java +++ b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java @@ -39,8 +39,8 @@ import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.ssl.SSLContextBuilder; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.junit.AfterClass; import org.junit.BeforeClass; 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 60cbf689d8853..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,7 +67,7 @@ import org.opensearch.client.RestClient; import org.opensearch.client.RestClientBuilder; import org.opensearch.client.RestClientBuilder.HttpClientConfigCallback; -import org.opensearch.common.crypto.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -85,7 +85,7 @@ import java.util.Iterator; import java.util.concurrent.CountDownLatch; -import static org.opensearch.common.crypto.KeyStoreType.PKCS_12; +import static org.opensearch.common.ssl.KeyStoreType.PKCS_12; /** * This class is used to generate the Java low-level REST client documentation. 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 b09e2a921f5fc..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 @@ -95,8 +95,7 @@ public void testMissingCreateWithEmptyPasswordWithoutPromptIfForced() throws Exc } public void testMissingNoCreate() throws Exception { - var password = randomFrom("", "keystorepassword"); - assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); + var password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addTextInput("n"); // explicit no execute("foo"); 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 11755400cb16b..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,8 +58,7 @@ protected Environment createEnv(Map settings) throws UserExcepti } public void testNotMatchingPasswords() throws Exception { - String password = randomFrom("", "keystorepassword"); - assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput("notthekeystorepasswordyouarelookingfor"); UserException e = expectThrows(UserException.class, () -> execute(randomFrom("-p", "--password"))); @@ -75,8 +74,7 @@ public void testDefaultNotPromptForPassword() throws Exception { } public void testPosix() throws Exception { - String password = randomFrom("", "keystorepassword"); - assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput(password); execute(randomFrom("-p", "--password")); @@ -85,8 +83,7 @@ public void testPosix() throws Exception { } public void testNotPosix() throws Exception { - String password = randomFrom("", "keystorepassword"); - assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput(password); env = setupEnv(false, fileSystems); @@ -96,8 +93,7 @@ public void testNotPosix() throws Exception { } public void testOverwrite() throws Exception { - String password = randomFrom("", "keystorepassword"); - assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); Path keystoreFile = KeyStoreWrapper.keystorePath(env.configDir()); byte[] content = "not a keystore".getBytes(StandardCharsets.UTF_8); 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 43af00622954b..63bb6be3b13f8 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 @@ -39,9 +39,9 @@ import org.apache.lucene.store.IndexOutput; import org.apache.lucene.store.NIOFSDirectory; import org.bouncycastle.crypto.CryptoServicesRegistrar; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; 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; 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 63e9bbbdf7849..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,8 +61,7 @@ public void testMissing() throws Exception { } public void testEmpty() throws Exception { - String password = randomFrom("", "keystorepassword"); - assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password); terminal.addSecretInput(password); execute(); @@ -70,8 +69,7 @@ public void testEmpty() throws Exception { } public void testOne() throws Exception { - String password = randomFrom("", "keystorepassword"); - assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password, "foo", "bar"); terminal.addSecretInput(password); execute(); @@ -79,8 +77,7 @@ public void testOne() throws Exception { } public void testMultiple() throws Exception { - String password = randomFrom("", "keystorepassword"); - assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm() && password.isEmpty()); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password, "foo", "1", "baz", "2", "bar", "3"); terminal.addSecretInput(password); execute(); 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 15b38e50fee4e..60e3085e864b3 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 @@ -94,7 +94,7 @@ static List systemJvmOptions(final Path config, Runtime.Version runtimeV private static String enableFips() { var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); - if (cryptoStandard != null && cryptoStandard.equals(FIPS_140_3)) { + if (FIPS_140_3.equals(cryptoStandard)) { return "-Dorg.bouncycastle.fips.approved_only=true"; } return ""; @@ -103,7 +103,7 @@ private static String enableFips() { private static String loadJavaSecurityProperties(final Path config) throws FileNotFoundException { String securityFile; var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); - if (cryptoStandard != null && cryptoStandard.equals(FIPS_140_3)) { + if (FIPS_140_3.equals(cryptoStandard)) { securityFile = "fips_java.security"; } else { securityFile = "java.security"; diff --git a/libs/common/build.gradle b/libs/common/build.gradle index e9de44f894296..41c145bc890fb 100644 --- a/libs/common/build.gradle +++ b/libs/common/build.gradle @@ -25,10 +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" - compileOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" - compileOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" - api "org.bouncycastle:bcpkix-fips:${versions.bouncycastle_pkix}" - /******* * !!!! NO THIRD PARTY DEPENDENCIES !!!! *******/ @@ -54,10 +50,6 @@ tasks.named('forbiddenApisMain').configure { ignoreSignaturesOfMissingClasses = true } -tasks.named("dependencyLicenses").configure { - mapping from: /bc.*/, to: 'bouncycastle' -} - compileJava { options.compilerArgs += ['--add-modules', 'jdk.incubator.vector'] options.compilerArgs -= '-Werror' // use of incubator modules is reported as a warning diff --git a/libs/common/src/main/java/org/opensearch/common/SecureRandomHolder.java b/libs/common/src/main/java/org/opensearch/common/SecureRandomHolder.java index 990f6968a57a2..14844293b3274 100644 --- a/libs/common/src/main/java/org/opensearch/common/SecureRandomHolder.java +++ b/libs/common/src/main/java/org/opensearch/common/SecureRandomHolder.java @@ -32,8 +32,6 @@ package org.opensearch.common; -import org.bouncycastle.crypto.CryptoServicesRegistrar; - import java.security.SecureRandom; /** @@ -43,5 +41,5 @@ */ class SecureRandomHolder { // class loading is atomic - this is a lazy & safe singleton to be used by this package - public static final SecureRandom INSTANCE = CryptoServicesRegistrar.getSecureRandom(); + public static final SecureRandom INSTANCE = new SecureRandom(); } 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 d82716085b504..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 @@ -33,8 +33,6 @@ package org.opensearch.common.ssl; import org.opensearch.common.Nullable; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; diff --git a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreFactory.java similarity index 93% rename from libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java rename to libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreFactory.java index a07c4b64b1a36..2cc41065aecdd 100644 --- a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreFactory.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreFactory.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.common.crypto; +package org.opensearch.common.ssl; import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.SuppressForbidden; @@ -17,8 +17,8 @@ import java.util.Objects; import java.util.stream.Collectors; -import static org.opensearch.common.crypto.KeyStoreType.SECURE_KEYSTORE_TYPES; -import static org.opensearch.common.crypto.KeyStoreType.inferStoreType; +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. diff --git a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreType.java similarity index 98% rename from libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java rename to libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreType.java index 6654ee9cef40f..df106f1b5017e 100644 --- a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreType.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.common.crypto; +package org.opensearch.common.ssl; import java.util.HashMap; import java.util.List; 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 4d45e8df14130..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 @@ -33,8 +33,6 @@ package org.opensearch.common.ssl; import org.opensearch.common.Nullable; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; 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 98bc97c1c8787..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 @@ -32,8 +32,6 @@ package org.opensearch.common.ssl; -import org.opensearch.common.crypto.KeyStoreType; - import javax.crypto.Cipher; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; @@ -49,7 +47,7 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static org.opensearch.common.crypto.KeyStoreType.inferStoreType; +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; 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 c60e4d1b0f65a..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 @@ -32,8 +32,6 @@ package org.opensearch.common.ssl; -import org.opensearch.common.crypto.KeyStoreType; - import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; 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 138fff71b8a3d..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 @@ -32,8 +32,6 @@ package org.opensearch.common.ssl; -import org.opensearch.common.crypto.KeyStoreType; - import javax.net.ssl.X509ExtendedTrustManager; import java.nio.file.Path; diff --git a/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/KeyStoreFactoryTests.java similarity index 98% rename from libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java rename to libs/ssl-config/src/test/java/org/opensearch/common/ssl/KeyStoreFactoryTests.java index 32b5ecfbcd23c..07d3ebfaa8fba 100644 --- a/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/KeyStoreFactoryTests.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.common.crypto; +package org.opensearch.common.ssl; import com.carrotsearch.randomizedtesting.generators.RandomStrings; 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 a0d15258ed3ca..71bb51a4edd9e 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,8 +32,6 @@ package org.opensearch.common.ssl; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; import org.opensearch.test.OpenSearchTestCase; import java.nio.file.Files; 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 6df03f72a0be8..1fd1828f1039c 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,9 +48,9 @@ import java.security.cert.X509Certificate; import java.util.Arrays; -import static org.opensearch.common.crypto.KeyStoreType.BCFKS; -import static org.opensearch.common.crypto.KeyStoreType.JKS; -import static org.opensearch.common.crypto.KeyStoreType.PKCS_12; +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; 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 3ba0c39e9d7e6..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,9 +49,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.opensearch.common.crypto.KeyStoreType.BCFKS; -import static org.opensearch.common.crypto.KeyStoreType.JKS; -import static org.opensearch.common.crypto.KeyStoreType.PKCS_12; +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; 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 0a801a0f58f02..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 @@ -10,13 +10,13 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.OpenSearchException; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; import org.opensearch.common.network.NetworkAddress; import org.opensearch.common.network.NetworkService; 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; 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 2502a77af50e0..c4272e03d27b5 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 @@ -11,11 +11,11 @@ import org.apache.lucene.tests.util.LuceneTestCase; import org.opensearch.Version; import org.opensearch.cluster.node.DiscoveryNode; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; 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; 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 f48b20d52f601..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 @@ -41,10 +41,10 @@ import org.apache.logging.log4j.LogManager; import org.opensearch.cloud.azure.classic.management.AzureComputeService; import org.opensearch.common.SuppressForbidden; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; 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; 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 80d3f70614ea1..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 @@ -46,8 +46,8 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.common.collect.MapBuilder; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.common.unit.TimeValue; import org.opensearch.core.common.Strings; 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/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 04ef48f61c95c..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,8 +48,8 @@ import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; diff --git a/server/build.gradle b/server/build.gradle index f12dfaaba91de..04332d816d7a4 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") compileOnly project(':libs:opensearch-plugin-classloader') testRuntimeOnly project(':libs:opensearch-plugin-classloader') diff --git a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java index 650bca01f0359..455a06293a0fe 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -199,7 +199,7 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot SecureRandomInitializer.init(); var cryptoStandard = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); - if (cryptoStandard != null && cryptoStandard.equals("FIPS-140-3")) { + if ("FIPS-140-3".equals(cryptoStandard)) { LogManager.getLogger(Bootstrap.class).info("running in FIPS-140-3 mode"); SecurityProviderManager.excludeSunJCE(); } 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 de35e95090122..dae5c78b9645f 100644 --- a/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java +++ b/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java @@ -45,9 +45,9 @@ import org.opensearch.cli.ExitCodes; import org.opensearch.cli.UserException; import org.opensearch.common.SetOnce; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; 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; diff --git a/test/framework/build.gradle b/test/framework/build.gradle index 84a536fdf62c8..dea05f42a7532 100644 --- a/test/framework/build.gradle +++ b/test/framework/build.gradle @@ -49,6 +49,7 @@ dependencies { api "org.mockito:mockito-core:${versions.mockito}" api "net.bytebuddy:byte-buddy:${versions.bytebuddy}" api "org.objenesis:objenesis:${versions.objenesis}" + api "org.bouncycastle:bcpkix-fips:${versions.bouncycastle_pkix}" annotationProcessor "org.apache.logging.log4j:log4j-core:${versions.log4j}" } diff --git a/libs/common/licenses/bcpkix-fips-2.0.7.jar.sha1 b/test/framework/licenses/bcpkix-fips-2.0.7.jar.sha1 similarity index 100% rename from libs/common/licenses/bcpkix-fips-2.0.7.jar.sha1 rename to test/framework/licenses/bcpkix-fips-2.0.7.jar.sha1 diff --git a/libs/common/licenses/bouncycastle-LICENSE.txt b/test/framework/licenses/bouncycastle-LICENSE.txt similarity index 100% rename from libs/common/licenses/bouncycastle-LICENSE.txt rename to test/framework/licenses/bouncycastle-LICENSE.txt diff --git a/libs/common/licenses/bouncycastle-NOTICE.txt b/test/framework/licenses/bouncycastle-NOTICE.txt similarity index 100% rename from libs/common/licenses/bouncycastle-NOTICE.txt rename to test/framework/licenses/bouncycastle-NOTICE.txt 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 90a5ce2768a30..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,8 +12,8 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.opensearch.common.crypto.KeyStoreFactory; -import org.opensearch.common.crypto.KeyStoreType; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500PrivateCredential; 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 d7b29b57f3934..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 @@ -60,9 +60,9 @@ import org.opensearch.client.WarningsHandler; import org.opensearch.common.CheckedRunnable; import org.opensearch.common.SetOnce; -import org.opensearch.common.crypto.KeyStoreFactory; 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; From 945ec7d14b46d8c4d251e1683b20ad0449a9a9b4 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Thu, 16 Jan 2025 20:54:45 +0100 Subject: [PATCH 15/21] loosen some direct BC dependencies Signed-off-by: Iwan Igonin --- build.gradle | 2 +- buildSrc/build.gradle | 5 +- .../gradle/SecureRandomProvider.java | 46 +++++++++++++++++++ .../gradle/http/WaitForHttpResource.java | 9 ++-- .../gradle/info/FipsBuildParams.java | 25 ++++++++++ .../gradle/info/GlobalBuildInfoPlugin.java | 10 ++-- client/rest/build.gradle | 15 ++++-- .../client/RestClientBuilderIntegTests.java | 2 +- .../HeapBufferedAsyncEntityConsumerTests.java | 18 ++++---- client/test/build.gradle | 8 +--- 10 files changed, 105 insertions(+), 35 deletions(-) create mode 100644 buildSrc/src/main/java/org/opensearch/gradle/SecureRandomProvider.java create mode 100644 buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java diff --git a/build.gradle b/build.gradle index 27e1bde68b4f6..d8dea5e5a9908 100644 --- a/build.gradle +++ b/build.gradle @@ -562,7 +562,7 @@ subprojects { // test with FIPS-140-3 enabled plugins.withType(JavaPlugin).configureEach { tasks.withType(Test).configureEach { testTask -> - if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-3') { + if (BuildParams.inFipsJvm == true) { testTask.jvmArgs += "-Dorg.bouncycastle.fips.approved_only=true" } } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 7b22d612bc267..75b3215dbaff7 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -123,8 +123,9 @@ dependencies { api 'org.jruby.joni:joni:2.2.1' api "com.fasterxml.jackson.core:jackson-databind:${props.getProperty('jackson_databind')}" api "org.ajoberstar.grgit:grgit-core:5.2.1" - api "org.bouncycastle:bc-fips:${props.getProperty('bouncycastle_jce')}" - + if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-3') { + api "org.bouncycastle:bc-fips:${props.getProperty('bouncycastle_jce')}" + } testFixturesApi "junit:junit:${props.getProperty('junit')}" testFixturesApi "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${props.getProperty('randomizedrunner')}" diff --git a/buildSrc/src/main/java/org/opensearch/gradle/SecureRandomProvider.java b/buildSrc/src/main/java/org/opensearch/gradle/SecureRandomProvider.java new file mode 100644 index 0000000000000..efcdc44249e61 --- /dev/null +++ b/buildSrc/src/main/java/org/opensearch/gradle/SecureRandomProvider.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.gradle; + +import org.opensearch.gradle.info.FipsBuildParams; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; + +public class SecureRandomProvider { + + private final static MethodHandle MH_BC_SECURE_RANDOM; + + static { + MethodHandle mh = null; + if (FipsBuildParams.isInFipsMode()) { + try { + final Class cryptoServicesRegistrarClass = Class.forName("org.bouncycastle.crypto.CryptoServicesRegistrar"); + mh = MethodHandles.publicLookup() + .findStatic(cryptoServicesRegistrarClass, "getSecureRandom", MethodType.methodType(SecureRandom.class)); + } catch (final Throwable ex) { + throw new SecurityException("Unable to find org.bouncycastle.crypto.CryptoServicesRegistrar instance", ex); + } + } + MH_BC_SECURE_RANDOM = mh; + } + + public static SecureRandom getSecureRandom() throws GeneralSecurityException { + if (MH_BC_SECURE_RANDOM == null) { + return new SecureRandom(); + } else try { + return (SecureRandom) MH_BC_SECURE_RANDOM.invoke(); + } catch (final Throwable ex) { + throw new GeneralSecurityException(ex); + } + } +} 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 62f2621be7924..c13c120d058ef 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java @@ -32,10 +32,11 @@ package org.opensearch.gradle.http; -import org.bouncycastle.crypto.CryptoServicesRegistrar; +import de.thetaphi.forbiddenapis.SuppressForbidden; + +import org.opensearch.gradle.SecureRandomProvider; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; -import org.gradle.internal.impldep.com.jcraft.jsch.annotations.SuppressForbiddenApi; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; @@ -240,7 +241,7 @@ private KeyStore buildTrustStoreFromCA() throws GeneralSecurityException, IOExce return store; } - @SuppressForbiddenApi("runs exclusively in test-context without KeyStoreFactory on classpath.") + @SuppressForbidden private KeyStore getKeyStoreInstance(String type) throws KeyStoreException { return KeyStore.getInstance(type); } @@ -250,7 +251,7 @@ private SSLContext createSslContext(KeyStore trustStore) throws GeneralSecurityE TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - sslContext.init(new KeyManager[0], tmf.getTrustManagers(), CryptoServicesRegistrar.getSecureRandom()); + sslContext.init(new KeyManager[0], tmf.getTrustManagers(), SecureRandomProvider.getSecureRandom()); return sslContext; } 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..965702b8dc011 --- /dev/null +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java @@ -0,0 +1,25 @@ +/* + * 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; + +public class FipsBuildParams { + + private static final String FIPS_MODE = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); + + private FipsBuildParams() {} + + public static boolean isInFipsMode() { + return "FIPS-140-3".equals(FIPS_MODE); + } + + public static String getFipsMode() { + return FIPS_MODE; + } + +} 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 6f55174e8d0f5..fe354097640f7 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java @@ -77,7 +77,6 @@ public class GlobalBuildInfoPlugin implements Plugin { private static final Logger LOGGER = Logging.getLogger(GlobalBuildInfoPlugin.class); private static final String DEFAULT_LEGACY_VERSION_JAVA_FILE_PATH = "libs/core/src/main/java/org/opensearch/LegacyESVersion.java"; private static final String DEFAULT_VERSION_JAVA_FILE_PATH = "libs/core/src/main/java/org/opensearch/Version.java"; - protected static final String OPENSEARCH_CRYPTO_STANDARD = "OPENSEARCH_CRYPTO_STANDARD"; private static Integer _defaultParallel = null; private final JvmMetadataDetector jvmMetadataDetector; @@ -113,8 +112,6 @@ public void apply(Project project) { BuildParams.init(params -> { // Initialize global build parameters boolean isInternal = GlobalBuildInfoPlugin.class.getResource("/buildSrc.marker") != null; - var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); - var inFipsJvm = "FIPS-140-3".equals(cryptoStandard); params.reset(); params.setRuntimeJavaHome(runtimeJavaHome); @@ -132,7 +129,7 @@ public void apply(Project project) { params.setIsCi(System.getenv("JENKINS_URL") != null); params.setIsInternal(isInternal); params.setDefaultParallel(findDefaultParallel(project)); - params.setInFipsJvm(inFipsJvm); + params.setInFipsJvm(FipsBuildParams.isInFipsMode()); params.setIsSnapshotBuild(Util.getBooleanProperty("build.snapshot", true)); if (isInternal) { params.setBwcVersions(resolveBwcVersions(rootDir)); @@ -166,7 +163,6 @@ private void logGlobalBuildInfo() { final String osArch = System.getProperty("os.arch"); final Jvm gradleJvm = Jvm.current(); final String gradleJvmDetails = getJavaInstallation(gradleJvm.getJavaHome()).getDisplayName(); - final String cryptStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); LOGGER.quiet("======================================="); LOGGER.quiet("OpenSearch Build Hamster says Hello!"); @@ -183,8 +179,8 @@ private void logGlobalBuildInfo() { LOGGER.quiet(" JAVA_HOME : " + gradleJvm.getJavaHome()); } LOGGER.quiet(" Random Testing Seed : " + BuildParams.getTestSeed()); - if (cryptStandard != null && cryptStandard.equals("FIPS-140-3")) { - LOGGER.quiet(" Crypto Standard : FIPS-140-3"); + if (FipsBuildParams.isInFipsMode()) { + LOGGER.quiet(" Crypto Standard : " + FipsBuildParams.getFipsMode()); } else { LOGGER.quiet(" Crypto Standard : any-supported"); } diff --git a/client/rest/build.gradle b/client/rest/build.gradle index 5ba3feb3a87e0..d3955602360b6 100644 --- a/client/rest/build.gradle +++ b/client/rest/build.gradle @@ -34,8 +34,8 @@ apply plugin: 'opensearch.build' apply plugin: 'opensearch.publish' java { - targetCompatibility = JavaVersion.VERSION_11 - sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 } base { @@ -51,15 +51,13 @@ dependencies { api "commons-codec:commons-codec:${versions.commonscodec}" api "commons-logging:commons-logging:${versions.commonslogging}" api "org.slf4j:slf4j-api:${versions.slf4j}" - api "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" - + runtimeOnly "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" // reactor api "io.projectreactor:reactor-core:${versions.reactor}" api "org.reactivestreams:reactive-streams:${versions.reactivestreams}" testImplementation project(":client:test") - testImplementation project(':libs:opensearch-ssl-config') testImplementation "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" testImplementation "junit:junit:${versions.junit}" testImplementation "org.hamcrest:hamcrest:${versions.hamcrest}" @@ -80,6 +78,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 { @@ -212,8 +211,14 @@ 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) +} 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 e7c83dcf68e94..82a37ac29b545 100644 --- a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java +++ b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java @@ -138,7 +138,7 @@ private static SSLContext getSslContext(boolean server) throws Exception { TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); - var sslContextBuilder = SSLContextBuilder.create() + SSLContextBuilder sslContextBuilder = SSLContextBuilder.create() .setProvider("BCJSSE") .setProtocol(getProtocol()) .setSecureRandom(secureRandom); diff --git a/client/rest/src/test/java/org/opensearch/client/nio/HeapBufferedAsyncEntityConsumerTests.java b/client/rest/src/test/java/org/opensearch/client/nio/HeapBufferedAsyncEntityConsumerTests.java index 6a4b176edd011..fdfe49ca901c9 100644 --- a/client/rest/src/test/java/org/opensearch/client/nio/HeapBufferedAsyncEntityConsumerTests.java +++ b/client/rest/src/test/java/org/opensearch/client/nio/HeapBufferedAsyncEntityConsumerTests.java @@ -35,34 +35,34 @@ public void tearDown() { } public void testConsumerAllocatesBufferLimit() throws IOException { - consumer.consume(randomByteBufferOfLength(1000).flip()); + consumer.consume((ByteBuffer) randomByteBufferOfLength(1000).flip()); assertThat(consumer.getBuffer().capacity(), equalTo(1000)); } public void testConsumerAllocatesEmptyBuffer() throws IOException { - consumer.consume(ByteBuffer.allocate(0).flip()); + consumer.consume((ByteBuffer) ByteBuffer.allocate(0).flip()); assertThat(consumer.getBuffer().capacity(), equalTo(0)); } public void testConsumerExpandsBufferLimits() throws IOException { - consumer.consume(randomByteBufferOfLength(1000).flip()); - consumer.consume(randomByteBufferOfLength(2000).flip()); - consumer.consume(randomByteBufferOfLength(3000).flip()); + consumer.consume((ByteBuffer) randomByteBufferOfLength(1000).flip()); + consumer.consume((ByteBuffer) randomByteBufferOfLength(2000).flip()); + consumer.consume((ByteBuffer) randomByteBufferOfLength(3000).flip()); assertThat(consumer.getBuffer().capacity(), equalTo(6000)); } public void testConsumerAllocatesLimit() throws IOException { - consumer.consume(randomByteBufferOfLength(BUFFER_LIMIT).flip()); + consumer.consume((ByteBuffer) randomByteBufferOfLength(BUFFER_LIMIT).flip()); assertThat(consumer.getBuffer().capacity(), equalTo(BUFFER_LIMIT)); } public void testConsumerFailsToAllocateOverLimit() throws IOException { - assertThrows(ContentTooLongException.class, () -> consumer.consume(randomByteBufferOfLength(BUFFER_LIMIT + 1).flip())); + assertThrows(ContentTooLongException.class, () -> consumer.consume((ByteBuffer) randomByteBufferOfLength(BUFFER_LIMIT + 1).flip())); } public void testConsumerFailsToExpandOverLimit() throws IOException { - consumer.consume(randomByteBufferOfLength(BUFFER_LIMIT).flip()); - assertThrows(ContentTooLongException.class, () -> consumer.consume(randomByteBufferOfLength(1).flip())); + consumer.consume((ByteBuffer) randomByteBufferOfLength(BUFFER_LIMIT).flip()); + assertThrows(ContentTooLongException.class, () -> consumer.consume((ByteBuffer) randomByteBufferOfLength(1).flip())); } private static ByteBuffer randomByteBufferOfLength(int length) { diff --git a/client/test/build.gradle b/client/test/build.gradle index 4c59fc1a07d99..f877bcd08d773 100644 --- a/client/test/build.gradle +++ b/client/test/build.gradle @@ -43,12 +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}" - api "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" -} - -tasks.named("dependencyLicenses").configure { - mapping from: /bc.*/, to: 'bouncycastle' + runtimeOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" + runtimeOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" } tasks.named('forbiddenApisMain').configure { From cfa5d7946762138ba4f5e1e224381addcbbb3ef2 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Mon, 27 Jan 2025 10:33:05 +0100 Subject: [PATCH 16/21] set default testcluster password for all project modules Signed-off-by: Iwan Igonin --- build.gradle | 9 +++- buildSrc/build.gradle | 3 -- .../gradle/SecureRandomProvider.java | 46 ------------------- .../gradle/http/WaitForHttpResource.java | 4 +- .../gradle/info/FipsBuildParams.java | 8 +++- .../gradle/testclusters/OpenSearchNode.java | 8 ++++ distribution/src/bin/opensearch-cli | 6 +++ distribution/src/bin/opensearch-cli.bat | 6 +++ .../ssl/TrustEverythingConfigTests.java | 30 ++++++++++++ .../shiro/realm/BCryptPasswordMatcher.java | 1 + .../realm/BCryptPasswordMatcherTests.java | 20 ++++++-- plugins/repository-s3/build.gradle | 11 ----- .../SecureRandomInitializerTests.java | 3 -- 13 files changed, 83 insertions(+), 72 deletions(-) delete mode 100644 buildSrc/src/main/java/org/opensearch/gradle/SecureRandomProvider.java create mode 100644 libs/ssl-config/src/test/java/org/opensearch/common/ssl/TrustEverythingConfigTests.java diff --git a/build.gradle b/build.gradle index d8dea5e5a9908..cc7b5ed029364 100644 --- a/build.gradle +++ b/build.gradle @@ -562,11 +562,18 @@ subprojects { // test with FIPS-140-3 enabled plugins.withType(JavaPlugin).configureEach { tasks.withType(Test).configureEach { testTask -> - if (BuildParams.inFipsJvm == true) { + 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/build.gradle b/buildSrc/build.gradle index 75b3215dbaff7..737c03d8086d7 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -123,9 +123,6 @@ dependencies { api 'org.jruby.joni:joni:2.2.1' api "com.fasterxml.jackson.core:jackson-databind:${props.getProperty('jackson_databind')}" api "org.ajoberstar.grgit:grgit-core:5.2.1" - if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-3') { - api "org.bouncycastle:bc-fips:${props.getProperty('bouncycastle_jce')}" - } testFixturesApi "junit:junit:${props.getProperty('junit')}" testFixturesApi "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${props.getProperty('randomizedrunner')}" diff --git a/buildSrc/src/main/java/org/opensearch/gradle/SecureRandomProvider.java b/buildSrc/src/main/java/org/opensearch/gradle/SecureRandomProvider.java deleted file mode 100644 index efcdc44249e61..0000000000000 --- a/buildSrc/src/main/java/org/opensearch/gradle/SecureRandomProvider.java +++ /dev/null @@ -1,46 +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. - */ - -package org.opensearch.gradle; - -import org.opensearch.gradle.info.FipsBuildParams; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.security.GeneralSecurityException; -import java.security.SecureRandom; - -public class SecureRandomProvider { - - private final static MethodHandle MH_BC_SECURE_RANDOM; - - static { - MethodHandle mh = null; - if (FipsBuildParams.isInFipsMode()) { - try { - final Class cryptoServicesRegistrarClass = Class.forName("org.bouncycastle.crypto.CryptoServicesRegistrar"); - mh = MethodHandles.publicLookup() - .findStatic(cryptoServicesRegistrarClass, "getSecureRandom", MethodType.methodType(SecureRandom.class)); - } catch (final Throwable ex) { - throw new SecurityException("Unable to find org.bouncycastle.crypto.CryptoServicesRegistrar instance", ex); - } - } - MH_BC_SECURE_RANDOM = mh; - } - - public static SecureRandom getSecureRandom() throws GeneralSecurityException { - if (MH_BC_SECURE_RANDOM == null) { - return new SecureRandom(); - } else try { - return (SecureRandom) MH_BC_SECURE_RANDOM.invoke(); - } catch (final Throwable ex) { - throw new GeneralSecurityException(ex); - } - } -} 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 c13c120d058ef..75f02007a5221 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java @@ -34,7 +34,6 @@ import de.thetaphi.forbiddenapis.SuppressForbidden; -import org.opensearch.gradle.SecureRandomProvider; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; @@ -54,6 +53,7 @@ import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.util.Arrays; @@ -251,7 +251,7 @@ private SSLContext createSslContext(KeyStore trustStore) throws GeneralSecurityE TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); - sslContext.init(new KeyManager[0], tmf.getTrustManagers(), SecureRandomProvider.getSecureRandom()); + sslContext.init(new KeyManager[0], tmf.getTrustManagers(), new SecureRandom()); return sslContext; } diff --git a/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java b/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java index 965702b8dc011..3c1852afdcc3d 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java @@ -10,7 +10,9 @@ public class FipsBuildParams { - private static final String FIPS_MODE = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); + private static final String FIPS_BUILD_PARAM = "OPENSEARCH_CRYPTO_STANDARD"; + + private static final String FIPS_MODE = System.getenv(FIPS_BUILD_PARAM); private FipsBuildParams() {} @@ -22,4 +24,8 @@ public static String getFipsMode() { return FIPS_MODE; } + public static String getFipsBuildParam() { + return FIPS_BUILD_PARAM; + } + } 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..4472dcc1dfb80 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.getFipsBuildParam(), FipsBuildParams.getFipsMode()); + } Set commonKeys = new HashSet<>(environment.keySet()); commonKeys.retainAll(defaultEnv.keySet()); 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/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/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java index 45bf634de1ec1..55e3e3414ac2c 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java @@ -40,6 +40,7 @@ public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo return check(userToken.getPassword(), (String) info.getCredentials()); } + @SuppressWarnings("removal") private boolean check(char[] password, String hash) { if (password == null || password.length == 0) { throw new IllegalStateException("Password cannot be empty or null"); diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java index 304903e27c953..451f0eae06234 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java @@ -43,12 +43,22 @@ public void testCredentialDoNotMatch() { } public void testEmptyPassword() { - final UsernamePasswordToken token = mock(UsernamePasswordToken.class); - when(token.getPassword()).thenReturn(randomFrom("".toCharArray(), null)); - final AuthenticationInfo info = mock(AuthenticationInfo.class); + { + final UsernamePasswordToken token = mock(UsernamePasswordToken.class); + when(token.getPassword()).thenReturn(null); + final AuthenticationInfo info = mock(AuthenticationInfo.class); - Exception e = assertThrows(IllegalStateException.class, () -> new BCryptPasswordMatcher().doCredentialsMatch(token, info)); - assertThat(e.getMessage(), equalTo("Password cannot be empty or null")); + Exception e = assertThrows(IllegalStateException.class, () -> new BCryptPasswordMatcher().doCredentialsMatch(token, info)); + assertThat(e.getMessage(), equalTo("Password cannot be empty or null")); + } + { + final UsernamePasswordToken token = mock(UsernamePasswordToken.class); + when(token.getPassword()).thenReturn("".toCharArray()); + final AuthenticationInfo info = mock(AuthenticationInfo.class); + + Exception e = assertThrows(IllegalStateException.class, () -> new BCryptPasswordMatcher().doCredentialsMatch(token, info)); + assertThat(e.getMessage(), equalTo("Password cannot be empty or null")); + } } public void testEmptyHash() { diff --git a/plugins/repository-s3/build.gradle b/plugins/repository-s3/build.gradle index 1832b6a165cc1..17e694e8eab04 100644 --- a/plugins/repository-s3/build.gradle +++ b/plugins/repository-s3/build.gradle @@ -144,13 +144,6 @@ def fixtureAddress = { fixture, name, port -> 'http://127.0.0.1:' + ephemeralPort } -def applyFipsConfig(OpenSearchCluster cluster) { - if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-3') { - cluster.keystorePassword 'notarealpasswordphrase' - cluster.environment 'OPENSEARCH_CRYPTO_STANDARD', 'FIPS-140-3' - } -} - // We test against two repositories, one which uses the usual two-part "permanent" credentials and // the other which uses three-part "temporary" or "session" credentials. @@ -268,7 +261,6 @@ yamlRestTest { } testClusters.yamlRestTest { - applyFipsConfig(delegate) keystore 's3.client.integration_test_permanent.access_key', s3PermanentAccessKey keystore 's3.client.integration_test_permanent.secret_key', s3PermanentSecretKey @@ -332,7 +324,6 @@ if (useFixture) { check.dependsOn(yamlRestTestMinio) testClusters.yamlRestTestMinio { - applyFipsConfig(delegate) keystore 's3.client.integration_test_permanent.access_key', s3PermanentAccessKey keystore 's3.client.integration_test_permanent.secret_key', s3PermanentSecretKey setting 's3.client.integration_test_permanent.endpoint', { "${-> fixtureAddress('minio-fixture', 'minio-fixture', '9000')}" }, IGNORE_VALUE @@ -361,7 +352,6 @@ if (useFixture) { check.dependsOn(yamlRestTestECS) testClusters.yamlRestTestECS { - applyFipsConfig(delegate) setting 's3.client.integration_test_ecs.endpoint', { "${-> fixtureAddress('s3-fixture', 's3-fixture-with-ecs', '80')}" }, IGNORE_VALUE plugin tasks.bundlePlugin.archiveFile environment 'AWS_CONTAINER_CREDENTIALS_FULL_URI', { "${-> fixtureAddress('s3-fixture', 's3-fixture-with-ecs', '80')}/ecs_credentials_endpoint" }, IGNORE_VALUE @@ -389,7 +379,6 @@ if (useFixture) { check.dependsOn(yamlRestTestEKS) testClusters.yamlRestTestEKS { - applyFipsConfig(delegate) keystore 's3.client.integration_test_eks.role_arn', "arn:aws:iam::000000000000:role/test" keystore 's3.client.integration_test_eks.role_session_name', "s3-test" keystore 's3.client.integration_test_eks.access_key', "access_key" diff --git a/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java b/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java index 865347438c8e5..34dc60030c40c 100644 --- a/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java +++ b/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java @@ -20,9 +20,6 @@ public class SecureRandomInitializerTests extends OpenSearchTestCase { @BeforeClass public static void setup() { - // Reset the global state if your CryptoServicesRegistrar allows it. - // If there's a method to unset or reset the SecureRandom, call it here. - // If not, the following lines are hypothetical and depend on your actual implementation. CryptoServicesRegistrar.setSecureRandom(null); } From b6097729db3ab9e70309e9fc596e09e9e15047c9 Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Fri, 31 Jan 2025 13:20:56 +0100 Subject: [PATCH 17/21] build with 'crypto.standard' gradle build parameter Signed-off-by: Iwan Igonin --- .../gradle/info/FipsBuildParams.java | 20 +++++++++++-------- .../gradle/info/GlobalBuildInfoPlugin.java | 2 ++ .../gradle/testclusters/OpenSearchNode.java | 2 +- .../tools/launchers/SystemJvmOptions.java | 16 ++++----------- .../org/opensearch/bootstrap/Bootstrap.java | 2 +- .../bootstrap/test-framework.policy | 1 + 6 files changed, 21 insertions(+), 22 deletions(-) diff --git a/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java b/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java index 3c1852afdcc3d..83aba1af2152d 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java @@ -8,24 +8,28 @@ package org.opensearch.gradle.info; +import java.util.function.Function; + public class FipsBuildParams { - private static final String FIPS_BUILD_PARAM = "OPENSEARCH_CRYPTO_STANDARD"; + public static final String FIPS_BUILD_PARAM = "crypto.standard"; + + public static final String FIPS_ENV_VAR = "OPENSEARCH_CRYPTO_STANDARD"; + + private static String fipsMode; - private static final String FIPS_MODE = System.getenv(FIPS_BUILD_PARAM); + public static void init(Function fipsValue) { + fipsMode = (String) fipsValue.apply(FIPS_BUILD_PARAM); + } private FipsBuildParams() {} public static boolean isInFipsMode() { - return "FIPS-140-3".equals(FIPS_MODE); + return "FIPS-140-3".equals(fipsMode); } public static String getFipsMode() { - return FIPS_MODE; - } - - public static String getFipsBuildParam() { - return FIPS_BUILD_PARAM; + 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 fe354097640f7..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; 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 4472dcc1dfb80..e86496766abf9 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java @@ -797,7 +797,7 @@ private Map getOpenSearchEnvironment() { defaultEnv.put("HOSTNAME", HOSTNAME_OVERRIDE); defaultEnv.put("COMPUTERNAME", COMPUTERNAME_OVERRIDE); if (FipsBuildParams.isInFipsMode()) { - defaultEnv.put(FipsBuildParams.getFipsBuildParam(), FipsBuildParams.getFipsMode()); + defaultEnv.put(FipsBuildParams.FIPS_ENV_VAR, FipsBuildParams.getFipsMode()); } Set commonKeys = new HashSet<>(environment.keySet()); 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 60e3085e864b3..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 @@ -44,6 +44,8 @@ final class 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( @@ -93,21 +95,11 @@ static List systemJvmOptions(final Path config, Runtime.Version runtimeV } private static String enableFips() { - var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); - if (FIPS_140_3.equals(cryptoStandard)) { - return "-Dorg.bouncycastle.fips.approved_only=true"; - } - return ""; + return IS_IN_FIPS_JVM ? "-Dorg.bouncycastle.fips.approved_only=true" : ""; } private static String loadJavaSecurityProperties(final Path config) throws FileNotFoundException { - String securityFile; - var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); - if (FIPS_140_3.equals(cryptoStandard)) { - securityFile = "fips_java.security"; - } else { - securityFile = "java.security"; - } + String securityFile = IS_IN_FIPS_JVM ? "fips_java.security" : "java.security"; var securityFilePath = config.resolve(securityFile); if (!Files.exists(securityFilePath)) { diff --git a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java index 455a06293a0fe..b3068338c0c8a 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -199,7 +199,7 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot SecureRandomInitializer.init(); var cryptoStandard = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); - if ("FIPS-140-3".equals(cryptoStandard)) { + 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(); } diff --git a/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy b/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy index e1a3b4618035e..78f302e9b23db 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy @@ -167,4 +167,5 @@ grant { permission org.opensearch.secure_sm.ThreadContextPermission "markAsSystemContext"; permission org.opensearch.secure_sm.ThreadContextPermission "stashAndMergeHeaders"; permission org.opensearch.secure_sm.ThreadContextPermission "stashWithOrigin"; + permission java.lang.RuntimePermission "setDefaultUncaughtExceptionHandler"; }; From f88d5a5b8868d1a2ac2af35f2c3577c384da562c Mon Sep 17 00:00:00 2001 From: Iwan Igonin Date: Tue, 4 Feb 2025 15:08:57 +0100 Subject: [PATCH 18/21] run REST-tests with JKS & BCFKS keystores Signed-off-by: Iwan Igonin --- .../precommit/TestingConventionsTasks.java | 13 +++++- client/rest/build.gradle | 5 +++ .../client/RestClientBuilderIntegTests.java | 22 ++++++---- .../client/RestClientFipsAwareTestCase.java | 26 ++++++++++++ .../src/test/resources/test_truststore.jks | Bin 0 -> 1097 bytes client/rest/src/test/resources/testks.jks | Bin 0 -> 2381 bytes client/test/build.gradle | 2 +- .../opensearch/client/RestClientTestCase.java | 5 +++ gradle/libs.versions.toml | 3 ++ .../ssl/SimpleSecureNetty4TransportTests.java | 8 ++-- server/build.gradle | 6 +-- .../org/opensearch/bootstrap/security.policy | 38 +++++++++--------- 12 files changed, 91 insertions(+), 37 deletions(-) create mode 100644 client/rest/src/test/java/org/opensearch/client/RestClientFipsAwareTestCase.java create mode 100644 client/rest/src/test/resources/test_truststore.jks create mode 100644 client/rest/src/test/resources/testks.jks 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/client/rest/build.gradle b/client/rest/build.gradle index d3955602360b6..fe950a8db8730 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' @@ -222,3 +223,7 @@ tasks.withType(JavaCompile) { 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 82a37ac29b545..96b87be327e93 100644 --- a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java +++ b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java @@ -66,14 +66,14 @@ /** * 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(); } @@ -93,6 +93,11 @@ public static void stopHttpServers() throws IOException { } public void testBuilderUsesDefaultSSLContext() throws Exception { + makeRequest(); + } + + @Override + public void makeRequest(KeyStoreType keyStoreType) throws Exception { final SSLContext defaultSSLContext = SSLContext.getDefault(); try { try (RestClient client = buildRestClient()) { @@ -104,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()); @@ -119,21 +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 = KeyStoreType.TYPE_TO_EXTENSION_MAP.get(keyStoreType).get(0); try ( - InputStream trustStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test_truststore.bcfks"); - InputStream keyStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/testks.bcfks") + InputStream trustStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test_truststore" + fileExtension); + InputStream keyStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/testks" + fileExtension) ) { - KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); + KeyStore keyStore = KeyStoreFactory.getInstance(keyStoreType); keyStore.load(keyStoreFile, password); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, password); - KeyStore trustStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); + KeyStore trustStore = KeyStoreFactory.getInstance(keyStoreType); trustStore.load(trustStoreFile, password); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 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/resources/test_truststore.jks b/client/rest/src/test/resources/test_truststore.jks new file mode 100644 index 0000000000000000000000000000000000000000..d27635fec5040d63969f7d124d499fa1b4788d1b GIT binary patch literal 1097 zcmezO_TO6u1_mY|W(3pRB}JvhC8;UNsYN9~vAkwc`ArP05qhQumJAFmtOiXij0R0i zFBdQ~F)}f+SnhC6G2mt6)N1o+`_9YA$j!=NkZ#Crz{$oO%EBhh6dDZUa0oLwI~s}^ zh=63+g?WAROY=$+GxHR}GE>V91q}E=;#|V)sX2+oC7H>FyawDL5pH3YlGNf7Lm>kJ z5SLk)6U0@>%TGx)kQ3)MG&e9eG&eLeG%_`d66ZAnaZRCI`Z=VDQ3*K^7+D#Zn;7{S z44N3Zn3@9EiBm+{dKY&Mx?Dh3UI&DWy9-kNdy3Y1&zCVq0?3+gn8M zW^BmU&9hebbM`WL%SM>)H`p6)xX$#V^e3^_c|yKH+g&^_eww-4(bN0WeUHXpj<24| z8#s44{mgM{<-Ekjav*FHySkd1LILvkfpKRps}T$r5>2Z+690q z4;e5vapvSFC+1}27nd}N!kKztR!V*@FxfP5!G%ql#1U*{H6}=k%#akBAuBR7kOeu2 zk420{B!;(W5m!xu6HAtD?Gnzr*6g$93L!@ePE8)StWekcf6T@8>!Hn2u@bR@rlREmQe3 z*E!m4Dbw#AUFZ2b>f&VICND7KS>ox+=yy2j@n+^nCE4?htut-6e$*Az+UNcJvug@p zxy3{0kBJ}Or$o&-*t0S9IK#8deQaWSimx8m{Sg0s=1fG3q~-m^ZtGbxL!O1KZ8*=P zSUgEB^1BHS@5>phh28)F literal 0 HcmV?d00001 diff --git a/client/rest/src/test/resources/testks.jks b/client/rest/src/test/resources/testks.jks new file mode 100644 index 0000000000000000000000000000000000000000..cd706db5e3834f4b44493530bca17697f32d8de2 GIT binary patch literal 2381 zcmcgt`9IYA8vlOB80%QF%P^K4yYI*ll4T;A?8cHMVh&T*Y{!}zYjfo2&U*Xwz`-tYJ4{loKmp6B&EKURON0ssIz4)Cv__YA&G z3_fNKX6Ang002DBTG4J7uREh5mJ7*;DV;dAlA=s5+KPl4wULQJdoL8~Pe7D3^*Cpn+O9sr03 zISo5KN{!m#CIL0)yz>t}H?Dji7ewQeMTpah_;lIM9lfx^{MI46EY=H2Gb87zSV!L9 zBNwGc)hcRNP-{ZbigjTAb+b#asOW1;eDvzR6aB?nZ0b51o)HL=(P6?em8T=Dtimf@ zkw!zNEH-cfeLsno#O=RbxSvAmmq_5eGP;=CTVxI0y~COhkJiND1UdLS2Ol7#CcE^d zL>{Nth-L14Z4j+X`yfibe=ag4rVlxJp#US7Q|y@QX^G~`3fl?pyl5Y^R8rIQWW4Id zQ{z=r`vr-qyIpkx)5AVH*xUo_`4Nx4b(N?wT{OYbRG2-rcbCh$ubPg4`&+@r5D zzPUX@zCiejz)?}CTe3&^u7rVdbjjY6C5l@V4|cw6NVPj+>;MVdCH6G z;*UbVqziZ{8{q|$yRDA6AbU?YM#cWH7~gF>>!8r(I*ID&@IdyK9Vh)mhm9(9QprF* zTcmmB5ofTGhE^wgc6e}~gaOj0+F~@7M8SRe&>AqUy`aoNh?gL%tOLG=Z`F!b~nE6fm|g zn~sp%GU*wT?a;|Lx{14y5rz@Zi*Ei`1^Asfnk+f1QoG!rBKWDm!N)n4tjm$w;#(Ms z*y4S9lR%xR>-qfN9B<|yeOK)-bH747=ZNpA3?IQ6RWFHIE8SgO`efbu{fcV~?x(fP{fMBSHJgUi@HE~B!hGqp zOO=HE;j=bw>XQc_Lz$N7+B!oz5O}9z4>`!sl^^yX__= zt-G_fDHBHnrXd?sZVgtfT~YRfOVPRZGOfh@@i4+lcQw=8yf9tQpirKyrJ1>K}mXUYH+n6o>({TY+BG7mQ%>U&6=* z!|7mnA?}kO1}>zLcYv3_Gsz2WMIr`aIF6U>vChoJK=g3-^C!7rkPyp>!omgjC;Itg z&OjU|l#Yw(grdE?U5St|9}=SqsbW+yDi|fK%5^@Z(h0@>hyMQ(p@3qii*S4+=qMmN z08v0@7zG4@>`*WMc%Za&v|oF7I$+-EMoqJWYD_z76t$P8`b?SG)ttMjYum12Jk&V7 zsHmP6>E*Mnu22@bNiF7pFuY}Fi}583GKns-grfA-Wjf!$M+>b?2e(*eKQE{W(|_ zqzG@m_}u1gR1!=yj)8OGH|*MNL9vesbIH50(wEe#5QVStcy5LF4wl>1k4ux85&=BQ z2HOs`T4SDJht6*D#{Bb)k$L@P!=cU0W*tNP=9B^XyUy|_5eP@s-Sg0mVfsMlT$VO- z8v~;Q0kD8v1Cc8sa`JH>QJipc97K+egWo;Aws9QCLw5Rs6ebUE7iSN5Z$Ezu&)-nt zFXZa&c|0~0=D%fS3g7?mX^-+LlgcTR%4w4dggQxx4bBVawnyH{XAX1Hh2PQ)FJzj~ zV0fH;=5%9^r~A0EAmDBKU?@>M=V)d1wfsQ-HBPDx*3~(vs9PE7?jqurC(74^6*Ip;MfXd)H`2nLpdhX1nEE+M3Y~js@@+eX5olR{E5MTxf=!t-%Qh!Tb~aJm hJ4YDTZ?{bYj5Ah4y>@bn_^A>sIjjET5{u3>b{aXM4 literal 0 HcmV?d00001 diff --git a/client/test/build.gradle b/client/test/build.gradle index f877bcd08d773..afecaf685c51c 100644 --- a/client/test/build.gradle +++ b/client/test/build.gradle @@ -43,7 +43,7 @@ dependencies { api "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" api "junit:junit:${versions.junit}" api "org.hamcrest:hamcrest:${versions.hamcrest}" - runtimeOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" + api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" runtimeOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" } 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 49ee3373a071e..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) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9dc0f5ad58d69..b8cd22663603f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -119,6 +119,9 @@ reactorcore = { group = "org.reactivestreams", name = "reactive-streams", versio roaringbitmap = { group = "org.roaringbitmap", name = "RoaringBitmap", version.ref = "roaringbitmap" } spatial4j = { group = "org.locationtech.spatial4j", name = "spatial4j", version.ref = "spatial4j" } tdigest = { group = "com.tdunning", name = "t-digest", version.ref = "tdigest" } +bcjce = { group = "org.bouncycastle", name = "bc-fips", version.ref = "bouncycastle_jce" } +bctls = { group = "org.bouncycastle", name = "bctls-fips", version.ref = "bouncycastle_tls" } +bcutil = { group = "org.bouncycastle", name = "bcutil-fips", version.ref = "bouncycastle_util" } [bundles] lucene = [ 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 c4272e03d27b5..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 @@ -8,7 +8,6 @@ package org.opensearch.transport.netty4.ssl; -import org.apache.lucene.tests.util.LuceneTestCase; import org.opensearch.Version; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.common.network.NetworkService; @@ -67,7 +66,6 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.lessThanOrEqualTo; -@LuceneTestCase.AwaitsFix(bugUrl = "") public class SimpleSecureNetty4TransportTests extends AbstractSimpleTransportTestCase { @Override protected Transport build(Settings settings, final Version version, ClusterSettings clusterSettings, boolean doHandshake) { @@ -81,9 +79,11 @@ public Optional buildServerTransportExceptionHandler( @Override public Optional buildSecureServerTransportEngine(Settings settings, Transport transport) throws SSLException { try { - final KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.JKS); + 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/server/build.gradle b/server/build.gradle index 04332d816d7a4..b55a0b47a0850 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -116,9 +116,9 @@ dependencies { api libs.roaringbitmap // bouncyCastle - api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" - api "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" - api "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" + api libs.bcjce + api libs.bctls + api libs.bcutil testImplementation 'org.awaitility:awaitility:4.2.2' testImplementation(project(":test:framework")) { diff --git a/server/src/main/resources/org/opensearch/bootstrap/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index 9c61ec1ddad27..7d416afa2bd24 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -93,26 +93,6 @@ grant codeBase "${codebase.reactor-core}" { permission java.net.SocketPermission "*", "connect,resolve"; }; -// security -grant { - permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; - permission java.lang.RuntimePermission "accessDeclaredMembers"; - permission java.lang.RuntimePermission "closeClassLoader"; - permission java.lang.RuntimePermission "getProtectionDomain"; - permission java.io.FilePermission "${java.home}/lib/security/cacerts", "read"; - permission java.io.FilePermission "${java.home}/lib/security/jssecacerts", "read"; - 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"; - permission java.security.SecurityPermission "getProperty.keystore.type.compat"; - permission java.security.SecurityPermission "getProperty.org.bouncycastle.*"; - permission java.security.SecurityPermission "removeProvider.SunJCE"; - permission java.util.PropertyPermission "java.runtime.name", "read"; - permission org.bouncycastle.crypto.CryptoServicesPermission "defaultRandomConfig"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; -}; - //// Everything else: grant { @@ -215,4 +195,22 @@ grant { permission java.io.FilePermission "/sys/fs/cgroup/cpuacct/-", "read"; permission java.io.FilePermission "/sys/fs/cgroup/memory", "read"; permission java.io.FilePermission "/sys/fs/cgroup/memory/-", "read"; + + // security + permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; + permission java.lang.RuntimePermission "accessDeclaredMembers"; + permission java.lang.RuntimePermission "closeClassLoader"; + permission java.lang.RuntimePermission "getProtectionDomain"; + permission java.io.FilePermission "${java.home}/lib/security/cacerts", "read"; + permission java.io.FilePermission "${java.home}/lib/security/jssecacerts", "read"; + 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"; + permission java.security.SecurityPermission "getProperty.keystore.type.compat"; + permission java.security.SecurityPermission "getProperty.org.bouncycastle.*"; + permission java.security.SecurityPermission "removeProvider.SunJCE"; + permission java.util.PropertyPermission "java.runtime.name", "read"; + permission org.bouncycastle.crypto.CryptoServicesPermission "defaultRandomConfig"; + permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; + permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; }; From 5c97dd677db82c8a731cc17d2afde050d16f823c Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 11 Feb 2025 20:37:19 -0500 Subject: [PATCH 19/21] Explicitly call closeConnectionChannel Signed-off-by: Craig Perkins --- .../opensearch/transport/AbstractSimpleTransportTestCase.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/framework/src/main/java/org/opensearch/transport/AbstractSimpleTransportTestCase.java b/test/framework/src/main/java/org/opensearch/transport/AbstractSimpleTransportTestCase.java index e43b0756e2f2b..f810950dd81b1 100644 --- a/test/framework/src/main/java/org/opensearch/transport/AbstractSimpleTransportTestCase.java +++ b/test/framework/src/main/java/org/opensearch/transport/AbstractSimpleTransportTestCase.java @@ -3122,6 +3122,7 @@ public TransportResponse read(final StreamInput in) { } } ); + closeConnectionChannel(connection); assertThat(te.get(), not(nullValue())); if (failToSendException instanceof IllegalStateException) { From c583753850950c64a0bdaa294e2f5b6a98214c6c Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 11 Feb 2025 20:43:50 -0500 Subject: [PATCH 20/21] Run on fork Signed-off-by: Craig Perkins --- .github/workflows/gradle-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle-check.yml b/.github/workflows/gradle-check.yml index 577ab0c79535b..0cb58c3f6d278 100644 --- a/.github/workflows/gradle-check.yml +++ b/.github/workflows/gradle-check.yml @@ -33,7 +33,7 @@ jobs: gradle-check: needs: check-files - if: github.repository == 'opensearch-project/OpenSearch' && needs.check-files.outputs.RUN_GRADLE_CHECK == 'true' + if: needs.check-files.outputs.RUN_GRADLE_CHECK == 'true' permissions: contents: read # to fetch code (actions/checkout) pull-requests: write # to create or update comment (peter-evans/create-or-update-comment) From 2ebf326ea900ccd0a0e41c89b449923ae5cd78a4 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 11 Feb 2025 20:47:23 -0500 Subject: [PATCH 21/21] Remove if Signed-off-by: Craig Perkins --- .github/workflows/gradle-check.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/gradle-check.yml b/.github/workflows/gradle-check.yml index 0cb58c3f6d278..685f33ee443e0 100644 --- a/.github/workflows/gradle-check.yml +++ b/.github/workflows/gradle-check.yml @@ -33,7 +33,6 @@ jobs: gradle-check: needs: check-files - if: needs.check-files.outputs.RUN_GRADLE_CHECK == 'true' permissions: contents: read # to fetch code (actions/checkout) pull-requests: write # to create or update comment (peter-evans/create-or-update-comment)