diff --git a/.github/workflows/gradle-check.yml b/.github/workflows/gradle-check.yml index 577ab0c79535b..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: github.repository == 'opensearch-project/OpenSearch' && 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) 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/build.gradle b/build.gradle index 679f7b9299248..cc7b5ed029364 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,6 @@ apply from: 'gradle/ide.gradle' apply from: 'gradle/forbidden-dependencies.gradle' apply from: 'gradle/formatting.gradle' apply from: 'gradle/local-distribution.gradle' -apply from: 'gradle/fips.gradle' apply from: 'gradle/run.gradle' apply from: 'gradle/missing-javadoc.gradle' apply from: 'gradle/code-coverage.gradle' @@ -472,8 +471,8 @@ gradle.projectsEvaluated { } } -// test retry configuration subprojects { + // test retry configuration tasks.withType(Test).configureEach { develocity.testRetry { if (BuildParams.isCi()) { @@ -559,6 +558,22 @@ subprojects { } } } + + // test with FIPS-140-3 enabled + plugins.withType(JavaPlugin).configureEach { + tasks.withType(Test).configureEach { testTask -> + if (BuildParams.inFipsJvm) { + testTask.jvmArgs += "-Dorg.bouncycastle.fips.approved_only=true" + } + } + } + plugins.withId('opensearch.testclusters') { + testClusters.configureEach { + if (BuildParams.inFipsJvm) { + keystorePassword 'notarealpasswordphrase' + } + } + } } // eclipse configuration diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 4f3c5de49dbc6..737c03d8086d7 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -229,12 +229,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/OpenSearchTestBasePlugin.java b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java index d0cb2da9c1dd3..cf2cad686f3ac 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java @@ -164,6 +164,12 @@ 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/" + securityFile + ); + // 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/http/WaitForHttpResource.java b/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java index 54c544a299b84..75f02007a5221 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/http/WaitForHttpResource.java @@ -32,6 +32,8 @@ package org.opensearch.gradle.http; +import de.thetaphi.forbiddenapis.SuppressForbidden; + import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; @@ -216,7 +218,7 @@ KeyStore buildTrustStore() throws GeneralSecurityException, IOException { } private KeyStore buildTrustStoreFromFile() throws GeneralSecurityException, IOException { - KeyStore keyStore = KeyStore.getInstance(trustStoreFile.getName().endsWith(".jks") ? "JKS" : "PKCS12"); + var keyStore = getKeyStoreInstance(trustStoreFile.getName().endsWith(".jks") ? "JKS" : "PKCS12"); try (InputStream input = new FileInputStream(trustStoreFile)) { keyStore.load(input, trustStorePassword == null ? null : trustStorePassword.toCharArray()); } @@ -224,7 +226,7 @@ private KeyStore buildTrustStoreFromFile() throws GeneralSecurityException, IOEx } private KeyStore buildTrustStoreFromCA() throws GeneralSecurityException, IOException { - final KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType()); + var store = getKeyStoreInstance(KeyStore.getDefaultType()); store.load(null, null); final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); int counter = 0; @@ -239,6 +241,11 @@ private KeyStore buildTrustStoreFromCA() throws GeneralSecurityException, IOExce return store; } + @SuppressForbidden + private KeyStore getKeyStoreInstance(String type) throws KeyStoreException { + return KeyStore.getInstance(type); + } + private SSLContext createSslContext(KeyStore trustStore) throws GeneralSecurityException { checkForTrustEntry(trustStore); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); diff --git a/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java b/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java new file mode 100644 index 0000000000000..83aba1af2152d --- /dev/null +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/FipsBuildParams.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.gradle.info; + +import java.util.function.Function; + +public class FipsBuildParams { + + public static final String FIPS_BUILD_PARAM = "crypto.standard"; + + public static final String FIPS_ENV_VAR = "OPENSEARCH_CRYPTO_STANDARD"; + + private static String fipsMode; + + public static void init(Function fipsValue) { + fipsMode = (String) fipsValue.apply(FIPS_BUILD_PARAM); + } + + private FipsBuildParams() {} + + public static boolean isInFipsMode() { + return "FIPS-140-3".equals(fipsMode); + } + + public static String getFipsMode() { + return fipsMode; + } + +} diff --git a/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java b/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java index 570ab4a9f70e1..4c3a09c394278 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/info/GlobalBuildInfoPlugin.java @@ -109,6 +109,8 @@ public void apply(Project project) { File rootDir = project.getRootDir(); GitInfo gitInfo = gitInfo(rootDir); + FipsBuildParams.init(project::findProperty); + BuildParams.init(params -> { // Initialize global build parameters boolean isInternal = GlobalBuildInfoPlugin.class.getResource("/buildSrc.marker") != null; @@ -129,7 +131,7 @@ public void apply(Project project) { params.setIsCi(System.getenv("JENKINS_URL") != null); params.setIsInternal(isInternal); params.setDefaultParallel(findDefaultParallel(project)); - params.setInFipsJvm(Util.getBooleanProperty("tests.fips.enabled", false)); + params.setInFipsJvm(FipsBuildParams.isInFipsMode()); params.setIsSnapshotBuild(Util.getBooleanProperty("build.snapshot", true)); if (isInternal) { params.setBwcVersions(resolveBwcVersions(rootDir)); @@ -179,7 +181,11 @@ private void logGlobalBuildInfo() { LOGGER.quiet(" JAVA_HOME : " + gradleJvm.getJavaHome()); } LOGGER.quiet(" Random Testing Seed : " + BuildParams.getTestSeed()); - LOGGER.quiet(" In FIPS 140 mode : " + BuildParams.isInFipsJvm()); + if (FipsBuildParams.isInFipsMode()) { + LOGGER.quiet(" Crypto Standard : " + FipsBuildParams.getFipsMode()); + } else { + LOGGER.quiet(" Crypto Standard : any-supported"); + } LOGGER.quiet("======================================="); } diff --git a/buildSrc/src/main/java/org/opensearch/gradle/precommit/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/buildSrc/src/main/java/org/opensearch/gradle/precommit/TestingConventionsTasks.java b/buildSrc/src/main/java/org/opensearch/gradle/precommit/TestingConventionsTasks.java index 9c1285914a03e..d6812704fc8f3 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/precommit/TestingConventionsTasks.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/precommit/TestingConventionsTasks.java @@ -40,6 +40,7 @@ import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; import org.gradle.api.Task; +import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTree; import org.gradle.api.plugins.jvm.JvmTestSuite; @@ -87,6 +88,7 @@ public class TestingConventionsTasks extends DefaultTask { private final NamedDomainObjectContainer naming; private final Project project; + private final ConfigurableFileCollection extraClassPath = getProject().files(); @Inject public TestingConventionsTasks(Project project) { @@ -398,7 +400,16 @@ private boolean isAnnotated(Method method, Class annotation) { @Classpath public FileCollection getTestsClassPath() { - return Util.getJavaTestSourceSet(project).get().getRuntimeClasspath(); + return Util.getJavaTestSourceSet(project).get().getRuntimeClasspath().plus(extraClassPath); + } + + @Classpath + public ConfigurableFileCollection getExtraClassPath() { + return extraClassPath; + } + + public void addExtraClassPath(Object... paths) { + extraClassPath.from(paths); } private Map walkPathAndLoadClasses(File testRoot) { diff --git a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java index aaa2daef2a158..e86496766abf9 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/testclusters/OpenSearchNode.java @@ -46,6 +46,7 @@ import org.opensearch.gradle.Version; import org.opensearch.gradle.VersionProperties; import org.opensearch.gradle.info.BuildParams; +import org.opensearch.gradle.info.FipsBuildParams; import org.gradle.api.Action; import org.gradle.api.Named; import org.gradle.api.NamedDomainObjectContainer; @@ -546,9 +547,13 @@ 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, "opensearch-keystore", "create", "-p"); + runOpenSearchBinScriptWithInput(keystorePassword + "\n" + keystorePassword + "\n", "opensearch-keystore", "create", "-p"); } else { runOpenSearchBinScript("opensearch-keystore", "-v", "create"); } @@ -556,7 +561,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 +743,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); } @@ -786,6 +796,9 @@ private Map getOpenSearchEnvironment() { // Override the system hostname variables for testing defaultEnv.put("HOSTNAME", HOSTNAME_OVERRIDE); defaultEnv.put("COMPUTERNAME", COMPUTERNAME_OVERRIDE); + if (FipsBuildParams.isInFipsMode()) { + defaultEnv.put(FipsBuildParams.FIPS_ENV_VAR, FipsBuildParams.getFipsMode()); + } Set commonKeys = new HashSet<>(environment.keySet()); commonKeys.retainAll(defaultEnv.keySet()); diff --git a/buildSrc/src/main/resources/cacerts.bcfks b/buildSrc/src/main/resources/cacerts.bcfks deleted file mode 100644 index 9c3863eda60c5..0000000000000 Binary files a/buildSrc/src/main/resources/cacerts.bcfks and /dev/null differ diff --git a/buildSrc/src/main/resources/fips_java_bcjsse_11.policy b/buildSrc/src/main/resources/fips_java_bcjsse_11.policy deleted file mode 100644 index 10193f4eb385d..0000000000000 --- a/buildSrc/src/main/resources/fips_java_bcjsse_11.policy +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -// Security Policy for JDK 11 and higher, with BouncyCastle FIPS provider and BouncyCastleJsseProvider in FIPS mode - -grant { - permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; - permission java.security.SecurityPermission "putProviderProperty.BCJSSE"; - permission java.lang.RuntimePermission "getProtectionDomain"; - permission java.util.PropertyPermission "java.runtime.name", "read"; - permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled"; - //io.netty.handler.codec.DecoderException - permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; - //java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec - permission java.lang.RuntimePermission "accessDeclaredMembers"; - permission java.util.PropertyPermission "intellij.debug.agent", "read"; - permission java.util.PropertyPermission "intellij.debug.agent", "write"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; - permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read"; -}; diff --git a/buildSrc/src/main/resources/fips_java_bcjsse_8.policy b/buildSrc/src/main/resources/fips_java_bcjsse_8.policy deleted file mode 100644 index 87dbe85c22ab5..0000000000000 --- a/buildSrc/src/main/resources/fips_java_bcjsse_8.policy +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -// Security Policy for JDK 8, with BouncyCastle FIPS provider and BouncyCastleJsseProvider in FIPS mode - -grant codeBase "file:${java.home}/lib/ext/localedata.jar" { - // Allow resource bundles to be loaded for non root locales. See - // https://github.com/elastic/elasticsearch/issues/39981 - permission java.lang.RuntimePermission "accessClassInPackage.sun.util.*"; -}; -grant { - permission java.security.SecurityPermission "putProviderProperty.BCFIPS"; - permission java.security.SecurityPermission "putProviderProperty.BCJSSE"; - permission java.lang.RuntimePermission "getProtectionDomain"; - permission java.util.PropertyPermission "java.runtime.name", "read"; - permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled"; - //io.netty.handler.codec.DecoderException - permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; - //java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec - permission java.lang.RuntimePermission "accessDeclaredMembers"; - permission java.util.PropertyPermission "intellij.debug.agent", "read"; - permission java.util.PropertyPermission "intellij.debug.agent", "write"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; - permission java.io.FilePermission "${javax.net.ssl.trustStore}", "read"; -}; diff --git a/buildSrc/src/main/resources/fips_java_bcjsse_8.security b/buildSrc/src/main/resources/fips_java_bcjsse_8.security deleted file mode 100644 index df21041f5191b..0000000000000 --- a/buildSrc/src/main/resources/fips_java_bcjsse_8.security +++ /dev/null @@ -1,134 +0,0 @@ -# Security Properties for JDK 8, with BouncyCastle FIPS provider and BouncyCastleJsseProvider in FIPS mode - -security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:HYBRID;ENABLE{All}; -security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS -security.provider.3=sun.security.provider.Sun -security.provider.4=sun.security.jgss.SunProvider -securerandom.source=file:/dev/urandom -securerandom.strongAlgorithms=NativePRNGBlocking:SUN -login.configuration.provider=sun.security.provider.ConfigFile -policy.provider=sun.security.provider.PolicyFile -policy.url.1=file:${java.home}/lib/security/java.policy -policy.url.2=file:${user.home}/.java.policy -policy.expandProperties=true -policy.allowSystemProperty=true -policy.ignoreIdentityScope=false -keystore.type=bcfks -keystore.type.compat=true -package.access=sun.,\ - org.GNOME.Accessibility.,\ - com.sun.xml.internal.,\ - com.sun.imageio.,\ - com.sun.istack.internal.,\ - com.sun.jmx.,\ - com.sun.media.sound.,\ - com.sun.naming.internal.,\ - com.sun.proxy.,\ - com.sun.corba.se.,\ - com.sun.org.apache.bcel.internal.,\ - com.sun.org.apache.regexp.internal.,\ - com.sun.org.apache.xerces.internal.,\ - com.sun.org.apache.xpath.internal.,\ - com.sun.org.apache.xalan.internal.extensions.,\ - com.sun.org.apache.xalan.internal.lib.,\ - com.sun.org.apache.xalan.internal.res.,\ - com.sun.org.apache.xalan.internal.templates.,\ - com.sun.org.apache.xalan.internal.utils.,\ - com.sun.org.apache.xalan.internal.xslt.,\ - com.sun.org.apache.xalan.internal.xsltc.cmdline.,\ - com.sun.org.apache.xalan.internal.xsltc.compiler.,\ - com.sun.org.apache.xalan.internal.xsltc.trax.,\ - com.sun.org.apache.xalan.internal.xsltc.util.,\ - com.sun.org.apache.xml.internal.res.,\ - com.sun.org.apache.xml.internal.resolver.helpers.,\ - com.sun.org.apache.xml.internal.resolver.readers.,\ - com.sun.org.apache.xml.internal.security.,\ - com.sun.org.apache.xml.internal.serializer.utils.,\ - com.sun.org.apache.xml.internal.utils.,\ - com.sun.org.glassfish.,\ - com.oracle.xmlns.internal.,\ - com.oracle.webservices.internal.,\ - oracle.jrockit.jfr.,\ - org.jcp.xml.dsig.internal.,\ - jdk.internal.,\ - jdk.nashorn.internal.,\ - jdk.nashorn.tools.,\ - jdk.xml.internal.,\ - com.sun.activation.registries. - -package.definition=sun.,\ - com.sun.xml.internal.,\ - com.sun.imageio.,\ - com.sun.istack.internal.,\ - com.sun.jmx.,\ - com.sun.media.sound.,\ - com.sun.naming.internal.,\ - com.sun.proxy.,\ - com.sun.corba.se.,\ - com.sun.org.apache.bcel.internal.,\ - com.sun.org.apache.regexp.internal.,\ - com.sun.org.apache.xerces.internal.,\ - com.sun.org.apache.xpath.internal.,\ - com.sun.org.apache.xalan.internal.extensions.,\ - com.sun.org.apache.xalan.internal.lib.,\ - com.sun.org.apache.xalan.internal.res.,\ - com.sun.org.apache.xalan.internal.templates.,\ - com.sun.org.apache.xalan.internal.utils.,\ - com.sun.org.apache.xalan.internal.xslt.,\ - com.sun.org.apache.xalan.internal.xsltc.cmdline.,\ - com.sun.org.apache.xalan.internal.xsltc.compiler.,\ - com.sun.org.apache.xalan.internal.xsltc.trax.,\ - com.sun.org.apache.xalan.internal.xsltc.util.,\ - com.sun.org.apache.xml.internal.res.,\ - com.sun.org.apache.xml.internal.resolver.helpers.,\ - com.sun.org.apache.xml.internal.resolver.readers.,\ - com.sun.org.apache.xml.internal.security.,\ - com.sun.org.apache.xml.internal.serializer.utils.,\ - com.sun.org.apache.xml.internal.utils.,\ - com.sun.org.glassfish.,\ - com.oracle.xmlns.internal.,\ - com.oracle.webservices.internal.,\ - oracle.jrockit.jfr.,\ - org.jcp.xml.dsig.internal.,\ - jdk.internal.,\ - jdk.nashorn.internal.,\ - jdk.nashorn.tools.,\ - jdk.xml.internal.,\ - com.sun.activation.registries. - -ssl.KeyManagerFactory.algorithm=PKIX -ssl.TrustManagerFactory.algorithm=PKIX -networkaddress.cache.negative.ttl=10 -krb5.kdc.bad.policy = tryLast -jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer, \ - RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224 - -jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySize < 1024 - - -jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 1024, \ - EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC - -jdk.tls.legacyAlgorithms= \ - K_NULL, C_NULL, M_NULL, \ - DH_anon, ECDH_anon, \ - RC4_128, RC4_40, DES_CBC, DES40_CBC, \ - 3DES_EDE_CBC -crypto.policy=unlimited - -jdk.xml.dsig.secureValidationPolicy=\ - disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\ - maxTransforms 5,\ - maxReferences 30,\ - disallowReferenceUriSchemes file http https,\ - minKeySize RSA 1024,\ - minKeySize DSA 1024,\ - minKeySize EC 224,\ - noDuplicateIds,\ - noRetrievalMethodLoops - -jceks.key.serialFilter = java.lang.Enum;java.security.KeyRep;\ - java.security.KeyRep$Type;javax.crypto.spec.SecretKeySpec;!* diff --git a/buildSrc/src/main/resources/fips_java_sunjsse.policy b/buildSrc/src/main/resources/fips_java_sunjsse.policy deleted file mode 100644 index 1d2f06e3a1314..0000000000000 --- a/buildSrc/src/main/resources/fips_java_sunjsse.policy +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -// Security Policy for JDK 8, with BouncyCastle FIPS provider and SunJSSE in FIPS mode - -grant codeBase "file:${java.home}/lib/ext/localedata.jar" { - // Allow resource bundles to be loaded for non root locales. See - // https://github.com/elastic/elasticsearch/issues/39981 - permission java.lang.RuntimePermission "accessClassInPackage.sun.util.*"; -}; -grant { - permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey"; - permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey"; - permission java.lang.RuntimePermission "getProtectionDomain"; - permission java.util.PropertyPermission "java.runtime.name", "read"; - permission org.bouncycastle.crypto.CryptoServicesPermission "tlsAlgorithmsEnabled"; - //io.netty.handler.codec.DecoderException - permission java.lang.RuntimePermission "accessClassInPackage.sun.security.internal.spec"; - //java.security.InvalidAlgorithmParameterException: Cannot process GCMParameterSpec - permission java.lang.RuntimePermission "accessDeclaredMembers"; -}; diff --git a/buildSrc/src/main/resources/fips_java_sunjsse.security b/buildSrc/src/main/resources/fips_java_sunjsse.security deleted file mode 100644 index 2959bea3b8596..0000000000000 --- a/buildSrc/src/main/resources/fips_java_sunjsse.security +++ /dev/null @@ -1,134 +0,0 @@ -# Security Properties for JDK 8, with BouncyCastle FIPS provider and SunJSSE in FIPS mode - -security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:HYBRID;ENABLE{All}; -security.provider.2=com.sun.net.ssl.internal.ssl.Provider BCFIPS -security.provider.3=sun.security.provider.Sun -security.provider.4=sun.security.jgss.SunProvider -securerandom.source=file:/dev/urandom -securerandom.strongAlgorithms=NativePRNGBlocking:SUN -login.configuration.provider=sun.security.provider.ConfigFile -policy.provider=sun.security.provider.PolicyFile -policy.url.1=file:${java.home}/lib/security/java.policy -policy.url.2=file:${user.home}/.java.policy -policy.expandProperties=true -policy.allowSystemProperty=true -policy.ignoreIdentityScope=false -keystore.type=bcfks -keystore.type.compat=true -package.access=sun.,\ - org.GNOME.Accessibility.,\ - com.sun.xml.internal.,\ - com.sun.imageio.,\ - com.sun.istack.internal.,\ - com.sun.jmx.,\ - com.sun.media.sound.,\ - com.sun.naming.internal.,\ - com.sun.proxy.,\ - com.sun.corba.se.,\ - com.sun.org.apache.bcel.internal.,\ - com.sun.org.apache.regexp.internal.,\ - com.sun.org.apache.xerces.internal.,\ - com.sun.org.apache.xpath.internal.,\ - com.sun.org.apache.xalan.internal.extensions.,\ - com.sun.org.apache.xalan.internal.lib.,\ - com.sun.org.apache.xalan.internal.res.,\ - com.sun.org.apache.xalan.internal.templates.,\ - com.sun.org.apache.xalan.internal.utils.,\ - com.sun.org.apache.xalan.internal.xslt.,\ - com.sun.org.apache.xalan.internal.xsltc.cmdline.,\ - com.sun.org.apache.xalan.internal.xsltc.compiler.,\ - com.sun.org.apache.xalan.internal.xsltc.trax.,\ - com.sun.org.apache.xalan.internal.xsltc.util.,\ - com.sun.org.apache.xml.internal.res.,\ - com.sun.org.apache.xml.internal.resolver.helpers.,\ - com.sun.org.apache.xml.internal.resolver.readers.,\ - com.sun.org.apache.xml.internal.security.,\ - com.sun.org.apache.xml.internal.serializer.utils.,\ - com.sun.org.apache.xml.internal.utils.,\ - com.sun.org.glassfish.,\ - com.oracle.xmlns.internal.,\ - com.oracle.webservices.internal.,\ - oracle.jrockit.jfr.,\ - org.jcp.xml.dsig.internal.,\ - jdk.internal.,\ - jdk.nashorn.internal.,\ - jdk.nashorn.tools.,\ - jdk.xml.internal.,\ - com.sun.activation.registries. - -package.definition=sun.,\ - com.sun.xml.internal.,\ - com.sun.imageio.,\ - com.sun.istack.internal.,\ - com.sun.jmx.,\ - com.sun.media.sound.,\ - com.sun.naming.internal.,\ - com.sun.proxy.,\ - com.sun.corba.se.,\ - com.sun.org.apache.bcel.internal.,\ - com.sun.org.apache.regexp.internal.,\ - com.sun.org.apache.xerces.internal.,\ - com.sun.org.apache.xpath.internal.,\ - com.sun.org.apache.xalan.internal.extensions.,\ - com.sun.org.apache.xalan.internal.lib.,\ - com.sun.org.apache.xalan.internal.res.,\ - com.sun.org.apache.xalan.internal.templates.,\ - com.sun.org.apache.xalan.internal.utils.,\ - com.sun.org.apache.xalan.internal.xslt.,\ - com.sun.org.apache.xalan.internal.xsltc.cmdline.,\ - com.sun.org.apache.xalan.internal.xsltc.compiler.,\ - com.sun.org.apache.xalan.internal.xsltc.trax.,\ - com.sun.org.apache.xalan.internal.xsltc.util.,\ - com.sun.org.apache.xml.internal.res.,\ - com.sun.org.apache.xml.internal.resolver.helpers.,\ - com.sun.org.apache.xml.internal.resolver.readers.,\ - com.sun.org.apache.xml.internal.security.,\ - com.sun.org.apache.xml.internal.serializer.utils.,\ - com.sun.org.apache.xml.internal.utils.,\ - com.sun.org.glassfish.,\ - com.oracle.xmlns.internal.,\ - com.oracle.webservices.internal.,\ - oracle.jrockit.jfr.,\ - org.jcp.xml.dsig.internal.,\ - jdk.internal.,\ - jdk.nashorn.internal.,\ - jdk.nashorn.tools.,\ - jdk.xml.internal.,\ - com.sun.activation.registries. - -ssl.KeyManagerFactory.algorithm=SunX509 -ssl.TrustManagerFactory.algorithm=PKIX -networkaddress.cache.negative.ttl=10 -krb5.kdc.bad.policy = tryLast -jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer, \ - RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224 - -jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySize < 1024 - - -jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 1024, \ - EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC - -jdk.tls.legacyAlgorithms= \ - K_NULL, C_NULL, M_NULL, \ - DH_anon, ECDH_anon, \ - RC4_128, RC4_40, DES_CBC, DES40_CBC, \ - 3DES_EDE_CBC -crypto.policy=unlimited - -jdk.xml.dsig.secureValidationPolicy=\ - disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\ - disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\ - maxTransforms 5,\ - maxReferences 30,\ - disallowReferenceUriSchemes file http https,\ - minKeySize RSA 1024,\ - minKeySize DSA 1024,\ - minKeySize EC 224,\ - noDuplicateIds,\ - noRetrievalMethodLoops - -jceks.key.serialFilter = java.lang.Enum;java.security.KeyRep;\ - java.security.KeyRep$Type;javax.crypto.spec.SecretKeySpec;!* diff --git a/buildSrc/src/main/resources/forbidden/jdk-signatures.txt b/buildSrc/src/main/resources/forbidden/jdk-signatures.txt index b2fd479dce5ff..1ece009c57ecd 100644 --- a/buildSrc/src/main/resources/forbidden/jdk-signatures.txt +++ b/buildSrc/src/main/resources/forbidden/jdk-signatures.txt @@ -37,6 +37,12 @@ java.nio.file.Path#toFile() java.nio.file.Files#createTempDirectory(java.lang.String,java.nio.file.attribute.FileAttribute[]) java.nio.file.Files#createTempFile(java.lang.String,java.lang.String,java.nio.file.attribute.FileAttribute[]) +@defaultMessage Use org.opensearch.common.ssl.KeyStoreFactory instead of java.security.KeyStore +java.security.KeyStore#getInstance(java.lang.String) +java.security.KeyStore#getInstance(java.lang.String,java.lang.String) +java.security.KeyStore#getInstance(java.lang.String,java.security.Provider) +java.security.KeyStore#getInstance(java.io.File,char[]) + @defaultMessage Don't use java serialization - this can break BWC without noticing it java.io.ObjectOutputStream java.io.ObjectOutput diff --git a/buildSrc/src/main/resources/test/ssl/test-node.bcfks b/buildSrc/src/main/resources/test/ssl/test-node.bcfks new file mode 100644 index 0000000000000..141eec9d66ded Binary files /dev/null and b/buildSrc/src/main/resources/test/ssl/test-node.bcfks differ diff --git a/client/rest/build.gradle b/client/rest/build.gradle index 93faf0024b51e..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' @@ -51,6 +52,7 @@ dependencies { api "commons-codec:commons-codec:${versions.commonscodec}" api "commons-logging:commons-logging:${versions.commonslogging}" api "org.slf4j:slf4j-api:${versions.slf4j}" + runtimeOnly "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" // reactor api "io.projectreactor:reactor-core:${versions.reactor}" @@ -70,13 +72,14 @@ 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') -} - -forbiddenPatterns { - exclude '**/*.der' + classpath += files(project(':libs:opensearch-ssl-config').jar.archiveFile) } tasks.named('forbiddenApisTest').configure { @@ -138,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' @@ -146,8 +212,18 @@ thirdPartyAudit { } tasks.withType(JavaCompile) { + classpath += files(project(':libs:opensearch-ssl-config').jar.archiveFile) // Suppressing '[options] target value 8 is obsolete and will be removed in a future release' configure(options) { options.compilerArgs << '-Xlint:-options' + options.compilerArgs -= '-Werror' // use of incubator modules is reported as a warning } } + +tasks.withType(Test).configureEach { + classpath += files(project(':libs:opensearch-ssl-config').jar.archiveFile) +} + +tasks.named("testingConventions", TestingConventionsTasks).configure { + addExtraClassPath(project(":libs:opensearch-ssl-config").jar.archiveFile) +} diff --git a/client/rest/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..387635e9e1594 --- /dev/null +++ b/client/rest/licenses/bctls-fips-2.0.19.jar.sha1 @@ -0,0 +1 @@ +9cc33650ede63bc1a8281ed5c8e1da314d50bc76 \ 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..96b87be327e93 100644 --- a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java +++ b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java @@ -38,44 +38,42 @@ import com.sun.net.httpserver.HttpsServer; import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.junit.AfterClass; import org.junit.BeforeClass; 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; 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 static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; /** * Integration test to validate the builder builds a client with the correct configuration */ -public class RestClientBuilderIntegTests extends RestClientTestCase { +public class RestClientBuilderIntegTests extends RestClientTestCase implements RestClientFipsAwareTestCase { private static HttpsServer httpsServer; @BeforeClass public static void startHttpServer() throws Exception { httpsServer = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); - httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext())); + httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext(true, KeyStoreType.BCFKS))); httpsServer.createContext("/", new ResponseHandler()); httpsServer.start(); } @@ -95,19 +93,23 @@ public static void stopHttpServers() throws IOException { } public void testBuilderUsesDefaultSSLContext() throws Exception { - assumeFalse("https://github.com/elastic/elasticsearch/issues/49094", inFipsJvm()); + makeRequest(); + } + + @Override + public void makeRequest(KeyStoreType keyStoreType) throws Exception { final SSLContext defaultSSLContext = SSLContext.getDefault(); try { try (RestClient client = buildRestClient()) { 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)); } } - SSLContext.setDefault(getSslContext()); + SSLContext.setDefault(getSslContext(false, keyStoreType)); try (RestClient client = buildRestClient()) { Response response = client.performRequest(new Request("GET", "/")); assertEquals(200, response.getStatusLine().getStatusCode()); @@ -122,34 +124,38 @@ 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, 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 certFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test.crt"); - InputStream keyStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test_truststore.jks") + InputStream trustStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test_truststore" + fileExtension); + InputStream keyStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/testks" + fileExtension) ) { - // 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); + 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); + trustStore.load(trustStoreFile, password); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + SSLContextBuilder 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/RestClientFipsAwareTestCase.java b/client/rest/src/test/java/org/opensearch/client/RestClientFipsAwareTestCase.java new file mode 100644 index 0000000000000..2f8e4718da5f8 --- /dev/null +++ b/client/rest/src/test/java/org/opensearch/client/RestClientFipsAwareTestCase.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.client; + +import org.opensearch.common.ssl.KeyStoreType; + +import static org.opensearch.client.RestClientTestCase.inFipsJvm; + +public interface RestClientFipsAwareTestCase { + + default void makeRequest() throws Exception { + if (inFipsJvm()) { + makeRequest(KeyStoreType.BCFKS); + } else { + makeRequest(KeyStoreType.JKS); + } + } + + void makeRequest(KeyStoreType keyStoreType) throws Exception; +} diff --git a/client/rest/src/test/java/org/opensearch/client/documentation/RestClientDocumentation.java b/client/rest/src/test/java/org/opensearch/client/documentation/RestClientDocumentation.java index d9c82307cae8a..4a642cc6fe300 100644 --- a/client/rest/src/test/java/org/opensearch/client/documentation/RestClientDocumentation.java +++ b/client/rest/src/test/java/org/opensearch/client/documentation/RestClientDocumentation.java @@ -67,6 +67,7 @@ import org.opensearch.client.RestClient; import org.opensearch.client.RestClientBuilder; import org.opensearch.client.RestClientBuilder.HttpClientConfigCallback; +import org.opensearch.common.ssl.KeyStoreFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -84,6 +85,8 @@ import java.util.Iterator; import java.util.concurrent.CountDownLatch; +import static org.opensearch.common.ssl.KeyStoreType.PKCS_12; + /** * This class is used to generate the Java low-level REST client documentation. * You need to wrap your code between two tags like: @@ -436,7 +439,7 @@ public HttpAsyncClientBuilder customizeHttpClient( String keyStorePass = ""; //tag::rest-client-config-encrypted-communication Path trustStorePath = Paths.get("/path/to/truststore.p12"); - KeyStore truststore = KeyStore.getInstance("pkcs12"); + KeyStore truststore = KeyStoreFactory.getInstance(PKCS_12); try (InputStream is = Files.newInputStream(trustStorePath)) { truststore.load(is, keyStorePass.toCharArray()); } @@ -478,7 +481,7 @@ public TlsDetails create(final SSLEngine sslEngine) { try (InputStream is = Files.newInputStream(caCertificatePath)) { trustedCa = factory.generateCertificate(is); } - KeyStore trustStore = KeyStore.getInstance("pkcs12"); + KeyStore trustStore = KeyStoreFactory.getInstance(PKCS_12); trustStore.load(null, null); trustStore.setCertificateEntry("ca", trustedCa); SSLContextBuilder sslContextBuilder = SSLContexts.custom() @@ -516,8 +519,8 @@ public TlsDetails create(final SSLEngine sslEngine) { //tag::rest-client-config-mutual-tls-authentication Path trustStorePath = Paths.get("/path/to/your/truststore.p12"); Path keyStorePath = Paths.get("/path/to/your/keystore.p12"); - KeyStore trustStore = KeyStore.getInstance("pkcs12"); - KeyStore keyStore = KeyStore.getInstance("pkcs12"); + KeyStore trustStore = KeyStoreFactory.getInstance(PKCS_12); + KeyStore keyStore = KeyStoreFactory.getInstance(PKCS_12); try (InputStream is = Files.newInputStream(trustStorePath)) { trustStore.load(is, trustStorePass.toCharArray()); } diff --git a/client/rest/src/test/resources/test_truststore.bcfks b/client/rest/src/test/resources/test_truststore.bcfks new file mode 100644 index 0000000000000..dfa168caf70ef Binary files /dev/null and b/client/rest/src/test/resources/test_truststore.bcfks differ diff --git a/client/rest/src/test/resources/testks.bcfks b/client/rest/src/test/resources/testks.bcfks new file mode 100644 index 0000000000000..988242f0db6b8 Binary files /dev/null and b/client/rest/src/test/resources/testks.bcfks differ diff --git a/client/test/build.gradle b/client/test/build.gradle index b77865df6decf..afecaf685c51c 100644 --- a/client/test/build.gradle +++ b/client/test/build.gradle @@ -43,6 +43,8 @@ dependencies { api "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" api "junit:junit:${versions.junit}" api "org.hamcrest:hamcrest:${versions.hamcrest}" + api "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" + runtimeOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" } tasks.named('forbiddenApisMain').configure { diff --git a/client/test/src/main/java/org/opensearch/client/RestClientTestCase.java b/client/test/src/main/java/org/opensearch/client/RestClientTestCase.java index b4eacdbf88827..9d52ee4cb3da7 100644 --- a/client/test/src/main/java/org/opensearch/client/RestClientTestCase.java +++ b/client/test/src/main/java/org/opensearch/client/RestClientTestCase.java @@ -45,6 +45,7 @@ import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite; import org.apache.hc.core5.http.Header; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import java.util.ArrayList; import java.util.HashMap; @@ -116,6 +117,10 @@ protected static void assertHeaders( assertTrue("some headers that were sent weren't returned " + expectedHeaders, expectedHeaders.isEmpty()); } + protected static boolean inFipsJvm() { + return CryptoServicesRegistrar.isInApprovedOnlyMode(); + } + private static void addValueToListEntry(final Map> map, final String name, final String value) { List values = map.get(name); if (values == null) { @@ -125,7 +130,4 @@ private static void addValueToListEntry(final Map> map, fin values.add(value); } - public static boolean inFipsJvm() { - return Boolean.parseBoolean(System.getProperty("tests.fips.enabled")); - } } diff --git a/distribution/src/bin/opensearch-cli b/distribution/src/bin/opensearch-cli index 19f5d3e46d6c4..0e7cbc23a31d6 100644 --- a/distribution/src/bin/opensearch-cli +++ b/distribution/src/bin/opensearch-cli @@ -20,6 +20,12 @@ done # avoid stealing many CPU cycles; a user can override by setting OPENSEARCH_JAVA_OPTS OPENSEARCH_JAVA_OPTS="-Xms4m -Xmx64m -XX:+UseSerialGC ${OPENSEARCH_JAVA_OPTS}" +if [ "$OPENSEARCH_CRYPTO_STANDARD" = "FIPS-140-3" ]; then + OPENSEARCH_JAVA_OPTS="-Dorg.bouncycastle.fips.approved_only=true \ + -Djava.security.properties=${OPENSEARCH_PATH_CONF}/fips_java.security \ + ${OPENSEARCH_JAVA_OPTS}" +fi + exec \ "$JAVA" \ "$XSHARE" \ diff --git a/distribution/src/bin/opensearch-cli.bat b/distribution/src/bin/opensearch-cli.bat index f080346a4478a..4eb17d146393d 100644 --- a/distribution/src/bin/opensearch-cli.bat +++ b/distribution/src/bin/opensearch-cli.bat @@ -16,6 +16,12 @@ rem use a small heap size for the CLI tools, and thus the serial collector to rem avoid stealing many CPU cycles; a user can override by setting OPENSEARCH_JAVA_OPTS set OPENSEARCH_JAVA_OPTS=-Xms4m -Xmx64m -XX:+UseSerialGC %OPENSEARCH_JAVA_OPTS% +if "%OPENSEARCH_CRYPTO_STANDARD%"=="FIPS-140-3" ( + set OPENSEARCH_JAVA_OPTS=-Dorg.bouncycastle.fips.approved_only=true ^ + -Djava.security.properties="%OPENSEARCH_PATH_CONF%\fips_java.security" ^ + %OPENSEARCH_JAVA_OPTS% +) + "%JAVA%" ^ %OPENSEARCH_JAVA_OPTS% ^ -Dopensearch.path.home="%OPENSEARCH_HOME%" ^ diff --git a/distribution/src/config/fips_java.security b/distribution/src/config/fips_java.security new file mode 100644 index 0000000000000..2e5bf13cf8d0e --- /dev/null +++ b/distribution/src/config/fips_java.security @@ -0,0 +1,55 @@ +# 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 + +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 +jdk.tls.legacyAlgorithms= \ + K_NULL, C_NULL, M_NULL, \ + DH_anon, ECDH_anon, \ + RC4_128, RC4_40, DES_CBC, DES40_CBC, \ + 3DES_EDE_CBC +jdk.tls.keyLimits=AES/GCM/NoPadding KeyUpdate 2^37 + +crypto.policy=unlimited + +jdk.xml.dsig.secureValidationPolicy=\ + disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\ + disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\ + 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/buildSrc/src/main/resources/fips_java_bcjsse_11.security b/distribution/src/config/java.security similarity index 79% rename from buildSrc/src/main/resources/fips_java_bcjsse_11.security rename to distribution/src/config/java.security index e6a025e7eb10d..5ddbbde240f8f 100644 --- a/buildSrc/src/main/resources/fips_java_bcjsse_11.security +++ b/distribution/src/config/java.security @@ -1,43 +1,47 @@ -# 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 non-approved 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.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=BCFKS keystore.type.compat=true + package.access=sun.misc.,\ sun.reflect. package.definition=sun.misc.,\ sun.reflect. + security.overridePropertiesFile=true + ssl.KeyManagerFactory.algorithm=PKIX ssl.TrustManagerFactory.algorithm=PKIX + networkaddress.cache.negative.ttl=10 krb5.kdc.bad.policy = tryLast -jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer, \ - RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224 -jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, \ - DSA keySize < 1024 -jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 1024, \ - EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC + +jdk.certpath.disabledAlgorithms=MD2, MD5, 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/TR/1999/REC-xslt-19991116,\ disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\ disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\ disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\ @@ -49,5 +53,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/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 3d188590d5c47..e017ac2241fc9 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddFileKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddFileKeyStoreCommandTests.java @@ -78,6 +78,7 @@ private void addFile(KeyStoreWrapper keystore, String setting, Path file, String } public void testMissingCreateWithEmptyPasswordWhenPrompted() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; Path file1 = createRandomFile(); terminal.addTextInput("y"); @@ -86,6 +87,7 @@ public void testMissingCreateWithEmptyPasswordWhenPrompted() throws Exception { } public void testMissingCreateWithEmptyPasswordWithoutPromptIfForced() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; Path file1 = createRandomFile(); execute("-f", "foo", file1.toString()); @@ -93,7 +95,8 @@ public void testMissingCreateWithEmptyPasswordWithoutPromptIfForced() throws Exc } public void testMissingNoCreate() throws Exception { - terminal.addSecretInput(randomFrom("", "keystorepassword")); + var password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); + terminal.addSecretInput(password); terminal.addTextInput("n"); // explicit no execute("foo"); assertNull(KeyStoreWrapper.load(env.configDir())); @@ -211,20 +214,17 @@ 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 { + 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 22012d1f44986..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 @@ -72,21 +72,18 @@ 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") + ) + ); } public void testMissingPromptCreateWithoutPasswordWhenPrompted() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); terminal.addTextInput("y"); terminal.addSecretInput("bar"); execute("foo"); @@ -94,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", ""); @@ -265,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)); @@ -274,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 1ce57332a9a31..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"); @@ -104,16 +106,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/CreateKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java index 5a06bc2400176..1596f24c024fd 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java @@ -58,7 +58,7 @@ protected Environment createEnv(Map settings) throws UserExcepti } public void testNotMatchingPasswords() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput("notthekeystorepasswordyouarelookingfor"); UserException e = expectThrows(UserException.class, () -> execute(randomFrom("-p", "--password"))); @@ -67,32 +67,34 @@ public void testNotMatchingPasswords() throws Exception { } public void testDefaultNotPromptForPassword() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); execute(); Path configDir = env.configDir(); assertNotNull(KeyStoreWrapper.load(configDir)); } public void testPosix() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput(password); - execute(); + execute(randomFrom("-p", "--password")); Path configDir = env.configDir(); assertNotNull(KeyStoreWrapper.load(configDir)); } public void testNotPosix() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput(password); env = setupEnv(false, fileSystems); - execute(); + execute(randomFrom("-p", "--password")); Path configDir = env.configDir(); assertNotNull(KeyStoreWrapper.load(configDir)); } public void testOverwrite() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); + Path keystoreFile = KeyStoreWrapper.keystorePath(env.configDir()); byte[] content = "not a keystore".getBytes(StandardCharsets.UTF_8); Files.write(keystoreFile, content); @@ -106,9 +108,9 @@ public void testOverwrite() throws Exception { assertArrayEquals(content, Files.readAllBytes(keystoreFile)); terminal.addTextInput("y"); - terminal.addSecretInput(password); - terminal.addSecretInput(password); - execute(); + terminal.addSecretInput(password); // enter password + terminal.addSecretInput(password); // enter password again + execute(randomFrom("-p", "--password")); assertNotNull(KeyStoreWrapper.load(env.configDir())); } } diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java index 9ebc92c55530b..39f645e587d3a 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java @@ -61,6 +61,7 @@ public void testFailsWithNoKeystore() throws Exception { } public void testFailsWhenKeystoreLacksPassword() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); createKeystore(""); UserException e = expectThrows(UserException.class, this::execute); assertEquals("Unexpected exit code", HasPasswordKeyStoreCommand.NO_PASSWORD_EXIT_CODE, e.exitCode); @@ -68,13 +69,13 @@ public void testFailsWhenKeystoreLacksPassword() throws Exception { } public void testSucceedsWhenKeystoreHasPassword() throws Exception { - createKeystore("password"); + createKeystore("thenewpassword"); String output = execute(); assertThat(output, containsString("Keystore is password-protected")); } public void testSilentSucceedsWhenKeystoreHasPassword() throws Exception { - createKeystore("password"); + createKeystore("thenewpassword"); String output = execute("--silent"); assertThat(output, is(emptyString())); } diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/KeyStoreWrapperTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/KeyStoreWrapperTests.java index efb833e8fd94a..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 @@ -38,8 +38,10 @@ import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexOutput; import org.apache.lucene.store.NIOFSDirectory; -import org.opensearch.common.Randomness; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.settings.KeyStoreWrapper; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.common.util.io.IOUtils; import org.opensearch.core.common.settings.SecureString; import org.opensearch.env.Environment; @@ -68,14 +70,20 @@ 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; 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 +93,8 @@ public class KeyStoreWrapperTests extends OpenSearchTestCase { + String STRONG_PASSWORD = "6!6428DQXwPpi7@$ggeg/="; // has to be at least 112 bit long. + Supplier passphraseSupplier = () -> inFipsJvm() ? STRONG_PASSWORD.toCharArray() : new char[0]; Environment env; List fileSystems = new ArrayList<>(); @@ -105,9 +115,9 @@ public void testFileSettingExhaustiveBytes() throws Exception { bytes[i] = (byte) i; } keystore.setFile("foo", bytes); - keystore.save(env.configDir(), new char[0]); + keystore.save(env.configDir(), passphraseSupplier.get()); keystore = KeyStoreWrapper.load(env.configDir()); - keystore.decrypt(new char[0]); + keystore.decrypt(passphraseSupplier.get()); try (InputStream stream = keystore.getFile("foo")) { for (int i = 0; i < 256; ++i) { int got = stream.read(); @@ -127,23 +137,19 @@ 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(), + anyOf( + containsString("Provided keystore password was incorrect"), + containsString("Keystore has been corrupted or tampered with") + ) ); - 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")); - } } public void testCannotReadStringFromClosedKeystore() throws Exception { @@ -185,12 +191,12 @@ public void testValueSHA256Digest() throws Exception { public void testUpgradeNoop() throws Exception { KeyStoreWrapper keystore = KeyStoreWrapper.create(); SecureString seed = keystore.getString(KeyStoreWrapper.SEED_SETTING.getKey()); - keystore.save(env.configDir(), new char[0]); + keystore.save(env.configDir(), passphraseSupplier.get()); // upgrade does not overwrite seed KeyStoreWrapper.upgrade(keystore, env.configDir(), new char[0]); assertEquals(seed.toString(), keystore.getString(KeyStoreWrapper.SEED_SETTING.getKey()).toString()); keystore = KeyStoreWrapper.load(env.configDir()); - keystore.decrypt(new char[0]); + keystore.decrypt(passphraseSupplier.get()); assertEquals(seed.toString(), keystore.getString(KeyStoreWrapper.SEED_SETTING.getKey()).toString()); } @@ -200,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]; @@ -217,7 +223,7 @@ public void testFailWhenCannotConsumeSecretStream() throws Exception { } KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir); - SecurityException e = expectThrows(SecurityException.class, () -> keystore.decrypt(new char[0])); + SecurityException e = expectThrows(SecurityException.class, () -> keystore.decrypt(passphraseSupplier.get())); assertThat(e.getMessage(), containsString("Keystore has been corrupted or tampered with")); assertThat(e.getCause(), instanceOf(EOFException.class)); } @@ -228,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]; @@ -257,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]; @@ -274,7 +280,7 @@ public void testFailWhenSecretStreamNotConsumed() throws Exception { } KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir); - SecurityException e = expectThrows(SecurityException.class, () -> keystore.decrypt(new char[0])); + SecurityException e = expectThrows(SecurityException.class, () -> keystore.decrypt(passphraseSupplier.get())); assertThat(e.getMessage(), containsString("Keystore has been corrupted or tampered with")); } @@ -284,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]; @@ -305,7 +311,7 @@ public void testFailWhenEncryptedBytesStreamIsNotConsumed() throws Exception { } private CipherOutputStream getCipherStream(ByteArrayOutputStream bytes, byte[] salt, byte[] iv) throws Exception { - PBEKeySpec keySpec = new PBEKeySpec(new char[0], salt, 10000, 128); + PBEKeySpec keySpec = new PBEKeySpec(passphraseSupplier.get(), salt, 10000, 128); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); SecretKey secretKey = keyFactory.generateSecret(keySpec); SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); @@ -345,12 +351,12 @@ private void possiblyAlterEncryptedBytes( public void testUpgradeAddsSeed() throws Exception { KeyStoreWrapper keystore = KeyStoreWrapper.create(); keystore.remove(KeyStoreWrapper.SEED_SETTING.getKey()); - keystore.save(env.configDir(), new char[0]); - KeyStoreWrapper.upgrade(keystore, env.configDir(), new char[0]); + keystore.save(env.configDir(), passphraseSupplier.get()); + KeyStoreWrapper.upgrade(keystore, env.configDir(), passphraseSupplier.get()); SecureString seed = keystore.getString(KeyStoreWrapper.SEED_SETTING.getKey()); assertNotNull(seed); keystore = KeyStoreWrapper.load(env.configDir()); - keystore.decrypt(new char[0]); + keystore.decrypt(passphraseSupplier.get()); assertEquals(seed.toString(), keystore.getString(KeyStoreWrapper.SEED_SETTING.getKey()).toString()); } @@ -364,31 +370,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"); - KeyStore keystore = KeyStore.getInstance("PKCS12"); - 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(NoSuchProviderException.class, this::generateV1); + assertThat(e.getMessage(), containsString("no such provider: SunJCE")); + } - 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(NoSuchProviderException.class, this::generateV2); + assertThat(e.getMessage(), containsString("no such provider: SunJCE")); + } + public void testBackcompatV1() throws Exception { + assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); + generateV1(); + Path configDir = env.configDir(); KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir); keystore.decrypt(new char[0]); SecureString testValue = keystore.getString("string_setting"); @@ -397,47 +396,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"); - KeyStore keystore = KeyStore.getInstance("PKCS12"); - 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"); @@ -459,12 +419,12 @@ public void testStringAndFileDistinction() throws Exception { final Path temp = createTempDir(); Files.write(temp.resolve("file_setting"), "file_value".getBytes(StandardCharsets.UTF_8)); wrapper.setFile("file_setting", Files.readAllBytes(temp.resolve("file_setting"))); - wrapper.save(env.configDir(), new char[0]); + wrapper.save(env.configDir(), passphraseSupplier.get()); wrapper.close(); final KeyStoreWrapper afterSave = KeyStoreWrapper.load(env.configDir()); assertNotNull(afterSave); - afterSave.decrypt(new char[0]); + afterSave.decrypt(passphraseSupplier.get()); assertThat(afterSave.getSettingNames(), equalTo(new HashSet<>(Arrays.asList("keystore.seed", "string_setting", "file_setting")))); assertThat(afterSave.getString("string_setting"), equalTo("string_value")); assertThat(toByteArray(afterSave.getFile("string_setting")), equalTo("string_value".getBytes(StandardCharsets.UTF_8))); @@ -497,6 +457,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/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..4ca5e88c47b30 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ListKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ListKeyStoreCommandTests.java @@ -61,7 +61,7 @@ public void testMissing() throws Exception { } public void testEmpty() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password); terminal.addSecretInput(password); execute(); @@ -69,7 +69,7 @@ public void testEmpty() throws Exception { } public void testOne() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password, "foo", "bar"); terminal.addSecretInput(password); execute(); @@ -77,7 +77,7 @@ public void testOne() throws Exception { } public void testMultiple() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password, "foo", "1", "baz", "2", "bar", "3"); terminal.addSecretInput(password); execute(); @@ -90,20 +90,17 @@ 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 { + 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 66d448400d4e3..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 @@ -107,21 +107,17 @@ 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 { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; createKeystore(password, "foo", "bar"); // will not be prompted for a password diff --git a/distribution/tools/launchers/build.gradle b/distribution/tools/launchers/build.gradle index aee205a24dea3..76e447953c95d 100644 --- a/distribution/tools/launchers/build.gradle +++ b/distribution/tools/launchers/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" testImplementation "junit:junit:${versions.junit}" testImplementation "org.hamcrest:hamcrest:${versions.hamcrest}" + testImplementation "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" } base { diff --git a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/JvmOptionsParser.java b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/JvmOptionsParser.java index 533d1f7e782ba..88654e8826a4c 100644 --- a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/JvmOptionsParser.java +++ b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/JvmOptionsParser.java @@ -148,7 +148,7 @@ private List jvmOptions(final Path config, final String opensearchJavaOp final List substitutedJvmOptions = substitutePlaceholders(jvmOptions, Collections.unmodifiableMap(substitutions)); final List ergonomicJvmOptions = JvmErgonomics.choose(substitutedJvmOptions); - final List systemJvmOptions = SystemJvmOptions.systemJvmOptions(); + final List systemJvmOptions = SystemJvmOptions.systemJvmOptions(config, Runtime.version()); final List finalJvmOptions = new ArrayList<>( systemJvmOptions.size() + substitutedJvmOptions.size() + ergonomicJvmOptions.size() ); diff --git a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java index af7138569972a..5349c81a8a851 100644 --- a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java +++ b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java @@ -32,6 +32,9 @@ package org.opensearch.tools.launchers; +import java.io.FileNotFoundException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -39,7 +42,12 @@ final class SystemJvmOptions { - static List systemJvmOptions() { + static final String OPENSEARCH_CRYPTO_STANDARD = "OPENSEARCH_CRYPTO_STANDARD"; + static final String FIPS_140_3 = "FIPS-140-3"; + static final boolean IS_IN_FIPS_JVM = FIPS_140_3.equals(System.getenv(OPENSEARCH_CRYPTO_STANDARD)) + || "true".equalsIgnoreCase(System.getProperty("org.bouncycastle.fips.approved_only")); + + static List systemJvmOptions(final Path config, Runtime.Version runtimeVersion) throws FileNotFoundException { return Collections.unmodifiableList( Arrays.asList( /* @@ -68,7 +76,7 @@ static List systemJvmOptions() { */ "-XX:-OmitStackTraceInFastThrow", // enable helpful NullPointerExceptions (https://openjdk.java.net/jeps/358), if they are supported - maybeShowCodeDetailsInExceptionMessages(), + maybeShowCodeDetailsInExceptionMessages(runtimeVersion), // flags to configure Netty "-Dio.netty.noUnsafe=true", "-Dio.netty.noKeySetOptimization=true", @@ -77,23 +85,39 @@ static List systemJvmOptions() { // log4j 2 "-Dlog4j.shutdownHookEnabled=false", "-Dlog4j2.disable.jmx=true", - // security manager - allowSecurityManagerOption(), + // security settings + enableFips(), + allowSecurityManagerOption(runtimeVersion), + loadJavaSecurityProperties(config), javaLocaleProviders() ) ).stream().filter(e -> e.isEmpty() == false).collect(Collectors.toList()); } - private static String allowSecurityManagerOption() { - if (Runtime.version().feature() > 17) { + private static String enableFips() { + return IS_IN_FIPS_JVM ? "-Dorg.bouncycastle.fips.approved_only=true" : ""; + } + + private static String loadJavaSecurityProperties(final Path config) throws FileNotFoundException { + String securityFile = IS_IN_FIPS_JVM ? "fips_java.security" : "java.security"; + var securityFilePath = config.resolve(securityFile); + + if (!Files.exists(securityFilePath)) { + throw new FileNotFoundException("Security file not found: " + securityFilePath.toAbsolutePath()); + } + return "-Djava.security.properties=" + securityFilePath.toAbsolutePath(); + } + + private static String allowSecurityManagerOption(Runtime.Version runtimeVersion) { + if (runtimeVersion.feature() > 17) { return "-Djava.security.manager=allow"; } else { return ""; } } - private static String maybeShowCodeDetailsInExceptionMessages() { - if (Runtime.version().feature() >= 14) { + private static String maybeShowCodeDetailsInExceptionMessages(Runtime.Version runtimeVersion) { + if (runtimeVersion.feature() >= 14) { return "-XX:+ShowCodeDetailsInExceptionMessages"; } else { return ""; diff --git a/distribution/tools/launchers/src/test/java/org/opensearch/tools/launchers/SystemJvmOptionsTests.java b/distribution/tools/launchers/src/test/java/org/opensearch/tools/launchers/SystemJvmOptionsTests.java new file mode 100644 index 0000000000000..fb9f0bcbd5936 --- /dev/null +++ b/distribution/tools/launchers/src/test/java/org/opensearch/tools/launchers/SystemJvmOptionsTests.java @@ -0,0 +1,87 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.tools.launchers; + +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.junit.After; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThrows; + +public class SystemJvmOptionsTests extends LaunchersTestCase { + + private static final String FILE_NAME = CryptoServicesRegistrar.isInApprovedOnlyMode() ? "fips_java.security" : "java.security"; + private static final int MIN_RUNTIME_VERSION = 11; + private static final int MAX_RUNTIME_VERSION = 21; + + private Path tempFile; + + @After + public void tearDown() throws IOException { + Files.deleteIfExists(tempFile); + } + + public void testSetJavaSecurityProperties() throws Exception { + createSecurityFile(FILE_NAME); + var jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir(), Runtime.version()); + assertThat(jvmOptions, hasItem("-Djava.security.properties=" + globalTempDir().toAbsolutePath() + "/" + FILE_NAME)); + } + + public void testFailSetJavaSecurityProperties() throws Exception { + createSecurityFile("unknown.security"); + assertThrows( + FileNotFoundException.class, + () -> SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), Runtime.version()) + ); + } + + public void testFipsOption() throws Exception { + createSecurityFile(FILE_NAME); + var jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), Runtime.version()); + var fipsProperty = "-Dorg.bouncycastle.fips.approved_only=true"; + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + assertThat(jvmOptions, hasItem(fipsProperty)); + } else { + assertThat(jvmOptions, not(hasItem(fipsProperty))); + } + } + + public void testSecurityManagerOption() throws Exception { + createSecurityFile(FILE_NAME); + var runtimeVersion = Runtime.Version.parse(String.valueOf(randomIntBetween(MIN_RUNTIME_VERSION, 17))); + var jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), runtimeVersion); + assertThat(jvmOptions, not(hasItem("-Djava.security.manager=allow"))); + + runtimeVersion = Runtime.Version.parse(String.valueOf(randomIntBetween(18, MAX_RUNTIME_VERSION))); + jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), runtimeVersion); + assertThat(jvmOptions, hasItem("-Djava.security.manager=allow")); + } + + public void testShowCodeDetailsOption() throws Exception { + createSecurityFile(FILE_NAME); + var runtimeVersion = Runtime.Version.parse(String.valueOf(randomIntBetween(MIN_RUNTIME_VERSION, 13))); + var jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), runtimeVersion); + assertThat(jvmOptions, not(hasItem("-XX:+ShowCodeDetailsInExceptionMessages"))); + + runtimeVersion = Runtime.Version.parse(String.valueOf(randomIntBetween(14, MAX_RUNTIME_VERSION))); + jvmOptions = SystemJvmOptions.systemJvmOptions(globalTempDir().toAbsolutePath(), runtimeVersion); + assertThat(jvmOptions, hasItem("-XX:+ShowCodeDetailsInExceptionMessages")); + } + + private void createSecurityFile(String fileName) throws Exception { + tempFile = Files.createFile(globalTempDir().resolve(fileName)); + } +} diff --git a/distribution/tools/plugin-cli/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/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/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/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/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0b6886ce011b2..b8cd22663603f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -58,7 +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="1.78" +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" @@ -115,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/libs/common/build.gradle b/libs/common/build.gradle index 576e78bbe19f4..41c145bc890fb 100644 --- a/libs/common/build.gradle +++ b/libs/common/build.gradle @@ -25,7 +25,6 @@ base { dependencies { // This dependency is used only by :libs:core for null-checking interop with other tools compileOnly "com.google.code.findbugs:jsr305:3.0.2" - /******* * !!!! NO THIRD PARTY DEPENDENCIES !!!! *******/ diff --git a/libs/ssl-config/build.gradle b/libs/ssl-config/build.gradle index 3226ec12ff6f7..ee2c153690b43 100644 --- a/libs/ssl-config/build.gradle +++ b/libs/ssl-config/build.gradle @@ -35,6 +35,11 @@ apply plugin: "opensearch.publish" dependencies { api project(':libs:opensearch-common') + // 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' } @@ -49,11 +54,8 @@ tasks.named('forbiddenApisMain').configure { replaceSignatureFiles 'jdk-signatures' } -forbiddenPatterns { - exclude '**/*.key' - exclude '**/*.pem' - exclude '**/*.p12' - exclude '**/*.jks' +tasks.named("dependencyLicenses").configure { + mapping from: /bc.*/, to: 'bouncycastle' } tasks.test { 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/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 859b74b200dc6..4b26c123ac026 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/DefaultJdkTrustConfig.java @@ -95,7 +95,7 @@ public X509ExtendedTrustManager createTrustManager() { private KeyStore getSystemTrustStore() { if (isPkcs11Truststore(systemProperties) && trustStorePassword != null) { try { - KeyStore keyStore = KeyStore.getInstance("PKCS11"); + KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_11); keyStore.load(null, trustStorePassword); return keyStore; } catch (GeneralSecurityException | IOException e) { diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreFactory.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreFactory.java new file mode 100644 index 0000000000000..2cc41065aecdd --- /dev/null +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreFactory.java @@ -0,0 +1,74 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.ssl; + +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.opensearch.common.SuppressForbidden; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchProviderException; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.opensearch.common.ssl.KeyStoreType.SECURE_KEYSTORE_TYPES; +import static org.opensearch.common.ssl.KeyStoreType.inferStoreType; + +/** + * Restricts types of keystores to PKCS#11 and BCFKS when running in FIPS JVM. + * Returns the keystore from specified provider or otherwise follows the priority of + * declared security providers and their support for different keystores. + */ +public final class KeyStoreFactory { + + private static final String FIPS_PROVIDER = "BCFIPS"; + + /** + * Makes best guess about the "type" (see {@link KeyStore#getType()}) of the keystore file located at the given {@code Path}. + * This method only references the file name of the keystore, it does not look at its contents. + */ + public static KeyStore getInstanceBasedOnFileExtension(String filePath) { + return getInstance(inferStoreType(filePath)); + } + + public static KeyStore getInstance(KeyStoreType type) { + return getInstance(type, null); + } + + /** + * Creates KeyStore instance with submitted provider and type. In FIPS enabled environment the parameters are limited to FIPS supported + * KeyStore-Types (see {@link KeyStoreType#SECURE_KEYSTORE_TYPES}) and also FIPS provider (see {@link KeyStoreFactory#FIPS_PROVIDER}). + */ + public static KeyStore getInstance(KeyStoreType type, String provider) { + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + if (!SECURE_KEYSTORE_TYPES.contains(type)) { + var secureKeyStoreNames = SECURE_KEYSTORE_TYPES.stream().map(KeyStoreType::name).collect(Collectors.joining(", ")); + throw new SecurityException("Only " + secureKeyStoreNames + " keystores are allowed in FIPS JVM"); + } + if (provider != null && !Objects.equals(provider, FIPS_PROVIDER)) { + throw new SecurityException("FIPS JVM does not support creation of KeyStore with any other provider than " + FIPS_PROVIDER); + } + provider = FIPS_PROVIDER; + } + return get(type, provider); + } + + @SuppressForbidden(reason = "centralized instantiation of a KeyStore") + private static KeyStore get(KeyStoreType type, String provider) { + try { + if (provider == null) { + return KeyStore.getInstance(type.getJcaName()); + } + return KeyStore.getInstance(type.getJcaName(), provider); + } catch (KeyStoreException | NoSuchProviderException e) { + throw new SecurityException(e); + } + } + +} diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreType.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreType.java new file mode 100644 index 0000000000000..df106f1b5017e --- /dev/null +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreType.java @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.ssl; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Enum representing the types of KeyStores supported by {@link KeyStoreFactory}. + */ +public enum KeyStoreType { + + JKS("JKS"), + PKCS_12("PKCS12"), + PKCS_11("PKCS11"), + BCFKS("BCFKS"); + + public static final Map> TYPE_TO_EXTENSION_MAP = new HashMap<>(); + + static { + TYPE_TO_EXTENSION_MAP.put(JKS, List.of(".jks", ".ks")); + TYPE_TO_EXTENSION_MAP.put(PKCS_12, List.of(".p12", ".pkcs12", ".pfx")); + TYPE_TO_EXTENSION_MAP.put(BCFKS, List.of(".bcfks")); // Bouncy Castle FIPS Keystore + } + + /** + * Specifies KeyStore formats that are appropriate for use in a FIPS-compliant JVM: + * - BCFKS KeyStore is specifically designed for FIPS compliance. + * - PKCS#11 is vendor-specific and requires proper configuration to operate in FIPS mode. + */ + public static final List SECURE_KEYSTORE_TYPES = List.of(PKCS_11, BCFKS); + + private final String jcaName; + + KeyStoreType(String jks) { + jcaName = jks; + } + + public String getJcaName() { + return jcaName; + } + + public static KeyStoreType inferStoreType(String filePath) { + return TYPE_TO_EXTENSION_MAP.entrySet() + .stream() + .filter(entry -> entry.getValue().stream().anyMatch(filePath::endsWith)) + .map(Map.Entry::getKey) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Unknown keystore type for file path: " + filePath)); + } + + public static KeyStoreType getByJcaName(String value) { + return Stream.of(KeyStoreType.values()).filter(type -> type.getJcaName().equals(value)).findFirst().orElse(null); + } + +} diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java index b6b6cdd90af14..48d890c25525d 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/KeyStoreUtil.java @@ -52,7 +52,7 @@ import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.Collection; -import java.util.Locale; +import java.util.Objects; /** * A variety of utility methods for working with or constructing {@link KeyStore} instances. @@ -63,42 +63,30 @@ private KeyStoreUtil() { throw new IllegalStateException("Utility class should not be instantiated"); } - /** - * Make a best guess about the "type" (see {@link KeyStore#getType()}) of the keystore file located at the given {@code Path}. - * This method only references the file name of the keystore, it does not look at its contents. - */ - static String inferKeyStoreType(Path path) { - String name = path == null ? "" : path.toString().toLowerCase(Locale.ROOT); - if (name.endsWith(".p12") || name.endsWith(".pfx") || name.endsWith(".pkcs12")) { - return "PKCS12"; - } else { - return "jks"; - } - } - /** * Read the given keystore file. * * @throws SslConfigException If there is a problem reading from the provided path * @throws GeneralSecurityException If there is a problem with the keystore contents */ - static KeyStore readKeyStore(Path path, String type, char[] password) throws GeneralSecurityException { + static KeyStore readKeyStore(Path path, KeyStoreType type, char[] password) throws GeneralSecurityException { if (Files.notExists(path)) { throw new SslConfigException( "cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + "] because the file does not exist" ); } try { - KeyStore keyStore = KeyStore.getInstance(type); + KeyStore keyStore = KeyStoreFactory.getInstance(type); try (InputStream in = Files.newInputStream(path)) { keyStore.load(in, password); } return keyStore; } catch (IOException e) { - throw new SslConfigException( - "cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + "] - " + e.getMessage(), - e - ); + var finalMessage = e.getMessage(); + if (Objects.equals(e.getMessage(), "BCFKS KeyStore corrupted: MAC calculation failed.")) { + finalMessage = "incorrect password or corrupt file."; + } + throw new SslConfigException("cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + "] - " + finalMessage, e); } } @@ -133,7 +121,7 @@ static KeyStore buildTrustStore(Iterable certificates) throws Gener } private static KeyStore buildNewKeyStore() throws GeneralSecurityException { - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); try { keyStore.load(null, null); } catch (IOException e) { diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java index 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..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 @@ -32,628 +32,135 @@ 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.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.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.Locale; 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 static final String BCFIPS = "BCFIPS"; - private PemUtils() { + PemUtils() { throw new IllegalStateException("Utility class should not be instantiated"); } /** * 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 * @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 certificate 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(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) + .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()); - } - 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"); + throw new SslConfigException( + String.format( + Locale.ROOT, + "error parsing private key [%s], invalid encrypted private key class: [%s]", + keyPath.toAbsolutePath(), + object.getClass().getName() + ) + ); } - 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 ) + * Supports PEM files that includes parameters. * - * @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"); + * @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 == null) { // ignore unknown objects; + continue; } - 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. - * - * @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() + "]"); + if (object instanceof ASN1ObjectIdentifier) { // ignore -----BEGIN EC PARAMETERS----- + continue; } - certificates.addAll(parsed); + return object; + } catch (IOException e) { // ignore -----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() + "]. 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 23acb0ff269e2..224699660b65b 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java @@ -32,13 +32,14 @@ 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; 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; @@ -66,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"); @@ -168,8 +164,12 @@ public SSLContext createSslContext() { * {@link #getSupportedProtocols() configured protocols}. */ private String contextProtocol() { - if (supportedProtocols.isEmpty()) { - throw new SslConfigException("no SSL/TLS protocols have been configured"); + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + if (!new HashSet<>(SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS).containsAll(supportedProtocols)) { + throw new SslConfigException( + "in FIPS mode only the following SSL/TLS protocols are allowed: " + SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS + ); + } } for (Entry entry : ORDERED_PROTOCOL_ALGORITHM_MAP.entrySet()) { if (supportedProtocols.contains(entry.getKey())) { diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfigurationLoader.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfigurationLoader.java index 0b06a0692197e..3733b1bcaecc7 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfigurationLoader.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfigurationLoader.java @@ -40,14 +40,14 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Objects; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; -import static org.opensearch.common.ssl.KeyStoreUtil.inferKeyStoreType; -import static org.opensearch.common.ssl.SslConfiguration.ORDERED_PROTOCOL_ALGORITHM_MAP; +import static org.opensearch.common.ssl.KeyStoreType.inferStoreType; import static org.opensearch.common.ssl.SslConfigurationKeys.CERTIFICATE; import static org.opensearch.common.ssl.SslConfigurationKeys.CERTIFICATE_AUTHORITIES; import static org.opensearch.common.ssl.SslConfigurationKeys.CIPHERS; @@ -84,11 +84,7 @@ */ public abstract class SslConfigurationLoader { - static final List DEFAULT_PROTOCOLS = Collections.unmodifiableList( - ORDERED_PROTOCOL_ALGORITHM_MAP.containsKey("TLSv1.3") - ? Arrays.asList("TLSv1.3", "TLSv1.2", "TLSv1.1") - : Arrays.asList("TLSv1.2", "TLSv1.1") - ); + static final List FIPS_APPROVED_PROTOCOLS = List.of("TLSv1.3", "TLSv1.2"); static final List DEFAULT_CIPHERS = loadDefaultCiphers(); private static final char[] EMPTY_PASSWORD = new char[0]; @@ -119,7 +115,7 @@ public SslConfigurationLoader(String settingPrefix) { this.defaultKeyConfig = EmptyKeyConfig.INSTANCE; this.defaultVerificationMode = SslVerificationMode.FULL; this.defaultClientAuth = SslClientAuthenticationMode.OPTIONAL; - this.defaultProtocols = DEFAULT_PROTOCOLS; + this.defaultProtocols = FIPS_APPROVED_PROTOCOLS; this.defaultCiphers = DEFAULT_CIPHERS; } @@ -167,7 +163,7 @@ public void setDefaultCiphers(List defaultCiphers) { /** * Change the default SSL/TLS protocol list. - * The initial protocol list is defined by {@link #DEFAULT_PROTOCOLS} + * The initial protocol list is defined by {@link #FIPS_APPROVED_PROTOCOLS} */ public void setDefaultProtocols(List defaultProtocols) { this.defaultProtocols = defaultProtocols; @@ -248,7 +244,10 @@ private SslTrustConfig buildTrustConfig(Path basePath, SslVerificationMode verif } if (trustStorePath != null) { final char[] password = resolvePasswordSetting(TRUSTSTORE_SECURE_PASSWORD, TRUSTSTORE_LEGACY_PASSWORD); - final String storeType = resolveSetting(TRUSTSTORE_TYPE, Function.identity(), inferKeyStoreType(trustStorePath)); + final Optional maybeStoreType = Optional.ofNullable(resolveSetting(TRUSTSTORE_TYPE, Function.identity(), null)); + final KeyStoreType storeType = maybeStoreType.map(KeyStoreType::getByJcaName) + .orElse(inferStoreType(trustStorePath.toString().toLowerCase(Locale.ROOT))); + final String algorithm = resolveSetting(TRUSTSTORE_ALGORITHM, Function.identity(), TrustManagerFactory.getDefaultAlgorithm()); return new StoreTrustConfig(trustStorePath, password, storeType, algorithm); } @@ -287,7 +286,11 @@ private SslKeyConfig buildKeyConfig(Path basePath) { if (keyPassword.length == 0) { keyPassword = storePassword; } - final String storeType = resolveSetting(KEYSTORE_TYPE, Function.identity(), inferKeyStoreType(keyStorePath)); + + final Optional maybeStoreType = Optional.ofNullable(resolveSetting(KEYSTORE_TYPE, Function.identity(), null)); + final KeyStoreType storeType = maybeStoreType.map(KeyStoreType::getByJcaName) + .orElse(inferStoreType(keyStorePath.toString().toLowerCase(Locale.ROOT))); + final String algorithm = resolveSetting(KEYSTORE_ALGORITHM, Function.identity(), KeyManagerFactory.getDefaultAlgorithm()); return new StoreKeyConfig(keyStorePath, storePassword, storeType, keyPassword, algorithm); } @@ -360,14 +363,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..858f68b3f19a2 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreKeyConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreKeyConfig.java @@ -51,7 +51,7 @@ public class StoreKeyConfig implements SslKeyConfig { private final Path path; private final char[] storePassword; - private final String type; + private final KeyStoreType type; private final char[] keyPassword; private final String algorithm; @@ -59,12 +59,12 @@ public class StoreKeyConfig implements SslKeyConfig { * @param path The path to the keystore file * @param storePassword The password for the keystore * @param type The {@link KeyStore#getType() type} of the keystore (typically "PKCS12" or "jks"). - * See {@link KeyStoreUtil#inferKeyStoreType(Path)}. + * See {@link KeyStoreType#inferStoreType(String)}. * @param keyPassword The password for the key(s) within the keystore * (see {@link javax.net.ssl.KeyManagerFactory#init(KeyStore, char[])}). * @param algorithm The algorithm to use for the Key Manager (see {@link KeyManagerFactory#getAlgorithm()}). */ - StoreKeyConfig(Path path, char[] storePassword, String type, char[] keyPassword, String algorithm) { + StoreKeyConfig(Path path, char[] storePassword, KeyStoreType type, char[] keyPassword, String algorithm) { this.path = path; this.storePassword = storePassword; this.type = type; diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreTrustConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreTrustConfig.java index 556cb052c4391..709ea1adc9acb 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreTrustConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreTrustConfig.java @@ -47,17 +47,17 @@ final class StoreTrustConfig implements SslTrustConfig { private final Path path; private final char[] password; - private final String type; + private final KeyStoreType type; private final String algorithm; /** * @param path The path to the keystore file * @param password The password for the keystore * @param type The {@link KeyStore#getType() type} of the keystore (typically "PKCS12" or "jks"). - * See {@link KeyStoreUtil#inferKeyStoreType(Path)}. + * See {@link KeyStoreType#inferStoreType(String)}. * @param algorithm The algorithm to use for the Trust Manager (see {@link javax.net.ssl.TrustManagerFactory#getAlgorithm()}). */ - StoreTrustConfig(Path path, char[] password, String type, String algorithm) { + StoreTrustConfig(Path path, char[] password, KeyStoreType type, String algorithm) { this.path = path; this.type = type; this.algorithm = algorithm; diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/TrustEverythingConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/TrustEverythingConfig.java index c366210133687..dd58606ec44c4 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/TrustEverythingConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/TrustEverythingConfig.java @@ -32,6 +32,8 @@ package org.opensearch.common.ssl; +import org.bouncycastle.crypto.CryptoServicesRegistrar; + import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; @@ -40,6 +42,7 @@ import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Collections; +import java.util.Locale; /** * A {@link SslTrustConfig} that trusts all certificates. Used when {@link SslVerificationMode#isCertificateVerificationEnabled()} is @@ -90,6 +93,15 @@ public Collection getDependentFiles() { @Override public X509ExtendedTrustManager createTrustManager() { + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + var message = String.format( + Locale.ROOT, + "The use of %s is not permitted in FIPS mode. This issue may be caused by the '%s' setting.", + TRUST_EVERYTHING.getClass().getSimpleName(), + "ssl.verification_mode=NONE" + ); + throw new IllegalStateException(message); + } return TRUST_MANAGER; } diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/KeyStoreFactoryTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/KeyStoreFactoryTests.java new file mode 100644 index 0000000000000..07d3ebfaa8fba --- /dev/null +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/KeyStoreFactoryTests.java @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.ssl; + +import com.carrotsearch.randomizedtesting.generators.RandomStrings; + +import org.opensearch.test.OpenSearchTestCase; + +import static org.hamcrest.Matchers.equalTo; + +public class KeyStoreFactoryTests extends OpenSearchTestCase { + + public void testPKCS12KeyStore() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12).getType(), equalTo("PKCS12")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12).getProvider().getName(), equalTo("BCFIPS")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "BCFIPS").getType(), equalTo("PKCS12")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "BCFIPS").getProvider().getName(), equalTo("BCFIPS")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN").getType(), equalTo("PKCS12")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN").getProvider().getName(), equalTo("SUN")); + + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(KeyStoreType.PKCS_12).forEach(extension -> { + var keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName(extension)); + assertThat(keyStore.getType(), equalTo(KeyStoreType.PKCS_12.getJcaName())); + }); + } + + public void testJKSKeyStore() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS).getType(), equalTo("JKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS).getProvider().getName(), equalTo("SUN")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS, "SUN").getType(), equalTo("JKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS, "SUN").getProvider().getName(), equalTo("SUN")); + + assertThrows("BCFKS not found", SecurityException.class, () -> KeyStoreFactory.getInstance(KeyStoreType.JKS, "BCFIPS")); + + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(KeyStoreType.JKS).forEach(extension -> { + var keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName(extension)); + assertThat(keyStore.getType(), equalTo(KeyStoreType.JKS.getJcaName())); + }); + } + + public void testBCFIPSKeyStore() { + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS).getType(), equalTo("BCFKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS).getProvider().getName(), equalTo("BCFIPS")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "BCFIPS").getType(), equalTo("BCFKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "BCFIPS").getProvider().getName(), equalTo("BCFIPS")); + + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(KeyStoreType.BCFKS).forEach(extension -> { + var keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName(extension)); + assertThat(keyStore.getType(), equalTo(KeyStoreType.BCFKS.getJcaName())); + }); + } + + public void testUnknownKeyStoreType() { + assertThrows( + "Unknown keystore type for file path: keystore.unknown", + IllegalArgumentException.class, + () -> KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName("unknown")) + ); + } + + private String createRandomFileName(String extension) { + return RandomStrings.randomAsciiAlphanumOfLengthBetween(random(), 0, 10) + "." + extension; + } +} diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java index 688f03a1e51fa..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 @@ -41,11 +41,11 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.security.GeneralSecurityException; import java.security.PrivateKey; 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; @@ -58,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"); @@ -68,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"); @@ -77,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"); } @@ -131,7 +132,7 @@ public void testKeyConfigReloadsFileContents() throws Exception { Files.copy(cert2, cert, StandardCopyOption.REPLACE_EXISTING); Files.copy(key2, key, StandardCopyOption.REPLACE_EXISTING); - assertPasswordIsIncorrect(keyConfig, key); + assertPasswordNotSet(keyConfig, key); Files.copy(cert1, cert, StandardCopyOption.REPLACE_EXISTING); Files.copy(key1, key, StandardCopyOption.REPLACE_EXISTING); @@ -166,7 +167,15 @@ private void assertPasswordIsIncorrect(PemKeyConfig keyConfig, Path key) { final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); assertThat(exception.getMessage(), containsString("private key file")); assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); - assertThat(exception.getCause(), instanceOf(GeneralSecurityException.class)); + assertThat(exception, instanceOf(SslConfigException.class)); + } + + private void assertPasswordNotSet(PemKeyConfig keyConfig, Path key) { + final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); + assertThat(exception.getMessage(), containsString("cannot read encrypted key")); + assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); + assertThat(exception.getMessage(), containsString("without a password")); + assertThat(exception, instanceOf(SslConfigException.class)); } private void assertFileNotFound(PemKeyConfig keyConfig, String type, Path file) { 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 e664e379d1e97..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 @@ -42,7 +42,6 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; -import java.security.GeneralSecurityException; import java.security.Principal; import java.security.cert.X509Certificate; import java.util.Arrays; @@ -74,7 +73,7 @@ public void testBadFileFormatFails() throws Exception { Files.write(ca, generateRandomByteArrayOfLength(128), StandardOpenOption.APPEND); final PemTrustConfig trustConfig = new PemTrustConfig(Collections.singletonList(ca)); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ca)); - assertInvalidFileFormat(trustConfig, ca); + assertFailedToParse(trustConfig, ca); } public void testEmptyFileFails() throws Exception { @@ -121,7 +120,7 @@ public void testTrustConfigReloadsFileContents() throws Exception { assertFileNotFound(trustConfig, ca1); Files.write(ca1, generateRandomByteArrayOfLength(128), StandardOpenOption.CREATE); - assertInvalidFileFormat(trustConfig, ca1); + assertFailedToParse(trustConfig, ca1); } private void assertCertificateChain(PemTrustConfig trustConfig, String... caNames) { @@ -139,20 +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 assertInvalidFileFormat(PemTrustConfig trustConfig, Path file) { + 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())); - // When running on BC-FIPS, an invalid file format *might* just fail to parse, without any errors (just like an empty file) - // or it might behave per the SUN provider, and throw a GSE (depending on exactly what was invalid) - if (inFipsJvm() && exception.getMessage().contains("failed to parse any certificates")) { - return; - } - assertThat(exception.getMessage(), Matchers.containsString("cannot create trust")); - assertThat(exception.getMessage(), Matchers.containsString("PEM")); - assertThat(exception.getCause(), Matchers.instanceOf(GeneralSecurityException.class)); + 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 c7ca19bb679d3..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 @@ -34,16 +34,15 @@ 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 +54,15 @@ public class PemUtilsTests extends OpenSearchTestCase { private static final Supplier EMPTY_PASSWORD = () -> new char[0]; private static final Supplier TESTNODE_PASSWORD = "testnode"::toCharArray; + private static final Supplier STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="::toCharArray; // has to be at least 112 bit long. + + public void testInstantiateWithDefaultConstructor() { + assertThrows("Utility class should not be instantiated", IllegalStateException.class, PemUtils::new); + } public void testReadPKCS8RsaKey() throws Exception { - Key key = getKeyFromKeystore("RSA"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("RSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/rsa_key_pkcs8_plain.pem"), EMPTY_PASSWORD); @@ -66,7 +71,8 @@ public void testReadPKCS8RsaKey() throws Exception { } public void testReadPKCS8RsaKeyWithBagAttrs() throws Exception { - Key key = getKeyFromKeystore("RSA"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("RSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/testnode_with_bagattrs.pem"), EMPTY_PASSWORD); @@ -74,7 +80,8 @@ public void testReadPKCS8RsaKeyWithBagAttrs() throws Exception { } public void testReadPKCS8DsaKey() throws Exception { - Key key = getKeyFromKeystore("DSA"); + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); + Key key = getKeyFromKeystore("DSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/dsa_key_pkcs8_plain.pem"), EMPTY_PASSWORD); @@ -94,7 +101,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 +112,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 +121,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 +131,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 +143,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 +154,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 +165,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 +179,8 @@ public void testReadOpenSslDsaKeyWithParams() throws Exception { } public void testReadEncryptedOpenSslDsaKey() throws Exception { - Key key = getKeyFromKeystore("DSA"); + assumeFalse("Can't run in a FIPS JVM, PBKDF-OPENSSL KeySpec is not available", inFipsJvm()); + Key key = getKeyFromKeystore("DSA", KeyStoreType.JKS); assertThat(key, notNullValue()); assertThat(key, instanceOf(PrivateKey.class)); PrivateKey privateKey = PemUtils.readPrivateKey(getDataPath("/certs/pem-utils/dsa_key_openssl_encrypted.pem"), TESTNODE_PASSWORD); @@ -176,7 +190,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 +201,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 +215,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,27 +225,54 @@ 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)); - 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() { @@ -239,11 +283,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, inFipsJvm() ? KeyStoreType.BCFKS : KeyStoreType.JKS); + } + + private Key getKeyFromKeystore(String algo, KeyStoreType keyStoreType) throws Exception { + var keystorePath = getDataPath("/certs/pem-utils/testnode" + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(keyStoreType).get(0)); + var alias = "testnode_" + algo.toLowerCase(Locale.ROOT); + var password = "testnode".toCharArray(); + try (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..2aef139eba7a0 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java @@ -53,6 +53,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; @@ -98,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 + ); + } } } @@ -113,7 +123,30 @@ public void testLoadTrustFromPemCAs() { assertThat(trustConfig.createTrustManager(), notNullValue()); } + public void testLoadTrustFromBCFKS() { + final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.bcfks"); + if (randomBoolean()) { + builder.put("test.ssl.truststore.password", "bcfks-pass"); + } else { + secureSettings.setString("test.ssl.truststore.secure_password", "bcfks-pass"); + } + if (randomBoolean()) { + // If this is not set, the loader will guess from the extension + builder.put("test.ssl.truststore.type", BCFKS); + } + if (randomBoolean()) { + builder.put("test.ssl.truststore.algorithm", TrustManagerFactory.getDefaultAlgorithm()); + } + settings = builder.build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslTrustConfig trustConfig = configuration.getTrustConfig(); + assertThat(trustConfig, instanceOf(StoreTrustConfig.class)); + assertThat(trustConfig.getDependentFiles(), containsInAnyOrder(getDataPath("/certs/ca-all/ca.bcfks"))); + assertThat(trustConfig.createTrustManager(), notNullValue()); + } + public void testLoadTrustFromPkcs12() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.p12"); if (randomBoolean()) { builder.put("test.ssl.truststore.password", "p12-pass"); @@ -136,6 +169,7 @@ public void testLoadTrustFromPkcs12() { } public void testLoadTrustFromJKS() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.jks"); if (randomBoolean()) { builder.put("test.ssl.truststore.password", "jks-pass"); @@ -166,9 +200,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 +219,30 @@ public void testLoadKeysFromPemFiles() { assertThat(keyConfig.createKeyManager(), notNullValue()); } + public void testLoadKeysFromBCFKS() { + final Settings.Builder builder = Settings.builder().put("test.ssl.keystore.path", "cert-all/certs.bcfks"); + if (randomBoolean()) { + builder.put("test.ssl.keystore.password", "bcfks-pass"); + } else { + secureSettings.setString("test.ssl.keystore.secure_password", "bcfks-pass"); + } + if (randomBoolean()) { + // If this is not set, the loader will guess from the extension + builder.put("test.ssl.keystore.type", BCFKS); + } + if (randomBoolean()) { + builder.put("test.ssl.keystore.algorithm", KeyManagerFactory.getDefaultAlgorithm()); + } + settings = builder.build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslKeyConfig keyConfig = configuration.getKeyConfig(); + assertThat(keyConfig, instanceOf(StoreKeyConfig.class)); + assertThat(keyConfig.getDependentFiles(), containsInAnyOrder(getDataPath("/certs/cert-all/certs.bcfks"))); + assertThat(keyConfig.createKeyManager(), notNullValue()); + } + public void testLoadKeysFromPKCS12() { + assumeFalse("Can't use PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Settings.Builder builder = Settings.builder().put("test.ssl.keystore.path", "cert-all/certs.p12"); if (randomBoolean()) { builder.put("test.ssl.keystore.password", "p12-pass"); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java index ee907952c52ff..410238e57984c 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java @@ -42,10 +42,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import org.mockito.Mockito; import static org.opensearch.common.ssl.SslConfigurationLoader.DEFAULT_CIPHERS; +import static org.opensearch.common.ssl.SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -166,7 +168,7 @@ public void testDependentFiles() { randomFrom(SslVerificationMode.values()), randomFrom(SslClientAuthenticationMode.values()), DEFAULT_CIPHERS, - SslConfigurationLoader.DEFAULT_PROTOCOLS + SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS ); final Path dir = createTempDir(); @@ -184,7 +186,7 @@ public void testDependentFiles() { public void testBuildSslContext() { final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); - final String protocol = randomFrom(SslConfigurationLoader.DEFAULT_PROTOCOLS); + final String protocol = randomFrom(SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS); final SslConfiguration configuration = new SslConfiguration( trustConfig, keyConfig, @@ -204,4 +206,80 @@ public void testBuildSslContext() { Mockito.verifyNoMoreInteractions(trustConfig, keyConfig); } + public void testCreateSslContextWithUnsupportedProtocols() { + assumeFalse("Test not in FIPS JVM", inFipsJvm()); + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + SslConfiguration configuration = new SslConfiguration( + trustConfig, + keyConfig, + randomFrom(SslVerificationMode.values()), + randomFrom(SslClientAuthenticationMode.values()), + DEFAULT_CIPHERS, + Collections.singletonList("DTLSv1.2") + ); + + Exception e = assertThrows(SslConfigException.class, configuration::createSslContext); + assertThat( + e.getMessage(), + containsString("no supported SSL/TLS protocol was found in the configured supported protocols: [DTLSv1.2]") + ); + } + + public void testNotSupportedProtocolsInFipsJvm() { + assumeTrue("Test in FIPS JVM", inFipsJvm()); + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + final String protocol = randomFrom(List.of("TLSv1.1", "TLSv1", "SSLv3", "SSLv2Hello", "SSLv2")); + final SslConfiguration configuration = new SslConfiguration( + trustConfig, + keyConfig, + randomFrom(SslVerificationMode.values()), + randomFrom(SslClientAuthenticationMode.values()), + DEFAULT_CIPHERS, + Collections.singletonList(protocol) + ); + + Mockito.when(trustConfig.createTrustManager()).thenReturn(null); + Mockito.when(keyConfig.createKeyManager()).thenReturn(null); + var exception = assertThrows(SslConfigException.class, configuration::createSslContext); + assertThat( + exception.getMessage(), + equalTo( + String.format(Locale.ROOT, "in FIPS mode only the following SSL/TLS protocols are allowed: %s", FIPS_APPROVED_PROTOCOLS) + ) + ); + } + + public void testInitValuesExist() { + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + + assertThrows( + "cannot configure SSL/TLS without any supported cipher suites", + SslConfigException.class, + () -> new SslConfiguration( + trustConfig, + keyConfig, + SslVerificationMode.CERTIFICATE, + SslClientAuthenticationMode.REQUIRED, + Collections.emptyList(), + List.of("SSLv2") + ) + ); + + assertThrows( + "cannot configure SSL/TLS without any supported protocols", + SslConfigException.class, + () -> new SslConfiguration( + trustConfig, + keyConfig, + SslVerificationMode.CERTIFICATE, + SslClientAuthenticationMode.REQUIRED, + DEFAULT_CIPHERS, + Collections.emptyList() + ) + ); + } + } diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java index c966b4259219f..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"); @@ -85,7 +97,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 +122,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 +146,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 +172,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 +197,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 +221,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 +247,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 +550,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 +583,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..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,6 +48,10 @@ import java.security.cert.X509Certificate; import java.util.Arrays; +import static org.opensearch.common.ssl.KeyStoreType.BCFKS; +import static org.opensearch.common.ssl.KeyStoreType.JKS; +import static org.opensearch.common.ssl.KeyStoreType.PKCS_12; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.containsInAnyOrder; 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()); + assumeFalse("Can't use PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path p12 = getDataPath("/certs/cert1/cert1.p12"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, PKCS_12, P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(p12)); assertKeysLoaded(keyConfig, "cert1"); } @@ -76,7 +81,7 @@ public void testLoadSingleKeyPKCS12() throws Exception { public void testLoadMultipleKeyPKCS12() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path p12 = getDataPath("/certs/cert-all/certs.p12"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, PKCS_12, P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(p12)); assertKeysLoaded(keyConfig, "cert1", "cert2"); } @@ -87,7 +92,7 @@ public void testLoadMultipleKeyJksWithSeparateKeyPassword() throws Exception { final StoreKeyConfig keyConfig = new StoreKeyConfig( jks, JKS_PASS, - "jks", + JKS, "key-pass".toCharArray(), KeyManagerFactory.getDefaultAlgorithm() ); @@ -95,13 +100,20 @@ public void testLoadMultipleKeyJksWithSeparateKeyPassword() throws Exception { assertKeysLoaded(keyConfig, "cert1", "cert2"); } - public void testKeyManagerFailsWithIncorrectStorePassword() throws Exception { + public void testLoadMultipleKeyBcfks() throws CertificateParsingException { + final Path bcfks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(bcfks, BCFKS_PASS, BCFKS, BCFKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(bcfks)); + assertKeysLoaded(keyConfig, "cert1", "cert2"); + } + + public void testKeyManagerFailsWithIncorrectJksStorePassword() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path jks = getDataPath("/certs/cert-all/certs.jks"); final StoreKeyConfig keyConfig = new StoreKeyConfig( jks, P12_PASS, - "jks", + JKS, "key-pass".toCharArray(), KeyManagerFactory.getDefaultAlgorithm() ); @@ -109,50 +121,76 @@ public void testKeyManagerFailsWithIncorrectStorePassword() throws Exception { assertPasswordIsIncorrect(keyConfig, jks); } - public void testKeyManagerFailsWithIncorrectKeyPassword() throws Exception { + public void testKeyManagerFailsWithIncorrectBcfksStorePassword() throws Exception { + final Path bcfks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(bcfks, P12_PASS, BCFKS, BCFKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(bcfks)); + assertPasswordIsIncorrect(keyConfig, bcfks); + } + + public void testKeyManagerFailsWithIncorrectJksKeyPassword() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path jks = getDataPath("/certs/cert-all/certs.jks"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, JKS_PASS, "jks", JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(jks, JKS_PASS, JKS, JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(jks)); assertPasswordIsIncorrect(keyConfig, jks); } + public void testKeyManagerFailsWithIncorrectBcfksKeyPassword() throws Exception { + final Path bcfks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig( + bcfks, + BCFKS_PASS, + BCFKS, + "nonsense".toCharArray(), + KeyManagerFactory.getDefaultAlgorithm() + ); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(bcfks)); + assertPasswordIsIncorrect(keyConfig, bcfks); + } + public void testKeyManagerFailsWithMissingKeystoreFile() throws Exception { - assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path path = getDataPath("/certs/cert-all/certs.jks").getParent().resolve("dne.jks"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(path, JKS_PASS, "jks", JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(path, JKS_PASS, JKS, JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(path)); assertFileNotFound(keyConfig, path); } - public void testMissingKeyEntriesFailsWithMeaningfulMessage() throws Exception { + public void testMissingKeyEntriesFailsForJksWithMeaningfulMessage() 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.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 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()); + 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); } 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"); 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 +249,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..0614e36ccfb1d 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreTrustConfigTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreTrustConfigTests.java @@ -49,6 +49,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.opensearch.common.ssl.KeyStoreType.BCFKS; +import static org.opensearch.common.ssl.KeyStoreType.JKS; +import static org.opensearch.common.ssl.KeyStoreType.PKCS_12; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.nullValue; @@ -56,20 +59,28 @@ 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 { + public void testBuildTrustConfigFromP12() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca1/ca.p12"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, "PKCS12", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, PKCS_12, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertCertificateChain(trustConfig, "CN=Test CA 1"); } - 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); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, JKS, DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 2", "CN=Test CA 3"); + } + + public void testBuildTrustConfigFromBcfks() throws Exception { + final Path ks = getDataPath("/certs/ca-all/ca.bcfks"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, BCFKS_PASS, BCFKS, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 2", "CN=Test CA 3"); } @@ -78,7 +89,7 @@ public void testBadKeyStoreFormatFails() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = createTempFile("ca", ".p12"); Files.write(ks, randomByteArrayOfLength(128), StandardOpenOption.APPEND); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], randomFrom("PKCS12", "jks"), DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], randomFrom(PKCS_12, JKS), DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertInvalidFileFormat(trustConfig, ks); } @@ -86,33 +97,50 @@ 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 { + public void testIncorrectPasswordFailsForP12WithMeaningfulMessage() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca1/ca.p12"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], "PKCS12", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], PKCS_12, DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertPasswordIsIncorrect(trustConfig, ks); + } + + public void testIncorrectPasswordFailsForBcfksWithMeaningfulMessage() throws Exception { + final Path ks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreTrustConfig trustConfig = new StoreTrustConfig( + ks, + randomAlphaOfLengthBetween(6, 8).toCharArray(), + BCFKS, + DEFAULT_ALGORITHM + ); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertPasswordIsIncorrect(trustConfig, ks); } - public void testMissingTrustEntriesFailsWithMeaningfulMessage() throws Exception { + public void testMissingTrustEntriesFailsForJksKeystoreWithMeaningfulMessage() 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.jks"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, 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 = getDataPath("/certs/cert-all/certs.p12"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, PKCS_12, DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertNoCertificateEntries(trustConfig, ks); + } + + public void testMissingTrustEntriesFailsForBcfksKeystoreWithMeaningfulMessage() throws Exception { + final Path ks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, BCFKS_PASS, BCFKS, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertNoCertificateEntries(trustConfig, ks); } @@ -124,7 +152,7 @@ public void testTrustConfigReloadsKeysStoreContents() throws Exception { final Path ks = createTempFile("ca", "p12"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, "PKCS12", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, PKCS_12, DEFAULT_ALGORITHM); Files.copy(ks1, ks, StandardCopyOption.REPLACE_EXISTING); assertCertificateChain(trustConfig, "CN=Test CA 1"); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/TrustEverythingConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/TrustEverythingConfigTests.java new file mode 100644 index 0000000000000..1ea237954d41e --- /dev/null +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/TrustEverythingConfigTests.java @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.ssl; + +import org.opensearch.test.OpenSearchTestCase; + +import static org.hamcrest.Matchers.containsString; + +public class TrustEverythingConfigTests extends OpenSearchTestCase { + + public void testGetDependentFiles() { + assertTrue(TrustEverythingConfig.TRUST_EVERYTHING.getDependentFiles().isEmpty()); + } + + public void testCreateTrustManager() { + if (inFipsJvm()) { + Exception e = assertThrows(IllegalStateException.class, TrustEverythingConfig.TRUST_EVERYTHING::createTrustManager); + assertThat(e.getMessage(), containsString("not permitted in FIPS mode")); + } else { + var trustManager = TrustEverythingConfig.TRUST_EVERYTHING.createTrustManager(); + assertNotNull(trustManager); + } + } +} diff --git a/libs/ssl-config/src/test/resources/certs/README.md b/libs/ssl-config/src/test/resources/certs/README.md new file mode 100644 index 0000000000000..e363e287c9521 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/README.md @@ -0,0 +1,172 @@ +#!/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 + +# 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/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 0000000000000..f27717d0ce67a Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/ca-all/ca.bcfks differ diff --git a/libs/ssl-config/src/test/resources/certs/cert-all/certs.bcfks b/libs/ssl-config/src/test/resources/certs/cert-all/certs.bcfks new file mode 100644 index 0000000000000..1eab5af506b2c Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/cert-all/certs.bcfks differ diff --git a/libs/ssl-config/src/test/resources/certs/cert-all/certs.p12 b/libs/ssl-config/src/test/resources/certs/cert-all/certs.p12 index b971a1e39c83b..73912976ca7cc 100644 Binary files a/libs/ssl-config/src/test/resources/certs/cert-all/certs.p12 and b/libs/ssl-config/src/test/resources/certs/cert-all/certs.p12 differ 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 0000000000000..1327d550f61fd Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/cert-all/empty.jks differ diff --git a/libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs1.crt b/libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs1.crt new file mode 100644 index 0000000000000..51f39295f62e3 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs1.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIUCYPL1cogC+8WOfSZytklHmrHQh4wDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAxMJVGVzdCBDQSAxMB4XDTE5MDEwMzA3NDA0MloXDTQ2MDUy +MDA3NDA0MlowEDEOMAwGA1UEAxMFY2VydDEwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCWz6ITDTlkTLueB30Jx0+7sWHdlM5ObZjWhMQ1eyJD0gYU/gkH +2C88IN/PtSv04tzFS6PA4KPDLIyaAhczPlGElSansiui//CpieCI4tt5c2BgVo3X +dJaylYoW3CRILUrlSBOMUmJCQEokverxMrz8DeppNxRfj99pQkoxUkmFMZj/C7XN +VYrTttdF1li5FUtWJxw234OUfum3PQIzz6YTmoPtLrJ2fB8I4CH8R5hwGcryhBSA +qq8pgy61aTPCgEBZ1c4Dvl65X8dG2QEVPjwMZnnbGjvlZefOgkmAWJ1VjihA3GVg +O2mx4tB4D2x5K/OAxh2foZkDVhqJfBkOblLnAgMBAAGjaTBnMB0GA1UdDgQWBBRM +RZ6Qlozj5hWTqf3+oTznFyZTsDAfBgNVHSMEGDAWgBSHd+NjwqkE6hVMEnltx6cT ++D11lzAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwCQYDVR0TBAIwADANBgkq +hkiG9w0BAQsFAAOCAQEAOTUJ64T6kO2H51j2bKIof4ij4yoDD86gLmUF7qXB2Wt4 +tMDCqs9+5VnRzSWY1652mpwPClcK/MfE26PR6DUunoES+8VSbARWh0OB6zsAAWyp +WJ4RxlfYdNpJZjpx3umLGj4yeCh0iOhfoArBUT3vaJJrea+rTro4UFE2Z29uWALr +NvjKZ0Qrn1DMP3N9b7y81dR9RMlzeqk5tlPhAqhHzQM0hDdFKA5uIFn71QQpd5SI +y8MpllWFGGq/+5m7FD0t71GQ/m5xCyfUiqQU31Nj3ThU21SPHBqZIZvQ/na/OaAf +GySn+0ZHAvyNRTL2y2Fk/YAY68kgx2E44H5YSqbFJA== +-----END CERTIFICATE----- diff --git a/libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs1.key b/libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs1.key new file mode 100644 index 0000000000000..461ae5ee31ba7 --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs1.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAls+iEw05ZEy7ngd9CcdPu7Fh3ZTOTm2Y1oTENXsiQ9IGFP4J +B9gvPCDfz7Ur9OLcxUujwOCjwyyMmgIXMz5RhJUmp7Irov/wqYngiOLbeXNgYFaN +13SWspWKFtwkSC1K5UgTjFJiQkBKJL3q8TK8/A3qaTcUX4/faUJKMVJJhTGY/wu1 +zVWK07bXRdZYuRVLViccNt+DlH7ptz0CM8+mE5qD7S6ydnwfCOAh/EeYcBnK8oQU +gKqvKYMutWkzwoBAWdXOA75euV/HRtkBFT48DGZ52xo75WXnzoJJgFidVY4oQNxl +YDtpseLQeA9seSvzgMYdn6GZA1YaiXwZDm5S5wIDAQABAoIBABCilJEfa045/JQA +5XT3rD7a4R2s9VjHVA2NlYsEqxHqD8uu/dYEraknQyjJJjEb+Rg2MLjszoOP3W57 +fo2jeSBzx1DGIXQYYTaCQ+c1htoNtPrLcVfrv1exkQrWe5YOkO1blvRqffYq20LU +RB8Y5qmy60Fx1uh3mUAmFML9/agYVJo4yCxnNDrMg9UjF4bn/39uOf6C7mEVJRTl +7ET5wcbdl10EOWW2m60hJOQLSOY9N1eafFEO+V2Xb80PC2t3Mqt5+T7n0CKCx/p9 +4F7QAz+hsfksY3oTUUXwL0KoJTLdJrjCoG4mWJ/Re3qEKJqmMfT4XpJKrF7HfgcK +RCyH06kCgYEA/5TVQK4G1Dc4LnSCmCb+ECQvmGRBtK6Alh3Txb4IwGHDGMfC8W4O +gt03A8ZE92pjITHd1+cLykKBsmaVmEtiyD7YL5G3mumR1YdMFEBSZdxOTeD95+aL +YxTofPsDIUIPSFecRWwri7TyYcvUGyDchL0vDc6Gp95/ZFFgt26uxAsCgYEAlw7e +g2McHws9cSAfouULbKbf6jXzy21t6CeqJGID/kjdUws63prcQvmFtFHWrv3rKO09 +hgb3Kd3gUz8t9tAD3F718bSZwzLASwO5ujPHZQVRTotutgCGeKPgXqzyVWeo45ji +4DfQl53jG0aA1DzoZSA3/owcuX6CVGPLzQnhehUCgYAHe9UuuqnKhv9nJNQ6HlIs +KNMX9D+USdPMEX2E+caJ05MB47+KkD1uiYm125VjZUMX0rz7OHG472+ayLQyrGpt +EKIF6o9kwtgZV4fbw/Jltyi30RG+O5rzQMZ5+mOiEqwd4yrZQYyY36iFQpGoZbLv +VBbPoa+BtNsoFdXuKRiG9wKBgBjJhdXFc53ceE6R2N8f+onvsBp8k+6znC9WIuMp +ekJFrpur4hMZEj+jNj9qlnHMlMP4efn+NpyWHfNLEL3JUHje1Di/S+Pt9gPZLqbR +TEzVXIwo8RfIakhti6m9c150ThBazA/C2OWoMNYO8aDiBbhiWw3X6/a8PaKfZZfV +oTwpAoGASCTx55uThl9rN+XDKFXN500K4r+Q9OBOEkfDuDUERpBohUfoy8dO5eiT +mGiqx5P0hoxEC70vnw5fJz4ZpSJ7LcpCfq2TezknJP3MZKwTdBM/pODSPMU5YCW4 +T9ocEQui5PKOTLlVo1QKrG8w6f/YMfZuGa9zP/LmTLZEado9nuk= +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs8.key b/libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs8.key deleted file mode 100644 index 1e8f219eecee7..0000000000000 --- a/libs/ssl-config/src/test/resources/certs/cert1/cert1-pkcs8.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCWz6ITDTlkTLue -B30Jx0+7sWHdlM5ObZjWhMQ1eyJD0gYU/gkH2C88IN/PtSv04tzFS6PA4KPDLIya -AhczPlGElSansiui//CpieCI4tt5c2BgVo3XdJaylYoW3CRILUrlSBOMUmJCQEok -verxMrz8DeppNxRfj99pQkoxUkmFMZj/C7XNVYrTttdF1li5FUtWJxw234OUfum3 -PQIzz6YTmoPtLrJ2fB8I4CH8R5hwGcryhBSAqq8pgy61aTPCgEBZ1c4Dvl65X8dG -2QEVPjwMZnnbGjvlZefOgkmAWJ1VjihA3GVgO2mx4tB4D2x5K/OAxh2foZkDVhqJ -fBkOblLnAgMBAAECggEAEKKUkR9rTjn8lADldPesPtrhHaz1WMdUDY2ViwSrEeoP -y6791gStqSdDKMkmMRv5GDYwuOzOg4/dbnt+jaN5IHPHUMYhdBhhNoJD5zWG2g20 -+stxV+u/V7GRCtZ7lg6Q7VuW9Gp99irbQtREHxjmqbLrQXHW6HeZQCYUwv39qBhU -mjjILGc0OsyD1SMXhuf/f245/oLuYRUlFOXsRPnBxt2XXQQ5ZbabrSEk5AtI5j03 -V5p8UQ75XZdvzQ8La3cyq3n5PufQIoLH+n3gXtADP6Gx+SxjehNRRfAvQqglMt0m -uMKgbiZYn9F7eoQomqYx9PhekkqsXsd+BwpELIfTqQKBgQD/lNVArgbUNzgudIKY -Jv4QJC+YZEG0roCWHdPFvgjAYcMYx8Lxbg6C3TcDxkT3amMhMd3X5wvKQoGyZpWY -S2LIPtgvkbea6ZHVh0wUQFJl3E5N4P3n5otjFOh8+wMhQg9IV5xFbCuLtPJhy9Qb -INyEvS8Nzoan3n9kUWC3bq7ECwKBgQCXDt6DYxwfCz1xIB+i5Qtspt/qNfPLbW3o -J6okYgP+SN1TCzremtxC+YW0Udau/eso7T2GBvcp3eBTPy320APcXvXxtJnDMsBL -A7m6M8dlBVFOi262AIZ4o+BerPJVZ6jjmOLgN9CXneMbRoDUPOhlIDf+jBy5foJU -Y8vNCeF6FQKBgAd71S66qcqG/2ck1DoeUiwo0xf0P5RJ08wRfYT5xonTkwHjv4qQ -PW6JibXblWNlQxfSvPs4cbjvb5rItDKsam0QogXqj2TC2BlXh9vD8mW3KLfREb47 -mvNAxnn6Y6ISrB3jKtlBjJjfqIVCkahlsu9UFs+hr4G02ygV1e4pGIb3AoGAGMmF -1cVzndx4TpHY3x/6ie+wGnyT7rOcL1Yi4yl6QkWum6viExkSP6M2P2qWccyUw/h5 -+f42nJYd80sQvclQeN7UOL9L4+32A9kuptFMTNVcjCjxF8hqSG2Lqb1zXnROEFrM -D8LY5agw1g7xoOIFuGJbDdfr9rw9op9ll9WhPCkCgYBIJPHnm5OGX2s35cMoVc3n -TQriv5D04E4SR8O4NQRGkGiFR+jLx07l6JOYaKrHk/SGjEQLvS+fDl8nPhmlInst -ykJ+rZN7OSck/cxkrBN0Ez+k4NI8xTlgJbhP2hwRC6Lk8o5MuVWjVAqsbzDp/9gx -9m4Zr3M/8uZMtkRp2j2e6Q== ------END PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/cert1/cert1.crt b/libs/ssl-config/src/test/resources/certs/cert1/cert1.crt index 51f39295f62e3..576de3bb28fb4 100644 --- a/libs/ssl-config/src/test/resources/certs/cert1/cert1.crt +++ b/libs/ssl-config/src/test/resources/certs/cert1/cert1.crt @@ -1,19 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIDGzCCAgOgAwIBAgIUCYPL1cogC+8WOfSZytklHmrHQh4wDQYJKoZIhvcNAQEL -BQAwFDESMBAGA1UEAxMJVGVzdCBDQSAxMB4XDTE5MDEwMzA3NDA0MloXDTQ2MDUy -MDA3NDA0MlowEDEOMAwGA1UEAxMFY2VydDEwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQCWz6ITDTlkTLueB30Jx0+7sWHdlM5ObZjWhMQ1eyJD0gYU/gkH -2C88IN/PtSv04tzFS6PA4KPDLIyaAhczPlGElSansiui//CpieCI4tt5c2BgVo3X -dJaylYoW3CRILUrlSBOMUmJCQEokverxMrz8DeppNxRfj99pQkoxUkmFMZj/C7XN -VYrTttdF1li5FUtWJxw234OUfum3PQIzz6YTmoPtLrJ2fB8I4CH8R5hwGcryhBSA -qq8pgy61aTPCgEBZ1c4Dvl65X8dG2QEVPjwMZnnbGjvlZefOgkmAWJ1VjihA3GVg -O2mx4tB4D2x5K/OAxh2foZkDVhqJfBkOblLnAgMBAAGjaTBnMB0GA1UdDgQWBBRM -RZ6Qlozj5hWTqf3+oTznFyZTsDAfBgNVHSMEGDAWgBSHd+NjwqkE6hVMEnltx6cT -+D11lzAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwCQYDVR0TBAIwADANBgkq -hkiG9w0BAQsFAAOCAQEAOTUJ64T6kO2H51j2bKIof4ij4yoDD86gLmUF7qXB2Wt4 -tMDCqs9+5VnRzSWY1652mpwPClcK/MfE26PR6DUunoES+8VSbARWh0OB6zsAAWyp -WJ4RxlfYdNpJZjpx3umLGj4yeCh0iOhfoArBUT3vaJJrea+rTro4UFE2Z29uWALr -NvjKZ0Qrn1DMP3N9b7y81dR9RMlzeqk5tlPhAqhHzQM0hDdFKA5uIFn71QQpd5SI -y8MpllWFGGq/+5m7FD0t71GQ/m5xCyfUiqQU31Nj3ThU21SPHBqZIZvQ/na/OaAf -GySn+0ZHAvyNRTL2y2Fk/YAY68kgx2E44H5YSqbFJA== +MIIDEDCCAfigAwIBAgIULU6478tMe6npOywF6dj4+azLeJEwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAxMJVGVzdCBDQSAxMB4XDTI0MDgyOTA5MzE0OFoXDTM0MDgy +NzA5MzE0OFowEDEOMAwGA1UEAwwFY2VydDEwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQC+9V4E9sliTrt7NgkpG95rrXntGcybvp/EwDjaHbVyagQ34aMT +gjhKRhzmGzwX7YkeYpob8JmMOwyurxur0KOpSQZMcUM7QneO16EV/8otRwWfyApF +AzD7gANdTyRcnO9h3aUEBJvvve4fDFleqnj9AexGBIqyHRcA8hjYTjIIKGQbgFIi +7DvS/405AinDU6sAQt70+pZ3P5pHF6AsHhEhma5+cX/I7q4XIJxaLwpsh8ySWq2P +FMaJxMKoLXhuNALZ/n5Ur1grDZG91cPF+vpJu4qV9zjalLHASM6u+CK+roFT8P3n +P98viKDLaFzEwjqoKkVoIY/73ESiuHOi7KQVAgMBAAGjXjBcMBoGA1UdEQQTMBGC +CWxvY2FsaG9zdIcEfwAAATAdBgNVHQ4EFgQUL0u1Ps+kFvjYsf0T6rXPc7H0ls8w +HwYDVR0jBBgwFoAUh3fjY8KpBOoVTBJ5bcenE/g9dZcwDQYJKoZIhvcNAQELBQAD +ggEBAGRPhduypDVICV0q7nEaK6MUsF//W826rYvy9ePZzAAcx99si1UyAyLFFKRA +GtmkTiyO98AWqO7g5ztSuaN+5RxzSjPtcmOIgO2lu7nWLHO2Mr5uG1m6ssjGvQBz +IjXL9F2+oJeiOllQ/kSvTO8J0nUnz5EJi9JVwHMIb0GCfHrB01rp1UKfbPzXYnFz +t3oWro4idhgVpoBp+RNxRV6Z/7fMNBdc5acZFLTpRThs88CQRx5XqRl/X9FSrsbR +nuFwg8jc7taHhMOxMPQ+GPM9hZ4fGLfF3WSqRuyypMR8ztxXi3VmnSgvLhQ8OSz8 +yM3mR4IpH+oys6uyqvcxvtY7Qk8= -----END CERTIFICATE----- diff --git a/libs/ssl-config/src/test/resources/certs/cert1/cert1.key b/libs/ssl-config/src/test/resources/certs/cert1/cert1.key index 461ae5ee31ba7..b7ad0d595d7b7 100644 --- a/libs/ssl-config/src/test/resources/certs/cert1/cert1.key +++ b/libs/ssl-config/src/test/resources/certs/cert1/cert1.key @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAls+iEw05ZEy7ngd9CcdPu7Fh3ZTOTm2Y1oTENXsiQ9IGFP4J -B9gvPCDfz7Ur9OLcxUujwOCjwyyMmgIXMz5RhJUmp7Irov/wqYngiOLbeXNgYFaN -13SWspWKFtwkSC1K5UgTjFJiQkBKJL3q8TK8/A3qaTcUX4/faUJKMVJJhTGY/wu1 -zVWK07bXRdZYuRVLViccNt+DlH7ptz0CM8+mE5qD7S6ydnwfCOAh/EeYcBnK8oQU -gKqvKYMutWkzwoBAWdXOA75euV/HRtkBFT48DGZ52xo75WXnzoJJgFidVY4oQNxl -YDtpseLQeA9seSvzgMYdn6GZA1YaiXwZDm5S5wIDAQABAoIBABCilJEfa045/JQA -5XT3rD7a4R2s9VjHVA2NlYsEqxHqD8uu/dYEraknQyjJJjEb+Rg2MLjszoOP3W57 -fo2jeSBzx1DGIXQYYTaCQ+c1htoNtPrLcVfrv1exkQrWe5YOkO1blvRqffYq20LU -RB8Y5qmy60Fx1uh3mUAmFML9/agYVJo4yCxnNDrMg9UjF4bn/39uOf6C7mEVJRTl -7ET5wcbdl10EOWW2m60hJOQLSOY9N1eafFEO+V2Xb80PC2t3Mqt5+T7n0CKCx/p9 -4F7QAz+hsfksY3oTUUXwL0KoJTLdJrjCoG4mWJ/Re3qEKJqmMfT4XpJKrF7HfgcK -RCyH06kCgYEA/5TVQK4G1Dc4LnSCmCb+ECQvmGRBtK6Alh3Txb4IwGHDGMfC8W4O -gt03A8ZE92pjITHd1+cLykKBsmaVmEtiyD7YL5G3mumR1YdMFEBSZdxOTeD95+aL -YxTofPsDIUIPSFecRWwri7TyYcvUGyDchL0vDc6Gp95/ZFFgt26uxAsCgYEAlw7e -g2McHws9cSAfouULbKbf6jXzy21t6CeqJGID/kjdUws63prcQvmFtFHWrv3rKO09 -hgb3Kd3gUz8t9tAD3F718bSZwzLASwO5ujPHZQVRTotutgCGeKPgXqzyVWeo45ji -4DfQl53jG0aA1DzoZSA3/owcuX6CVGPLzQnhehUCgYAHe9UuuqnKhv9nJNQ6HlIs -KNMX9D+USdPMEX2E+caJ05MB47+KkD1uiYm125VjZUMX0rz7OHG472+ayLQyrGpt -EKIF6o9kwtgZV4fbw/Jltyi30RG+O5rzQMZ5+mOiEqwd4yrZQYyY36iFQpGoZbLv -VBbPoa+BtNsoFdXuKRiG9wKBgBjJhdXFc53ceE6R2N8f+onvsBp8k+6znC9WIuMp -ekJFrpur4hMZEj+jNj9qlnHMlMP4efn+NpyWHfNLEL3JUHje1Di/S+Pt9gPZLqbR -TEzVXIwo8RfIakhti6m9c150ThBazA/C2OWoMNYO8aDiBbhiWw3X6/a8PaKfZZfV -oTwpAoGASCTx55uThl9rN+XDKFXN500K4r+Q9OBOEkfDuDUERpBohUfoy8dO5eiT -mGiqx5P0hoxEC70vnw5fJz4ZpSJ7LcpCfq2TezknJP3MZKwTdBM/pODSPMU5YCW4 -T9ocEQui5PKOTLlVo1QKrG8w6f/YMfZuGa9zP/LmTLZEado9nuk= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+9V4E9sliTrt7 +NgkpG95rrXntGcybvp/EwDjaHbVyagQ34aMTgjhKRhzmGzwX7YkeYpob8JmMOwyu +rxur0KOpSQZMcUM7QneO16EV/8otRwWfyApFAzD7gANdTyRcnO9h3aUEBJvvve4f +DFleqnj9AexGBIqyHRcA8hjYTjIIKGQbgFIi7DvS/405AinDU6sAQt70+pZ3P5pH +F6AsHhEhma5+cX/I7q4XIJxaLwpsh8ySWq2PFMaJxMKoLXhuNALZ/n5Ur1grDZG9 +1cPF+vpJu4qV9zjalLHASM6u+CK+roFT8P3nP98viKDLaFzEwjqoKkVoIY/73ESi +uHOi7KQVAgMBAAECggEABuyt3p82WUbCnKqudVup2py9TaBAX6tvbrqLtTkbkc0P +XxljCPuRX/wf4yY8rR9zd/MaZIX6g2/Gu3TlG3ti2+omfNIknnsAC+F82WffpHmt +VghyeuMtPQl81b7fci5Mre9UIwupve1Uu7J+cSTcY0xVDYrvnprYcTPWO83GGa6R +NbYqZFMwqFer/emaIzLAVb4LWmmgpLN9Ki28s05j9CVyRvobevg3HLikhJKziEj/ +hq6RVY7wavdqGmWD20nRzTLVlwb3xLhd041oFhRxGd/KwWFzBqgfJCDEo31KeKJu +8h1YMX93NRP01Ny1l4HNOr1T/GvcJ4FbBn/EMFeq5wKBgQDrV13/A1i1yCFsCBee +kDblk3RpAwRo4w7+BJKpiX+6K+ZqT0mH+KaNtDd9MulxaJqFEeVsblKB4ILbIWhV +EcihGpdN43hED69r/bBpFSYmWAptzSRJ8sAn3X6n4p0TJ4yHSOi249/Y/yZ8lO1f +FepcYSI9R8Io8Tm/yp9ECzwnQwKBgQDPuJ8OEy3xtN7ZAjV1aFa4xLheFumDxkQf +VrpMbkGQQ3xj+GRngy+cbzef9wr8Y8uwx7SlkItVNOHqx0hQyWoiIt6DdN8XlmzK +5XJAn6WSk7dmcoRcSQhtotAZ86C1ATHbZCP7hHZM50bYBPpgYuk9ZBwmvxAcg0gj +KAzU4R31xwKBgCesSsQ6pDHA0yGLG7A3T4nUGcO6JnwoCtb3nmHGNCoGTImPZC3v +1OP+hXwtoPzlTWcxZSOqVW5fkq+uKhBtNw0xlmAjAJs4qbHiymJslknQfbGU65Er +wwj1ZeyXXzNjb2U+/TwXnhzOpoZJ9Noar69zEHqUJj8Qq1ORrNejrThRAoGAciwy +EJRuLmqSColyRMRC1nAaPm0tkOaLTwZmg9geZWMSnghLd7Hdm4ht9JjfCvb3YIWE +P3SBgSX9/wPmNbFfir/Lukmkzdw6lBMlh2u4oCZdKgxLwEXMQia5Z2pHrPWpJ8OV +G4wlUgPVJqsly5DSTpIV/x4JhwKJxfkfhGrwAsUCgYALt60wjxSkhFKTMB6+jViY +Nso8Tdn4VIxCd6LsMpGgIyfbYyEsBgs3HypjfkSurAfW7VgsLoZqfIgTPSG9RVhY +7I87ST2/qVa0Hd6i4SeoB6PQwiowkplkdN6486tGev1lbWpMAeQAkJ2NPmSDNL4M +0j367yIVK4mvUArXQZhEqA== +-----END PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/cert1/cert1.p12 b/libs/ssl-config/src/test/resources/certs/cert1/cert1.p12 index 2635a887bc87e..af4918e031995 100644 Binary files a/libs/ssl-config/src/test/resources/certs/cert1/cert1.p12 and b/libs/ssl-config/src/test/resources/certs/cert1/cert1.p12 differ diff --git a/libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs1.crt b/libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs1.crt new file mode 100644 index 0000000000000..3b7ca01e48bae --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs1.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIULGiQ5jnAntO91sS7Al5aUv8/jg8wDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAxMJVGVzdCBDQSAxMB4XDTE5MDEwMzA3NDE1NVoXDTQ2MDUy +MDA3NDE1NVowEDEOMAwGA1UEAxMFY2VydDIwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDQbV6x3P9sstd5zKkjOylk+/X1j0vI6C93HwkF3d9NwhMoV1zq +aSj2SmAQCz4mIjMlAFR9mm6F+3sb8xkMFJD2Mypc5dW6TS5krhbhJdMpoVbceZtS +yuIsvQi1GT2Uwyu89doDiUNBIANqaexrK5x2S/Fy4L8dNl1x2k6PJi6zVpvbnNLV +TSbuSMp3oY23PpX/m6wzJlCYicO8ucMhPwmC0OL9WJNKny4vuEPdiV6/LwCVS4Vr +UpfNqgXLzRMJEbx7C2QEG7T6o2g2101oANBuPaDZcwIQ//EI3IkT10JcABIbwsdj +/Oqj0cf7iEW1IfJWx+kRVT8NfEA5QjL1KQ4HAgMBAAGjaTBnMB0GA1UdDgQWBBS+ +/gUxdwfGB0XcPHsbM6Qw50S2OTAfBgNVHSMEGDAWgBSHd+NjwqkE6hVMEnltx6cT ++D11lzAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwCQYDVR0TBAIwADANBgkq +hkiG9w0BAQsFAAOCAQEAiztFGa3uZVb6Fs8evN4CU+hPFYdhF57lEfT6Xa1OUD1B +5e5rDOZfVloy4gzLdtNCS9lieTgB7Yc3wDUCm0cS48JCMxykSRTI6M0Fmofsgd5d +OKR7CB+jR1Egj6nZren62XCgqjuJ+dbP4DinY6TifFzFX1vOD4RTb0mEn2WHL/JB +DnDxOETmBtKFyueprMtXkTO23dXsYXQeo2Gyih+t5ksqMnxCW2GFkkOqrOUQY8CF +6CVmmb9UCYk3f3Av9TedqJ8Cmoe+HSP0R2oxpnc7cd1v37QPxWgHETro9zS4FBrt +6KgNTP99b+aLxeeuMKJuRVR+TCZPuyYbBzUfp2ZZOg== +-----END CERTIFICATE----- diff --git a/libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs1.key b/libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs1.key new file mode 100644 index 0000000000000..1384b2daf309a --- /dev/null +++ b/libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs1.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,68F4EFD94D4D9BDF + +TL01d1JqaN0P1sb8284jOGpLn42cubkLY7JPj+bmpL2PEH9cT2xo7MM5cwvNSbPX +nuZ1CMFSFqAyxulh/rfZwU1BumKLiwM+ep0A/lWwKJEor+ancCBdkIHWfSg3Jc0i ++nHhL3e7+W+XRfSlafQQkhFkeOXs0hSa4WYX6tymvMAf8OLAenKB/0MOvKjd6EOu +ZIvnBxAjAaDoRr4X4izIVlNmsIbvYARW9WjjcK2FZhNn+WLLTkfwEkl0glRl440L +ET9qAjoH7j5KL5yKw5h2HhALSLaXdiAfCGvhpU4K0Afpb225mvR/uL3HLpJEGKKb +DuoK99zvjHqReq1YndIR688ioc0O4dUvujZKgn8OR2JvGJVSc+mgaIWadfs2aADz +CJIlqnSJa1EW6qm6knLJwieEBoNHbeFszrCKrYdy7uicys9PR1XsoMrOly+HhVnR +PChA3gV6AVIjyMAUkFLg4NAHjgDbb81lu8ENFmlJcjgVeXDMykrpBmhzmhSPtOEb +6bdQCKuA7zXIpJCmP66ZSuNHxikrfqLJjXW3PojLuCx5nfO0akpxjSeyLHzv/YXV +YxnpLJxMybG1KUHyDHRmDrd+UPzLnh52O/g9NoiAlluUcblH+BKer18dJdGSl95G +P+Ted08S0yP2niNz6XHv5KbztzsP73n0w82akCQB8ZAsGJ0CNw7S7N6tWam/UkMN +tvM9nFCjvwah1funu8nj9QyWrzac6ngPP0s8tcKG0ahLEJYn7Hx2Oqn7K39fbMkX +UOJVNEBQP2Anf5dJMfYPEI0xihv3ec1RxpipOm9DKQ878jxnqxgZxa+Pab9j/JxQ +j+BGaoOnYj2jHBnt4nCP75F0BEZaGxsZX2MjJySK+5jy2WW3JRC/E0qPkL6oBvT2 +3e37fep4XKSjqR9lSYjW+AlAVw2uPkwxDp5sD7XFGsH3SM1qj54NWwljpKXnOTbQ +Xws18VEiWsQ3kD7ft11w8/67Bb27TsWEJRo1vCC4KsYBCyjEOrp13aJxpSiNI24W +oSmrQTsCco1Yrxfs0noNu0FzfJHV6qmHR7ps0fj+AWJyquYbIEK1762+r2uutgSg +ZVaPWLkm9uq5K5rNXsj3w1L1CK4YcDre0yt+Kg4Pt3OQR2lF8CpABguA3gR9kO2g +9N8hAfiOq5MDliU+9r6Cr/dPkdzV0Eo971HdfaLD7S/pjSP37fcTcOpDhwNt3UcK +amhR/Zm8Ll414zc/iNiVTcu6+chzSY9Yhwfa8A/XuIfoqMrTiaBMPlxe0tNKaKVs +09d6U5JoTQJcnei/4ODkIIYOzsmMtHgKtmeg86AB8yeLNgqWCrJi+Fxnha+cJ9No ++STMQP4vS8qgRe5XkRGaAZBHyPAwxOou1Iu167LjlFFl0YSYUZTChha5XBG2Uhm8 +FeIH1Ip+83fxMoyiYROOdeMuLXtQIndA3fmjduBEO0kPtAwQ2xfH4g59XnnSL97S +9AslnPnUWzDR0zBr4GQBNAKLaMIFGzhDZEzaooYetoeDYSczil/Rf1D6wyM6Cgjq +BK7c0kNum9uXaDbYCh6spzYua0j1noqsBTm9V25178lNbkQ6yAPdeYCxRmUXHtXk +-----END RSA PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs8.key b/libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs8.key deleted file mode 100644 index 5463978f1c885..0000000000000 --- a/libs/ssl-config/src/test/resources/certs/cert2/cert2-pkcs8.key +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIE6TAbBgkqhkiG9w0BBQMwDgQI3LMAczRe1pICAggABIIEyIfF+k/Z7mEPN+gu -rrV4slxmFjnIzyjoOsWbFBN9j5W8kZGewzWvjnYAzVMVeMUXXW27EMOC818aF8Ry -20BUVkmGNAUFVEslayeF0/S2sYR9xBPqYGLF0DI2VDTBIn8zrTdvQ0DYuuLf4g3W -nY69paCGSVjP24xrnEck+X6PjwIQ89MsJlVLn2chPEetvgu2jB+OdRneSA+lqBSW -2sDuKeXAmYdm3nCVz9do6T22OC9PUkzICGUvMyhYO4CD9dI3FFHa4ncKsr4+htfp -p3eppDczSneDKTPmGJg41UrEVdfENvqYLbYrX2ZZRnWUeuLsGbZUsWyfMT5TT2yz -sZXSS9+WRF8IDDhzKjsoSpsuNwMj8KMmc4oIrBNPysI+Bv0sQzPBlMJkHJ+Swb+W -I2wWC7NS7cwGFEEzwXKYAYI/34e/fAa+oOHd/aNdpGDhhnRyG+Eut3GbojlODiXN -ntLhDIZ+lQclJEkmxP2QfhIvjqkNBkPv+8Xt2Ami3ueF2iNj9PxQHUdx3aht1VMg -uVNLc0qLl98fDYx7+U2q5Y4L1mViZrCwGW+lcaY6a8hscPuasqW9aolFTE95YLVU -yFeUOZlUh1g5FYepISESR5jm5k9wf/WV2cp/KyAzCftdx2iRQtgvyQtSITFtthDa -kR7jI/T/HE2aPqiEcd0Sx+66aSH2JspseJ4VsLGkpg2lU0FPtPaAsl8lIhlfdoDA -43kOPKe5q2YT4QaUNZJROAuyJAlRbE8+LNlfAlZCm0UTQhgiXPzkEuVQFM4DNplq -FkBZnW4R/hD2rP81oy/SISxIANTyAB0FfCRxrvSRP6xe0XMYTIqsUVt9gpszI6AY -B+KTKlJR68cI8Gs1Jq6otn1ExAlCX45mwfaFO6Be89U0YNCZXdJ0X66SFNPmsMrg -eNbRLGziUhyoVUuPEhLXrxtuaGUO0LdHDSCQQGVNKCwNKbcWOE1clYWcJ1Lj0a7T -BTb4y1Nqt7LOHm5En5RtSaourgEKvi6PS2EcemBSUG4xSSP1pEoV/nQnvl2PFsG7 -182571HDc7FRZplzE4fhRKetS5WUuKax2FIqljQXS0CJZwV65boJwfJ2FosxdHj4 -eZK/wYHxi8R8LQB478bLqp3xZt28/EhsMjtLy/nO9SZfSy7Ajgq2mk2w4HZke23K -qv6JY8/ofu5nBp0QSJIPQj35arkxXnEYjzgqJD1IIDNckt3jRSpeX3OMfmVWDXus -a/wEwNaBACo5lgUYAwzXiPFyLEa6RkbD04z55GqsIjwZhIKrC1glyb0gmtxyY/N8 -QfPaSc4aXgUQK2mrgsR22eKf735MhM0pHOykErJzrIV92lRfSuQvfIoqTeFV0fWY -Yxns7HEIBln7/9udgZpf+EWnzM3kpPjRX1iYNtAsMAKEMrK/lnt3jZTkKY2Drem5 -NGjtEWNmzKOLH/S0TLcpHldoDJGdno7CytNGasrkFTaDLEhF68QI85zbMmyotKiZ -FKooG78oyxWCYqqPJ9vVBGwsMNfDMJP56T9UNhL72FwNqzSCaEcAUlDm2zCiQWKd -NpOfYzRx8uLp7UyzXbl959GjHWfTA74Yidug13eSHRgKwLXLuro05awG8yNXeDeL -wDswgDyKmlV7JDJjQw== ------END ENCRYPTED PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/cert2/cert2.crt b/libs/ssl-config/src/test/resources/certs/cert2/cert2.crt index 3b7ca01e48bae..6438d1e56e906 100644 --- a/libs/ssl-config/src/test/resources/certs/cert2/cert2.crt +++ b/libs/ssl-config/src/test/resources/certs/cert2/cert2.crt @@ -1,19 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIDGzCCAgOgAwIBAgIULGiQ5jnAntO91sS7Al5aUv8/jg8wDQYJKoZIhvcNAQEL -BQAwFDESMBAGA1UEAxMJVGVzdCBDQSAxMB4XDTE5MDEwMzA3NDE1NVoXDTQ2MDUy -MDA3NDE1NVowEDEOMAwGA1UEAxMFY2VydDIwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDQbV6x3P9sstd5zKkjOylk+/X1j0vI6C93HwkF3d9NwhMoV1zq -aSj2SmAQCz4mIjMlAFR9mm6F+3sb8xkMFJD2Mypc5dW6TS5krhbhJdMpoVbceZtS -yuIsvQi1GT2Uwyu89doDiUNBIANqaexrK5x2S/Fy4L8dNl1x2k6PJi6zVpvbnNLV -TSbuSMp3oY23PpX/m6wzJlCYicO8ucMhPwmC0OL9WJNKny4vuEPdiV6/LwCVS4Vr -UpfNqgXLzRMJEbx7C2QEG7T6o2g2101oANBuPaDZcwIQ//EI3IkT10JcABIbwsdj -/Oqj0cf7iEW1IfJWx+kRVT8NfEA5QjL1KQ4HAgMBAAGjaTBnMB0GA1UdDgQWBBS+ -/gUxdwfGB0XcPHsbM6Qw50S2OTAfBgNVHSMEGDAWgBSHd+NjwqkE6hVMEnltx6cT -+D11lzAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwCQYDVR0TBAIwADANBgkq -hkiG9w0BAQsFAAOCAQEAiztFGa3uZVb6Fs8evN4CU+hPFYdhF57lEfT6Xa1OUD1B -5e5rDOZfVloy4gzLdtNCS9lieTgB7Yc3wDUCm0cS48JCMxykSRTI6M0Fmofsgd5d -OKR7CB+jR1Egj6nZren62XCgqjuJ+dbP4DinY6TifFzFX1vOD4RTb0mEn2WHL/JB -DnDxOETmBtKFyueprMtXkTO23dXsYXQeo2Gyih+t5ksqMnxCW2GFkkOqrOUQY8CF -6CVmmb9UCYk3f3Av9TedqJ8Cmoe+HSP0R2oxpnc7cd1v37QPxWgHETro9zS4FBrt -6KgNTP99b+aLxeeuMKJuRVR+TCZPuyYbBzUfp2ZZOg== +MIIDEDCCAfigAwIBAgIULU6478tMe6npOywF6dj4+azLeJIwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAxMJVGVzdCBDQSAxMB4XDTI0MDgyOTA5MzE0OFoXDTM0MDgy +NzA5MzE0OFowEDEOMAwGA1UEAwwFY2VydDIwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCKWDmVjvSYQmq5bY2XX9HIJajm9ly5hm9iG+M8pxWLtzH4FNYX +2VlwoP/aROMmbAPL4EH1jdKaD0hUKDRZOJAr33zm9qGsIB16dAk47HY7mR6OwObC +VVhVMgVPBDLb8rgwKThlET+Hos3E6a3oUU1mVqur+EGu5wlXYyR+b9d0fl/oizV1 +uEtFLLvGVhBg5Md0vAjgE2JRidBzFRXUaAPSwmxZHFMwcZSTAd3Jfn8OE8TizkE6 +NVh9jrsQMTubp9eMbRaYZBzscKxGKU2/+IC99UENVJg09LGa4wg+96lpDx3bNQxN +HbXBVcaNufjWNtofHZ8Yxzl9w+tooftvAvVdAgMBAAGjXjBcMBoGA1UdEQQTMBGC +CWxvY2FsaG9zdIcEfwAAATAdBgNVHQ4EFgQUfw8Izdeensd4lhKbY8hzG655o3ow +HwYDVR0jBBgwFoAUh3fjY8KpBOoVTBJ5bcenE/g9dZcwDQYJKoZIhvcNAQELBQAD +ggEBAAC2tOJGQUCfMnRZA+sX4mX8F6oxpAwsBEgTN9dY6JqJkmx7rFpTXVi170Md +VUascZdRXVlvBVfMt3rulDO1LWU7+LeWYLXp6EHhvGiPO8+3bwEwfGFpovLgvYmT +kdCa9km83w7qreuQTayu9ZoU5UbLKTygdQCs3wwgq/td744/moLgbiUQJL3P5r8f +RSPTID9XsdVo5qFSdFLXm8VaJExfcsvxZRL1KX+76WWNI77zo/uWnqnIvlFiLWSD +UXUfX1ItcaeeNLA++gI+hC1Rfx/jeH77AcyEIuVIRIN89+9vxer9KBVhE3F7xQ8d +iWSLz1sb6baSGdvkDhbzkuA8Y/I= -----END CERTIFICATE----- diff --git a/libs/ssl-config/src/test/resources/certs/cert2/cert2.key b/libs/ssl-config/src/test/resources/certs/cert2/cert2.key index 1384b2daf309a..6d631e7ddffdd 100644 --- a/libs/ssl-config/src/test/resources/certs/cert2/cert2.key +++ b/libs/ssl-config/src/test/resources/certs/cert2/cert2.key @@ -1,30 +1,30 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,68F4EFD94D4D9BDF - -TL01d1JqaN0P1sb8284jOGpLn42cubkLY7JPj+bmpL2PEH9cT2xo7MM5cwvNSbPX -nuZ1CMFSFqAyxulh/rfZwU1BumKLiwM+ep0A/lWwKJEor+ancCBdkIHWfSg3Jc0i -+nHhL3e7+W+XRfSlafQQkhFkeOXs0hSa4WYX6tymvMAf8OLAenKB/0MOvKjd6EOu -ZIvnBxAjAaDoRr4X4izIVlNmsIbvYARW9WjjcK2FZhNn+WLLTkfwEkl0glRl440L -ET9qAjoH7j5KL5yKw5h2HhALSLaXdiAfCGvhpU4K0Afpb225mvR/uL3HLpJEGKKb -DuoK99zvjHqReq1YndIR688ioc0O4dUvujZKgn8OR2JvGJVSc+mgaIWadfs2aADz -CJIlqnSJa1EW6qm6knLJwieEBoNHbeFszrCKrYdy7uicys9PR1XsoMrOly+HhVnR -PChA3gV6AVIjyMAUkFLg4NAHjgDbb81lu8ENFmlJcjgVeXDMykrpBmhzmhSPtOEb -6bdQCKuA7zXIpJCmP66ZSuNHxikrfqLJjXW3PojLuCx5nfO0akpxjSeyLHzv/YXV -YxnpLJxMybG1KUHyDHRmDrd+UPzLnh52O/g9NoiAlluUcblH+BKer18dJdGSl95G -P+Ted08S0yP2niNz6XHv5KbztzsP73n0w82akCQB8ZAsGJ0CNw7S7N6tWam/UkMN -tvM9nFCjvwah1funu8nj9QyWrzac6ngPP0s8tcKG0ahLEJYn7Hx2Oqn7K39fbMkX -UOJVNEBQP2Anf5dJMfYPEI0xihv3ec1RxpipOm9DKQ878jxnqxgZxa+Pab9j/JxQ -j+BGaoOnYj2jHBnt4nCP75F0BEZaGxsZX2MjJySK+5jy2WW3JRC/E0qPkL6oBvT2 -3e37fep4XKSjqR9lSYjW+AlAVw2uPkwxDp5sD7XFGsH3SM1qj54NWwljpKXnOTbQ -Xws18VEiWsQ3kD7ft11w8/67Bb27TsWEJRo1vCC4KsYBCyjEOrp13aJxpSiNI24W -oSmrQTsCco1Yrxfs0noNu0FzfJHV6qmHR7ps0fj+AWJyquYbIEK1762+r2uutgSg -ZVaPWLkm9uq5K5rNXsj3w1L1CK4YcDre0yt+Kg4Pt3OQR2lF8CpABguA3gR9kO2g -9N8hAfiOq5MDliU+9r6Cr/dPkdzV0Eo971HdfaLD7S/pjSP37fcTcOpDhwNt3UcK -amhR/Zm8Ll414zc/iNiVTcu6+chzSY9Yhwfa8A/XuIfoqMrTiaBMPlxe0tNKaKVs -09d6U5JoTQJcnei/4ODkIIYOzsmMtHgKtmeg86AB8yeLNgqWCrJi+Fxnha+cJ9No -+STMQP4vS8qgRe5XkRGaAZBHyPAwxOou1Iu167LjlFFl0YSYUZTChha5XBG2Uhm8 -FeIH1Ip+83fxMoyiYROOdeMuLXtQIndA3fmjduBEO0kPtAwQ2xfH4g59XnnSL97S -9AslnPnUWzDR0zBr4GQBNAKLaMIFGzhDZEzaooYetoeDYSczil/Rf1D6wyM6Cgjq -BK7c0kNum9uXaDbYCh6spzYua0j1noqsBTm9V25178lNbkQ6yAPdeYCxRmUXHtXk ------END RSA PRIVATE KEY----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQmZ6ZHPPGAIJiIR8A +rWSsNQICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEDnJhtyOur+FM/85 +ikBGVwMEggTQADlZDyF6ebcLYgXSJI2rCmtDoeHM8Fan9o8gu3zru3ISPtKTxijl +l0pyj+CPR56kk4RAGQSUwyYt3itWFKmXcZuTQLIJOKaKYJsSgwoIq3rkU6hU1qYE +TNTALBvfeokGrw9RT/ODfKUd8GmTWQqPEZbZGfj57uV029x4x8zf4CayDdS+7ABW +d9ulWAcjauE/+n4xAlUoch/rcostqt0/ZVZnHSQ39DLtB9u5OCCO4Fv9x5VGnutL +yAAWYxrhupwlHkY9gz7hgpKle0VIrfdZUaHQBz0he2w/t6Iv9Y69wTuimKOqtI35 +v68Xo3qwiIaklnf7GkqNoDZ3O4uvaMlapqQ9Pv0w8dFRcf3MfOkXZTut1qWuJBz/ +8WdbmCKp6jq8hjTXeX1xb5/9zFd0N7VtmEwwx+K5FwLHjSc0Xd5sgWe24wcB3Xkt +fwCO+vLAZ0kaYeqj171BH+d4wyhaVH6YOviHI6vbs95Eb0jA8fjsf6PMZkL69dcC +vWT3Tda9OOVfMhNRzhR8rAOPMOkAbFNYggxrPyaaEQrEs9wEFBLzHwub6n79n1qm +u54BariExLxwBbr8GgLc6NlD8/a2ycwHKHO3qTLt+lX1RK8lFvoktdMbWUCZKVQz +jt02eYfVadNzF2F0atFMnOHYsP6txSKbgF5pcK4FRrdkkEf8N5zR6YHGmlwjBMxi +xdbCVU2AbsFpDhQ5rLJDQFwo9KfuefUTWBKLAboqtSzpwY/g3svD8mu0vcJWnSIy +SrNJTgjxsWSOkVFKYmYLLYgnAD3zToeVVmnvfyaG6NjH7Zb+PFher8LwXW7CwFYt +sNhBaGyAn2DXqD/gVvbr6V8zNYEYQ3wVdLyORYRbYV3ds02sYUpxperGqSIbxMUn +Wm3Rw6GsYrqIQ7p5ZSQ0l54sNaTHAIUXlDa/R4uISpUu7td/SquUM4a9N1r5hDRw +s/TK+eCBFQZweGrk+rsUpXxfJ+EYpUtUPkOH5d3QSpqqWdGxmwDBQlvMX5N6JKiu +5bRhnH7ntoysdDqOoDmg8IZM7BCf9dFzie3wvieBB1plfZubz++Nyuzc1OA9k3r0 +68BYTYKrfrj46rQq1XoIusl36Xn1bIWK2peoIzazdZC7wB0ZeAd+8BvPp2FkBnrI +YyJm4nuyw/TlI9wIkX9CkJI4keVRvQzRPFe6WtSpB80PKEzJ22Hq7CemRdHMxuBG +KUkuyAACmJcJJ1UwOiPL9kGjVwgm0RRVwpZswvU8IFA2CfpiyhANOd4U3TtuMxz4 +SnRc86f21oL5dZ8HPg5rKSzOlEo8qzI4bvXyrNoyTSAuQcarveFpgURo+ocMkNlm +p7DaVVfjjAbjEmmPl8eKy7ssLT1NO2FEwedbhDkcsvG3UIy5TvfJcifXwi8ovRiJ ++MAPJ47jv+y7D1MXPw+0uZRPgDG8czYaNKeEl4xLaJY0O7rLDYaQyqerTVg/vaqS +IE+um0LE0Yg/pEFpniFI/HUv7TmPdpX1GnTj10+KOY6xwgyZQMmPxQ0FbI3cydAe ++MQtCzTQYVrKDOVOH3GE6OM0e7/xn9vkjrKFI8Yqe2ZFCR8mZ91Wj9O7Pyp9Vw+9 +N1FKCq3aqnS3UroPPWdz0hP2FsKwOf6Pxpj3j52QTnpx7gyLGoAqcaU= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/libs/ssl-config/src/test/resources/certs/cert2/cert2.p12 b/libs/ssl-config/src/test/resources/certs/cert2/cert2.p12 index 269e855f77098..5222f8cf3c691 100644 Binary files a/libs/ssl-config/src/test/resources/certs/cert2/cert2.p12 and b/libs/ssl-config/src/test/resources/certs/cert2/cert2.p12 differ diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/README.md b/libs/ssl-config/src/test/resources/certs/pem-utils/README.md index 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 0000000000000..8d1a016d790b1 Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.bcfks differ 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 ebe6146124e8f..25363b6d8a5a4 100644 Binary files a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.jks and b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.jks differ diff --git a/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java b/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java index 8a4f3b4a898b4..eeb942166eeec 100644 --- a/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java +++ b/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java @@ -164,7 +164,6 @@ public void testInvalidJavaPattern() { } public void testJavaPatternLocale() { - assumeFalse("Can't run in a FIPS JVM, Joda parse date error", inFipsJvm()); DateProcessor dateProcessor = new DateProcessor( randomAlphaOfLength(10), null, diff --git a/modules/reindex/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/reindex/src/test/java/org/opensearch/index/reindex/ReindexRestClientSslTests.java b/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexRestClientSslTests.java index 1123ae4623300..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 @@ -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,11 +64,15 @@ 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; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -84,6 +87,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 -> {}; @@ -108,18 +112,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(); @@ -129,7 +146,6 @@ private static SSLContext buildServerSslContext() throws Exception { } public void testClientFailsWithUntrustedCertificate() throws IOException { - assumeFalse("https://github.com/elastic/elasticsearch/issues/49094", inFipsJvm()); final List threads = new ArrayList<>(); final Settings settings = Settings.builder() .put("path.home", createTempDir()) @@ -138,7 +154,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") + ); } } @@ -159,7 +181,6 @@ public void testClientSucceedsWithCertificateAuthorities() throws IOException { } public void testClientSucceedsWithVerificationDisabled() throws IOException { - assumeFalse("Cannot disable verification in FIPS JVM", inFipsJvm()); final List threads = new ArrayList<>(); final Settings settings = Settings.builder() .put("path.home", createTempDir()) @@ -167,10 +188,21 @@ public void testClientSucceedsWithVerificationDisabled() throws IOException { .put("reindex.ssl.supported_protocols", "TLSv1.2") .build(); final Environment environment = TestEnvironment.newEnvironment(settings); - final ReindexSslConfig ssl = new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class)); - try (RestClient client = Reindexer.buildRestClient(getRemoteInfo(), ssl, 1L, threads)) { - final Response response = client.performRequest(new Request("GET", "/")); - assertThat(response.getStatusLine().getStatusCode(), Matchers.is(200)); + + if (inFipsJvm()) { + try { + new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class)); + fail("expected IllegalStateException"); + } catch (Exception e) { + assertThat(e, Matchers.instanceOf(IllegalStateException.class)); + assertThat(e.getMessage(), Matchers.containsString("The use of TrustEverythingConfig is not permitted in FIPS mode")); + } + } else { + final ReindexSslConfig ssl = new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class)); + try (RestClient client = Reindexer.buildRestClient(getRemoteInfo(), ssl, 1L, threads)) { + final Response response = client.performRequest(new Request("GET", "/")); + assertThat(response.getStatusLine().getStatusCode(), Matchers.is(200)); + } } } @@ -184,7 +216,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<>(); @@ -206,8 +238,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/build.gradle b/modules/transport-netty4/build.gradle index 4e68a4ce17f73..95e9ee2e80f8c 100644 --- a/modules/transport-netty4/build.gradle +++ b/modules/transport-netty4/build.gradle @@ -147,17 +147,6 @@ thirdPartyAudit { 'io.netty.internal.tcnative.SSLContext', 'io.netty.internal.tcnative.SSLPrivateKeyMethod', - // from io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator (netty) - 'org.bouncycastle.cert.X509v3CertificateBuilder', - 'org.bouncycastle.cert.jcajce.JcaX509CertificateConverter', - 'org.bouncycastle.operator.jcajce.JcaContentSignerBuilder', - 'org.bouncycastle.openssl.PEMEncryptedKeyPair', - 'org.bouncycastle.openssl.PEMParser', - 'org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter', - 'org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder', - 'org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder', - 'org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo', - // from io.netty.handler.ssl.JettyNpnSslEngine (netty) 'org.eclipse.jetty.npn.NextProtoNego$ClientProvider', 'org.eclipse.jetty.npn.NextProtoNego$ServerProvider', diff --git a/modules/transport-netty4/src/test/java/org/opensearch/http/netty4/ssl/SecureNetty4HttpServerTransportTests.java b/modules/transport-netty4/src/test/java/org/opensearch/http/netty4/ssl/SecureNetty4HttpServerTransportTests.java index f80ad901ce765..949b5e4ddd303 100644 --- a/modules/transport-netty4/src/test/java/org/opensearch/http/netty4/ssl/SecureNetty4HttpServerTransportTests.java +++ b/modules/transport-netty4/src/test/java/org/opensearch/http/netty4/ssl/SecureNetty4HttpServerTransportTests.java @@ -15,6 +15,8 @@ import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.MockBigArrays; import org.opensearch.common.util.MockPageCacheRecycler; @@ -121,13 +123,13 @@ public Optional buildHttpServerExceptionHandler(Setti @Override public Optional buildSecureHttpServerEngine(Settings settings, HttpServerTransport transport) throws SSLException { try { - final KeyStore keyStore = KeyStore.getInstance("PKCS12"); + final KeyStore keyStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); keyStore.load( - SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-secure.jks"), + SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-secure.bcfks"), "password".toCharArray() ); - 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..c938052d321ca 100644 --- a/modules/transport-netty4/src/test/java/org/opensearch/transport/netty4/ssl/SimpleSecureNetty4TransportTests.java +++ b/modules/transport-netty4/src/test/java/org/opensearch/transport/netty4/ssl/SimpleSecureNetty4TransportTests.java @@ -13,6 +13,8 @@ import org.opensearch.common.network.NetworkService; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.common.util.PageCacheRecycler; import org.opensearch.common.util.io.IOUtils; import org.opensearch.common.util.net.NetUtils; @@ -77,13 +79,15 @@ public Optional buildServerTransportExceptionHandler( @Override public Optional buildSecureServerTransportEngine(Settings settings, Transport transport) throws SSLException { try { - final KeyStore keyStore = KeyStore.getInstance("PKCS12"); + var keyStoreType = inFipsJvm() ? KeyStoreType.BCFKS : KeyStoreType.JKS; + var fileExtension = KeyStoreType.TYPE_TO_EXTENSION_MAP.get(keyStoreType).get(0); + final KeyStore keyStore = KeyStoreFactory.getInstance(keyStoreType); keyStore.load( - SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-secure.jks"), + SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-secure" + fileExtension), "password".toCharArray() ); - 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/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 c8cec5d3803a4..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 server.p12 -name netty4-secure -password pass:password - -# 3. Import the certificate into JDK keystore (PKCS12 type) - -keytool -importkeystore -srcstorepass password -destkeystore netty4-secure.jks -srckeystore server.p12 -srcstoretype PKCS12 -alias netty4-secure -deststorepass password \ No newline at end of file diff --git a/modules/transport-netty4/src/test/resources/netty4-secure.bcfks b/modules/transport-netty4/src/test/resources/netty4-secure.bcfks new file mode 100644 index 0000000000000..034f2a1331729 Binary files /dev/null and b/modules/transport-netty4/src/test/resources/netty4-secure.bcfks differ diff --git a/modules/transport-netty4/src/test/resources/netty4-secure.jks b/modules/transport-netty4/src/test/resources/netty4-secure.jks index 59dfd31c2a156..d158f1fe60ef7 100644 Binary files a/modules/transport-netty4/src/test/resources/netty4-secure.jks and b/modules/transport-netty4/src/test/resources/netty4-secure.jks differ 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 0000000000000..822d7ff8236f3 Binary files /dev/null and b/modules/transport-netty4/src/test/resources/netty4-secure.p12 differ diff --git a/modules/transport-netty4/src/yamlRestTest/java/org/opensearch/http/netty4/Netty4ClientYamlTestSuiteIT.java b/modules/transport-netty4/src/yamlRestTest/java/org/opensearch/http/netty4/Netty4ClientYamlTestSuiteIT.java index 45693078174a8..9c10dc98202eb 100644 --- a/modules/transport-netty4/src/yamlRestTest/java/org/opensearch/http/netty4/Netty4ClientYamlTestSuiteIT.java +++ b/modules/transport-netty4/src/yamlRestTest/java/org/opensearch/http/netty4/Netty4ClientYamlTestSuiteIT.java @@ -39,15 +39,10 @@ import org.apache.lucene.tests.util.TimeUnits; import org.opensearch.test.rest.yaml.ClientYamlTestCandidate; import org.opensearch.test.rest.yaml.OpenSearchClientYamlSuiteTestCase; -import org.junit.BeforeClass; //TODO: This is a *temporary* workaround to ensure a timeout does not mask other problems @TimeoutSuite(millis = 30 * TimeUnits.MINUTE) public class Netty4ClientYamlTestSuiteIT extends OpenSearchClientYamlSuiteTestCase { - @BeforeClass - public static void muteInFips() { - assumeFalse("We run with DEFAULT distribution in FIPS mode and default to security4 instead of netty4", inFipsJvm()); - } public Netty4ClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); diff --git a/plugins/analysis-icu/src/test/java/org/opensearch/index/analysis/IcuAnalyzerTests.java b/plugins/analysis-icu/src/test/java/org/opensearch/index/analysis/IcuAnalyzerTests.java index c363bc6eb43f8..c74dbb22bc21e 100644 --- a/plugins/analysis-icu/src/test/java/org/opensearch/index/analysis/IcuAnalyzerTests.java +++ b/plugins/analysis-icu/src/test/java/org/opensearch/index/analysis/IcuAnalyzerTests.java @@ -35,6 +35,7 @@ import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.tests.analysis.BaseTokenStreamTestCase; import org.opensearch.Version; +import org.opensearch.bootstrap.SecureRandomInitializer; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.common.settings.Settings; import org.opensearch.index.IndexSettings; @@ -47,6 +48,10 @@ public class IcuAnalyzerTests extends BaseTokenStreamTestCase { + static { + SecureRandomInitializer.init(); + } + public void testMixedAlphabetTokenization() throws IOException { Settings settings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build(); diff --git a/plugins/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 a4b733ec7d894..fcc116f993181 100644 --- a/plugins/discovery-azure-classic/src/internalClusterTest/java/org/opensearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java +++ b/plugins/discovery-azure-classic/src/internalClusterTest/java/org/opensearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java @@ -43,6 +43,8 @@ import org.opensearch.common.SuppressForbidden; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.core.util.FileSystemUtils; import org.opensearch.discovery.DiscoveryModule; import org.opensearch.env.Environment; @@ -278,14 +280,14 @@ 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); } - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ks, passphrase); - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); tmf.init(ks); SSLContext ssl = SSLContext.getInstance(getProtocol()); ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); 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/licenses/bcprov-jdk18on-1.78.jar.sha1 b/plugins/identity-shiro/licenses/bcprov-jdk18on-1.78.jar.sha1 deleted file mode 100644 index 47fb5fd5e5f5d..0000000000000 --- a/plugins/identity-shiro/licenses/bcprov-jdk18on-1.78.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -619aafb92dc0b4c6cc4cf86c487ca48ee2d67a8e \ No newline at end of file diff --git a/plugins/identity-shiro/licenses/bcprov-jdk18on-LICENSE.txt b/plugins/identity-shiro/licenses/bcprov-jdk18on-LICENSE.txt deleted file mode 100644 index 9f27bafe96885..0000000000000 --- a/plugins/identity-shiro/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/identity-shiro/licenses/password4j-1.8.2.jar.sha1 b/plugins/identity-shiro/licenses/password4j-1.8.2.jar.sha1 new file mode 100644 index 0000000000000..bee14467d32a2 --- /dev/null +++ b/plugins/identity-shiro/licenses/password4j-1.8.2.jar.sha1 @@ -0,0 +1 @@ +f8ac106c667c0b081075e81a90dc92861b9bb66e \ No newline at end of file diff --git a/plugins/identity-shiro/licenses/password4j-LICENSE.txt b/plugins/identity-shiro/licenses/password4j-LICENSE.txt new file mode 100644 index 0000000000000..261eeb9e9f8b2 --- /dev/null +++ b/plugins/identity-shiro/licenses/password4j-LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/identity-shiro/licenses/bcprov-jdk18on-NOTICE.txt b/plugins/identity-shiro/licenses/password4j-NOTICE.txt similarity index 100% rename from plugins/identity-shiro/licenses/bcprov-jdk18on-NOTICE.txt rename to plugins/identity-shiro/licenses/password4j-NOTICE.txt 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..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 @@ -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 org.opensearch.SpecialPermission; + +import java.nio.CharBuffer; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import com.password4j.BcryptFunction; +import com.password4j.Password; + +import static org.opensearch.core.common.Strings.isNullOrEmpty; /** * Password matcher for BCrypt @@ -28,7 +37,25 @@ 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()); + } + + @SuppressWarnings("removal") + 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); + SecurityManager securityManager = System.getSecurityManager(); + if (securityManager != null) { + securityManager.checkPermission(new SpecialPermission()); + } + return AccessController.doPrivileged( + (PrivilegedAction) () -> Password.check(passwordBuffer, hash).with(BcryptFunction.getInstanceFromHash(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..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 @@ -41,4 +41,33 @@ public void testCredentialDoNotMatch() { assertThat(result, equalTo(false)); } + + public void testEmptyPassword() { + { + 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")); + } + { + 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() { + 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")); + } } diff --git a/plugins/ingest-attachment/build.gradle b/plugins/ingest-attachment/build.gradle index e0ad602266602..97a78261f85ba 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}" @@ -145,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/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/plugins/repository-azure/build.gradle b/plugins/repository-azure/build.gradle index 332651e37cfa4..3e0e5e23c59f1 100644 --- a/plugins/repository-azure/build.gradle +++ b/plugins/repository-azure/build.gradle @@ -213,16 +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', - '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', @@ -313,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-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..4d6c6e2e425a2 100644 --- a/plugins/repository-gcs/src/main/java/org/opensearch/repositories/gcs/GoogleCloudStorageService.java +++ b/plugins/repository-gcs/src/main/java/org/opensearch/repositories/gcs/GoogleCloudStorageService.java @@ -32,10 +32,10 @@ package org.opensearch.repositories.gcs; -import com.google.api.client.googleapis.GoogleUtils; import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.util.SecurityUtils; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.ServiceOptions; @@ -46,10 +46,13 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.common.collect.MapBuilder; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.common.unit.TimeValue; import org.opensearch.core.common.Strings; import java.io.IOException; +import java.io.InputStream; import java.net.Authenticator; import java.net.PasswordAuthentication; import java.net.Proxy; @@ -185,9 +188,12 @@ public HttpRequestInitializer getHttpRequestInitializer(ServiceOptions ser private HttpTransport createHttpTransport(final GoogleCloudStorageClientSettings clientSettings) throws IOException { return SocketAccess.doPrivilegedIOException(() -> { final NetHttpTransport.Builder builder = new NetHttpTransport.Builder(); - // requires java.lang.RuntimePermission "setFactory" - // Pin the TLS trust certificates. - builder.trustCertificates(GoogleUtils.getCertificateTrustStore()); + // use the BCFIPS trustStore format instead of PKCS#12 to ensure compatibility with BC-FIPS + var certTrustStore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); + InputStream keyStoreStream = getClass().getResourceAsStream("/google.bcfks"); + SecurityUtils.loadKeyStore(certTrustStore, keyStoreStream, "notasecret"); + + builder.trustCertificates(certTrustStore); final ProxySettings proxySettings = clientSettings.getProxySettings(); if (proxySettings != ProxySettings.NO_PROXY_SETTINGS) { if (proxySettings.isAuthenticated()) { diff --git a/plugins/repository-gcs/src/main/resources/README.md b/plugins/repository-gcs/src/main/resources/README.md new file mode 100644 index 0000000000000..bda781bcea537 --- /dev/null +++ b/plugins/repository-gcs/src/main/resources/README.md @@ -0,0 +1,19 @@ +# +# This is README describes how the certificates in this directory were created. +# This file can also be executed as a script. +# google-api-java-client provides its own trusted certificates inside google keystore which comes in JKS or PKCS#12 formats. +# Since BCFIPS requires its own BCFKS format this script creates it. +# + +``` +keytool -importkeystore -noprompt \ + -srckeystore $KEY_STORE_PATH/google.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass notasecret \ + -destkeystore google.bcfks \ + -deststoretype BCFKS \ + -deststorepass notasecret \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar +``` diff --git a/plugins/repository-gcs/src/main/resources/google.bcfks b/plugins/repository-gcs/src/main/resources/google.bcfks new file mode 100644 index 0000000000000..13202c5c21eb3 Binary files /dev/null and b/plugins/repository-gcs/src/main/resources/google.bcfks differ diff --git a/plugins/repository-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/plugins/repository-s3/build.gradle b/plugins/repository-s3/build.gradle index 6e84edddcc252..17e694e8eab04 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 @@ -292,7 +293,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 { @@ -474,17 +475,6 @@ thirdPartyAudit { 'net.jpountz.xxhash.XXHash32', 'net.jpountz.xxhash.XXHashFactory', - // from io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator (netty) - 'org.bouncycastle.cert.X509v3CertificateBuilder', - 'org.bouncycastle.cert.jcajce.JcaX509CertificateConverter', - 'org.bouncycastle.operator.jcajce.JcaContentSignerBuilder', - 'org.bouncycastle.openssl.PEMEncryptedKeyPair', - 'org.bouncycastle.openssl.PEMParser', - 'org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter', - 'org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder', - 'org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder', - 'org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo', - 'org.conscrypt.AllocatedBuffer', 'org.conscrypt.BufferAllocator', 'org.conscrypt.Conscrypt', diff --git a/plugins/repository-s3/src/internalClusterTest/java/org/opensearch/repositories/s3/S3BlobStoreRepositoryTests.java b/plugins/repository-s3/src/internalClusterTest/java/org/opensearch/repositories/s3/S3BlobStoreRepositoryTests.java index 5bea51706cfae..689023f81658a 100644 --- a/plugins/repository-s3/src/internalClusterTest/java/org/opensearch/repositories/s3/S3BlobStoreRepositoryTests.java +++ b/plugins/repository-s3/src/internalClusterTest/java/org/opensearch/repositories/s3/S3BlobStoreRepositoryTests.java @@ -144,7 +144,7 @@ protected HttpHandler createErroneousHttpHandler(final HttpHandler delegate) { protected Settings nodeSettings(int nodeOrdinal) { final MockSecureSettings secureSettings = new MockSecureSettings(); secureSettings.setString(S3ClientSettings.ACCESS_KEY_SETTING.getConcreteSettingForNamespace("test").getKey(), "access"); - secureSettings.setString(S3ClientSettings.SECRET_KEY_SETTING.getConcreteSettingForNamespace("test").getKey(), "secret"); + secureSettings.setString(S3ClientSettings.SECRET_KEY_SETTING.getConcreteSettingForNamespace("test").getKey(), "secret_password"); final Settings.Builder builder = Settings.builder() .put(ThreadPool.ESTIMATED_TIME_INTERVAL_SETTING.getKey(), 0) // We have tests that verify an exact wait time diff --git a/plugins/repository-s3/src/main/java/org/opensearch/repositories/s3/S3Service.java b/plugins/repository-s3/src/main/java/org/opensearch/repositories/s3/S3Service.java index 3d5e121778ba9..e17234f6921e0 100644 --- a/plugins/repository-s3/src/main/java/org/opensearch/repositories/s3/S3Service.java +++ b/plugins/repository-s3/src/main/java/org/opensearch/repositories/s3/S3Service.java @@ -64,6 +64,7 @@ import org.apache.http.protocol.HttpContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.cluster.metadata.RepositoryMetadata; import org.opensearch.common.Nullable; import org.opensearch.common.SuppressForbidden; @@ -88,7 +89,6 @@ import java.nio.file.Path; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -341,7 +341,8 @@ private static SSLConnectionSocketFactory createSocksSslConnectionSocketFactory( // This part was taken from AWS settings try { final SSLContext sslCtx = SSLContext.getInstance("TLS"); - sslCtx.init(SystemPropertyTlsKeyManagersProvider.create().keyManagers(), null, new SecureRandom()); + sslCtx.init(SystemPropertyTlsKeyManagersProvider.create().keyManagers(), null, CryptoServicesRegistrar.getSecureRandom()); + return new SdkTlsSocketFactory(sslCtx, new DefaultHostnameVerifier()) { @Override public Socket createSocket(final HttpContext ctx) throws IOException { diff --git a/plugins/repository-s3/src/test/java/org/opensearch/repositories/s3/S3BlobContainerRetriesTests.java b/plugins/repository-s3/src/test/java/org/opensearch/repositories/s3/S3BlobContainerRetriesTests.java index 96ef28d24c14f..dbecc7c7eb417 100644 --- a/plugins/repository-s3/src/test/java/org/opensearch/repositories/s3/S3BlobContainerRetriesTests.java +++ b/plugins/repository-s3/src/test/java/org/opensearch/repositories/s3/S3BlobContainerRetriesTests.java @@ -222,7 +222,10 @@ protected AsyncMultiStreamBlobContainer createBlobContainer( final MockSecureSettings secureSettings = new MockSecureSettings(); secureSettings.setString(S3ClientSettings.ACCESS_KEY_SETTING.getConcreteSettingForNamespace(clientName).getKey(), "access"); - secureSettings.setString(S3ClientSettings.SECRET_KEY_SETTING.getConcreteSettingForNamespace(clientName).getKey(), "secret"); + secureSettings.setString( + S3ClientSettings.SECRET_KEY_SETTING.getConcreteSettingForNamespace(clientName).getKey(), + "secret_password" + ); clientSettings.setSecureSettings(secureSettings); service.refreshAndClearCache(S3ClientSettings.load(clientSettings.build(), configPath())); asyncService.refreshAndClearCache(S3ClientSettings.load(clientSettings.build(), configPath())); diff --git a/plugins/telemetry-otel/build.gradle b/plugins/telemetry-otel/build.gradle index 54f4f2f897562..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/plugins/transport-grpc/build.gradle b/plugins/transport-grpc/build.gradle index 5c6bc8efe1098..c94b71f2e0b6c 100644 --- a/plugins/transport-grpc/build.gradle +++ b/plugins/transport-grpc/build.gradle @@ -51,17 +51,6 @@ thirdPartyAudit { 'org.apache.log4j.Level', 'org.apache.log4j.Logger', - // from io.netty.handler.ssl.util.BouncyCastleSelfSignedCertGenerator (netty) - 'org.bouncycastle.cert.X509v3CertificateBuilder', - 'org.bouncycastle.cert.jcajce.JcaX509CertificateConverter', - 'org.bouncycastle.operator.jcajce.JcaContentSignerBuilder', - 'org.bouncycastle.openssl.PEMEncryptedKeyPair', - 'org.bouncycastle.openssl.PEMParser', - 'org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter', - 'org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder', - 'org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder', - 'org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo', - // from io.netty.handler.ssl.JettyNpnSslEngine (netty) 'org.eclipse.jetty.npn.NextProtoNego$ClientProvider', 'org.eclipse.jetty.npn.NextProtoNego$ServerProvider', diff --git a/plugins/transport-reactor-netty4/build.gradle b/plugins/transport-reactor-netty4/build.gradle index 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/qa/os/build.gradle b/qa/os/build.gradle index 082ed5277575a..0ab861e96d79a 100644 --- a/qa/os/build.gradle +++ b/qa/os/build.gradle @@ -49,6 +49,7 @@ dependencies { api project(':libs:opensearch-common') api project(':libs:opensearch-core') + api project(':libs:opensearch-ssl-config') testImplementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" testImplementation "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" diff --git a/qa/os/src/test/java/org/opensearch/packaging/util/ServerUtils.java b/qa/os/src/test/java/org/opensearch/packaging/util/ServerUtils.java index 42eac9fdf4961..4c6e0319c7b1c 100644 --- a/qa/os/src/test/java/org/opensearch/packaging/util/ServerUtils.java +++ b/qa/os/src/test/java/org/opensearch/packaging/util/ServerUtils.java @@ -48,6 +48,8 @@ import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -95,7 +97,7 @@ private static HttpResponse execute(Request request, String username, String pas try (InputStream inStream = Files.newInputStream(caCert)) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream); - KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType()); + KeyStore truststore = KeyStoreFactory.getInstance(KeyStoreType.BCFKS); truststore.load(null, null); truststore.setCertificateEntry("myClusterCA", cert); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); diff --git a/qa/smoke-test-plugins/build.gradle b/qa/smoke-test-plugins/build.gradle index 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 } diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/10_basic.yml index 0866c71b87e12..6b5c75fdd76ab 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/10_basic.yml @@ -24,6 +24,9 @@ setup: --- "node_reload_secure_settings test correct(empty) password": + - skip: + version: "3.0.0 - " + reason: "Running this test in active FIPS mode is not supported" - do: nodes.reload_secure_settings: {} diff --git a/server/build.gradle b/server/build.gradle index 5d98874cbef23..b55a0b47a0850 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') @@ -113,6 +114,12 @@ dependencies { // https://mvnrepository.com/artifact/org.roaringbitmap/RoaringBitmap api libs.roaringbitmap + + // bouncyCastle + api libs.bcjce + api libs.bctls + api libs.bcutil + testImplementation 'org.awaitility:awaitility:4.2.2' testImplementation(project(":test:framework")) { // tests use the locally compiled version of server @@ -149,6 +156,10 @@ tasks.named("forbiddenPatterns").configure { exclude '**/*.meta' } +tasks.named("dependencyLicenses").configure { + mapping from: /bc.*/, to: 'bouncycastle' +} + tasks.named("testingConventions").configure { naming.clear() naming { 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/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/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 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/internalClusterTest/java/org/opensearch/action/admin/ReloadSecureSettingsIT.java b/server/src/internalClusterTest/java/org/opensearch/action/admin/ReloadSecureSettingsIT.java index c81d491719e4b..2bb14e09beaf6 100644 --- a/server/src/internalClusterTest/java/org/opensearch/action/admin/ReloadSecureSettingsIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/action/admin/ReloadSecureSettingsIT.java @@ -68,6 +68,9 @@ @OpenSearchIntegTestCase.ClusterScope(minNumDataNodes = 2) public class ReloadSecureSettingsIT extends OpenSearchIntegTestCase { + // Minimal required characters to fulfill the requirement of 112 bit strong passwords + protected static final int MIN_112_BIT_STRONG = 14; + public void testMissingKeystoreFile() throws Exception { final PluginsService pluginsService = internalCluster().getInstance(PluginsService.class); final MockReloadablePlugin mockReloadablePlugin = pluginsService.filterPlugins(MockReloadablePlugin.class) @@ -182,7 +185,7 @@ public void testReloadAllNodesWithPasswordWithoutTLSFails() throws Exception { final Environment environment = internalCluster().getInstance(Environment.class); final AtomicReference reloadSettingsError = new AtomicReference<>(); final int initialReloadCount = mockReloadablePlugin.getReloadCount(); - final char[] password = randomAlphaOfLength(12).toCharArray(); + final char[] password = randomAlphaOfLength(MIN_112_BIT_STRONG).toCharArray(); writeEmptyKeystore(environment, password); final CountDownLatch latch = new CountDownLatch(1); client().admin() @@ -229,7 +232,7 @@ public void onFailure(Exception e) { public void testReloadLocalNodeWithPasswordWithoutTLSSucceeds() throws Exception { final Environment environment = internalCluster().getInstance(Environment.class); final AtomicReference reloadSettingsError = new AtomicReference<>(); - final char[] password = randomAlphaOfLength(12).toCharArray(); + final char[] password = randomAlphaOfLength(MIN_112_BIT_STRONG).toCharArray(); writeEmptyKeystore(environment, password); final CountDownLatch latch = new CountDownLatch(1); client().admin() @@ -275,14 +278,15 @@ public void testWrongKeystorePassword() throws Exception { final Environment environment = internalCluster().getInstance(Environment.class); final AtomicReference reloadSettingsError = new AtomicReference<>(); final int initialReloadCount = mockReloadablePlugin.getReloadCount(); + final char[] password = inFipsJvm() ? randomAlphaOfLength(MIN_112_BIT_STRONG).toCharArray() : new char[0]; // "some" keystore should be present in this case - writeEmptyKeystore(environment, new char[0]); + writeEmptyKeystore(environment, password); final CountDownLatch latch = new CountDownLatch(1); client().admin() .cluster() .prepareReloadSecureSettings() .setNodesIds("_local") - .setSecureStorePassword(new SecureString(new char[] { 'W', 'r', 'o', 'n', 'g' })) + .setSecureStorePassword(new SecureString("thewrongkeystorepassword".toCharArray())) .execute(new ActionListener() { @Override public void onResponse(NodesReloadSecureSettingsResponse nodesReloadResponse) { @@ -316,6 +320,7 @@ public void onFailure(Exception e) { } public void testMisbehavingPlugin() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); final Environment environment = internalCluster().getInstance(Environment.class); final PluginsService pluginsService = internalCluster().getInstance(PluginsService.class); final MockReloadablePlugin mockReloadablePlugin = pluginsService.filterPlugins(MockReloadablePlugin.class) @@ -382,6 +387,7 @@ public void onFailure(Exception e) { } public void testReloadWhileKeystoreChanged() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); final PluginsService pluginsService = internalCluster().getInstance(PluginsService.class); final MockReloadablePlugin mockReloadablePlugin = pluginsService.filterPlugins(MockReloadablePlugin.class) .stream() diff --git a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java index b40ac67b04917..b3068338c0c8a 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -196,6 +196,14 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot BootstrapSettings.CTRLHANDLER_SETTING.get(settings) ); + SecureRandomInitializer.init(); + + var cryptoStandard = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); + if ("FIPS-140-3".equals(cryptoStandard) || "true".equalsIgnoreCase(System.getProperty("org.bouncycastle.fips.approved_only"))) { + LogManager.getLogger(Bootstrap.class).info("running in FIPS-140-3 mode"); + SecurityProviderManager.excludeSunJCE(); + } + // initialize probes before the security manager is installed initializeProbes(); diff --git a/server/src/main/java/org/opensearch/bootstrap/SecureRandomInitializer.java b/server/src/main/java/org/opensearch/bootstrap/SecureRandomInitializer.java new file mode 100644 index 0000000000000..633694556847c --- /dev/null +++ b/server/src/main/java/org/opensearch/bootstrap/SecureRandomInitializer.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.bootstrap; + +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.fips.FipsDRBG; +import org.bouncycastle.crypto.util.BasicEntropySourceProvider; + +import java.security.GeneralSecurityException; +import java.security.SecureRandom; + +/** + * Instantiates {@link SecureRandom} + */ +public class SecureRandomInitializer { + + private SecureRandomInitializer() {} + + /** + * Instantiates a new {@link SecureRandom} if it is not already set. The specific implementation used depends on whether the JVM + * is running in a FIPS-approved mode or not. An instance of {@link SecureRandom} can be obtained from + * {@link CryptoServicesRegistrar#getSecureRandom()} + */ + public static void init() { + CryptoServicesRegistrar.setSecureRandom(CryptoServicesRegistrar.getSecureRandomIfSet(SecureRandomInitializer::getSecureRandom)); + } + + private static SecureRandom getSecureRandom() { + try { + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + var entropySource = SecureRandom.getInstance("DEFAULT", "BCFIPS"); + return FipsDRBG.SHA512_HMAC.fromEntropySource(new BasicEntropySourceProvider(entropySource, true)).build(null, true); + } + return SecureRandom.getInstanceStrong(); + } catch (GeneralSecurityException e) { + throw new SecurityException("Failed to instantiate SecureRandom: " + e.getMessage(), e); + } + } + +} diff --git a/server/src/main/java/org/opensearch/bootstrap/SecurityProviderManager.java b/server/src/main/java/org/opensearch/bootstrap/SecurityProviderManager.java new file mode 100644 index 0000000000000..0b53d48b88f55 --- /dev/null +++ b/server/src/main/java/org/opensearch/bootstrap/SecurityProviderManager.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.bootstrap; + +import java.security.Security; + +/** + * Provides additional control over declared security providers in 'java.security' file. + */ +public class SecurityProviderManager { + + public static final String SUN_JCE = "SunJCE"; + + private SecurityProviderManager() { + // singleton constructor + } + + /** + * Removes the SunJCE provider from the list of installed security providers. This method is intended to be used when running + * in a FIPS JVM and when the security file specifies additional configuration, instead of a complete replacement. + */ + public static void excludeSunJCE() { + Security.removeProvider(SUN_JCE); + } + + /** + * Returns the position at which the provider is found by its name, otherwise returns -1. + * Provider's position starts by 1 and will not always represent the configured value at 'java.security' file. + */ + public static int getPosition(String providerName) { + var provider = java.security.Security.getProvider(providerName); + if (provider != null) { + var providers = java.security.Security.getProviders(); + for (int i = 0; i < providers.length; i++) { + if (providers[i].getName().equals(providerName)) { + return i + 1; // provider positions starts at 1 + } + } + } + return -1; + } +} diff --git a/server/src/main/java/org/opensearch/common/Randomness.java b/server/src/main/java/org/opensearch/common/Randomness.java index 221bc95c41f31..479d3b195bee6 100644 --- a/server/src/main/java/org/opensearch/common/Randomness.java +++ b/server/src/main/java/org/opensearch/common/Randomness.java @@ -36,7 +36,6 @@ import org.opensearch.common.settings.Settings; import java.lang.reflect.Method; -import java.security.SecureRandom; import java.util.Collections; import java.util.List; import java.util.Random; @@ -125,22 +124,6 @@ public static Random get() { } } - /** - * Provides a secure source of randomness. - *

- * This acts exactly similar to {@link #get()}, but returning a new {@link SecureRandom}. - */ - public static SecureRandom createSecure() { - if (currentMethod != null && getRandomMethod != null) { - // tests, so just use a seed from the non secure random - byte[] seed = new byte[16]; - get().nextBytes(seed); - return new SecureRandom(seed); - } else { - return new SecureRandom(); - } - } - @SuppressForbidden(reason = "ThreadLocalRandom is okay when not running tests") private static Random getWithoutSeed() { assert currentMethod == null && getRandomMethod == null : "running under tests but tried to create non-reproducible random"; diff --git a/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java b/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java index ed58e6b21e165..dae5c78b9645f 100644 --- a/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java +++ b/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java @@ -40,11 +40,14 @@ import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexOutput; import org.apache.lucene.store.NIOFSDirectory; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.opensearch.bootstrap.SecureRandomInitializer; import org.opensearch.cli.ExitCodes; import org.opensearch.cli.UserException; -import org.opensearch.common.Randomness; import org.opensearch.common.SetOnce; import org.opensearch.common.hash.MessageDigests; +import org.opensearch.common.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.KeyStoreType; import org.opensearch.core.common.settings.SecureString; import javax.crypto.AEADBadTagException; @@ -75,7 +78,6 @@ import java.nio.file.attribute.PosixFilePermissions; import java.security.GeneralSecurityException; import java.security.KeyStore; -import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; import java.util.Enumeration; @@ -119,6 +121,11 @@ private static class Entry { } } + static { + // Instantiates new SecureRandom if caller is KeyStoreCli, otherwise obtains the existent. + SecureRandomInitializer.init(); + } + /** * A regex for the valid characters that a setting name in the keystore may use. */ @@ -218,11 +225,10 @@ public static KeyStoreWrapper create() { /** Add the bootstrap seed setting, which may be used as a unique, secure, random value by the node */ public static void addBootstrapSeed(KeyStoreWrapper wrapper) { assert wrapper.getSettingNames().contains(SEED_SETTING.getKey()) == false; - SecureRandom random = Randomness.createSecure(); int passwordLength = 20; // Generate 20 character passwords char[] characters = new char[passwordLength]; for (int i = 0; i < passwordLength; ++i) { - characters[i] = SEED_CHARS[random.nextInt(SEED_CHARS.length)]; + characters[i] = SEED_CHARS[CryptoServicesRegistrar.getSecureRandom().nextInt(SEED_CHARS.length)]; } wrapper.setString(SEED_SETTING.getKey(), characters); Arrays.fill(characters, (char) 0); @@ -448,8 +454,11 @@ private byte[] encrypt(char[] password, byte[] salt, byte[] iv) throws GeneralSe } private void decryptLegacyEntries() throws GeneralSecurityException, IOException { + if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { + throw new SecurityException("Legacy KeyStore formats v1 & v2 are not supported in FIPS JVM"); + } // v1 and v2 keystores never had passwords actually used, so we always use an empty password - KeyStore keystore = KeyStore.getInstance("PKCS12"); + KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); Map settingTypes = new HashMap<>(); ByteArrayInputStream inputBytes = new ByteArrayInputStream(dataBytes); try (DataInputStream input = new DataInputStream(inputBytes)) { @@ -488,7 +497,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()) { @@ -532,15 +541,14 @@ public synchronized void save(Path configDir, char[] password) throws Exception output.writeByte(password.length == 0 ? (byte) 0 : (byte) 1); // new cipher params - SecureRandom random = Randomness.createSecure(); // use 64 bytes salt, which surpasses that recommended by OWASP // see https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet byte[] salt = new byte[64]; - random.nextBytes(salt); + CryptoServicesRegistrar.getSecureRandom().nextBytes(salt); // use 96 bits (12 bytes) for IV as recommended by NIST // see http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf section 5.2.1.1 byte[] iv = new byte[12]; - random.nextBytes(iv); + CryptoServicesRegistrar.getSecureRandom().nextBytes(iv); // encrypted data byte[] encryptedBytes = encrypt(password, salt, iv); diff --git a/server/src/main/resources/org/opensearch/bootstrap/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index a6d6014b26bfb..7d416afa2bd24 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -195,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"; }; diff --git a/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java b/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java new file mode 100644 index 0000000000000..34dc60030c40c --- /dev/null +++ b/server/src/test/java/org/opensearch/bootstrap/SecureRandomInitializerTests.java @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.bootstrap; + +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.fips.FipsSecureRandom; +import org.opensearch.test.OpenSearchTestCase; +import org.junit.BeforeClass; + +import java.security.SecureRandom; +import java.util.Arrays; + +public class SecureRandomInitializerTests extends OpenSearchTestCase { + + @BeforeClass + public static void setup() { + CryptoServicesRegistrar.setSecureRandom(null); + } + + public void testInitInNonFipsMode() { + // given + assertThrows(IllegalStateException.class, CryptoServicesRegistrar::getSecureRandom); + + // when + SecureRandomInitializer.init(); + SecureRandom secureRandom = CryptoServicesRegistrar.getSecureRandom(); + + // then + assertNotNull("SecureRandom should be initialized in non-FIPS mode", secureRandom); + byte[] randomBytes = new byte[16]; + secureRandom.nextBytes(randomBytes); + assertEquals(inFipsJvm() ? "BCFIPS_RNG" : "SUN", secureRandom.getProvider().getName()); + // BCFIPS 'DEFAULT' RNG algorithm defaults to 'HMAC-DRBG-SHA512' + assertEquals(inFipsJvm() ? "HMAC-DRBG-SHA512" : "NativePRNGBlocking", secureRandom.getAlgorithm()); + assertEquals(inFipsJvm() ? FipsSecureRandom.class : SecureRandom.class, secureRandom.getClass()); + assertFalse("Random bytes should not be all zeros", allZeros(randomBytes)); + + byte[] seed1 = secureRandom.generateSeed(16); + byte[] seed2 = secureRandom.generateSeed(16); + assertNotNull(seed1); + assertNotNull(seed2); + assertFalse("Seeds should not be identical", Arrays.equals(seed1, seed2)); + } + + private boolean allZeros(byte[] data) { + for (byte b : data) { + if (b != 0) { + return false; + } + } + return true; + } +} diff --git a/server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java b/server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java new file mode 100644 index 0000000000000..2a92f35abae5b --- /dev/null +++ b/server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java @@ -0,0 +1,171 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.bootstrap; + +import org.opensearch.test.OpenSearchTestCase; +import org.junit.AfterClass; +import org.junit.Before; + +import javax.crypto.Cipher; + +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.util.Arrays; +import java.util.Locale; + +import static org.opensearch.bootstrap.BootstrapForTesting.sunJceInsertFunction; + +public class SecurityProviderManagerTests extends OpenSearchTestCase { + + private static final String BC_FIPS = "BCFIPS"; + private static final String SUN_JCE = "SunJCE"; + + // BCFIPS will only provide legacy ciphers when running in general mode, otherwise approved-only mode forbids the use. + private static final String MODE_DEPENDENT_CIPHER_PROVIDER = inFipsJvm() ? SUN_JCE : BC_FIPS; + + private static final String AES = "AES"; + private static final String RC_4 = "RC4"; + private static final String TRIPLE_DES = "DESedeWrap"; + private static final String DES = "DES"; + private static final String PBE = "PBE"; + private static final String BLOWFISH = "Blowfish"; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + var notInstalled = Arrays.stream(Security.getProviders()).noneMatch(provider -> SUN_JCE.equals(provider.getName())); + if (notInstalled && sunJceInsertFunction != null) { + sunJceInsertFunction.get(); + } + assumeTrue( + String.format( + Locale.ROOT, + "SunJCE provider has to be initially installed through '%s' file", + System.getProperty("java.security.properties", "UNDEFINED") + ), + Arrays.stream(Security.getProviders()).anyMatch(provider -> SUN_JCE.equals(provider.getName())) + ); + } + + @AfterClass + // restore the same state as before running the tests. + public static void removeSunJCE() { + if (inFipsJvm()) { + SecurityProviderManager.excludeSunJCE(); + } + } + + public void testCipherRC4() throws Exception { + // given + var cipher = Cipher.getInstance(RC_4); + assertEquals(RC_4, cipher.getAlgorithm()); + assertEquals(MODE_DEPENDENT_CIPHER_PROVIDER, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + if (inFipsJvm()) { + expectThrows(NoSuchAlgorithmException.class, () -> Cipher.getInstance(RC_4)); + } else { + cipher = Cipher.getInstance(RC_4); + assertEquals(RC_4, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + } + } + + public void testCipherAES() throws Exception { + // given + var cipher = Cipher.getInstance(AES); + assertEquals(AES, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + cipher = Cipher.getInstance(AES); + assertEquals(AES, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + } + + public void testCipher3Des() throws Exception { + // given + var cipher = Cipher.getInstance(TRIPLE_DES); + assertEquals(TRIPLE_DES, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + cipher = Cipher.getInstance(TRIPLE_DES); + assertEquals(TRIPLE_DES, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + } + + public void testCipherDes() throws Exception { + // given + var cipher = Cipher.getInstance(DES); + assertEquals(DES, cipher.getAlgorithm()); + assertEquals(MODE_DEPENDENT_CIPHER_PROVIDER, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + if (inFipsJvm()) { + expectThrows(NoSuchAlgorithmException.class, () -> Cipher.getInstance(DES)); + } else { + cipher = Cipher.getInstance(DES); + assertEquals(DES, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + } + } + + public void testCipherPBE() throws Exception { + // given + var cipher = Cipher.getInstance(PBE); + assertEquals(PBE, cipher.getAlgorithm()); + assertEquals(SUN_JCE, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + expectThrows(NoSuchAlgorithmException.class, () -> Cipher.getInstance(PBE)); + } + + public void testCipherBlowfish() throws Exception { + // given + var cipher = Cipher.getInstance(BLOWFISH); + assertEquals(BLOWFISH, cipher.getAlgorithm()); + assertEquals(MODE_DEPENDENT_CIPHER_PROVIDER, cipher.getProvider().getName()); + + // when + SecurityProviderManager.excludeSunJCE(); + + // then + if (inFipsJvm()) { + expectThrows(NoSuchAlgorithmException.class, () -> Cipher.getInstance(BLOWFISH)); + } else { + cipher = Cipher.getInstance(BLOWFISH); + assertEquals(BLOWFISH, cipher.getAlgorithm()); + assertEquals(BC_FIPS, cipher.getProvider().getName()); + } + } + + public void testGetPosition() { + assertTrue(SUN_JCE + " is installed", SecurityProviderManager.getPosition(SUN_JCE) > 0); + SecurityProviderManager.excludeSunJCE(); + assertTrue(SUN_JCE + " is uninstalled", SecurityProviderManager.getPosition(SUN_JCE) < 0); + } + +} diff --git a/server/src/test/resources/org/opensearch/bootstrap/test.policy b/server/src/test/resources/org/opensearch/bootstrap/test.policy index c2b5a8e9c0a4e..fe319686c38a6 100644 --- a/server/src/test/resources/org/opensearch/bootstrap/test.policy +++ b/server/src/test/resources/org/opensearch/bootstrap/test.policy @@ -10,4 +10,5 @@ grant { // allow to test Security policy and codebases permission java.util.PropertyPermission "*", "read,write"; permission java.security.SecurityPermission "createPolicy.JavaPolicy"; + permission java.security.SecurityPermission "insertProvider"; }; diff --git a/test/fixtures/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/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpFixtureWithEC2.java b/test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpFixtureWithEC2.java index 9e02f9ee86744..6a901e686b8c5 100644 --- a/test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpFixtureWithEC2.java +++ b/test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpFixtureWithEC2.java @@ -92,7 +92,7 @@ protected String buildCredentialResponse(final String ec2AccessKey, final String + "\"AccessKeyId\": \"" + ec2AccessKey + "\"," + "\"Expiration\": \"" + ZonedDateTime.now().plusDays(1L).format(DateTimeFormatter.ISO_DATE_TIME) + "\"," + "\"RoleArn\": \"arn\"," - + "\"SecretAccessKey\": \"secret\"," + + "\"SecretAccessKey\": \"secret_key\"," + "\"Token\": \"" + ec2SessionToken + "\"" + "}"; } diff --git a/test/framework/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/test/framework/licenses/bcpkix-fips-2.0.7.jar.sha1 b/test/framework/licenses/bcpkix-fips-2.0.7.jar.sha1 new file mode 100644 index 0000000000000..ff463e602ad88 --- /dev/null +++ b/test/framework/licenses/bcpkix-fips-2.0.7.jar.sha1 @@ -0,0 +1 @@ +01eea0f325315ca6295b0a6926ff862d8001cdf9 diff --git a/test/framework/licenses/bouncycastle-LICENSE.txt b/test/framework/licenses/bouncycastle-LICENSE.txt new file mode 100644 index 0000000000000..5c7c14696849d --- /dev/null +++ b/test/framework/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/test/framework/licenses/bouncycastle-NOTICE.txt b/test/framework/licenses/bouncycastle-NOTICE.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/test/framework/licenses/bouncycastle-NOTICE.txt @@ -0,0 +1 @@ + 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..d30528f9da132 --- /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.ssl.KeyStoreFactory; +import org.opensearch.common.ssl.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); + } + +} 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..153a64a23642c 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; @@ -253,8 +254,6 @@ public void tearDown() throws Exception { public static final String DEFAULT_TEST_WORKER_ID = "--not-gradle--"; - public static final String FIPS_SYSPROP = "tests.fips.enabled"; - static { TEST_WORKER_VM_ID = System.getProperty(TEST_WORKER_SYS_PROPERTY, DEFAULT_TEST_WORKER_ID); setTestSysProps(); @@ -1758,7 +1757,7 @@ private static boolean isUnusableLocale() { } public static boolean inFipsJvm() { - return Boolean.parseBoolean(System.getProperty(FIPS_SYSPROP)); + return CryptoServicesRegistrar.isInApprovedOnlyMode(); } /** diff --git a/test/framework/src/main/java/org/opensearch/test/junit/listeners/ReproduceInfoPrinter.java b/test/framework/src/main/java/org/opensearch/test/junit/listeners/ReproduceInfoPrinter.java index e2d59773a76cb..42ae6a57b829a 100644 --- a/test/framework/src/main/java/org/opensearch/test/junit/listeners/ReproduceInfoPrinter.java +++ b/test/framework/src/main/java/org/opensearch/test/junit/listeners/ReproduceInfoPrinter.java @@ -193,7 +193,6 @@ private ReproduceErrorMessageBuilder appendESProperties() { appendOpt("tests.locale", Locale.getDefault().toLanguageTag()); appendOpt("tests.timezone", TimeZone.getDefault().getID()); appendOpt("runtime.java", Integer.toString(Runtime.version().version().get(0))); - appendOpt(OpenSearchTestCase.FIPS_SYSPROP, System.getProperty(OpenSearchTestCase.FIPS_SYSPROP)); return this; } diff --git a/test/framework/src/main/java/org/opensearch/test/rest/OpenSearchRestTestCase.java b/test/framework/src/main/java/org/opensearch/test/rest/OpenSearchRestTestCase.java index 8c612d258f183..32af1e39628e2 100644 --- a/test/framework/src/main/java/org/opensearch/test/rest/OpenSearchRestTestCase.java +++ b/test/framework/src/main/java/org/opensearch/test/rest/OpenSearchRestTestCase.java @@ -62,6 +62,7 @@ import org.opensearch.common.SetOnce; import org.opensearch.common.io.PathUtils; import org.opensearch.common.settings.Settings; +import org.opensearch.common.ssl.KeyStoreFactory; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.util.io.IOUtils; @@ -841,8 +842,7 @@ protected static void configureClient(RestClientBuilder builder, Settings settin throw new IllegalStateException(TRUSTSTORE_PATH + " is set but points to a non-existing file"); } try { - final String keyStoreType = keystorePath.endsWith(".p12") ? "PKCS12" : "jks"; - KeyStore keyStore = KeyStore.getInstance(keyStoreType); + KeyStore keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(keystorePath); try (InputStream is = Files.newInputStream(path)) { keyStore.load(is, keystorePass.toCharArray()); } 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) {