diff --git a/CHANGELOG.md b/CHANGELOG.md index 328b66bc10fcf..5d1588b9ba887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,54 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Add temporal routing processors for time-based document routing ([#18920](https://github.com/opensearch-project/OpenSearch/issues/18920)) - The dynamic mapping parameter supports false_allow_templates ([#19065](https://github.com/opensearch-project/OpenSearch/pull/19065)) +- [Feature Request] Enhance Terms lookup query to support query clause instead of docId ([#18195](https://github.com/opensearch-project/OpenSearch/issues/18195)) +- Add hierarchical routing processors for ingest and search pipelines ([#18826](https://github.com/opensearch-project/OpenSearch/pull/18826)) +- Add ACL-aware routing processors for ingest and search pipelines ([#18834](https://github.com/opensearch-project/OpenSearch/pull/18834)) +- Add support for Warm Indices Write Block on Flood Watermark breach ([#18375](https://github.com/opensearch-project/OpenSearch/pull/18375)) +- FS stats for warm nodes based on addressable space ([#18767](https://github.com/opensearch-project/OpenSearch/pull/18767)) +- Add support for custom index name resolver from cluster plugin ([#18593](https://github.com/opensearch-project/OpenSearch/pull/18593)) +- Rename WorkloadGroupTestUtil to WorkloadManagementTestUtil ([#18709](https://github.com/opensearch-project/OpenSearch/pull/18709)) +- Disallow resize for Warm Index, add Parameterized ITs for close in remote store ([#18686](https://github.com/opensearch-project/OpenSearch/pull/18686)) +- Ability to run Code Coverage with Gradle and produce the jacoco reports locally ([#18509](https://github.com/opensearch-project/OpenSearch/issues/18509)) +- Extend BooleanQuery must_not rewrite to numeric must, term, and terms queries ([#18498](https://github.com/opensearch-project/OpenSearch/pull/18498)) +- [Workload Management] Update logging and Javadoc, rename QueryGroup to WorkloadGroup ([#18711](https://github.com/opensearch-project/OpenSearch/issues/18711)) +- Add NodeResourceUsageStats to ClusterInfo ([#18480](https://github.com/opensearch-project/OpenSearch/issues/18472)) +- Introduce SecureHttpTransportParameters experimental API (to complement SecureTransportParameters counterpart) ([#18572](https://github.com/opensearch-project/OpenSearch/issues/18572)) +- Create equivalents of JSM's AccessController in the java agent ([#18346](https://github.com/opensearch-project/OpenSearch/issues/18346)) +- [WLM] Add WLM mode validation for workload group CRUD requests ([#18652](https://github.com/opensearch-project/OpenSearch/issues/18652)) +- Introduced a new cluster-level API to fetch remote store metadata (segments and translogs) for each shard of an index. ([#18257](https://github.com/opensearch-project/OpenSearch/pull/18257)) +- Add last index request timestamp columns to the `_cat/indices` API. ([10766](https://github.com/opensearch-project/OpenSearch/issues/10766)) +- Introduce a new pull-based ingestion plugin for file-based indexing (for local testing) ([#18591](https://github.com/opensearch-project/OpenSearch/pull/18591)) +- Add support for search pipeline in search and msearch template ([#18564](https://github.com/opensearch-project/OpenSearch/pull/18564)) +- [Workload Management] Modify logging message in WorkloadGroupService ([#18712](https://github.com/opensearch-project/OpenSearch/pull/18712)) +- Add BooleanQuery rewrite moving constant-scoring must clauses to filter clauses ([#18510](https://github.com/opensearch-project/OpenSearch/issues/18510)) +- Add functionality for plugins to inject QueryCollectorContext during QueryPhase ([#18637](https://github.com/opensearch-project/OpenSearch/pull/18637)) +- Add support for non-timing info in profiler ([#18460](https://github.com/opensearch-project/OpenSearch/issues/18460)) +- [Rule-based auto tagging] Bug fix and improvements ([#18726](https://github.com/opensearch-project/OpenSearch/pull/18726)) +- Extend Approximation Framework to other numeric types ([#18530](https://github.com/opensearch-project/OpenSearch/issues/18530)) +- Add Semantic Version field type mapper and extensive unit tests([#18454](https://github.com/opensearch-project/OpenSearch/pull/18454)) +- Pass index settings to system ingest processor factories. ([#18708](https://github.com/opensearch-project/OpenSearch/pull/18708)) +- Add fetch phase profiling. ([#18664](https://github.com/opensearch-project/OpenSearch/pull/18664)) +- Include named queries from rescore contexts in matched_queries array ([#18697](https://github.com/opensearch-project/OpenSearch/pull/18697)) +- Add the configurable limit on rule cardinality ([#18663](https://github.com/opensearch-project/OpenSearch/pull/18663)) +- Disable approximation framework when dealing with multiple sorts ([#18763](https://github.com/opensearch-project/OpenSearch/pull/18763)) +- [Experimental] Start in "clusterless" mode if a clusterless ClusterPlugin is loaded ([#18479](https://github.com/opensearch-project/OpenSearch/pull/18479)) +- [Star-Tree] Add star-tree search related stats ([#18707](https://github.com/opensearch-project/OpenSearch/pull/18707)) +- Add support for plugins to profile information ([#18656](https://github.com/opensearch-project/OpenSearch/pull/18656)) +- Add support for Combined Fields query ([#18724](https://github.com/opensearch-project/OpenSearch/pull/18724)) +- Make GRPC transport extensible to allow plugins to register and expose their own GRPC services ([#18516](https://github.com/opensearch-project/OpenSearch/pull/18516)) +- Added approximation support for range queries with now in date field ([#18511](https://github.com/opensearch-project/OpenSearch/pull/18511)) +- Upgrade to protobufs 0.6.0 and clean up deprecated TermQueryProtoUtils code ([#18880](https://github.com/opensearch-project/OpenSearch/pull/18880)) +- Expand fetch phase profiling to multi-shard queries ([#18887](https://github.com/opensearch-project/OpenSearch/pull/18887)) +- Prevent shard initialization failure due to streaming consumer errors ([#18877](https://github.com/opensearch-project/OpenSearch/pull/18877)) +- APIs for stream transport and new stream-based search api action ([#18722](https://github.com/opensearch-project/OpenSearch/pull/18722)) +- Added the core process for warming merged segments in remote-store enabled domains ([#18683](https://github.com/opensearch-project/OpenSearch/pull/18683)) +- Streaming aggregation ([#18874](https://github.com/opensearch-project/OpenSearch/pull/18874)) +- Optimize Composite Aggregations by removing unnecessary object allocations ([#18531](https://github.com/opensearch-project/OpenSearch/pull/18531)) +- [Star-Tree] Add search support for ip field type ([#18671](https://github.com/opensearch-project/OpenSearch/pull/18671)) +- [Derived Source] Add integration of derived source feature across various paths like get/search/recovery ([#18565](https://github.com/opensearch-project/OpenSearch/pull/18565)) +- Add build-tooling to run in the FIPS environment ([#18921](https://github.com/opensearch-project/OpenSearch/pull/18921)) +- Make test-suite runnable under FIPS compliance support ([#18491](https://github.com/opensearch-project/OpenSearch/pull/18491)) ### Changed - Add CompletionStage variants to methods in the Client Interface and default to ActionListener impl ([#18998](https://github.com/opensearch-project/OpenSearch/pull/18998)) diff --git a/build.gradle b/build.gradle index b8abd78970f13..9e20567533474 100644 --- a/build.gradle +++ b/build.gradle @@ -419,7 +419,11 @@ gradle.projectsEvaluated { jvmArgs += ["-javaagent:" + project(':libs:agent-sm:agent').jar.archiveFile.get()] } if (BuildParams.inFipsJvm) { - task.jvmArgs += ["-Dorg.bouncycastle.fips.approved_only=true"] + def fipsSecurityFile = project.rootProject.file('distribution/src/config/fips_java.security') + task.jvmArgs += [ + "-Dorg.bouncycastle.fips.approved_only=true", + "-Djava.security.properties=${fipsSecurityFile}" + ] } } } diff --git a/buildSrc/src/main/groovy/org/opensearch/gradle/test/StandaloneRestTestPlugin.groovy b/buildSrc/src/main/groovy/org/opensearch/gradle/test/StandaloneRestTestPlugin.groovy index 0d327ac31dbf5..51a37cd7f9bdd 100644 --- a/buildSrc/src/main/groovy/org/opensearch/gradle/test/StandaloneRestTestPlugin.groovy +++ b/buildSrc/src/main/groovy/org/opensearch/gradle/test/StandaloneRestTestPlugin.groovy @@ -31,6 +31,8 @@ package org.opensearch.gradle.test import groovy.transform.CompileStatic +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension import org.opensearch.gradle.OpenSearchJavaPlugin import org.opensearch.gradle.ExportOpenSearchBuildResourcesTask import org.opensearch.gradle.RepositoriesSetupPlugin @@ -92,6 +94,10 @@ class StandaloneRestTestPlugin implements Plugin { // create a compileOnly configuration as others might expect it project.configurations.create("compileOnly") project.dependencies.add('testImplementation', project.project(':test:framework')) + if (BuildParams.inFipsJvm) { + VersionCatalog libs = project.extensions.getByType(VersionCatalogsExtension).named("libs") + project.dependencies.add('testImplementation', libs.findBundle("bouncycastle").get()) + } EclipseModel eclipse = project.extensions.getByType(EclipseModel) eclipse.classpath.sourceSets = [testSourceSet] diff --git a/buildSrc/src/main/java/org/opensearch/gradle/test/rest/RestTestUtil.java b/buildSrc/src/main/java/org/opensearch/gradle/test/rest/RestTestUtil.java index 061c477fab086..c122ce88d46df 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/test/rest/RestTestUtil.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/test/rest/RestTestUtil.java @@ -102,6 +102,18 @@ static void setupDependencies(Project project, SourceSet sourceSet) { ); } + if (BuildParams.isInFipsJvm()) { + project.getDependencies() + .add( + sourceSet.getImplementationConfigurationName(), + "org.bouncycastle:bc-fips:" + VersionProperties.getVersions().get("bouncycastle_jce") + ); + project.getDependencies() + .add( + sourceSet.getImplementationConfigurationName(), + "org.bouncycastle:bctls-fips:" + VersionProperties.getVersions().get("bouncycastle_tls") + ); + } } } 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-high-level/build.gradle b/client/rest-high-level/build.gradle index b08df5bf09bac..c38704001166a 100644 --- a/client/rest-high-level/build.gradle +++ b/client/rest-high-level/build.gradle @@ -36,6 +36,7 @@ apply plugin: 'opensearch.build' apply plugin: 'opensearch.rest-test' apply plugin: 'opensearch.publish' apply plugin: 'opensearch.rest-resources' +apply from: "$rootDir/gradle/fips.gradle" base { group = 'org.opensearch.client' @@ -66,6 +67,7 @@ dependencies { testImplementation "junit:junit:${versions.junit}" //this is needed to make RestHighLevelClientTests#testApiNamingConventions work from IDEs testImplementation project(":rest-api-spec") + testFipsRuntimeOnly libs.bundles.bouncycastle } tasks.named('forbiddenApisMain').configure { 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 3ad32d1595f13..69ea090887021 100644 --- a/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java +++ b/client/rest/src/test/java/org/opensearch/client/RestClientBuilderIntegTests.java @@ -56,22 +56,22 @@ import java.security.PrivilegedAction; import java.security.SecureRandom; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; /** * Integration test to validate the builder builds a client with the correct configuration */ -public class RestClientBuilderIntegTests extends RestClientTestCase { +public class RestClientBuilderIntegTests extends RestClientTestCase implements RestClientFipsAwareTestCase { private static HttpsServer httpsServer; @BeforeClass public static void startHttpServer() throws Exception { httpsServer = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); - httpsServer.setHttpsConfigurator(new HttpsConfigurator(getSslContext(true))); + httpsServer.setHttpsConfigurator(new HttpsConfigurator(new RestClientBuilderIntegTests().getSslContext(true))); httpsServer.createContext("/", new ResponseHandler()); httpsServer.start(); } @@ -91,7 +91,6 @@ public static void stopHttpServers() throws IOException { } public void testBuilderUsesDefaultSSLContext() throws Exception { - assumeFalse("https://github.com/elastic/elasticsearch/issues/49094", inFipsJvm()); final SSLContext defaultSSLContext = SSLContext.getDefault(); try { try (RestClient client = buildRestClient()) { @@ -118,24 +117,23 @@ private RestClient buildRestClient() { return RestClient.builder(new HttpHost("https", address.getHostString(), address.getPort())).build(); } - private static SSLContext getSslContext(boolean server) throws Exception { + @Override + public SSLContext getSslContext(boolean server, String keyStoreType, SecureRandom secureRandom, String fileExtension) throws Exception { SSLContext sslContext; char[] password = "password".toCharArray(); - SecureRandom secureRandom = SecureRandom.getInstanceStrong(); - String fileExtension = ".jks"; try ( InputStream trustStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test_truststore" + fileExtension); InputStream keyStoreFile = RestClientBuilderIntegTests.class.getResourceAsStream("/testks" + fileExtension) ) { - KeyStore keyStore = KeyStore.getInstance("JKS"); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(keyStoreFile, password); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, password); - KeyStore trustStore = KeyStore.getInstance("JKS"); + KeyStore trustStore = KeyStore.getInstance(keyStoreType); trustStore.load(trustStoreFile, password); - TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); SSLContextBuilder sslContextBuilder = SSLContextBuilder.create().setProtocol(getProtocol()).setSecureRandom(secureRandom); 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..0aed1f4dc30be --- /dev/null +++ b/client/rest/src/test/java/org/opensearch/client/RestClientFipsAwareTestCase.java @@ -0,0 +1,28 @@ +/* + * 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 javax.net.ssl.SSLContext; + +import java.security.SecureRandom; + +import static org.opensearch.client.RestClientTestCase.inFipsJvm; + +public interface RestClientFipsAwareTestCase { + + default SSLContext getSslContext(boolean server) throws Exception { + String keyStoreType = inFipsJvm() ? "BCFKS" : "JKS"; + String fileExtension = inFipsJvm() ? ".bcfks" : ".jks"; + SecureRandom secureRandom = inFipsJvm() ? SecureRandom.getInstance("DEFAULT", "BCFIPS") : new SecureRandom(); + + return getSslContext(server, keyStoreType, secureRandom, fileExtension); + } + + SSLContext getSslContext(boolean server, String keyStoreType, SecureRandom secureRandom, String fileExtension) throws Exception; +} 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/sniffer/build.gradle b/client/sniffer/build.gradle index 4b50a996d1f9f..435989f839ee6 100644 --- a/client/sniffer/build.gradle +++ b/client/sniffer/build.gradle @@ -29,6 +29,7 @@ */ apply plugin: 'opensearch.build' apply plugin: 'opensearch.publish' +apply from: "$rootDir/gradle/fips.gradle" java { targetCompatibility = JavaVersion.VERSION_11 @@ -47,6 +48,9 @@ dependencies { api "commons-codec:commons-codec:${versions.commonscodec}" api "commons-logging:commons-logging:${versions.commonslogging}" api "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" + fipsRuntimeOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" + fipsRuntimeOnly "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" + fipsRuntimeOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" testImplementation project(":client:test") testImplementation "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" @@ -57,6 +61,10 @@ dependencies { testImplementation "net.bytebuddy:byte-buddy-agent:${versions.bytebuddy}" } +tasks.named("dependencyLicenses").configure { + mapping from: /bc.*/, to: 'bouncycastle' +} + tasks.named('forbiddenApisMain').configure { //client does not depend on server, so only jdk signatures should be checked replaceSignatureFiles 'jdk-signatures' diff --git a/client/sniffer/licenses/bc-fips-2.0.0.jar.sha1 b/client/sniffer/licenses/bc-fips-2.0.0.jar.sha1 new file mode 100644 index 0000000000000..79f0e3e9930bb --- /dev/null +++ b/client/sniffer/licenses/bc-fips-2.0.0.jar.sha1 @@ -0,0 +1 @@ +ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab \ No newline at end of file diff --git a/client/sniffer/licenses/bctls-fips-2.0.20.jar.sha1 b/client/sniffer/licenses/bctls-fips-2.0.20.jar.sha1 new file mode 100644 index 0000000000000..66cd82b49b537 --- /dev/null +++ b/client/sniffer/licenses/bctls-fips-2.0.20.jar.sha1 @@ -0,0 +1 @@ +1138f7896e0d1bb0d924bc868ed2dfda4f69470e \ No newline at end of file diff --git a/client/sniffer/licenses/bcutil-fips-2.0.3.jar.sha1 b/client/sniffer/licenses/bcutil-fips-2.0.3.jar.sha1 new file mode 100644 index 0000000000000..d553536576656 --- /dev/null +++ b/client/sniffer/licenses/bcutil-fips-2.0.3.jar.sha1 @@ -0,0 +1 @@ +a1857cd639295b10cc90e6d31ecbc523cdafcc19 \ No newline at end of file diff --git a/client/sniffer/licenses/bouncycastle-LICENSE.txt b/client/sniffer/licenses/bouncycastle-LICENSE.txt new file mode 100644 index 0000000000000..5c7c14696849d --- /dev/null +++ b/client/sniffer/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/sniffer/licenses/bouncycastle-NOTICE.txt b/client/sniffer/licenses/bouncycastle-NOTICE.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/client/sniffer/licenses/bouncycastle-NOTICE.txt @@ -0,0 +1 @@ + diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index cc371a3275570..e9219ac0c3466 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -295,3 +295,10 @@ subprojects { Project subProject -> tasks.named("composeUp").configure { dependsOn preProcessFixture } + +dockerCompose { + useComposeFiles = ['docker-compose.yml'] + if (BuildParams.inFipsJvm) { + environment.put("KEYSTORE_PASSWORD", "notarealpasswordphrase") + } +} diff --git a/distribution/docker/docker-compose.yml b/distribution/docker/docker-compose.yml index 5ed2b159ffe2b..14aab4842c3f9 100644 --- a/distribution/docker/docker-compose.yml +++ b/distribution/docker/docker-compose.yml @@ -16,6 +16,7 @@ services: - cluster.routing.allocation.disk.watermark.high=1b - cluster.routing.allocation.disk.watermark.flood_stage=1b - node.store.allow_mmap=false + - "KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD}" volumes: - ./build/repo:/tmp/opensearch-repo - ./build/logs/1:/usr/share/opensearch/logs @@ -40,6 +41,7 @@ services: - cluster.routing.allocation.disk.watermark.high=1b - cluster.routing.allocation.disk.watermark.flood_stage=1b - node.store.allow_mmap=false + - "KEYSTORE_PASSWORD=${KEYSTORE_PASSWORD}" volumes: - ./build/repo:/tmp/opensearch-repo - ./build/logs/2:/usr/share/opensearch/logs diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/bootstrap/BootstrapTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/bootstrap/BootstrapTests.java index 149145bb2d66b..83303b85bd36a 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/bootstrap/BootstrapTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/bootstrap/BootstrapTests.java @@ -71,6 +71,7 @@ public void setupEnv() throws IOException { } public void testLoadSecureSettings() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); final Path configPath = env.configDir(); final SecureString seed; try (KeyStoreWrapper keyStoreWrapper = KeyStoreWrapper.create()) { diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddFileKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddFileKeyStoreCommandTests.java index db6bb2d5473f4..e017ac2241fc9 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddFileKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddFileKeyStoreCommandTests.java @@ -78,6 +78,7 @@ private void addFile(KeyStoreWrapper keystore, String setting, Path file, String } public void testMissingCreateWithEmptyPasswordWhenPrompted() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; Path file1 = createRandomFile(); terminal.addTextInput("y"); @@ -86,6 +87,7 @@ public void testMissingCreateWithEmptyPasswordWhenPrompted() throws Exception { } public void testMissingCreateWithEmptyPasswordWithoutPromptIfForced() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; Path file1 = createRandomFile(); execute("-f", "foo", file1.toString()); @@ -93,7 +95,8 @@ public void testMissingCreateWithEmptyPasswordWithoutPromptIfForced() throws Exc } public void testMissingNoCreate() throws Exception { - terminal.addSecretInput(randomFrom("", "keystorepassword")); + var password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); + terminal.addSecretInput(password); terminal.addTextInput("n"); // explicit no execute("foo"); assertNull(KeyStoreWrapper.load(env.configDir())); @@ -221,6 +224,7 @@ public void testIncorrectPassword() throws Exception { } public void testAddToUnprotectedKeystore() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; Path file = createRandomFile(); KeyStoreWrapper keystore = createKeystore(password); diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddStringKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddStringKeyStoreCommandTests.java index 41ab7c45690dc..75edd71c483f0 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddStringKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/AddStringKeyStoreCommandTests.java @@ -82,6 +82,7 @@ public void testInvalidPassphrease() throws Exception { } public void testMissingPromptCreateWithoutPasswordWhenPrompted() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); terminal.addTextInput("y"); terminal.addSecretInput("bar"); execute("foo"); @@ -89,6 +90,7 @@ public void testMissingPromptCreateWithoutPasswordWhenPrompted() throws Exceptio } public void testMissingPromptCreateWithoutPasswordWithoutPromptIfForced() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); terminal.addSecretInput("bar"); execute("-f", "foo"); assertSecureString("foo", "bar", ""); @@ -260,7 +262,9 @@ public void testMissingSettingName() throws Exception { } public void testSpecialCharacterInName() throws Exception { - createKeystore(""); + final String password = "keystorepassword"; + createKeystore(password); + terminal.addSecretInput(password); terminal.addSecretInput("value"); final String key = randomAlphaOfLength(4) + '@' + randomAlphaOfLength(4); final UserException e = expectThrows(UserException.class, () -> execute(key)); @@ -269,6 +273,7 @@ public void testSpecialCharacterInName() throws Exception { } public void testAddToUnprotectedKeystore() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; createKeystore(password, "foo", "bar"); terminal.addTextInput(""); diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ChangeKeyStorePasswordCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ChangeKeyStorePasswordCommandTests.java index 1aa62cf71ed65..b3be29517766e 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ChangeKeyStorePasswordCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ChangeKeyStorePasswordCommandTests.java @@ -54,6 +54,7 @@ protected Environment createEnv(Map settings) throws UserExcepti } public void testSetKeyStorePassword() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); createKeystore(""); loadKeystore(""); terminal.addSecretInput("thepassword"); @@ -67,14 +68,15 @@ public void testChangeKeyStorePassword() throws Exception { createKeystore("theoldpassword"); loadKeystore("theoldpassword"); terminal.addSecretInput("theoldpassword"); - terminal.addSecretInput("thepassword"); - terminal.addSecretInput("thepassword"); + terminal.addSecretInput("thenewpassword"); + terminal.addSecretInput("thenewpassword"); // Prompted thrice: Once for the existing and twice for the new password execute(); - loadKeystore("thepassword"); + loadKeystore("thenewpassword"); } public void testChangeKeyStorePasswordToEmpty() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); createKeystore("theoldpassword"); loadKeystore("theoldpassword"); terminal.addSecretInput("theoldpassword"); diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java index 5a06bc2400176..1596f24c024fd 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/CreateKeyStoreCommandTests.java @@ -58,7 +58,7 @@ protected Environment createEnv(Map settings) throws UserExcepti } public void testNotMatchingPasswords() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput("notthekeystorepasswordyouarelookingfor"); UserException e = expectThrows(UserException.class, () -> execute(randomFrom("-p", "--password"))); @@ -67,32 +67,34 @@ public void testNotMatchingPasswords() throws Exception { } public void testDefaultNotPromptForPassword() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); execute(); Path configDir = env.configDir(); assertNotNull(KeyStoreWrapper.load(configDir)); } public void testPosix() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput(password); - execute(); + execute(randomFrom("-p", "--password")); Path configDir = env.configDir(); assertNotNull(KeyStoreWrapper.load(configDir)); } public void testNotPosix() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); terminal.addSecretInput(password); terminal.addSecretInput(password); env = setupEnv(false, fileSystems); - execute(); + execute(randomFrom("-p", "--password")); Path configDir = env.configDir(); assertNotNull(KeyStoreWrapper.load(configDir)); } public void testOverwrite() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); + Path keystoreFile = KeyStoreWrapper.keystorePath(env.configDir()); byte[] content = "not a keystore".getBytes(StandardCharsets.UTF_8); Files.write(keystoreFile, content); @@ -106,9 +108,9 @@ public void testOverwrite() throws Exception { assertArrayEquals(content, Files.readAllBytes(keystoreFile)); terminal.addTextInput("y"); - terminal.addSecretInput(password); - terminal.addSecretInput(password); - execute(); + terminal.addSecretInput(password); // enter password + terminal.addSecretInput(password); // enter password again + execute(randomFrom("-p", "--password")); assertNotNull(KeyStoreWrapper.load(env.configDir())); } } diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java index 9ebc92c55530b..39f645e587d3a 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/HasPasswordKeyStoreCommandTests.java @@ -61,6 +61,7 @@ public void testFailsWithNoKeystore() throws Exception { } public void testFailsWhenKeystoreLacksPassword() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); createKeystore(""); UserException e = expectThrows(UserException.class, this::execute); assertEquals("Unexpected exit code", HasPasswordKeyStoreCommand.NO_PASSWORD_EXIT_CODE, e.exitCode); @@ -68,13 +69,13 @@ public void testFailsWhenKeystoreLacksPassword() throws Exception { } public void testSucceedsWhenKeystoreHasPassword() throws Exception { - createKeystore("password"); + createKeystore("thenewpassword"); String output = execute(); assertThat(output, containsString("Keystore is password-protected")); } public void testSilentSucceedsWhenKeystoreHasPassword() throws Exception { - createKeystore("password"); + createKeystore("thenewpassword"); String output = execute("--silent"); assertThat(output, is(emptyString())); } diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/KeyStoreWrapperTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/KeyStoreWrapperTests.java index cd7eacbe05421..e0ff0d1a95ee0 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 @@ -79,6 +79,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.function.Supplier; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; @@ -88,6 +89,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<>(); @@ -108,9 +111,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(); @@ -130,11 +133,11 @@ public void testCreate() throws Exception { public void testDecryptKeyStoreWithWrongPassword() throws Exception { KeyStoreWrapper keystore = KeyStoreWrapper.create(); - keystore.save(env.configDir(), new char[0]); + keystore.save(env.configDir(), passphraseSupplier.get()); final KeyStoreWrapper loadedKeystore = KeyStoreWrapper.load(env.configDir()); final SecurityException exception = expectThrows( SecurityException.class, - () -> loadedKeystore.decrypt(new char[] { 'i', 'n', 'v', 'a', 'l', 'i', 'd' }) + () -> loadedKeystore.decrypt("wrong_password_<1234567890%&!\"/>_but_a_strong_one".toCharArray()) ); assertThat( exception.getMessage(), @@ -184,12 +187,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()); } @@ -216,7 +219,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)); } @@ -273,7 +276,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")); } @@ -304,7 +307,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"); @@ -344,12 +347,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()); } @@ -363,6 +366,18 @@ public void testIllegalSettingName() throws Exception { assertTrue(e.getMessage().contains("does not match the allowed setting name pattern")); } + public void testFailLoadV1KeystoresInFipsJvm() throws Exception { + assumeTrue("Test in FIPS JVM", inFipsJvm()); + Exception e = assertThrows(NoSuchProviderException.class, this::generateV1); + assertThat(e.getMessage(), containsString("no such provider: SunJCE")); + } + + public void testFailLoadV2KeystoresInFipsJvm() throws Exception { + assumeTrue("Test in FIPS JVM", inFipsJvm()); + Exception e = assertThrows(NoSuchProviderException.class, this::generateV2); + assertThat(e.getMessage(), containsString("no such provider: SunJCE")); + } + public void testBackcompatV1() throws Exception { assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); generateV1(); @@ -398,12 +413,12 @@ public void testStringAndFileDistinction() throws Exception { final Path temp = createTempDir(); Files.write(temp.resolve("file_setting"), "file_value".getBytes(StandardCharsets.UTF_8)); wrapper.setFile("file_setting", Files.readAllBytes(temp.resolve("file_setting"))); - wrapper.save(env.configDir(), new char[0]); + wrapper.save(env.configDir(), passphraseSupplier.get()); wrapper.close(); final KeyStoreWrapper afterSave = KeyStoreWrapper.load(env.configDir()); assertNotNull(afterSave); - afterSave.decrypt(new char[0]); + afterSave.decrypt(passphraseSupplier.get()); assertThat(afterSave.getSettingNames(), equalTo(new HashSet<>(Arrays.asList("keystore.seed", "string_setting", "file_setting")))); assertThat(afterSave.getString("string_setting"), equalTo("string_value")); assertThat(toByteArray(afterSave.getFile("string_setting")), equalTo("string_value".getBytes(StandardCharsets.UTF_8))); diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ListKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ListKeyStoreCommandTests.java index 36bef3a82281d..4ca5e88c47b30 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ListKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/ListKeyStoreCommandTests.java @@ -61,7 +61,7 @@ public void testMissing() throws Exception { } public void testEmpty() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password); terminal.addSecretInput(password); execute(); @@ -69,7 +69,7 @@ public void testEmpty() throws Exception { } public void testOne() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password, "foo", "bar"); terminal.addSecretInput(password); execute(); @@ -77,7 +77,7 @@ public void testOne() throws Exception { } public void testMultiple() throws Exception { - String password = randomFrom("", "keystorepassword"); + String password = inFipsJvm() ? "keystorepassword" : randomFrom("", "keystorepassword"); createKeystore(password, "foo", "1", "baz", "2", "bar", "3"); terminal.addSecretInput(password); execute(); @@ -100,6 +100,7 @@ public void testListWithIncorrectPassword() throws Exception { } public void testListWithUnprotectedKeystore() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); createKeystore("", "foo", "bar"); execute(); // Not prompted for a password diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/RemoveSettingKeyStoreCommandTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/RemoveSettingKeyStoreCommandTests.java index 276af6cfa659f..7fe189dce84e7 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/RemoveSettingKeyStoreCommandTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/tools/cli/keystore/RemoveSettingKeyStoreCommandTests.java @@ -117,6 +117,7 @@ public void testRemoveWithIncorrectPassword() throws Exception { } public void testRemoveFromUnprotectedKeystore() throws Exception { + assumeFalse("Can't use empty password in a FIPS JVM", inFipsJvm()); String password = ""; createKeystore(password, "foo", "bar"); // will not be prompted for a password diff --git a/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..0d63d73f2f281 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 @@ -102,6 +102,11 @@ private KeyStore getSystemTrustStore() { throw new SslConfigException("failed to load the system PKCS#11 truststore", e); } } + if (KeyStoreUtil.inFipsMode.get() && !isBcfksTruststore(systemProperties) && !isPkcs11Truststore(systemProperties)) { + throw new SslConfigException( + "only %s truststores are supported in a FIPS JVM".formatted(KeyStoreUtil.FIPS_COMPLIANT_KEYSTORE_TYPES) + ); + } return null; } @@ -109,6 +114,10 @@ private static boolean isPkcs11Truststore(BiFunction sys return systemProperties.apply("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("PKCS11"); } + private static boolean isBcfksTruststore(BiFunction systemProperties) { + return systemProperties.apply("javax.net.ssl.trustStoreType", "").equalsIgnoreCase("BCFKS"); + } + private static char[] getSystemTrustStorePassword(BiFunction systemProperties) { return systemProperties.apply("javax.net.ssl.trustStorePassword", "").toCharArray(); } 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..502b4adee8c82 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 @@ -32,6 +32,7 @@ package org.opensearch.common.ssl; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.opensearch.common.Nullable; import javax.net.ssl.KeyManager; @@ -52,13 +53,39 @@ import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.Collection; +import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.function.Supplier; /** * A variety of utility methods for working with or constructing {@link KeyStore} instances. */ final class KeyStoreUtil { + public static final Supplier inFipsMode = () -> { + try { + // Equivalent to: boolean approvedOnly = CryptoServicesRegistrar.isInApprovedOnlyMode() + var registrarClass = Class.forName("org.bouncycastle.crypto.CryptoServicesRegistrar"); + var isApprovedOnlyMethod = registrarClass.getMethod("isInApprovedOnlyMode"); + return (Boolean) isApprovedOnlyMethod.invoke(null); + } catch (ReflectiveOperationException e) { + return false; + } + }; + + public static final Map> TYPE_TO_EXTENSION_MAP = new HashMap<>(); + public static final List FIPS_COMPLIANT_KEYSTORE_TYPES = List.of("PKCS11", "BCFKS"); + public static final String STORE_PROVIDER = inFipsMode.get() ? "BCFIPS" : "SUN"; + public static final String STORE_TYPE = inFipsMode.get() ? "BCFKS" : KeyStore.getDefaultType(); + + static { + TYPE_TO_EXTENSION_MAP.put("JKS", List.of(".jks", ".ks")); + TYPE_TO_EXTENSION_MAP.put("PKCS12", List.of(".p12", ".pkcs12", ".pfx")); + TYPE_TO_EXTENSION_MAP.put("BCFKS", List.of(".bcfks")); // Bouncy Castle FIPS Keystore + } + private KeyStoreUtil() { throw new IllegalStateException("Utility class should not be instantiated"); } @@ -67,13 +94,13 @@ private KeyStoreUtil() { * 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"; - } + static String 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)); } /** @@ -89,16 +116,25 @@ static KeyStore readKeyStore(Path path, String type, char[] password) throws Gen ); } try { - KeyStore keyStore = KeyStore.getInstance(type); + if (CryptoServicesRegistrar.isInApprovedOnlyMode() && !FIPS_COMPLIANT_KEYSTORE_TYPES.contains(type)) { + throw new SslConfigException( + "cannot use a [" + + type.toUpperCase(Locale.ROOT) + + "] keystore in FIPS JVM. Allowed types are " + + FIPS_COMPLIANT_KEYSTORE_TYPES + ); + } + KeyStore keyStore = KeyStore.getInstance(type, STORE_PROVIDER); 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 - ); + } catch (IOException | IllegalArgumentException e) { + var finalMessage = e.getMessage(); + if ("BCFKS KeyStore corrupted: MAC calculation failed.".equals(e.getMessage())) { + finalMessage = "incorrect password or corrupt file: " + e.getMessage(); + } + throw new SslConfigException("cannot read a [" + type + "] keystore from [" + path.toAbsolutePath() + "] - " + finalMessage, e); } } @@ -133,7 +169,7 @@ static KeyStore buildTrustStore(Iterable certificates) throws Gener } private static KeyStore buildNewKeyStore() throws GeneralSecurityException { - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + KeyStore keyStore = KeyStore.getInstance(STORE_TYPE, STORE_PROVIDER); try { keyStore.load(null, null); } catch (IOException e) { diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java index d957ffa457149..1865b13d644aa 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemKeyConfig.java @@ -84,7 +84,12 @@ public X509ExtendedKeyManager createKeyManager() { private PrivateKey getPrivateKey() { try { - final PrivateKey privateKey = PemUtils.readPrivateKey(key, () -> keyPassword); + final PrivateKey privateKey = PemUtils.readPrivateKey(key, () -> { + if (keyPassword.length == 0) { + throw new SslConfigException("cannot read encrypted key [" + key.toAbsolutePath() + "] without a password"); + } + return keyPassword; + }); if (privateKey == null) { throw new SslConfigException("could not load ssl private key file [" + key + "]"); } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java index 546d7f0ebd994..bfd8b490957f7 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 @@ -162,8 +162,13 @@ public SSLContext createSslContext() { * {@link #getSupportedProtocols() configured protocols}. */ private String contextProtocol() { - if (supportedProtocols.isEmpty()) { - throw new SslConfigException("no SSL/TLS protocols have been configured"); + if (KeyStoreUtil.inFipsMode.get()) { + if (SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS.stream().noneMatch(supportedProtocols::contains)) { + throw new SslConfigException( + "in FIPS mode only the following SSL/TLS protocols are allowed: %s. This issue may be caused by the '%s' setting." + .formatted(SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS, "ssl.supported_protocols") + ); + } } for (Entry entry : ORDERED_PROTOCOL_ALGORITHM_MAP.entrySet()) { if (supportedProtocols.contains(entry.getKey())) { diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfigurationLoader.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfigurationLoader.java index 433bec734e0b8..6aea4dd589f60 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,13 @@ 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.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.KeyStoreUtil.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 +83,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 +114,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 +162,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 +243,11 @@ private SslTrustConfig buildTrustConfig(Path basePath, SslVerificationMode verif } if (trustStorePath != null) { final char[] password = resolvePasswordSetting(TRUSTSTORE_SECURE_PASSWORD, TRUSTSTORE_LEGACY_PASSWORD); - final String storeType = resolveSetting(TRUSTSTORE_TYPE, Function.identity(), inferKeyStoreType(trustStorePath)); + final String storeType = resolveSetting( + TRUSTSTORE_TYPE, + Function.identity(), + 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 String storeType = resolveSetting( + KEYSTORE_TYPE, + Function.identity(), + inferStoreType(keyStorePath.toString().toLowerCase(Locale.ROOT)) + ); final String algorithm = resolveSetting(KEYSTORE_ALGORITHM, Function.identity(), KeyManagerFactory.getDefaultAlgorithm()); return new StoreKeyConfig(keyStorePath, storePassword, storeType, keyPassword, algorithm); } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreKeyConfig.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/StoreKeyConfig.java index b3b7b7dc346a6..01f79f01af257 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 @@ -59,7 +59,7 @@ 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 KeyStoreUtil#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()}). 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..c57c7d8240e1e 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 @@ -54,7 +54,7 @@ final class StoreTrustConfig implements SslTrustConfig { * @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 KeyStoreUtil#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) { 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/DefaultJdkTrustConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/DefaultJdkTrustConfigTests.java index 82f4e94e31ae6..b7f02b3c77a2b 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/DefaultJdkTrustConfigTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/DefaultJdkTrustConfigTests.java @@ -32,6 +32,8 @@ package org.opensearch.common.ssl; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.opensearch.bootstrap.MultiProviderTrustStoreHandler; import org.opensearch.test.OpenSearchTestCase; import org.junit.Assert; @@ -50,16 +52,42 @@ public class DefaultJdkTrustConfigTests extends OpenSearchTestCase { private static final BiFunction EMPTY_SYSTEM_PROPERTIES = (key, defaultValue) -> defaultValue; + private static final BiFunction PKCS11_SYSTEM_PROPERTIES = (key, defaultValue) -> { + if ("javax.net.ssl.trustStoreType".equals(key)) { + return "PKCS11"; + } + return defaultValue; + }; + private static final BiFunction BCFKS_SYSTEM_PROPERTIES = (key, defaultValue) -> { + if ("javax.net.ssl.trustStoreType".equals(key)) { + return "BCFKS"; + } + return defaultValue; + }; + private static final BiFunction FIPS_AWARE_SYSTEM_PROPERTIES = CryptoServicesRegistrar.isInApprovedOnlyMode() + ? BCFKS_SYSTEM_PROPERTIES + : EMPTY_SYSTEM_PROPERTIES; + + public void testGetSystemPKCS11TrustStoreWithSystemProperties() throws Exception { + assumeTrue( + "Should only run when PKCS11 provider is installed.", + MultiProviderTrustStoreHandler.findPKCS11ProviderService().isPresent() + ); + final DefaultJdkTrustConfig trustConfig = new DefaultJdkTrustConfig(PKCS11_SYSTEM_PROPERTIES); + assertThat(trustConfig.getDependentFiles(), emptyIterable()); + final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); + assertStandardIssuers(trustManager); + } public void testGetSystemTrustStoreWithNoSystemProperties() throws Exception { - final DefaultJdkTrustConfig trustConfig = new DefaultJdkTrustConfig(EMPTY_SYSTEM_PROPERTIES); + final DefaultJdkTrustConfig trustConfig = new DefaultJdkTrustConfig(FIPS_AWARE_SYSTEM_PROPERTIES); assertThat(trustConfig.getDependentFiles(), emptyIterable()); final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); assertStandardIssuers(trustManager); } public void testGetNonPKCS11TrustStoreWithPasswordSet() throws Exception { - final DefaultJdkTrustConfig trustConfig = new DefaultJdkTrustConfig(EMPTY_SYSTEM_PROPERTIES, "fakepassword".toCharArray()); + final DefaultJdkTrustConfig trustConfig = new DefaultJdkTrustConfig(FIPS_AWARE_SYSTEM_PROPERTIES, "fakepassword".toCharArray()); assertThat(trustConfig.getDependentFiles(), emptyIterable()); final X509ExtendedTrustManager trustManager = trustConfig.createTrustManager(); assertStandardIssuers(trustManager); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java index 70cb76ceaec51..a685d0b346161 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemKeyConfigTests.java @@ -69,6 +69,7 @@ public void testBuildKeyConfigFromPkcs1PemFilesWithoutPassword() throws Exceptio } public void testBuildKeyConfigFromPkcs1PemFilesWithPassword() throws Exception { + assumeFalse("Can't run in a FIPS JVM, PBKDF-OPENSSL KeySpec is not available", inFipsJvm()); final Path cert = getDataPath("/certs/cert2/cert2-pkcs1.crt"); final Path key = getDataPath("/certs/cert2/cert2-pkcs1.key"); final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, "c2-pass".toCharArray()); @@ -85,7 +86,6 @@ public void testBuildKeyConfigFromPkcs8PemFilesWithoutPassword() throws Exceptio } public void testBuildKeyConfigFromPkcs8PemFilesWithPassword() throws Exception { - assumeFalse("Can't run in a FIPS JVM, PBE KeySpec is not available", inFipsJvm()); final Path cert = getDataPath("/certs/cert2/cert2.crt"); final Path key = getDataPath("/certs/cert2/cert2.key"); final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, STRONG_PRIVATE_SECRET.get()); @@ -132,7 +132,7 @@ public void testKeyConfigReloadsFileContents() throws Exception { Files.copy(cert2, cert, StandardCopyOption.REPLACE_EXISTING); Files.copy(key2, key, StandardCopyOption.REPLACE_EXISTING); - assertPasswordIsIncorrect(keyConfig, key); + assertPasswordNotSet(keyConfig, key); Files.copy(cert1, cert, StandardCopyOption.REPLACE_EXISTING); Files.copy(key1, key, StandardCopyOption.REPLACE_EXISTING); @@ -170,6 +170,14 @@ private void assertPasswordIsIncorrect(PemKeyConfig keyConfig, Path key) { assertThat(exception, instanceOf(SslConfigException.class)); } + private void assertPasswordNotSet(PemKeyConfig keyConfig, Path key) { + final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); + assertThat(exception.getMessage(), containsString("cannot read encrypted key")); + assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); + assertThat(exception.getMessage(), containsString("without a password")); + assertThat(exception, instanceOf(SslConfigException.class)); + } + private void assertFileNotFound(PemKeyConfig keyConfig, String type, Path file) { final SslConfigException exception = expectThrows(SslConfigException.class, keyConfig::createKeyManager); assertThat(exception.getMessage(), containsString(type + " file")); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java index 366e936ca4852..33871253e2b37 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 { + private static final String BCFKS_PASSWORD = "bcfks-pass"; + private static final String BCFKS = "BCFKS"; private final String STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="; private final Path certRoot = getDataPath("/certs/ca1/ca.crt").getParent().getParent(); @@ -99,6 +101,14 @@ public void testBasicConfigurationOptions() { if (verificationMode == SslVerificationMode.NONE) { final SslTrustConfig trustConfig = configuration.getTrustConfig(); assertThat(trustConfig, instanceOf(TrustEverythingConfig.class)); + + if (inFipsJvm()) { + assertThrows( + "TrustEverythingConfig is not allowed in FIPS JVM", + IllegalStateException.class, + trustConfig::createTrustManager + ); + } } } @@ -114,7 +124,49 @@ public void testLoadTrustFromPemCAs() { assertThat(trustConfig.createTrustManager(), notNullValue()); } + public void testLoadTrustFromBCFKS() { + assumeTrue("BCFKS only available with BCFIPS provider", inFipsJvm()); + final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.bcfks"); + if (randomBoolean()) { + builder.put("test.ssl.truststore.password", BCFKS_PASSWORD); + } else { + secureSettings.setString("test.ssl.truststore.secure_password", BCFKS_PASSWORD); + } + 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 testLoadTrustFromPkcs12WithoutMAC() { + 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_nomac.p12"); + if (randomBoolean()) { + // If this is not set, the loader will guess from the extension + builder.put("test.ssl.truststore.type", "PKCS12"); + } + 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_nomac.p12"))); + assertThat(trustConfig.createTrustManager(), notNullValue()); + } + public void testLoadTrustFromPkcs12() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.p12"); if (randomBoolean()) { builder.put("test.ssl.truststore.password", "p12-pass"); @@ -137,6 +189,7 @@ public void testLoadTrustFromPkcs12() { } public void testLoadTrustFromJKS() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Settings.Builder builder = Settings.builder().put("test.ssl.truststore.path", "ca-all/ca.jks"); if (randomBoolean()) { builder.put("test.ssl.truststore.password", "jks-pass"); @@ -186,7 +239,31 @@ public void testLoadKeysFromPemFiles() { assertThat(keyConfig.createKeyManager(), notNullValue()); } + public void testLoadKeysFromBCFKS() { + assumeTrue("BCFKS only available with BCFIPS provider", inFipsJvm()); + final Settings.Builder builder = Settings.builder().put("test.ssl.keystore.path", "cert-all/certs.bcfks"); + if (randomBoolean()) { + builder.put("test.ssl.keystore.password", BCFKS_PASSWORD); + } else { + secureSettings.setString("test.ssl.keystore.secure_password", BCFKS_PASSWORD); + } + if (randomBoolean()) { + // If this is not set, the loader will guess from the extension + builder.put("test.ssl.keystore.type", BCFKS); + } + if (randomBoolean()) { + builder.put("test.ssl.keystore.algorithm", KeyManagerFactory.getDefaultAlgorithm()); + } + settings = builder.build(); + final SslConfiguration configuration = loader.load(certRoot); + final SslKeyConfig keyConfig = configuration.getKeyConfig(); + assertThat(keyConfig, instanceOf(StoreKeyConfig.class)); + assertThat(keyConfig.getDependentFiles(), containsInAnyOrder(getDataPath("/certs/cert-all/certs.bcfks"))); + assertThat(keyConfig.createKeyManager(), notNullValue()); + } + public void testLoadKeysFromPKCS12() { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Settings.Builder builder = Settings.builder().put("test.ssl.keystore.path", "cert-all/certs.p12"); if (randomBoolean()) { builder.put("test.ssl.keystore.password", "p12-pass"); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java index ee907952c52ff..66d4c0d716e9d 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,99 @@ 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); + + { + var sslConfiguration = new SslConfiguration( + trustConfig, + keyConfig, + randomFrom(SslVerificationMode.values()), + randomFrom(SslClientAuthenticationMode.values()), + DEFAULT_CIPHERS, + List.of("TLSv1.2") + ); + sslConfiguration.createSslContext(); + } + + { + final String nonFipsProtocols = randomFrom(List.of("TLSv1.1", "TLSv1", "SSLv3", "SSLv2Hello", "SSLv2")); + var sslConfiguration = new SslConfiguration( + trustConfig, + keyConfig, + randomFrom(SslVerificationMode.values()), + randomFrom(SslClientAuthenticationMode.values()), + DEFAULT_CIPHERS, + Collections.singletonList(nonFipsProtocols) + ); + var exception = assertThrows( + "cannot create SSL/TLS context without any supported protocols", + SslConfigException.class, + sslConfiguration::createSslContext + ); + assertThat( + exception.getMessage(), + equalTo( + String.format( + Locale.ROOT, + "in FIPS mode only the following SSL/TLS protocols are allowed: %s. This issue may be caused by the 'ssl.supported_protocols' setting.", + FIPS_APPROVED_PROTOCOLS + ) + ) + ); + } + } + + public void testInitValuesExist() { + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + + assertThrows( + "cannot configure SSL/TLS without any supported cipher suites", + SslConfigException.class, + () -> new SslConfiguration( + trustConfig, + keyConfig, + SslVerificationMode.CERTIFICATE, + SslClientAuthenticationMode.REQUIRED, + Collections.emptyList(), + List.of("SSLv2") + ) + ); + + assertThrows( + "cannot configure SSL/TLS without any supported protocols", + SslConfigException.class, + () -> new SslConfiguration( + trustConfig, + keyConfig, + SslVerificationMode.CERTIFICATE, + SslClientAuthenticationMode.REQUIRED, + DEFAULT_CIPHERS, + Collections.emptyList() + ) + ); + } } diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreKeyConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreKeyConfigTests.java index 1745c547d04ee..8dc0e924f71af 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 @@ -65,11 +65,15 @@ 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(); + private static final String PKCS12 = "PKCS12"; + private static final String JKS = "JKS"; + private static final String BCFKS = "BCFKS"; public void testLoadSingleKeyPKCS12() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path p12 = getDataPath("/certs/cert1/cert1.p12"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, PKCS12, P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(p12)); assertKeysLoaded(keyConfig, "cert1"); } @@ -77,7 +81,7 @@ public void testLoadSingleKeyPKCS12() throws Exception { public void testLoadMultipleKeyPKCS12() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path p12 = getDataPath("/certs/cert-all/certs.p12"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, PKCS12, P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(p12)); assertKeysLoaded(keyConfig, "cert1", "cert2"); } @@ -88,7 +92,7 @@ public void testLoadMultipleKeyJksWithSeparateKeyPassword() throws Exception { final StoreKeyConfig keyConfig = new StoreKeyConfig( jks, JKS_PASS, - "jks", + JKS, "key-pass".toCharArray(), KeyManagerFactory.getDefaultAlgorithm() ); @@ -96,13 +100,21 @@ public void testLoadMultipleKeyJksWithSeparateKeyPassword() throws Exception { assertKeysLoaded(keyConfig, "cert1", "cert2"); } + public void testLoadMultipleKeyBcfks() throws CertificateParsingException { + assumeTrue("BCFKS only available with BCFIPS provider", inFipsJvm()); + final Path bcfks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig(bcfks, BCFKS_PASS, BCFKS, BCFKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(bcfks)); + assertKeysLoaded(keyConfig, "cert1", "cert2"); + } + public void testKeyManagerFailsWithIncorrectJksStorePassword() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path jks = getDataPath("/certs/cert-all/certs.jks"); final StoreKeyConfig keyConfig = new StoreKeyConfig( jks, P12_PASS, - "jks", + JKS, "key-pass".toCharArray(), KeyManagerFactory.getDefaultAlgorithm() ); @@ -110,18 +122,39 @@ public void testKeyManagerFailsWithIncorrectJksStorePassword() throws Exception assertPasswordIsIncorrect(keyConfig, jks); } + public void testKeyManagerFailsWithIncorrectBcfksStorePassword() throws Exception { + assumeTrue("BCFKS only available with BCFIPS provider", inFipsJvm()); + 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 { + assumeTrue("BCFKS only available with BCFIPS provider", inFipsJvm()); + final Path bcfks = getDataPath("/certs/cert-all/certs.bcfks"); + final StoreKeyConfig keyConfig = new StoreKeyConfig( + bcfks, + BCFKS_PASS, + BCFKS, + "nonsense".toCharArray(), + KeyManagerFactory.getDefaultAlgorithm() + ); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(bcfks)); + assertPasswordIsIncorrect(keyConfig, bcfks); + } + public void testKeyManagerFailsWithMissingKeystoreFile() throws Exception { - assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path path = getDataPath("/certs/cert-all/certs.jks").getParent().resolve("dne.jks"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(path, JKS_PASS, "jks", JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(path, JKS_PASS, JKS, JKS_PASS, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(path)); assertFileNotFound(keyConfig, path); } @@ -130,7 +163,7 @@ public void testMissingKeyEntriesFailsForJksWithMeaningfulMessage() throws Excep assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca-all/ca.jks"); final char[] password = JKS_PASS; - final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, "jks", password, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, JKS, password, KeyManagerFactory.getDefaultAlgorithm()); assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertNoPrivateKeyEntries(keyConfig, ks); } @@ -139,12 +172,21 @@ public void testMissingKeyEntriesFailsForP12WithMeaningfulMessage() throws Excep assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca-all/ca.p12"); final char[] password = P12_PASS; - final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, "PKCS12", password, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(ks, password, PKCS12, password, KeyManagerFactory.getDefaultAlgorithm()); + assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertNoPrivateKeyEntries(keyConfig, ks); + } + + public void testMissingKeyEntriesFailsForBcfksWithMeaningfulMessage() throws Exception { + assumeTrue("BCFKS only available with BCFIPS provider", inFipsJvm()); + 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 { + public void testKeyConfigReloadsFileContentsForP12Keystore() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path cert1 = getDataPath("/certs/cert1/cert1.p12"); final Path cert2 = getDataPath("/certs/cert2/cert2.p12"); @@ -152,7 +194,7 @@ public void testKeyConfigReloadsFileContents() throws Exception { final Path p12 = createTempFile("cert", ".p12"); - final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, "PKCS12", P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); + final StoreKeyConfig keyConfig = new StoreKeyConfig(p12, P12_PASS, PKCS12, P12_PASS, KeyManagerFactory.getDefaultAlgorithm()); Files.copy(cert1, p12, StandardCopyOption.REPLACE_EXISTING); assertKeysLoaded(keyConfig, "cert1"); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreTrustConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/StoreTrustConfigTests.java index 8058ffe95dc93..4d96825d0d862 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 @@ -38,7 +38,6 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -56,12 +55,16 @@ 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 PKCS12 = "PKCS12"; + private static final String JKS = "JKS"; + private static final String BCFKS = "BCFKS"; private static final String DEFAULT_ALGORITHM = TrustManagerFactory.getDefaultAlgorithm(); public void testBuildTrustConfigFromP12() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca1/ca.p12"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, "PKCS12", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, PKCS12, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertCertificateChain(trustConfig, "CN=Test CA 1"); } @@ -69,7 +72,15 @@ public void testBuildTrustConfigFromP12() throws Exception { public void testBuildTrustConfigFromJks() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca-all/ca.jks"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, "jks", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, JKS, DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 2", "CN=Test CA 3"); + } + + public void testBuildTrustConfigFromBcfks() throws Exception { + assumeTrue("BCFKS only available with BCFIPS provider", inFipsJvm()); + 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,30 +89,43 @@ 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(PKCS12), DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertInvalidFileFormat(trustConfig, ks); } 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(PKCS12, JKS), DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertFileNotFound(trustConfig, ks); } public void testIncorrectPasswordFailsForP12WithMeaningfulMessage() throws Exception { + assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/ca1/ca.p12"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], "PKCS12", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, new char[0], PKCS12, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertPasswordIsIncorrect(trustConfig, ks); } + public void testIncorrectPasswordFailsForBcfksWithMeaningfulMessage() throws Exception { + assumeTrue("BCFKS only available with BCFIPS provider", inFipsJvm()); + 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)); + assertCannotCreatePasswordManager(trustConfig, ks); + } + public void testMissingTrustEntriesFailsForJksKeystoreWithMeaningfulMessage() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/cert-all/certs.jks"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, "jks", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, JKS_PASS, JKS, DEFAULT_ALGORITHM); assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); assertNoCertificateEntries(trustConfig, ks); } @@ -109,19 +133,49 @@ public void testMissingTrustEntriesFailsForJksKeystoreWithMeaningfulMessage() th public void testMissingTrustEntriesFailsForP12KeystoreWithMeaningfulMessage() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks = getDataPath("/certs/cert-all/certs.p12"); - final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, "PKCS12", DEFAULT_ALGORITHM); + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, P12_PASS, PKCS12, DEFAULT_ALGORITHM); + assertThat(trustConfig.getDependentFiles(), Matchers.containsInAnyOrder(ks)); + assertMissingCertificateEntries(trustConfig, ks); + } + + public void testMissingTrustEntriesFailsForBcfksKeystoreWithMeaningfulMessage() throws Exception { + assumeTrue("BCFKS only available with BCFIPS provider", inFipsJvm()); + 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); } - public void testTrustConfigReloadsKeysStoreContents() throws Exception { + public void testTrustConfigReloadsKeysStoreContentsForP12Keystore() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); final Path ks1 = getDataPath("/certs/ca1/ca.p12"); final Path ksAll = getDataPath("/certs/ca-all/ca.p12"); - final Path ks = createTempFile("ca", "p12"); + 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, PKCS12, DEFAULT_ALGORITHM); + + Files.copy(ks1, ks, StandardCopyOption.REPLACE_EXISTING); + assertCertificateChain(trustConfig, "CN=Test CA 1"); + + Files.delete(ks); + assertFileNotFound(trustConfig, ks); + + Files.write(ks, randomByteArrayOfLength(128), StandardOpenOption.CREATE); + assertInvalidFileFormat(trustConfig, ks); + + Files.copy(ksAll, ks, StandardCopyOption.REPLACE_EXISTING); + assertCertificateChain(trustConfig, "CN=Test CA 1", "CN=Test CA 2", "CN=Test CA 3"); + } + + public void testTrustConfigReloadsKeysStoreContentsForBcfksKeystore() throws Exception { + assumeTrue("BCFKS only available with BCFIPS provider", inFipsJvm()); + final Path ks1 = getDataPath("/certs/ca1/ca.bcfks"); + final Path ksAll = getDataPath("/certs/ca-all/ca.bcfks"); + + final Path ks = createTempFile("ca", ".bcfks"); + + final StoreTrustConfig trustConfig = new StoreTrustConfig(ks, BCFKS_PASS, BCFKS, DEFAULT_ALGORITHM); Files.copy(ks1, ks, StandardCopyOption.REPLACE_EXISTING); assertCertificateChain(trustConfig, "CN=Test CA 1"); @@ -152,7 +206,6 @@ private void assertInvalidFileFormat(StoreTrustConfig trustConfig, Path file) { assertThat(exception.getMessage(), Matchers.containsString("cannot read")); assertThat(exception.getMessage(), Matchers.containsString("keystore")); assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); - assertThat(exception.getCause(), Matchers.instanceOf(IOException.class)); } private void assertFileNotFound(StoreTrustConfig trustConfig, Path file) { @@ -165,9 +218,14 @@ private void assertFileNotFound(StoreTrustConfig trustConfig, Path file) { private void assertPasswordIsIncorrect(StoreTrustConfig trustConfig, Path key) { final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); - assertThat(exception.getMessage(), containsString("keystore")); assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); - assertThat(exception.getMessage(), containsString("password")); + assertThat(exception.getMessage(), containsString("keystore password was incorrect")); + } + + private void assertCannotCreatePasswordManager(StoreTrustConfig trustConfig, Path key) { + final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); + assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); + assertThat(exception.getMessage(), containsString("incorrect password or corrupt file")); } private void assertNoCertificateEntries(StoreTrustConfig trustConfig, Path file) { @@ -177,4 +235,13 @@ private void assertNoCertificateEntries(StoreTrustConfig trustConfig, Path file) assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); } + private void assertMissingCertificateEntries(StoreTrustConfig trustConfig, Path key) { + final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); + assertThat(exception.getMessage(), containsString(key.toAbsolutePath().toString())); + assertThat( + exception.getMessage(), + containsString(inFipsJvm() ? "keystore password was incorrect" : "does not contain any trusted certificate entries") + ); + } + } diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/TrustEverythingConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/TrustEverythingConfigTests.java new file mode 100644 index 0000000000000..1ea237954d41e --- /dev/null +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/TrustEverythingConfigTests.java @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.ssl; + +import org.opensearch.test.OpenSearchTestCase; + +import static org.hamcrest.Matchers.containsString; + +public class TrustEverythingConfigTests extends OpenSearchTestCase { + + public void testGetDependentFiles() { + assertTrue(TrustEverythingConfig.TRUST_EVERYTHING.getDependentFiles().isEmpty()); + } + + public void testCreateTrustManager() { + if (inFipsJvm()) { + Exception e = assertThrows(IllegalStateException.class, TrustEverythingConfig.TRUST_EVERYTHING::createTrustManager); + assertThat(e.getMessage(), containsString("not permitted in FIPS mode")); + } else { + var trustManager = TrustEverythingConfig.TRUST_EVERYTHING.createTrustManager(); + assertNotNull(trustManager); + } + } +} diff --git a/libs/ssl-config/src/test/resources/certs/README.md b/libs/ssl-config/src/test/resources/certs/README.md index 79790a4918f3e..c25d836f204ac 100644 --- a/libs/ssl-config/src/test/resources/certs/README.md +++ b/libs/ssl-config/src/test/resources/certs/README.md @@ -78,7 +78,7 @@ openssl x509 -req \ rm cert2/cert2.csr ``` -# Convert CAs to PKCS#12 +# Convert Certs to PKCS#12 ```bash for n in 1 2 3 @@ -88,7 +88,7 @@ do done ``` -# Convert CAs to JKS +# Convert Certs to JKS ```bash for n in 1 2 3 @@ -106,6 +106,33 @@ do done ``` +# Convert Certs to PKCS#12 without MAC + +```bash +cat ca1/ca.crt ca2/ca.crt ca3/ca.crt >> chain.crt +openssl pkcs12 -export \ + -out ca-all/ca_nomac.p12 \ + -in chain.crt \ + -caname "Test CA 1" \ + -caname "Test CA 2" \ + -caname "Test CA 3" \ + -passout pass: \ + -nomac \ + -nokeys \ + -jdktrust anyExtendedKeyUsage +rm -f chain.crt +``` + +# Convert Certs to BCFKS + +```bash +for n in 1 2 3 +do + keytool -importcert -file ca${n}/ca.crt -alias ca -keystore ca${n}/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 + keytool -importcert -file ca${n}/ca.crt -alias ca${n} -keystore ca-all/ca.bcfks -storetype BCFKS -storepass bcfks-pass -providername BCFIPS -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -providerpath $LIB_PATH/bc-fips-2.0.0.jar -v +done +``` + # Import Certs into single PKCS#12 keystore ```bash @@ -129,6 +156,34 @@ do done ``` +# Import Certs into single BCFKS keystore with separate key-password + +```bash +for Cert in cert1 cert2 +do + keytool -importkeystore -noprompt \ + -srckeystore $Cert/$Cert.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass p12-pass \ + -destkeystore cert-all/certs.bcfks \ + -deststoretype BCFKS \ + -deststorepass bcfks-pass \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar + keytool -keypasswd -noprompt \ + -keystore cert-all/certs.bcfks \ + -alias $Cert \ + -keypass p12-pass \ + -new bcfks-pass \ + -storepass bcfks-pass \ + -storetype BCFKS \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar +done +``` + # Create a mimic of the first CA ("ca1b") for testing certificates with the same name but different keys ```bash diff --git a/libs/ssl-config/src/test/resources/certs/ca-all/ca.bcfks b/libs/ssl-config/src/test/resources/certs/ca-all/ca.bcfks new file mode 100644 index 0000000000000..f27717d0ce67a Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/ca-all/ca.bcfks differ diff --git a/libs/ssl-config/src/test/resources/certs/ca-all/ca_nomac.p12 b/libs/ssl-config/src/test/resources/certs/ca-all/ca_nomac.p12 new file mode 100644 index 0000000000000..b63b10209855b Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/ca-all/ca_nomac.p12 differ diff --git a/libs/ssl-config/src/test/resources/certs/ca1/ca.bcfks b/libs/ssl-config/src/test/resources/certs/ca1/ca.bcfks new file mode 100644 index 0000000000000..cc3d63d89644b Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/ca1/ca.bcfks differ diff --git a/libs/ssl-config/src/test/resources/certs/ca2/ca.bcfks b/libs/ssl-config/src/test/resources/certs/ca2/ca.bcfks new file mode 100644 index 0000000000000..b67eb1fe2e613 Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/ca2/ca.bcfks differ diff --git a/libs/ssl-config/src/test/resources/certs/ca3/ca.bcfks b/libs/ssl-config/src/test/resources/certs/ca3/ca.bcfks new file mode 100644 index 0000000000000..8bc54fc133d32 Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/ca3/ca.bcfks differ diff --git a/libs/ssl-config/src/test/resources/certs/cert-all/certs.bcfks b/libs/ssl-config/src/test/resources/certs/cert-all/certs.bcfks new file mode 100644 index 0000000000000..1eab5af506b2c Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/cert-all/certs.bcfks differ diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/README.md b/libs/ssl-config/src/test/resources/certs/pem-utils/README.md index 576b34317bd0a..c4adb75d7b8ea 100644 --- a/libs/ssl-config/src/test/resources/certs/pem-utils/README.md +++ b/libs/ssl-config/src/test/resources/certs/pem-utils/README.md @@ -179,3 +179,58 @@ and the respective certificates ```bash openssl genpkey -algorithm EC -out key_EC_enc_pbkdf2.pem -pkeyopt ec_paramgen_curve:secp384r1 -pkeyopt ec_param_enc:named_curve -pass stdin ``` + +```bash +export KEY_PW='6!6428DQXwPpi7@$ggeg/=' +export LIB_PATH="/path/to/lib/folder" +``` + +```bash +for key_file in key*pbkdf2.pem; do + # generate self-signed certificate + openssl req -x509 -key "$key_file" -sha256 -days 3650 -subj "/CN=OpenSearch Test Node" -passin pass:"$KEY_PW" \ + -addext "subjectAltName=DNS:localhost,DNS:localhost.localdomain,DNS:localhost4,DNS:localhost4.localdomain4,DNS:localhost6,DNS:localhost6.localdomain6,IP:127.0.0.1,IP:0:0:0:0:0:0:0:1" \ + -out ca_temp.pem + if [ $? -ne 0 ]; then + echo "An error occurred while generating cert for $key_file" + exit 1 + fi + # create a new P12 keystore with key + cert + algo=$(echo "$key_file" | sed -n 's/key_\(.*\)_enc_pbkdf2.pem/\1/p') + openssl pkcs12 -export -inkey "$key_file" -in ca_temp.pem -name "testnode_${algo}_pbkdf2" -out testnode.p12 \ + -passin pass:"$KEY_PW" \ + -passout pass:"$STORE_PW" + if [ $? -ne 0 ]; then + echo "An error occurred while adding key + cert to P12 keystore for $key_file" + exit 1 + fi + # migrate from P12 to BCFKS keystore (keytool 21.0.2) + keytool -importkeystore -noprompt \ + -srckeystore testnode.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass "$STORE_PW" \ + -destkeystore testnode.bcfks \ + -deststoretype BCFKS \ + -deststorepass "$STORE_PW" \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar + if [ $? -ne 0 ]; then + echo "An error occurred while migrating to BCFKS for $key_file" + exit 1 + fi + # import from P12 to JKS keystore (keytool 21.0.2) + keytool -importkeystore -noprompt \ + -srckeystore testnode.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass "$STORE_PW" \ + -destkeystore testnode.jks \ + -deststoretype JKS \ + -deststorepass "$STORE_PW" + if [ $? -ne 0 ]; then + echo "An error occurred while migrating to JKS for $key_file" + exit 1 + fi +done +rm ca_temp.pem testnode.p12 +``` diff --git a/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.bcfks b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.bcfks new file mode 100644 index 0000000000000..8d1a016d790b1 Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/pem-utils/testnode.bcfks differ diff --git a/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java b/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java index 8a4f3b4a898b4..eeb942166eeec 100644 --- a/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java +++ b/modules/ingest-common/src/test/java/org/opensearch/ingest/common/DateProcessorTests.java @@ -164,7 +164,6 @@ public void testInvalidJavaPattern() { } public void testJavaPatternLocale() { - assumeFalse("Can't run in a FIPS JVM, Joda parse date error", inFipsJvm()); DateProcessor dateProcessor = new DateProcessor( randomAlphaOfLength(10), null, diff --git a/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexRestClientSslTests.java b/modules/reindex/src/test/java/org/opensearch/index/reindex/ReindexRestClientSslTests.java index 170f89838dd0d..2de595bd4b386 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 @@ -84,7 +84,7 @@ @SuppressForbidden(reason = "use http server") public class ReindexRestClientSslTests extends OpenSearchTestCase { - private static final String STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="; + private static final String STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="; // has to be at least 112 bit long. private static HttpsServer server; private static Consumer handler = ignore -> {}; @@ -129,7 +129,6 @@ private static SSLContext buildServerSslContext() throws Exception { } public void testClientFailsWithUntrustedCertificate() throws IOException { - assumeFalse("https://github.com/elastic/elasticsearch/issues/49094", inFipsJvm()); final List threads = new ArrayList<>(); final Settings settings = Settings.builder() .put("path.home", createTempDir()) @@ -165,7 +164,6 @@ public void testClientSucceedsWithCertificateAuthorities() throws IOException { } public void testClientSucceedsWithVerificationDisabled() throws IOException { - assumeFalse("Cannot disable verification in FIPS JVM", inFipsJvm()); final List threads = new ArrayList<>(); final Settings settings = Settings.builder() .put("path.home", createTempDir()) @@ -173,10 +171,21 @@ public void testClientSucceedsWithVerificationDisabled() throws IOException { .put("reindex.ssl.supported_protocols", "TLSv1.2") .build(); final Environment environment = TestEnvironment.newEnvironment(settings); - final ReindexSslConfig ssl = new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class)); - try (RestClient client = Reindexer.buildRestClient(getRemoteInfo(), ssl, 1L, threads)) { - final Response response = client.performRequest(new Request("GET", "/")); - assertThat(response.getStatusLine().getStatusCode(), Matchers.is(200)); + + if (inFipsJvm()) { + try { + new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class)); + fail("expected IllegalStateException"); + } catch (Exception e) { + assertThat(e, Matchers.instanceOf(IllegalStateException.class)); + assertThat(e.getMessage(), Matchers.containsString("The use of TrustEverythingConfig is not permitted in FIPS mode")); + } + } else { + final ReindexSslConfig ssl = new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class)); + try (RestClient client = Reindexer.buildRestClient(getRemoteInfo(), ssl, 1L, threads)) { + final Response response = client.performRequest(new Request("GET", "/")); + assertThat(response.getStatusLine().getStatusCode(), Matchers.is(200)); + } } } diff --git a/modules/transport-grpc/build.gradle b/modules/transport-grpc/build.gradle index 1e51154877d3b..3db90f89cbedd 100644 --- a/modules/transport-grpc/build.gradle +++ b/modules/transport-grpc/build.gradle @@ -8,6 +8,7 @@ apply plugin: 'opensearch.testclusters' apply plugin: 'opensearch.internal-cluster-test' +apply from: "$rootDir/gradle/fips.gradle" opensearchplugin { description = 'gRPC based transport implementation' @@ -36,6 +37,7 @@ dependencies { implementation "io.perfmark:perfmark-api:0.27.0" implementation "org.opensearch:protobufs:0.6.0" testImplementation project(':test:framework') + testFipsRuntimeOnly libs.bundles.bouncycastle } tasks.named("dependencyLicenses").configure { diff --git a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/ssl/SecureNetty4GrpcServerTransport.java b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/ssl/SecureNetty4GrpcServerTransport.java index edcd7d802c5b6..7ce383192871e 100644 --- a/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/ssl/SecureNetty4GrpcServerTransport.java +++ b/modules/transport-grpc/src/main/java/org/opensearch/transport/grpc/ssl/SecureNetty4GrpcServerTransport.java @@ -120,7 +120,7 @@ private JdkSslContext getSslContext(Settings settings, SecureAuxTransportSetting return new JdkSslContext( sslContext.get(), false, - params.cipherSuites(), + null, SupportedCipherSuiteFilter.INSTANCE, new ApplicationProtocolConfig( ApplicationProtocolConfig.Protocol.ALPN, diff --git a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/ssl/SecureSettingsHelpers.java b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/ssl/SecureSettingsHelpers.java index 475fd57132d02..933c1ec5779ed 100644 --- a/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/ssl/SecureSettingsHelpers.java +++ b/modules/transport-grpc/src/test/java/org/opensearch/transport/grpc/ssl/SecureSettingsHelpers.java @@ -8,7 +8,9 @@ package org.opensearch.transport.grpc.ssl; +import org.opensearch.common.Randomness; import org.opensearch.common.settings.Settings; +import org.opensearch.fips.FipsMode; import org.opensearch.plugins.SecureAuxTransportSettingsProvider; import javax.net.ssl.KeyManagerFactory; @@ -16,14 +18,11 @@ import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; -import java.io.IOException; +import java.io.InputStream; import java.security.KeyManagementException; import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; +import java.security.NoSuchProviderException; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -32,17 +31,13 @@ import io.grpc.netty.shaded.io.netty.handler.ssl.ClientAuth; import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import static org.opensearch.test.OpenSearchTestCase.randomFrom; - public class SecureSettingsHelpers { - private static final String TEST_PASS = "password"; // used for all keystores - static final String SERVER_KEYSTORE = "/netty4-server-secure.jks"; - static final String CLIENT_KEYSTORE = "/netty4-client-secure.jks"; - static final String[] DEFAULT_SSL_PROTOCOLS = { "TLSv1.3", "TLSv1.2", "TLSv1.1" }; - static final String[] DEFAULT_CIPHERS = { - "TLS_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" }; + private static final String keyStoreType = FipsMode.CHECK.isFipsEnabled() ? "BCFKS" : "JKS"; + private static final String fileExtension = FipsMode.CHECK.isFipsEnabled() ? ".bcfks" : ".jks"; + private static final String provider = FipsMode.CHECK.isFipsEnabled() ? "BCJSSE" : "SunJSSE"; + private static final char[] TEST_PASS = "password".toCharArray(); // used for all keystores + static final String SERVER_KEYSTORE = "/netty4-server-secure"; + static final String CLIENT_KEYSTORE = "/netty4-client-secure"; /** * Exception messages for various types of TLS client/server connection failure. @@ -78,26 +73,25 @@ public static ConnectExceptions get(Throwable e) { } public static KeyManagerFactory getTestKeyManagerFactory(String keystorePath) { - KeyManagerFactory keyManagerFactory; - try { - final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(SecureNetty4GrpcServerTransport.class.getResourceAsStream(keystorePath), TEST_PASS.toCharArray()); - keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, TEST_PASS.toCharArray()); - } catch (UnrecoverableKeyException | CertificateException | KeyStoreException | IOException | NoSuchAlgorithmException e) { + try (InputStream keyStoreFile = SecureNetty4GrpcServerTransport.class.getResourceAsStream(keystorePath + fileExtension)) { + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(keyStoreFile, TEST_PASS); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, TEST_PASS); + return keyManagerFactory; + } catch (Exception e) { throw new RuntimeException(e); } - return keyManagerFactory; } static TrustManagerFactory getTestTrustManagerFactory(String keystorePath) { - try { - final KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(SecureNetty4GrpcServerTransport.class.getResourceAsStream(keystorePath), TEST_PASS.toCharArray()); + try (InputStream trustStoreFile = SecureNetty4GrpcServerTransport.class.getResourceAsStream(keystorePath + fileExtension);) { + final KeyStore trustStore = KeyStore.getInstance(keyStoreType); + trustStore.load(trustStoreFile, TEST_PASS); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); return trustManagerFactory; - } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { + } catch (Exception e) { throw new RuntimeException(e); } } @@ -112,13 +106,12 @@ static SecureAuxTransportSettingsProvider getSecureSettingsProvider( public Optional buildSecureAuxServerTransportContext(Settings settings, String auxTransportType) throws SSLException { // Choose a random protocol from among supported test defaults - String protocol = randomFrom(DEFAULT_SSL_PROTOCOLS); // Default JDK provider SSLContext testContext; try { - testContext = SSLContext.getInstance(protocol); - testContext.init(keyMngerFactory.getKeyManagers(), trustMngerFactory.getTrustManagers(), new SecureRandom()); - } catch (NoSuchAlgorithmException | KeyManagementException e) { + testContext = SSLContext.getInstance("TLS", provider); + testContext.init(keyMngerFactory.getKeyManagers(), trustMngerFactory.getTrustManagers(), Randomness.createSecure()); + } catch (NoSuchAlgorithmException | KeyManagementException | NoSuchProviderException e) { throw new SSLException("Failed to build mock provider", e); } return Optional.of(testContext); @@ -134,7 +127,7 @@ public Optional clientAuth() { @Override public Collection cipherSuites() { - return List.of(DEFAULT_CIPHERS); + return List.of(); } }); } diff --git a/modules/transport-grpc/src/test/resources/README.md b/modules/transport-grpc/src/test/resources/README.md new file mode 100644 index 0000000000000..05f3d5546d5f6 --- /dev/null +++ b/modules/transport-grpc/src/test/resources/README.md @@ -0,0 +1,117 @@ +#!/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 server & client certificate key + +```bash +export PW='password' +export LIB_PATH="/path/to/lib/folder" +``` + +```bash +cat <<'EOF' > openssl-san.conf +[ req ] +default_bits = 2048 +prompt = no +default_md = sha256 +req_extensions = SAN +distinguished_name = dn + +[ dn ] +CN = OpenSearch Test Root CA +OU = opensearch +O = org + +[ SAN ] +subjectAltName = DNS:localhost, IP:127.0.0.1, IP:::1 +EOF + +openssl req -x509 -sha256 \ + -newkey rsa:2048 \ + -keyout server.key \ + -out server.crt \ + -days 8192 \ + -config openssl-san.conf \ + -extensions SAN \ + -passout pass:"$PW" + +openssl req -x509 -sha256 \ + -newkey rsa:2048 \ + -keyout client.key \ + -out client.crt \ + -days 8192 \ + -config openssl-san.conf \ + -extensions SAN \ + -passout pass:"$PW" +``` + +# 2. Export the certificates in pkcs12 format + +```bash +openssl pkcs12 -export \ + -in server.crt \ + -inkey server.key \ + -out netty4-server-secure.p12 \ + -name netty4-server-secure \ + -passout pass:"$PW" \ + -passin pass:"$PW" + +openssl pkcs12 -export \ + -in client.crt \ + -inkey client.key \ + -out netty4-client-secure.p12 \ + -name netty4-client-secure \ + -passout pass:"$PW" \ + -passin pass:"$PW" +``` + +# 3. Import the certificate into JDK keystore (PKCS12 type) + +```bash +keytool -importkeystore \ + -srcstorepass "$PW" \ + -destkeystore netty4-server-secure.jks \ + -srckeystore netty4-server-secure.p12 \ + -srcstoretype PKCS12 \ + -alias netty4-server-secure \ + -deststorepass "$PW" + +keytool -importkeystore \ + -srcstorepass "$PW" \ + -destkeystore netty4-client-secure.jks \ + -srckeystore netty4-client-secure.p12 \ + -srcstoretype PKCS12 \ + -alias netty4-client-secure \ + -deststorepass "$PW" +``` + +# 4. Import the certificate into BCFKS keystore + +```bash +keytool -importkeystore \ + -noprompt \ + -srckeystore netty4-server-secure.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass "$PW" \ + -destkeystore netty4-server-secure.bcfks \ + -deststoretype BCFKS \ + -deststorepass "$PW" \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar + +keytool -importkeystore \ + -noprompt \ + -srckeystore netty4-client-secure.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass "$PW" \ + -destkeystore netty4-client-secure.bcfks \ + -deststoretype BCFKS \ + -deststorepass "$PW" \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar +``` diff --git a/modules/transport-grpc/src/test/resources/client.crt b/modules/transport-grpc/src/test/resources/client.crt new file mode 100644 index 0000000000000..330e44f2ec69e --- /dev/null +++ b/modules/transport-grpc/src/test/resources/client.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIUCF0cBCansfehDiFjyDguY/tcRXEwDQYJKoZIhvcNAQEL +BQAwRTEgMB4GA1UEAwwXT3BlblNlYXJjaCBUZXN0IFJvb3QgQ0ExEzARBgNVBAsM +Cm9wZW5zZWFyY2gxDDAKBgNVBAoMA29yZzAeFw0yNTA2MTgxMTI2MzJaFw00NzEx +MjIxMTI2MzJaMEUxIDAeBgNVBAMMF09wZW5TZWFyY2ggVGVzdCBSb290IENBMRMw +EQYDVQQLDApvcGVuc2VhcmNoMQwwCgYDVQQKDANvcmcwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDQmjaZHHabdRvOWnqqrlGX5WzyJ6CIstMyw4+0WFio +tRKiZSamrUiMSkk9sCrbKPCoRqTI2Vn84GRKM11s9ltCzEh7TmMjsys33rVniupd +cpAK7GjX25PUmejyCUWzuLI/ktkgxGC6xdxiJ/4M7J0AzVGLkkZUitI+Ld+UpaB/ +aGHtANFUReafM9IxWRXDHGgsInM2t1cEjRW9g4KU3MymiSbef3j5e+qDxlYnpAan +P/ijlZT9plEamCH9yxthCZilPlQOGbD+xZs96sN4joWHkD4W3QfEREjjqReasN+M +Ri1v2Vmo1CXxUUlxvGrjrB/KTacELRmUva4GDUnxEv5BAgMBAAGjTzBNMCwGA1Ud +EQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAdBgNVHQ4E +FgQUGVBazxU5nZdzy74u9doD62qTRJowDQYJKoZIhvcNAQELBQADggEBALJdkSe7 +BNdFVD5dctTP2q5Mvmpzo1BLCgzCwdU9v0T+BzTNIUnfo9K5mI97tlqIL/tBAoL9 +pxOaQlHqXs8ay1A1LGuQzXqLhwaweWpPF/68m5w6Hz0ad2/uZPmeT0MK9WZsJYNw +/mQpJOSA7QJZ2OxByO8JPCr6iUIdz4VUYotOj6D5Z4aC2wC9Ss7zIrJgX88M7lHW +yzT4GhPubzxkFkRX5WwTRrPNytD7dG0Aqe0wmbZKANN/KUkU/oVsdO3xMRIaDcHd +nqJ7xLLn1iwO62tK9Rwo3dhf1xAZw8vESVNgCZoo7YuCb0exUz6QAQXOthecvJN0 +tZkz/s1RAua6nRo= +-----END CERTIFICATE----- diff --git a/modules/transport-grpc/src/test/resources/client.key b/modules/transport-grpc/src/test/resources/client.key new file mode 100644 index 0000000000000..2813772b5bf7f --- /dev/null +++ b/modules/transport-grpc/src/test/resources/client.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQdHad+vUXUVqmPe1g +GsI2YgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEA6rcJm1m592T2kr +m5Hs2EMEggTQ44Aa2bdFhNHeVxPwlSvj1drpvzevP2PSf0nWlGTSyaPoD8mdnLd+ +OMrmLdtxUFCsmvdP59t7jiDMxRLFx71WdervibpYJWenx0x7gPvJeaqO8MOCp7mm +J3JfxGI/UIXjmLYWof49FIWAepzMS+yRqXbvaAR0B0DTg4MsE3pmZTebOESec+46 +LtTlQELltExPbn4eoho4Tn+kRoDbZgVZo/Tud8M0/Ga2IMbQUO0FYM1dpWDtDg5M +gxWIdcR/hwHqL3gu+SNDKhfvbYtggeFco6qiVldRRTWCitZp2gfVwRwJ45ExZsOB +9Vf/YhTgW9o+2xrFh8PUUvh4IcHuLlm1UZ9+7hAfcX/xeo8iG9JzVzohBxAloz5g +B4SjomaEBJ82IkAiNIOpW3Ty3YjF6pu9jmyR4T2h7PpLgLMWbTLoj2O9wOVpJJdY +j/JDwGznwBJHWNbF4Qck9FS2sDxfOHDlbFoF+6EAVAXsysZhIQauNKXO8WR37Dre +TtpgisZkwAZmbek4Ma2yRAwvm6zmIOCAJH5QXnzC3wy2Ttwr9M/V2B+PKmM3Vh4c +l1EaL3E+X+UT2PV3IT9g9kUmGrShttr7AAYqZO6zmRdLib9o+wflqnCkqIFkEML6 +P0RyCg+MtWiN50UTEfvpJk0ztDuqETYjfn9a6P5iZUfdEgwrxbYPrztepJx7Ic5Y +awvjlyp07HHBE1c119Bl8hLz1LHmy/ZchVFm5LwpMvPdBraMXGWiShOzZg9Ls2up +UT85vtazIJItBe+BItalvoUxnqbjQMsuhzTn9mNaMCqogW+AudOYN+8a/ZbPYl1q +cZiOW561LjYG7B0h2Qvi9tMqdoDCYGkI5vtd+DlnxmHdWmm0dS80Rj6qdqFl3Jnp +Ba/C1ewhtdgUkjvS2839Y23fJ9iuY9bQL2ZCVicIkQD62AnAEIXX1cpSD5vTE91a +thSIUJ/GGSphu5GglIi8++vkstlfuZ0UdvjjV2FzZwHN68SLGPAoh29gsm1abYU4 +gJlXmWAzILcvuxxH3M9sSBL7l0XZ5vORwOcPAajvVTJDAyzPiVhKiljXNnICZGwd +PvY/x0hu6zz+dVkwTK7taciz0lBM9HIvzts4K5vCGFfcSCTXzxtYzeFkHT6MVwy6 +ljyhYYAlLuhnIEMgzYX5Q61CaToFAVH1BWBZ/DhnHUvTyUZNRrRhyLAjrsWDTpjB +fe+C6g6ctyClnEBRbTYRAWFF1uahsRz/hf0RfZZq3ec3pKunQEgW145BWZyrapAS +TTk8+uBl+PNd08umPTDB8wFxMaB4yJA3zeJmbZX3Eaq239yW1+vHnMbBxhTtNe5l +FS/3lUSnQ7jM/IHnZQNbs2VNbaixSmquvT8iJ2OCYbxAsUai7xph682kkzlfov8y +bymvgh6NsEECGgJWrE1DdZymAHi2PlN+jRFEx7wmwL+myT4sEn4TmdzSeKwMizOD +v6rwClJLReZEZ4XB2qoW1kXJklNnVx7XlOd+dOCXov7SWPSINRlgguoCVqqvt/a9 +jaYT+GZ8O/9KWeHx+s+Wp2pI0wQesCJZ6Ok4QeaLtnWzh+pcuUUHJkqHqDqVbQCo +/KQ6Kcynd+yh7INIBk/vHXdVG1IQSvve+nzyVv/201ytwMsDJVcSpnM= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/modules/transport-grpc/src/test/resources/netty4-client-secure.bcfks b/modules/transport-grpc/src/test/resources/netty4-client-secure.bcfks new file mode 100644 index 0000000000000..f87144626ab9f Binary files /dev/null and b/modules/transport-grpc/src/test/resources/netty4-client-secure.bcfks differ diff --git a/modules/transport-grpc/src/test/resources/netty4-client-secure.jks b/modules/transport-grpc/src/test/resources/netty4-client-secure.jks index 3497de56fc956..6e6c05e16967f 100644 Binary files a/modules/transport-grpc/src/test/resources/netty4-client-secure.jks and b/modules/transport-grpc/src/test/resources/netty4-client-secure.jks differ diff --git a/modules/transport-grpc/src/test/resources/netty4-client-secure.p12 b/modules/transport-grpc/src/test/resources/netty4-client-secure.p12 new file mode 100644 index 0000000000000..00a70268a9ffa Binary files /dev/null and b/modules/transport-grpc/src/test/resources/netty4-client-secure.p12 differ diff --git a/modules/transport-grpc/src/test/resources/netty4-server-secure.bcfks b/modules/transport-grpc/src/test/resources/netty4-server-secure.bcfks new file mode 100644 index 0000000000000..1416485476cd4 Binary files /dev/null and b/modules/transport-grpc/src/test/resources/netty4-server-secure.bcfks differ diff --git a/modules/transport-grpc/src/test/resources/netty4-server-secure.jks b/modules/transport-grpc/src/test/resources/netty4-server-secure.jks index 5e7d09ded52d0..d5c2a578da89d 100644 Binary files a/modules/transport-grpc/src/test/resources/netty4-server-secure.jks and b/modules/transport-grpc/src/test/resources/netty4-server-secure.jks differ diff --git a/modules/transport-grpc/src/test/resources/netty4-server-secure.p12 b/modules/transport-grpc/src/test/resources/netty4-server-secure.p12 new file mode 100644 index 0000000000000..6d0a86df3684f Binary files /dev/null and b/modules/transport-grpc/src/test/resources/netty4-server-secure.p12 differ diff --git a/modules/transport-grpc/src/test/resources/openssl-san.conf b/modules/transport-grpc/src/test/resources/openssl-san.conf new file mode 100644 index 0000000000000..104afdcd945b3 --- /dev/null +++ b/modules/transport-grpc/src/test/resources/openssl-san.conf @@ -0,0 +1,12 @@ +[ req ] +default_bits = 2048 +prompt = no +default_md = sha256 +req_extensions = SAN +distinguished_name = dn +[ dn ] +CN = OpenSearch Test Root CA +OU = opensearch +O = org +[ SAN ] +subjectAltName = DNS:localhost, IP:127.0.0.1, IP:::1 diff --git a/modules/transport-grpc/src/test/resources/server.crt b/modules/transport-grpc/src/test/resources/server.crt new file mode 100644 index 0000000000000..413bea435ac27 --- /dev/null +++ b/modules/transport-grpc/src/test/resources/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIUbN35Jz7X2bYT+F0ZSYKUM6Myq6MwDQYJKoZIhvcNAQEL +BQAwRTEgMB4GA1UEAwwXT3BlblNlYXJjaCBUZXN0IFJvb3QgQ0ExEzARBgNVBAsM +Cm9wZW5zZWFyY2gxDDAKBgNVBAoMA29yZzAeFw0yNTA2MTgxMTI2MzJaFw00NzEx +MjIxMTI2MzJaMEUxIDAeBgNVBAMMF09wZW5TZWFyY2ggVGVzdCBSb290IENBMRMw +EQYDVQQLDApvcGVuc2VhcmNoMQwwCgYDVQQKDANvcmcwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCqFJD5S4Fm7VKJVxMwZcXIvnMk558JDMY6lVY47ydc +NCvlCJZc/TAEDVu/g0QeXbgRX6gJ5AkbDEd4HcFEIDSZ8lEE+kgarwwawxq9wjtM +YOQ7s2jaQ2CxoaS3lAVevdxnqS/mHHacyuI422vs9q/2Kv1mzY+z6zzYEaJO6tEm +J9nnyS6LQ5VFLaBz0xgOOEdlvpmSsR7TGSjL0G5RtLneOE/Z0yITMWC9GI6tZuUc +dv7eiLo/DkIcNN3Za6Oy3/+miXxTlTd3N/qEd56AeusWqrGvQjDSlH6RK8Y3KbVk +rCAnjrMP0FB8EYRNlUI0bNicDjZh3g/f2WYDze0j7JUFAgMBAAGjTzBNMCwGA1Ud +EQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAdBgNVHQ4E +FgQUXA5kNWiRTyC+OVmeOrSJgwaA9xcwDQYJKoZIhvcNAQELBQADggEBAHIX7F8Q +br805enKjk+6RzCpWGYti+xddsnBK1mEatXvJmqdynprLfB96R/vk/crGMDoW1bL +qRxHQf0wtlvNtpARq8iCqskob5iCrVLFvOeKv45ZRS1bmt8bOig5i2EsY43/+ldW +wUm+djhTxPHNzCbsshvRJ+mWwvwFUI5PFizQ0ETx40L465RlL5FhW/fE1WF46JCO +VRcuc7dh3KtTQtWrqwAqIlmRVl6rcbmAVb8IshqgOlV1Su4D3hCP/dDRc7rOc0Mk +fRvfHO6S5KHAJrxUld/O8b7F+wxLZ8oE9n3LTwCh0Ok/0PXpm59zLsGgCiYOarZz +JAoTXpW0HVpf73c= +-----END CERTIFICATE----- diff --git a/modules/transport-grpc/src/test/resources/server.key b/modules/transport-grpc/src/test/resources/server.key new file mode 100644 index 0000000000000..dcad678784d8d --- /dev/null +++ b/modules/transport-grpc/src/test/resources/server.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ2hNWGXK+n/r02VwY +CD+AUgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEFN/yUNGsRocTsTD +l7on/LAEggTQUL81D5AsLqeTpeDzaq27PJgi890pvD07sNPI33a53XCt2cWggLCX +X6Iyhu2k/xDd3Jk+xUnGIEdQkNrEVk+PG95bdNUIsN2Hc2XWKomDaSIAXL1FZ3MM +NUiYGLVNCMjfo1KzLhHgdPeZ1SWlLwHoxJg7lZvGLyt9S6dwOb7z0kxv+FUbE/po +wCWN1S7wfe1MmhHVJufsMeOeYuCofKABAW6JLk0Yk6JR4qAH8Ai0tQzQ0Q6ccNlJ +P/VmSZZnSPk8StzmI6Gs8hJlOBCy+wmDGzLPo67xiZ9oqYwNB8LBnelYUcmsrfgD +g7OiJT7kFs2Z3+YayoNb2zdmwRq1j2L4fmNTch99on01Ez/Vfz9CB+OkNcBV24dU +ZcfNS3lePetDGZp/eqnBw8bFb/Jfw5iM+L59jND3p7EIM6Nadi9MlbTqvqrVnM7u +ySjRe5+pYpNhvlIhtGFnlUHF6D2SBzfiO6m1+JysWJt2aTrYzY389T9fzcm7LCpU +MNKYqddJ6LdUciIPycWf2mCS0TtVBkJIh/x8aty3oKJa7tJ1NMf2+QNLlV1SwhXD +lrK3Gx+cpmfLJDmiNR0njyrwloI5XtEYwzr8WIEpqPxsCqi+M46QLVv7dFuQ0juj +sRdIkNtl3/0Aa4aUmPAJ+TCcgrr4hrFd5C2rJ77aAK4k5Mb4ZaylO1Vr8pkLaHLL +ad2jbf8WJUHscOXr3f/IoE2ChzXy/heIur+KpjSvJYSP42X5Fw9+SrR0Hc71eh7B +ThoCdAk5uEmMdZpKOCmZJ9cs0op6Mpq9ccpoiNOrEYe1JJLZMw9pdW6GhoQwdgW6 +H6PfYd1gfGU0wDHW54g9cvECQK4CKzsL+PNHSPa694ZVrA6z1MsmRgO7PsIpkmxB +B7PgMkuybo5FqCDr8C3h7d506l64bX6RMRVIC2IQNHuyvHcThLlUAoR+wfvxa7H8 +pgychjTgjcniV7mLdScrztxcgUE+naxQVNnmIFiH/s2KPScxgt0uAFpnQ/I8PqnW +S8/ffgTAli3FZsrf+GukGeknmZzYs6vU3eTih3umdMv7fvLAGi92JaoZQwl6J2X9 +bGq+w8rKtsWqdHYDJHQ1RmEMU2QJxKb5/6lPl2AUHeST85bAQrCt2YLDRdq0liXH +SqvpqHSObagP5Ct+9qqYyO/U4eRI30NCbrCSAgSznrIGLeWMSBYB8rreVUA9Iq7V +Ab/Io0Xua9U9Mfr6dq8DisACzpLYaQhsAwXVSLbNoVNmled5uDD0XlKeuPkTrqGO +7YrCQVus+Gx+mcPAO2w5no4/ZHTJ51SRgBqTgfS2r3Y2fn3Ws5z1/7/athPiscTp +xr34DG1o0ZPj9gTJdiO/+BoRcLe8i+t04mW3HnJtPAGGMVNEiRXzdoxa9WB2jx8F +PQLEHdw4OeI9JNlsh1LiSGHSnPQCdsmRW/+4AcTk8Co7p+v43A7pjRnqyL2tL8qK +4m3liRdDlc0Ge3d/z77v5NfoSEiL8ceiBvA5SZ3Yx3YkaJx0BHeNIiISGf2jWY97 +KvLhiWcI6KC5xHD5FQRmz5UI8ct5PuPxSxAiL3y5XKX8CIpp7lw9D7yS9PR2ZHHD +HkCm1y03zp/qFuAqMGPjclMzjTIg/TiBXL4/YByU2NqfKblgW7RfqMY= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/modules/transport-netty4/build.gradle b/modules/transport-netty4/build.gradle index 4e68a4ce17f73..2cb3c3a060ccd 100644 --- a/modules/transport-netty4/build.gradle +++ b/modules/transport-netty4/build.gradle @@ -38,6 +38,7 @@ import org.opensearch.gradle.test.InternalClusterTestPlugin apply plugin: 'opensearch.yaml-rest-test' apply plugin: 'opensearch.java-rest-test' apply plugin: 'opensearch.internal-cluster-test' +apply from: "$rootDir/gradle/fips.gradle" // The transport-netty4 plugin is published to maven apply plugin: 'opensearch.publish' @@ -65,6 +66,7 @@ dependencies { api "io.netty:netty-resolver:${versions.netty}" api "io.netty:netty-transport:${versions.netty}" api "io.netty:netty-transport-native-unix-common:${versions.netty}" + testFipsRuntimeOnly libs.bundles.bouncycastle } restResources { diff --git a/modules/transport-netty4/src/test/java/org/opensearch/http/netty4/ssl/SecureNetty4HttpServerTransportTests.java b/modules/transport-netty4/src/test/java/org/opensearch/http/netty4/ssl/SecureNetty4HttpServerTransportTests.java index 563f89b70545e..3751f567f1532 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 @@ -23,6 +23,7 @@ import org.opensearch.core.common.transport.TransportAddress; import org.opensearch.core.common.unit.ByteSizeValue; import org.opensearch.core.indices.breaker.NoneCircuitBreakerService; +import org.opensearch.fips.KeyManagerFipsAwareTestCase; import org.opensearch.http.BindHttpException; import org.opensearch.http.CorsHandler; import org.opensearch.http.HttpServerTransport; @@ -52,10 +53,6 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; import java.util.Collections; import java.util.Optional; import java.util.concurrent.CountDownLatch; @@ -97,7 +94,11 @@ /** * Tests for the {@link SecureNetty4HttpServerTransport} class. */ -public class SecureNetty4HttpServerTransportTests extends OpenSearchTestCase { +public class SecureNetty4HttpServerTransportTests extends OpenSearchTestCase implements KeyManagerFipsAwareTestCase { + + private static final char[] PASSWORD = "password".toCharArray(); + + private final KeyManagerFactory keyManagerFactory = createKeyManagerFactory(); private NetworkService networkService; private ThreadPool threadPool; @@ -105,6 +106,26 @@ public class SecureNetty4HttpServerTransportTests extends OpenSearchTestCase { private ClusterSettings clusterSettings; private SecureHttpTransportSettingsProvider secureHttpTransportSettingsProvider; + public KeyManagerFactory createKeyManagerFactory(String keyStoreType, String fileExtension, String jcaProvider, String jsseProvider) { + try { + final KeyStore keyStore = KeyStore.getInstance(keyStoreType, jcaProvider); + + keyStore.load( + SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-server-keystore" + fileExtension), + PASSWORD + ); + + final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( + KeyManagerFactory.getDefaultAlgorithm(), + jsseProvider + ); + keyManagerFactory.init(keyStore, PASSWORD); + return keyManagerFactory; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + @Before public void setup() throws Exception { networkService = new NetworkService(Collections.emptyList()); @@ -120,25 +141,12 @@ public Optional buildHttpServerExceptionHandler(Setti @Override public Optional buildSecureHttpServerEngine(Settings settings, HttpServerTransport transport) throws SSLException { - try { - final KeyStore keyStore = KeyStore.getInstance("PKCS12"); - keyStore.load( - SecureNetty4HttpServerTransportTests.class.getResourceAsStream("/netty4-secure.jks"), - "password".toCharArray() - ); - - final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, "password".toCharArray()); - - SSLEngine engine = SslContextBuilder.forServer(keyManagerFactory) + return Optional.of( + SslContextBuilder.forServer(keyManagerFactory) .trustManager(InsecureTrustManagerFactory.INSTANCE) .build() - .newEngine(NettyAllocator.getAllocator()); - return Optional.of(engine); - } catch (final IOException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException - | CertificateException ex) { - throw new SSLException(ex); - } + .newEngine(NettyAllocator.getAllocator()) + ); } }; } diff --git a/modules/transport-netty4/src/test/java/org/opensearch/transport/netty4/ssl/SimpleSecureNetty4TransportTests.java b/modules/transport-netty4/src/test/java/org/opensearch/transport/netty4/ssl/SimpleSecureNetty4TransportTests.java index e573a9d018862..f75d9fe09fa32 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 @@ -20,6 +20,8 @@ import org.opensearch.core.common.io.stream.NamedWriteableRegistry; import org.opensearch.core.common.transport.TransportAddress; import org.opensearch.core.indices.breaker.NoneCircuitBreakerService; +import org.opensearch.fips.KeyManagerFipsAwareTestCase; +import org.opensearch.fips.TrustManagerFipsAwareTestCase; import org.opensearch.plugins.SecureTransportSettingsProvider; import org.opensearch.plugins.TransportExceptionHandler; import org.opensearch.telemetry.tracing.noop.NoopTracer; @@ -40,22 +42,18 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManagerFactory; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.channels.SocketChannel; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; import java.util.Collections; import java.util.Optional; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; @@ -64,7 +62,50 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.lessThanOrEqualTo; -public class SimpleSecureNetty4TransportTests extends AbstractSimpleTransportTestCase { +public class SimpleSecureNetty4TransportTests extends AbstractSimpleTransportTestCase + implements + TrustManagerFipsAwareTestCase, + KeyManagerFipsAwareTestCase { + + private static final char[] PASSWORD = "password".toCharArray(); + + private final KeyManagerFactory serverKeyManagersFactory = createKeyManagerFactory(); + private final TrustManagerFactory clientTrustManagerFactory = createTrustManagerFactory(); + + @Override + public KeyManagerFactory createKeyManagerFactory(String keyStoreType, String fileExtension, String jcaProvider, String jsseProvider) { + try { + var keyStore = KeyStore.getInstance(keyStoreType, jcaProvider); + keyStore.load(SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-server-keystore" + fileExtension), PASSWORD); + var keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm(), jsseProvider); + keyManagerFactory.init(keyStore, PASSWORD); + return keyManagerFactory; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public TrustManagerFactory createTrustManagerFactory( + String keyStoreType, + String fileExtension, + String jcaProvider, + String jsseProvider + ) { + try { + final KeyStore trustStore = KeyStore.getInstance(keyStoreType, jcaProvider); + trustStore.load( + SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-client-truststore" + fileExtension), + PASSWORD + ); + var trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm(), jsseProvider); + trustManagerFactory.init(trustStore); + return trustManagerFactory; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override protected Transport build(Settings settings, final Version version, ClusterSettings clusterSettings, boolean doHandshake) { NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(Collections.emptyList()); @@ -76,27 +117,12 @@ public Optional buildServerTransportExceptionHandler( @Override public Optional buildSecureServerTransportEngine(Settings settings, Transport transport) throws SSLException { - try { - final KeyStore keyStore = KeyStore.getInstance("PKCS12"); - keyStore.load( - SimpleSecureNetty4TransportTests.class.getResourceAsStream("/netty4-secure.jks"), - "password".toCharArray() - ); - - final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, "password".toCharArray()); - - SSLEngine engine = SslContextBuilder.forServer(keyManagerFactory) + return Optional.of( + SslContextBuilder.forServer(serverKeyManagersFactory) .clientAuth(ClientAuth.NONE) - .trustManager(InsecureTrustManagerFactory.INSTANCE) .build() - .newEngine(NettyAllocator.getAllocator()); - return Optional.of(engine); - } catch (final IOException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException - | CertificateException ex) { - throw new SSLException(ex); - } - + .newEngine(NettyAllocator.getAllocator()) + ); } @Override @@ -104,7 +130,7 @@ public Optional buildSecureClientTransportEngine(Settings settings, S return Optional.of( SslContextBuilder.forClient() .clientAuth(ClientAuth.NONE) - .trustManager(InsecureTrustManagerFactory.INSTANCE) + .trustManager(clientTrustManagerFactory) .build() .newEngine(NettyAllocator.getAllocator()) ); diff --git a/modules/transport-netty4/src/test/resources/README.md b/modules/transport-netty4/src/test/resources/README.md index 50cbd432d32c6..151d7f67093eb 100644 --- a/modules/transport-netty4/src/test/resources/README.md +++ b/modules/transport-netty4/src/test/resources/README.md @@ -4,23 +4,123 @@ # This file can also be executed as a script # -# 1. Create certificate key +```bash +export PW='password' +export LIB_PATH="/path/to/lib/folder" +``` + +# 1 Create a server & root (self-signed) CAs +Root CA +└── Server certificate +```bash +printf '%s\n' \ +"[ req ] +default_bits = 2048 +prompt = no +default_md = sha256 +req_extensions = SAN +distinguished_name = dn + +[ dn ] +CN = OpenSearch Test +OU = opensearch +O = org -`openssl req -x509 -sha256 -newkey rsa:2048 -keyout certificate.key -out certificate.crt -days 1024 -nodes` +[ SAN ] +subjectAltName = DNS:localhost, IP:127.0.0.1, IP:::1 +" > openssl.conf -# 2. Export the certificate in pkcs12 format +openssl req -x509 -new -nodes -keyout root.key -out root.crt -days 8192 -subj "/CN=Root CA" -`openssl pkcs12 -export -in certificate.crt -inkey certificate.key -out netty4-secure.p12 -name netty4-secure -password pass:password` +openssl req -new -nodes \ + -keyout server.key \ + -out server.csr \ + -config openssl.conf +openssl x509 -req \ + -in server.csr \ + -CA root.crt \ + -CAkey root.key \ + -CAcreateserial \ + -out server.crt \ + -days 8192 \ + -extfile openssl.conf \ + -sigopt rsa_padding_mode:pss \ + -sigopt rsa_pss_saltlen:-1 +``` -# 3. Migrate from P12 to JKS keystore +# 2. Create a client certificate and key +```bash +openssl req -new -nodes \ + -keyout client.key \ + -out client.csr \ + -config openssl.conf +openssl x509 -req \ + -in client.csr \ + -CA root.crt \ + -CAkey root.key \ + -CAcreateserial \ + -out client.crt \ + -days 8192 \ + -extfile openssl.conf \ + -sigopt rsa_padding_mode:pss \ + -sigopt rsa_pss_saltlen:-1 +``` +# 3. Export the certificates in pkcs12 format +```bash +keytool -import -trustcacerts \ + -file root.crt \ + -alias root-ca \ + -keystore netty4-client-truststore.p12 \ + -storepass "$PW" \ + -noprompt +openssl pkcs12 -export \ + -in server.crt \ + -inkey server.key \ + -out netty4-server-keystore.p12 \ + -name netty4-server-keystore \ + -passout pass:"$PW" \ + -passin pass:"$PW" ``` -keytool -importkeystore -noprompt \ - -srckeystore netty4-secure.p12 \ + +# 4. Import the certificate into JDK keystore (PKCS12 type) +```bash +keytool -importkeystore \ + -srcstorepass "$PW" \ + -destkeystore netty4-server-keystore.jks \ + -srckeystore netty4-server-keystore.p12 \ + -srcstoretype PKCS12 \ + -deststorepass "$PW" +keytool -importkeystore \ + -srcstorepass "$PW" \ + -destkeystore netty4-client-truststore.jks \ + -srckeystore netty4-client-truststore.p12 \ + -srcstoretype PKCS12 \ + -deststorepass "$PW" +``` + +# 5. Import the certificate into BCFKS keystore +```bash +keytool -importkeystore \ + -noprompt \ + -srckeystore netty4-server-keystore.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass "$PW" \ + -destkeystore netty4-server-keystore.bcfks \ + -deststoretype BCFKS \ + -deststorepass "$PW" \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar +keytool -importkeystore \ + -noprompt \ + -srckeystore netty4-client-truststore.p12 \ -srcstoretype PKCS12 \ - -srcstorepass password \ - -alias netty4-secure \ - -destkeystore netty4-secure.jks \ - -deststoretype JKS \ - -deststorepass password + -srcstorepass "$PW" \ + -destkeystore netty4-client-truststore.bcfks \ + -deststoretype BCFKS \ + -deststorepass "$PW" \ + -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/certificate.crt b/modules/transport-netty4/src/test/resources/certificate.crt deleted file mode 100644 index 54c78fdbcf6de..0000000000000 --- a/modules/transport-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/modules/transport-netty4/src/test/resources/certificate.key b/modules/transport-netty4/src/test/resources/certificate.key deleted file mode 100644 index 228350180935d..0000000000000 --- a/modules/transport-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/modules/transport-netty4/src/test/resources/client.crt b/modules/transport-netty4/src/test/resources/client.crt new file mode 100644 index 0000000000000..c6c6b992139d5 --- /dev/null +++ b/modules/transport-netty4/src/test/resources/client.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhzCCAjugAwIBAgIUQVngW8CGIudrvkw+rvcGn58oRkwwQQYJKoZIhvcNAQEK +MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF +AKIDAgEgMBIxEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjUwNzIxMTEzMTIzWhcNNDcx +MjI1MTEzMTIzWjA9MRgwFgYDVQQDDA9PcGVuU2VhcmNoIFRlc3QxEzARBgNVBAsM +Cm9wZW5zZWFyY2gxDDAKBgNVBAoMA29yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOAmcIyJ4waI/kKSIi+V0JRHBOwHRqqUak2sCVbu/xV/Aa5qH2Ya +nNcCCxxzAzvwzMpTMRXp1SOvSzEsbNZrUJR/eFHmbI0R9YYvUe8QFGG7bP0Jr70i +PVQEmyMbceCQ5PPlQWI7AkYAUvSeClL5jiaN0MWYtmzcE0GUefFTt2iskTvs0phR +VzdVveMuQLwEsSDwKWTC7QH/fM+V80DsX8tosrCy4McXlCrHBH0SX1/GJQYJmLdK +3qMYbO7R4w6TxnWhPsHizAoMJJgs6QmOLKk+S2vMWwurrFLlpuMna79cyGQrhoth +Yno2qlHs02FdZAr5SQLT+laanHp+SfjbjDMCAwEAAaNCMEAwHQYDVR0OBBYEFC7o +UKsrGIl+9F3ov6n7qPLAZx2GMB8GA1UdIwQYMBaAFHv9Md6x7STnuKJHj+WiT2/+ +p7O0MEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B +AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEANmgEWxLFkWETwViRwFJcPNJJ71ct +eW9EMH3jtleY2axR7qGS1n73+eim+EGMk4bIBEMGyUUWsx3tvZu+sKetDi4JnSqh +3vpDFA0AdhiQ97V3HHbrWr8qcNcrBd0J86y9DEQWJwwkc8oD0QBtTFTJWsMsdDIP +5qz9P7vG6HvblkFsy2ucmU0WD6UYSC8KJuSb1vH4ImTUl3Tf7XSLaSMEjAHyStdI +8zpozbxNzTQF1ye1lQhY5zh6bVmmeUmNNy6uP82YVhafinmVIaT1kRXwlFXWB2Ka +5uF04W8lfrV1r1OjPOtU8JTwzdnKIk7I0Or+uTXYGV3Y5GwyMcEKnKEHvg== +-----END CERTIFICATE----- diff --git a/modules/transport-netty4/src/test/resources/client.csr b/modules/transport-netty4/src/test/resources/client.csr new file mode 100644 index 0000000000000..78ecbd8cc7058 --- /dev/null +++ b/modules/transport-netty4/src/test/resources/client.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICwTCCAakCAQAwPTEYMBYGA1UEAwwPT3BlblNlYXJjaCBUZXN0MRMwEQYDVQQL +DApvcGVuc2VhcmNoMQwwCgYDVQQKDANvcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDgJnCMieMGiP5CkiIvldCURwTsB0aqlGpNrAlW7v8VfwGuah9m +GpzXAgsccwM78MzKUzEV6dUjr0sxLGzWa1CUf3hR5myNEfWGL1HvEBRhu2z9Ca+9 +Ij1UBJsjG3HgkOTz5UFiOwJGAFL0ngpS+Y4mjdDFmLZs3BNBlHnxU7dorJE77NKY +UVc3Vb3jLkC8BLEg8Clkwu0B/3zPlfNA7F/LaLKwsuDHF5QqxwR9El9fxiUGCZi3 +St6jGGzu0eMOk8Z1oT7B4swKDCSYLOkJjiypPktrzFsLq6xS5abjJ2u/XMhkK4aL +YWJ6NqpR7NNhXWQK+UkC0/pWmpx6fkn424wzAgMBAAGgPzA9BgkqhkiG9w0BCQ4x +MDAuMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAA +ATANBgkqhkiG9w0BAQsFAAOCAQEAiU+D1vbZZAMDXrvVjPDuUGOyqcv7tkxJVs2N +BbVKHJpnfvUWXRDRF0Bs6dVNmJ/EK7nt3yici9+9ZA1iv3BnEtIs9FFwd+eRqNdP +QXBFuOzUhUCdDpo9X3ZAYkOcTt4HYtM/YXiKk5qK92oFuKvxPrt4EUNi2EFl+CF8 +/cNFhWXyLV7lG+Mfxel1MhqgacFv01bSHbTFgbWtqUxSx4JrBUlXrFrYk+UBSIFX +vTEe3tXV3xf4HkfasPV6Egx0Ob8FdqW7k0bC9e/8SEhyWEhhwKmOHaB4Ew6TAeIN +c18XpGZogSP38YfLCRZvs0GnM+a6UMpcr+yNB1ftaEE3Dz5BxA== +-----END CERTIFICATE REQUEST----- diff --git a/modules/transport-netty4/src/test/resources/client.key b/modules/transport-netty4/src/test/resources/client.key new file mode 100644 index 0000000000000..f113e95b2e40f --- /dev/null +++ b/modules/transport-netty4/src/test/resources/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDgJnCMieMGiP5C +kiIvldCURwTsB0aqlGpNrAlW7v8VfwGuah9mGpzXAgsccwM78MzKUzEV6dUjr0sx +LGzWa1CUf3hR5myNEfWGL1HvEBRhu2z9Ca+9Ij1UBJsjG3HgkOTz5UFiOwJGAFL0 +ngpS+Y4mjdDFmLZs3BNBlHnxU7dorJE77NKYUVc3Vb3jLkC8BLEg8Clkwu0B/3zP +lfNA7F/LaLKwsuDHF5QqxwR9El9fxiUGCZi3St6jGGzu0eMOk8Z1oT7B4swKDCSY +LOkJjiypPktrzFsLq6xS5abjJ2u/XMhkK4aLYWJ6NqpR7NNhXWQK+UkC0/pWmpx6 +fkn424wzAgMBAAECggEAaP0IPYN+PzreYP8CnSg+XJ1RLF6SFmbSzdO1Qi9Kcl6d +Qnz5fQEdEkYmPyqNyr8UpPXHaXyvdPnvrbYhumLkiNGV4UKc+Ix/ZFz4sxFkgoxf +1bt6QYvujtJjedOqiKVFYAmzENgTzgbr1CDMNCYok11cepj3ZVDFgjOR1FvCbfd5 +d1AhoKH1BgbJw7ao43K3OTV8X4u1fwUs1+gVaSUqXCw2eoK7wzYhuafY3i7aoImB +rH/l7slbRMwIEVTjskMU2GbhAwAMd1nSl7+WSRZJCj+Ipkr5V4fw32HtsZ+atHQv +QHyuVvkMO5XMKNdh367/JSLCvoWq+2Nwfd8WK3RQ2QKBgQD+UIJW8gaDU2XjFLyM +HVIysOX0Y49pehZpVfdfW+p7hzeKRBcB0G6R9jboByeygpHXp83OtMcEgNOkhv0v +bcWxze6lnUFlOrN8otdChPEyjKfVvoPGWlu/X5dgYmbH0gYPFmGdvVn+IZPbNud6 +gtE3vzSL3I8IgmTK56JnrqhbeQKBgQDhosBPyN9JKd8EwVJ/W4DYnZgDzNTQ5YFw +iYlRL/02ift4QRlMRtoBRNVGwVXFcS7c62Y2upye3c0iVGZSOBKT+TMls51GEr/o +CCF7KZ40v3dEDYkIfmEUyEhoZBP9JidrVZ7JQ0HKAqlf3op7BAb9PxJ0F1vP5fqI +WkGy8NcOCwKBgQDYf9F2EzRYOHdUiiK5ZXzEirQ5CUjdkoHhyr19ANLttn8hxjWP +5dE7kU/M18qDvYRLAx/CCUQkIUgLmJ+R8PGMHLQ9aVJyyzZhtH9ssaBkWDnJTRcB +lYrlsIs+EodNNQ7TaUpQ7xHdttgYlvUY5qUVwEELkAh0Rg1obyli+t14gQKBgQDR +GmH8zpxXEm2y23IXwvYKSSo6w1Wqbjbh0yQl0EJqi783d6T5MmQQbvj0BHH3c2fJ +poHoUFKbS9Qyb2MP6RUWewgydV1YNkIQx5A3sp8CJTpjH+QRsgVIHEA2NiDsJrK7 +XvKzdBgBHSxYQ0W0sUvE3/G7EnasmWXvfS2LpnS2dwKBgHavMqth5uTqEFiZN3PE +6mLaDs9yEUzJ97IpO5mEUmnkwB3ppNZSMKna/TWh1jgd9QAoa4npdAe62eKpr5h8 +siPFsljkm6QB5o3h1PvvIdGDpujpLo7srL23LUKlWUMSp9jJQB1k/UMZ/J70aDaH +S6BfBcG5YUJyIINXecbFQtEq +-----END PRIVATE KEY----- diff --git a/modules/transport-netty4/src/test/resources/netty4-client-truststore.bcfks b/modules/transport-netty4/src/test/resources/netty4-client-truststore.bcfks new file mode 100644 index 0000000000000..b52a6f494df17 Binary files /dev/null and b/modules/transport-netty4/src/test/resources/netty4-client-truststore.bcfks differ diff --git a/modules/transport-netty4/src/test/resources/netty4-client-truststore.jks b/modules/transport-netty4/src/test/resources/netty4-client-truststore.jks new file mode 100644 index 0000000000000..cf197eb0f6e50 Binary files /dev/null and b/modules/transport-netty4/src/test/resources/netty4-client-truststore.jks differ diff --git a/modules/transport-netty4/src/test/resources/netty4-client-truststore.p12 b/modules/transport-netty4/src/test/resources/netty4-client-truststore.p12 new file mode 100644 index 0000000000000..53f0f2e04aada Binary files /dev/null and b/modules/transport-netty4/src/test/resources/netty4-client-truststore.p12 differ diff --git a/modules/transport-netty4/src/test/resources/netty4-secure.jks b/modules/transport-netty4/src/test/resources/netty4-secure.jks deleted file mode 100644 index d158f1fe60ef7..0000000000000 Binary files a/modules/transport-netty4/src/test/resources/netty4-secure.jks and /dev/null differ diff --git a/modules/transport-netty4/src/test/resources/netty4-secure.p12 b/modules/transport-netty4/src/test/resources/netty4-secure.p12 deleted file mode 100644 index 822d7ff8236f3..0000000000000 Binary files a/modules/transport-netty4/src/test/resources/netty4-secure.p12 and /dev/null differ diff --git a/modules/transport-netty4/src/test/resources/netty4-server-keystore.bcfks b/modules/transport-netty4/src/test/resources/netty4-server-keystore.bcfks new file mode 100644 index 0000000000000..cd7b3b9129dab Binary files /dev/null and b/modules/transport-netty4/src/test/resources/netty4-server-keystore.bcfks differ diff --git a/modules/transport-netty4/src/test/resources/netty4-server-keystore.jks b/modules/transport-netty4/src/test/resources/netty4-server-keystore.jks new file mode 100644 index 0000000000000..0fe68909c5686 Binary files /dev/null and b/modules/transport-netty4/src/test/resources/netty4-server-keystore.jks differ diff --git a/modules/transport-netty4/src/test/resources/netty4-server-keystore.p12 b/modules/transport-netty4/src/test/resources/netty4-server-keystore.p12 new file mode 100644 index 0000000000000..e2b224c2d2489 Binary files /dev/null and b/modules/transport-netty4/src/test/resources/netty4-server-keystore.p12 differ diff --git a/modules/transport-netty4/src/test/resources/openssl.conf b/modules/transport-netty4/src/test/resources/openssl.conf new file mode 100644 index 0000000000000..b0d75d329f9b7 --- /dev/null +++ b/modules/transport-netty4/src/test/resources/openssl.conf @@ -0,0 +1,13 @@ +[ req ] +default_bits = 2048 +prompt = no +default_md = sha256 +req_extensions = SAN +distinguished_name = dn +[ dn ] +CN = OpenSearch Test +OU = opensearch +O = org +[ SAN ] +subjectAltName = DNS:localhost, IP:127.0.0.1, IP:::1 + diff --git a/modules/transport-netty4/src/test/resources/root.crt b/modules/transport-netty4/src/test/resources/root.crt new file mode 100644 index 0000000000000..5e07dd6a2ede8 --- /dev/null +++ b/modules/transport-netty4/src/test/resources/root.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBTCCAe2gAwIBAgIUKe1lffcl4su2VY1U/7jXobepZ+wwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yNTA3MjExMTMwMjRaFw00NzEyMjUx +MTMwMjRaMBIxEDAOBgNVBAMMB1Jvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDSq31KtiyJRCBYORke40kg9hmDxMWDoMVoAWVhoa2s18CBWNfZ +xsCr7dpOBkvpD4LWYnnZeZxI7RuWxQxp2veAaDMvwffrjfeCkq6Hya83FSFtr+aR +bH12ktUGUii3zZj8j/K3pelmiYVgipc6ScgV/i/lvm3/z9CfJpG/4/u6dKtLA4zi +3po3kySEvda4JNkxmmTc2zYqp9c0Iak14pa5/VxSXeXESchKS931dGEyf4r4mOUP +wWPWrKJF39ofApmQt4na7jYWhY5C40j+xLn+3hHjMoGMXkTPCDDR3JOhYS6vScoE +6873tAvEmsOr5a3z2E1PQul9pppNiZimbW2RAgMBAAGjUzBRMB0GA1UdDgQWBBR7 +/THese0k57iiR4/lok9v/qeztDAfBgNVHSMEGDAWgBR7/THese0k57iiR4/lok9v +/qeztDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCnhaJ4IE2V +e4BNJ+2X2exyAzFeKkNhbZR1JHGwmw4hWtWHcmCE8C+XLtOMF8dNRD+oAVxDumi6 +aVbrsXPBgxlpPKZjITpGDnYwH9aPPStmnPq1iBnMonBcphnGLQQ+J6NugmjPa0tj +Tkp8g2bc5F9o1QRQW7OWkv2TVNvjZoLpWQ1/KNHFXy5jZ4Ysvu2n0m1+AFlUs0aC +p16rLS8wWyOF9XkbV+zHBaAUzenO1eryOCSocqRKL1BXuUY946KX08QcY6QZ8wCI +CA/4Ugw8cP2e0LL5TP75N+02QWrZBo7qcEQvjtrT+2ex7goc+H9iHrnRVsWv/I8S +cZBsQ5sMo5MQ +-----END CERTIFICATE----- diff --git a/modules/transport-netty4/src/test/resources/root.key b/modules/transport-netty4/src/test/resources/root.key new file mode 100644 index 0000000000000..641650ec29e1f --- /dev/null +++ b/modules/transport-netty4/src/test/resources/root.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDSq31KtiyJRCBY +ORke40kg9hmDxMWDoMVoAWVhoa2s18CBWNfZxsCr7dpOBkvpD4LWYnnZeZxI7RuW +xQxp2veAaDMvwffrjfeCkq6Hya83FSFtr+aRbH12ktUGUii3zZj8j/K3pelmiYVg +ipc6ScgV/i/lvm3/z9CfJpG/4/u6dKtLA4zi3po3kySEvda4JNkxmmTc2zYqp9c0 +Iak14pa5/VxSXeXESchKS931dGEyf4r4mOUPwWPWrKJF39ofApmQt4na7jYWhY5C +40j+xLn+3hHjMoGMXkTPCDDR3JOhYS6vScoE6873tAvEmsOr5a3z2E1PQul9pppN +iZimbW2RAgMBAAECggEAGSqdVBCBL4Cw8HkZtCPot4ROtfwKsFfDXVJVXx1mVvCL +MaiAWKaym5dVbd64sMXGln+7GKy7a8bPdepiOj8LNA6+lqTwTfZuq+2D5frE8KqY +jLbnr1Wrgz+1LQUhkhuAfCNiFmg+gp5JDahgunjX6zCrXiGqmFWmEW0VGjxM9RAF +bXtiKP0Idw1vPoqf7DH/R0AgJKnnLrel0uG8bdOy84BidHameV+iDxbgaN29MjYN +E9oL7xaWJF1spToOv8VszCVTaYdCBC7oTIzmYqjXsqCg346j+hzDZZys7dL8ekoA +g0n6Bu7vcD9A6CFlgUDTzcbzvwwXfrgyKfxEu1N9sQKBgQDqpcxDOeYMVj2ZdAs5 +WEFIY2M7O9ocvcB6iOS/Ejm8RM4If5JVka7egkGnphWwyW25CruwegYqKn+iWSys +iSA2sMC57GGXHHg/6rtz3ZDPV4ummj3ZlDSv3cigeHBbysUtgAHQCAdTgeQ4Ircf +IhD6L/cYgAn3VOIn457V26g9jQKBgQDl1x7n8YYHF6Z/AAaJqbnLKj/yHO6IBo/G +ytbqnIK7z/I27CqF5ZAbwSR8XA1dY7LrE+vtS/psv/kKbrAa64RkEOkPeHv1nVH9 +oN8cuNpgD2Rau7ZQgf84U6MU7VsCM/tceHpjUGgmE8pcZkTmu4W7frq8Nz45tfBV +LMngAWclFQKBgCPSXeWhcCpP/CVyHsiJXaYEbkC7uWeL3FeYkHtO9/ty8ZVAjIF9 +tRmI/ybt5v/PMG4AYnjlmA1V4ZhSTsykPoO65iE8IgXktcLkPevJVnMg5VPw1iJi +SszSae99k4Gf6gwxL5WntB6ehM3BFv2swLTBH9LgsSTOE5yNVlFFsGB9AoGBAKwb +iV4gAr/+2sNRDpQ5TxIughHXzlp3p2Qx8fte1Zqz3Wl5MWJwg5f06lBhUfSzun/D +vDBGa7DRlDhWqHKQF+QisZLPIuvKMWJd+5Rza7rw67HxzFS+85GHN6BtGHAu4rPc +17DUvhh9S1QjlMa+dybugAB+0Y6/iccOMeEWy9j9AoGBAKLW2IQZlOuKrARDNfMi +phsUR0xN+LQGFwnW3iXmQ64gbHARnfAltVpqyBwhhUZwmtU+yRIJd2QvfceMUdWW +E6DkllOuD7kQY6SE7TlDL31VJ/QXALRtt6SUzZl6sC0njUBl9igw6N7tnptQ4ORh +3G3ZVsXGmtw3NFhFSqNde76Y +-----END PRIVATE KEY----- diff --git a/modules/transport-netty4/src/test/resources/root.srl b/modules/transport-netty4/src/test/resources/root.srl new file mode 100644 index 0000000000000..4894395797130 --- /dev/null +++ b/modules/transport-netty4/src/test/resources/root.srl @@ -0,0 +1 @@ +4159E05BC08622E76BBE4C3EAEF7069F9F28464C diff --git a/modules/transport-netty4/src/test/resources/server.crt b/modules/transport-netty4/src/test/resources/server.crt new file mode 100644 index 0000000000000..104f4edba2cfe --- /dev/null +++ b/modules/transport-netty4/src/test/resources/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhzCCAjugAwIBAgIUQVngW8CGIudrvkw+rvcGn58oRkswQQYJKoZIhvcNAQEK +MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF +AKIDAgEgMBIxEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjUwNzIxMTEzMDI0WhcNNDcx +MjI1MTEzMDI0WjA9MRgwFgYDVQQDDA9PcGVuU2VhcmNoIFRlc3QxEzARBgNVBAsM +Cm9wZW5zZWFyY2gxDDAKBgNVBAoMA29yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMt3KC2rHxM2i4zIAZBglJYf2elMRH9xH+BCNIpfUBUaTZYmyayi +ki4iA0gxyjj6rWh/bpPzA79UYosYq9pRB6gRJR528tDihsQyLB2012XuIB/UPdsB +UJRItH6v2tGZf6WwaR/C91qWjh7OPPIyNVQtKYQWrbuTkdNvYxh11y6qWzmynrYB +kCcFJZaiEADAf0B1Qk4Du7MCVC3Q60ioiCmDy2HUcUilJbKpg/SHXjZmVnR7SSa2 +9PVIkroiRyUSM8tz/6twisOo/NCokEcf0LW6X8hjlYQNaOI7nu1Jtu0OJuaggDL5 +Ld0QAQklgv0AEuEX0rinnZkKAm9TIaE03ckCAwEAAaNCMEAwHQYDVR0OBBYEFB9u +hM53/UkEojoKo4csCXvb4J62MB8GA1UdIwQYMBaAFHv9Md6x7STnuKJHj+WiT2/+ +p7O0MEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B +AQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEABcg2KYUNtq3cRrJaOYU9oJojVe1R +ej76jdTvdfaQeKb+mie9dkQgj8dEJWJT1lKIF/eX8o6F1MNgQgdw/d458HXSN047 +JvKMTTFc/5p7LMOExCmIAKFzc0tFLNphB9DvsjKRZsR+7A3iwZ30n8bh4VazslZC +rS85PX9S/X1nF2mIGc2MnlmvabgaZtrRSfzYKarH9d5NZEjszNR2coA8ZZw3Ftdm +LAf9I1y+gh+u+iin46rtyIjmfdZCHKPzQlK5EmoSxGCL/n7t87e1tSByYB2b6w2K +zBi6qwJUxiuwm3s4qLOWmn2uu44e3JTnAf2OWVRBsUm3mYNsuu8Oj+xitA== +-----END CERTIFICATE----- diff --git a/modules/transport-netty4/src/test/resources/server.csr b/modules/transport-netty4/src/test/resources/server.csr new file mode 100644 index 0000000000000..14ebd9ad727cd --- /dev/null +++ b/modules/transport-netty4/src/test/resources/server.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICwTCCAakCAQAwPTEYMBYGA1UEAwwPT3BlblNlYXJjaCBUZXN0MRMwEQYDVQQL +DApvcGVuc2VhcmNoMQwwCgYDVQQKDANvcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDLdygtqx8TNouMyAGQYJSWH9npTER/cR/gQjSKX1AVGk2WJsms +opIuIgNIMco4+q1of26T8wO/VGKLGKvaUQeoESUedvLQ4obEMiwdtNdl7iAf1D3b +AVCUSLR+r9rRmX+lsGkfwvdalo4ezjzyMjVULSmEFq27k5HTb2MYddcuqls5sp62 +AZAnBSWWohAAwH9AdUJOA7uzAlQt0OtIqIgpg8th1HFIpSWyqYP0h142ZlZ0e0km +tvT1SJK6IkclEjPLc/+rcIrDqPzQqJBHH9C1ul/IY5WEDWjiO57tSbbtDibmoIAy ++S3dEAEJJYL9ABLhF9K4p52ZCgJvUyGhNN3JAgMBAAGgPzA9BgkqhkiG9w0BCQ4x +MDAuMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAA +ATANBgkqhkiG9w0BAQsFAAOCAQEArjyDIf4ceTw63wl245iR9BB/5f0ncWXqsWUK +k0xnbCKC496vj4r4VRJhOEA00QM/mt0C1Ft20kZKvBdnBdsmX9DUYZxala75jlhI +rRLS6XbBz/imQYMvlcfUrtcag6WT2K2Tq+alFBZ4Cb9/I3PvzB1T50pJw1XlDwDJ +/oSyLQJPdvfwDln0gtSNBxfv5Z+HZM88LXCIw/c8kTg0ZQjoseJZsoPhusdVemFQ +fjsjiazajGKBFvfbqgZ4eOgH1a2G91dY+8blMC4QhEbkWExQRvFISFGP04eca1v+ ++/XEkUavA2idQfJKIVGbEzPHKwlJJriKPJZfiBBglMUFxgl/hg== +-----END CERTIFICATE REQUEST----- diff --git a/modules/transport-netty4/src/test/resources/server.key b/modules/transport-netty4/src/test/resources/server.key new file mode 100644 index 0000000000000..746431674d6e8 --- /dev/null +++ b/modules/transport-netty4/src/test/resources/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDLdygtqx8TNouM +yAGQYJSWH9npTER/cR/gQjSKX1AVGk2WJsmsopIuIgNIMco4+q1of26T8wO/VGKL +GKvaUQeoESUedvLQ4obEMiwdtNdl7iAf1D3bAVCUSLR+r9rRmX+lsGkfwvdalo4e +zjzyMjVULSmEFq27k5HTb2MYddcuqls5sp62AZAnBSWWohAAwH9AdUJOA7uzAlQt +0OtIqIgpg8th1HFIpSWyqYP0h142ZlZ0e0kmtvT1SJK6IkclEjPLc/+rcIrDqPzQ +qJBHH9C1ul/IY5WEDWjiO57tSbbtDibmoIAy+S3dEAEJJYL9ABLhF9K4p52ZCgJv +UyGhNN3JAgMBAAECggEABaFCBUTP+uVeZlJuHqYEKS+aRUG/vyICHopeb09qBRzM +SvcUS1itQFBfHwsyUVzNrw+gSkDmGJAicOm0GaShLbCZwmvKq2Z/5vkMw56qe3l5 +E0BkHMWpdTeaIkubO65VEUszbCD7h1q/9dDBmsxdBuZVWNW9esGqKOZTC40j243n +scJtFYEmeBlfEc88nGNZW/qS1TVigXR0F+pjB9AmEmiIbzaTl23hrExD4DPWjOO6 +CUC5dy6IyZL+03ZO+JuA0WnN12lu4WhLZUCMIn8+CMmK7EDR45TI0Gub8L5tMH79 +6JpCdpj2crzO87aiGVwZHhpuxmh9paAIe9iYt9sn/QKBgQD1livEepNQnh3pgKJK +SBKTo81W3NukDZeINzNkiSLq5YqP4GEGB8buEfmnywxcP+XpQcJgBHN/HN5bpgIE +7LGeuO7yjU709rnBVagYs6/2o5sDDAhQnwxMFPzTBdcOrDheoOAXBnkyZlvNK7W4 +lpJPpOPKHro9/kYSzSpZeVtyhQKBgQDUF8NjscfG9sSNvS74OxpBxgEzdSw6BSjj +Fi5u5cDyCrPbmtAW3kO9R0/B7rk9XQLCrfBNA1jpJ3codjVZd2DE8NVdUZ6Vz6+m +vVMRR713pcmGVtIvTDjgrnUhnXJQULbzO8+HvttKDiqnGoocKXxvjUBSIFGesPBd +nzT447ebdQKBgHYrx22oiaA+JSiJhA34NjAinpjQd0OuYp7h6PHUZx/eSdlHX4hW +T6uYrKfHtW/iM1AVG2G2YpjknD4/WDNkbWdxl1DRHBAVdHaKL/OK6v3j5aAGty2M +Co/FfY5Q/hWKWyl1gb6yl7jftga4pJ1onoiCUDWeqYFRDAjZ/inFknI9AoGAWzTo +4nt2VPqTpfxgMYGBFh3nMFDINx826QkF6MzGv4+YruyRZfQpH0GGa/iUEm5oHH4e +Y4/38TP2iyDialnq9GY46wuJbhVFDShHi5vVP5m84xd9Bbf6kayUVGNX4HAFbO4g +ndiWhu82qmvATsIMcBBHQ8oAL4UZqLkpV4rdz20CgYBrk44bsS4YIhmfeC3P4d5l +1Z0xYHGGHEtG8DcxGV1k44ywzjW9XNiAsMneCW3AXwT6C5wV5yCeLwxeWyRyWSfI +6RqK/wDBSdYs90Eb2lE/VyeIIPS6K2RJbdi2bI5abZ6od/MKlQF60Rt0Tmgsp86k +CsDwCGW8dCRitcnfGjIx7A== +-----END PRIVATE KEY----- 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/arrow-flight-rpc/build.gradle b/plugins/arrow-flight-rpc/build.gradle index eb14e4ecea577..90ccd7f6632cf 100644 --- a/plugins/arrow-flight-rpc/build.gradle +++ b/plugins/arrow-flight-rpc/build.gradle @@ -10,6 +10,7 @@ */ apply plugin: 'opensearch.internal-cluster-test' +apply from: "$rootDir/gradle/fips.gradle" opensearchplugin { description = 'Arrow flight based transport and stream implementation. It also provides Arrow vector and memory dependencies as' + @@ -73,6 +74,7 @@ dependencies { // Javassist for bytecode injection chaos testing internalClusterTestImplementation 'org.javassist:javassist:3.29.2-GA' + testFipsRuntimeOnly libs.bundles.bouncycastle } tasks.named('test').configure { diff --git a/plugins/discovery-azure-classic/build.gradle b/plugins/discovery-azure-classic/build.gradle index 2627b3061bdf2..f2c918b743662 100644 --- a/plugins/discovery-azure-classic/build.gradle +++ b/plugins/discovery-azure-classic/build.gradle @@ -100,6 +100,10 @@ TaskProvider createKey = tasks.register("createKey", LoggedExec) { } //no unit tests tasks.named("test").configure { enabled = false } +tasks.withType(Test).configureEach { + onlyIf { BuildParams.inFipsJvm == false } +} + // add keystore to test classpath: it expects it there tasks.named("processInternalClusterTestResources").configure { from createKey diff --git a/plugins/discovery-azure-classic/src/internalClusterTest/java/org/opensearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java b/plugins/discovery-azure-classic/src/internalClusterTest/java/org/opensearch/discovery/azure/classic/AzureDiscoveryClusterFormationTests.java index a4a2e672f3afe..7168931956bde 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 @@ -278,8 +278,10 @@ public static void startHttpd() throws Exception { private static SSLContext getSSLContext() throws Exception { char[] passphrase = "keypass".toCharArray(); - KeyStore ks = KeyStore.getInstance("JKS"); - try (InputStream stream = AzureDiscoveryClusterFormationTests.class.getResourceAsStream("/test-node.jks")) { + final String fileExtension = inFipsJvm() ? ".bcfks" : ".jks"; + final String keyStoreType = inFipsJvm() ? "BCFKS" : "JKS"; + KeyStore ks = KeyStore.getInstance(keyStoreType); + try (InputStream stream = AzureDiscoveryClusterFormationTests.class.getResourceAsStream("/test-node" + fileExtension)) { assertNotNull("can't find keystore file", stream); ks.load(stream, passphrase); } @@ -298,14 +300,16 @@ private static SSLContext getSSLContext() throws Exception { */ @SuppressWarnings("removal") private static String getProtocol() { - if (Runtime.version().compareTo(Version.parse("12")) < 0) { - return "TLSv1.2"; - } else { - Version full = AccessController.doPrivileged( - (PrivilegedAction) () -> Version.parse(System.getProperty("java.version")) - ); - if (full.compareTo(Version.parse("12.0.1")) < 0) { + if (!inFipsJvm()) { + if (Runtime.version().compareTo(Version.parse("12")) < 0) { return "TLSv1.2"; + } else { + Version full = AccessController.doPrivileged( + (PrivilegedAction) () -> Version.parse(System.getProperty("java.version")) + ); + if (full.compareTo(Version.parse("12.0.1")) < 0) { + return "TLSv1.2"; + } } } return "TLS"; diff --git a/plugins/discovery-ec2/build.gradle b/plugins/discovery-ec2/build.gradle index 7a7eb8da24fb6..92e0a81343ba3 100644 --- a/plugins/discovery-ec2/build.gradle +++ b/plugins/discovery-ec2/build.gradle @@ -32,6 +32,7 @@ import org.opensearch.gradle.info.BuildParams apply plugin: 'opensearch.yaml-rest-test' apply plugin: 'opensearch.internal-cluster-test' +apply from: "$rootDir/gradle/fips.gradle" opensearchplugin { description = 'The EC2 discovery plugin allows to use AWS API for the unicast discovery mechanism.' @@ -74,6 +75,7 @@ dependencies { api "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" api "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" api "org.reactivestreams:reactive-streams:${versions.reactivestreams}" + testFipsRuntimeOnly libs.bundles.bouncycastle } restResources { diff --git a/plugins/discovery-ec2/qa/amazon-ec2/build.gradle b/plugins/discovery-ec2/qa/amazon-ec2/build.gradle index 41c423c57ba36..1ff2a5d7d0c85 100644 --- a/plugins/discovery-ec2/qa/amazon-ec2/build.gradle +++ b/plugins/discovery-ec2/qa/amazon-ec2/build.gradle @@ -38,9 +38,11 @@ import org.opensearch.gradle.test.rest.YamlRestTestPlugin import static org.opensearch.gradle.PropertyNormalization.IGNORE_VALUE apply plugin: 'opensearch.yaml-rest-test' +apply from: "$rootDir/gradle/fips.gradle" dependencies { yamlRestTestImplementation project(':plugins:discovery-ec2') + testFipsRuntimeOnly libs.bundles.bouncycastle } restResources { diff --git a/plugins/discovery-ec2/qa/amazon-ec2/src/yamlRestTest/java/org/opensearch/discovery/ec2/AmazonEC2Fixture.java b/plugins/discovery-ec2/qa/amazon-ec2/src/yamlRestTest/java/org/opensearch/discovery/ec2/AmazonEC2Fixture.java index 8dc9db69b674f..696ae8d9ceade 100644 --- a/plugins/discovery-ec2/qa/amazon-ec2/src/yamlRestTest/java/org/opensearch/discovery/ec2/AmazonEC2Fixture.java +++ b/plugins/discovery-ec2/qa/amazon-ec2/src/yamlRestTest/java/org/opensearch/discovery/ec2/AmazonEC2Fixture.java @@ -145,7 +145,7 @@ protected Response handle(final Request request) throws IOException { + "test" + "\"," + "\"SecretAccessKey\": \"" - + "test" + + "ec2_integration_test_secret_key" + "\"," + "\"Token\": \"" + "test" diff --git a/plugins/discovery-gce/build.gradle b/plugins/discovery-gce/build.gradle index a9338bfc43a2c..181cb40054f59 100644 --- a/plugins/discovery-gce/build.gradle +++ b/plugins/discovery-gce/build.gradle @@ -11,6 +11,7 @@ apply plugin: 'opensearch.yaml-rest-test' apply plugin: 'opensearch.internal-cluster-test' +apply from: "$rootDir/gradle/fips.gradle" opensearchplugin { description = 'The Google Compute Engine (GCE) Discovery plugin allows to use GCE API for the unicast discovery mechanism.' @@ -34,6 +35,7 @@ dependencies { api 'io.opencensus:opencensus-api:0.31.1' api 'io.opencensus:opencensus-contrib-http-util:0.31.1' runtimeOnly "com.google.guava:guava:${versions.guava}" + testFipsRuntimeOnly libs.bundles.bouncycastle } restResources { diff --git a/plugins/discovery-gce/src/main/java/org/opensearch/cloud/gce/GceInstancesServiceImpl.java b/plugins/discovery-gce/src/main/java/org/opensearch/cloud/gce/GceInstancesServiceImpl.java index 46cc1c8eab537..3aada1f700876 100644 --- a/plugins/discovery-gce/src/main/java/org/opensearch/cloud/gce/GceInstancesServiceImpl.java +++ b/plugins/discovery-gce/src/main/java/org/opensearch/cloud/gce/GceInstancesServiceImpl.java @@ -58,7 +58,9 @@ import org.opensearch.discovery.gce.RetryHttpInitializerWrapper; import java.io.IOException; +import java.io.InputStream; import java.security.GeneralSecurityException; +import java.security.KeyStore; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -82,6 +84,17 @@ public class GceInstancesServiceImpl implements GceInstancesService { Property.NodeScope ); + private static final Supplier inFipsMode = () -> { + try { + // Equivalent to: boolean approvedOnly = CryptoServicesRegistrar.isInApprovedOnlyMode() + var registrarClass = Class.forName("org.bouncycastle.crypto.CryptoServicesRegistrar"); + var isApprovedOnlyMethod = registrarClass.getMethod("isInApprovedOnlyMode"); + return (Boolean) isApprovedOnlyMethod.invoke(null); + } catch (ReflectiveOperationException e) { + return false; + } + }; + private final String project; private final List zones; @@ -191,7 +204,16 @@ private static boolean headerContainsMetadataFlavor(HttpResponse response) { protected synchronized HttpTransport getGceHttpTransport() throws GeneralSecurityException, IOException { if (gceHttpTransport == null) { if (validateCerts) { - gceHttpTransport = GoogleNetHttpTransport.newTrustedTransport(); + if (inFipsMode.get()) { + KeyStore trustStore = KeyStore.getInstance("BCFKS"); + try (InputStream in = getClass().getResourceAsStream("/google.bcfks")) { + trustStore.load(in, "notasecret".toCharArray()); + } + gceHttpTransport = new NetHttpTransport.Builder().trustCertificates(trustStore).build(); + } else { + gceHttpTransport = GoogleNetHttpTransport.newTrustedTransport(); + ; + } } else { // this is only used for testing - alternative we could use the defaul keystore but this requires special configs too.. gceHttpTransport = new NetHttpTransport.Builder().doNotValidateCertificate().build(); diff --git a/plugins/discovery-gce/src/main/resources/google.bcfks b/plugins/discovery-gce/src/main/resources/google.bcfks new file mode 100644 index 0000000000000..13202c5c21eb3 Binary files /dev/null and b/plugins/discovery-gce/src/main/resources/google.bcfks differ diff --git a/plugins/ingest-attachment/build.gradle b/plugins/ingest-attachment/build.gradle index f6a5f104cac79..9869dc259c892 100644 --- a/plugins/ingest-attachment/build.gradle +++ b/plugins/ingest-attachment/build.gradle @@ -142,12 +142,3 @@ thirdPartyAudit { 'com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper$1' ) } - -if (BuildParams.inFipsJvm) { - // FIPS JVM includes many classes from bouncycastle which count as jar hell for the third party audit, - // rather than provide a long list of exclusions, disable the check on FIPS. - jarHell.enabled = false - test.enabled = false - yamlRestTest.enabled = false; - testingConventions.enabled = false; -} diff --git a/plugins/ingestion-kinesis/build.gradle b/plugins/ingestion-kinesis/build.gradle index a8100018c7f4a..4ef6d2b36e840 100644 --- a/plugins/ingestion-kinesis/build.gradle +++ b/plugins/ingestion-kinesis/build.gradle @@ -10,6 +10,7 @@ */ apply plugin: 'opensearch.internal-cluster-test' +apply from: "$rootDir/gradle/fips.gradle" opensearchplugin { description = 'Pull-based ingestion plugin to consume from Kinesis' @@ -93,6 +94,7 @@ dependencies { testImplementation "org.apache.commons:commons-compress:${versions.commonscompress}" testImplementation "commons-io:commons-io:${versions.commonsio}" testImplementation 'org.awaitility:awaitility:4.2.0' + testFipsRuntimeOnly libs.bundles.bouncycastle } internalClusterTest{ diff --git a/plugins/ingestion-kinesis/src/internalClusterTest/java/org/opensearch/plugin/kinesis/KinesisIngestionBaseIT.java b/plugins/ingestion-kinesis/src/internalClusterTest/java/org/opensearch/plugin/kinesis/KinesisIngestionBaseIT.java index 1d2b9cc00b867..3a6680330ebca 100644 --- a/plugins/ingestion-kinesis/src/internalClusterTest/java/org/opensearch/plugin/kinesis/KinesisIngestionBaseIT.java +++ b/plugins/ingestion-kinesis/src/internalClusterTest/java/org/opensearch/plugin/kinesis/KinesisIngestionBaseIT.java @@ -9,6 +9,7 @@ package org.opensearch.plugin.kinesis; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters; +import com.carrotsearch.randomizedtesting.generators.RandomStrings; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; @@ -30,10 +31,13 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import org.testcontainers.containers.localstack.LocalStackContainer; import org.testcontainers.utility.DockerImageName; +import static com.carrotsearch.randomizedtesting.RandomizedTest.getRandom; + /** * Base test class for kinesis ingestion tests */ @@ -62,10 +66,13 @@ protected void cleanup() { stopKinesis(); } + Supplier passwordSupplier = () -> RandomStrings.randomAsciiLettersOfLengthBetween(getRandom(), 16, 24); + private void setupKinesis() throws InterruptedException { - localstack = new LocalStackContainer(DockerImageName.parse("localstack/localstack:latest")).withServices( - LocalStackContainer.Service.KINESIS - ); + localstack = new LocalStackContainer(DockerImageName.parse("localstack/localstack:latest")).withEnv( + "AWS_ACCESS_KEY_ID", + passwordSupplier.get() + ).withEnv("AWS_SECRET_ACCESS_KEY", passwordSupplier.get()).withServices(LocalStackContainer.Service.KINESIS); localstack.start(); // Initialize AWS Kinesis Client with LocalStack endpoint diff --git a/plugins/repository-azure/build.gradle b/plugins/repository-azure/build.gradle index db4e06e8d7ab2..2fc484e2f4e55 100644 --- a/plugins/repository-azure/build.gradle +++ b/plugins/repository-azure/build.gradle @@ -330,6 +330,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 038d46a53cc67..cce066b068d19 100644 --- a/plugins/repository-gcs/build.gradle +++ b/plugins/repository-gcs/build.gradle @@ -41,6 +41,7 @@ import static org.opensearch.gradle.PropertyNormalization.IGNORE_VALUE */ apply plugin: 'opensearch.yaml-rest-test' apply plugin: 'opensearch.internal-cluster-test' +apply from: "$rootDir/gradle/fips.gradle" opensearchplugin { description = 'The GCS repository plugin adds Google Cloud Storage support for repositories.' @@ -86,6 +87,7 @@ dependencies { api 'io.opencensus:opencensus-api:0.31.1' api 'io.opencensus:opencensus-contrib-http-util:0.31.1' + testFipsRuntimeOnly libs.bundles.bouncycastle testImplementation project(':test:fixtures:gcs-fixture') } 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..4a15c8e61f49a 100644 --- a/plugins/repository-gcs/src/main/java/org/opensearch/repositories/gcs/GoogleCloudStorageService.java +++ b/plugins/repository-gcs/src/main/java/org/opensearch/repositories/gcs/GoogleCloudStorageService.java @@ -36,6 +36,7 @@ import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.util.SecurityUtils; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.ServiceOptions; @@ -50,10 +51,13 @@ 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; import java.net.URI; +import java.security.KeyStore; +import java.security.Security; import java.util.Map; import static java.util.Collections.emptyMap; @@ -185,9 +189,18 @@ 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()); + KeyStore certTrustStore; + if (Security.getProvider("BCFIPS") != null) { + certTrustStore = KeyStore.getInstance("BCFKS"); + InputStream keyStoreStream = getClass().getResourceAsStream("/google.bcfks"); + SecurityUtils.loadKeyStore(certTrustStore, keyStoreStream, "notasecret"); + } else { + // requires java.lang.RuntimePermission "setFactory" + // Pin the TLS trust certificates. + certTrustStore = GoogleUtils.getCertificateTrustStore(); + } + + 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..39e0f6be9ec80 --- /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. +# + +```bash +keytool -importkeystore -noprompt \ + -srckeystore $KEY_STORE_PATH/google.p12 \ + -srcstoretype PKCS12 \ + -srcstorepass notasecret \ + -destkeystore google.bcfks \ + -deststoretype BCFKS \ + -deststorepass notasecret \ + -providername BCFIPS \ + -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ + -providerpath $LIB_PATH/bc-fips-2.0.0.jar +``` diff --git a/plugins/repository-gcs/src/main/resources/google.bcfks b/plugins/repository-gcs/src/main/resources/google.bcfks new file mode 100644 index 0000000000000..13202c5c21eb3 Binary files /dev/null and b/plugins/repository-gcs/src/main/resources/google.bcfks differ diff --git a/plugins/repository-s3/build.gradle b/plugins/repository-s3/build.gradle index b1a83565f0e87..1ad25361f1cbf 100644 --- a/plugins/repository-s3/build.gradle +++ b/plugins/repository-s3/build.gradle @@ -39,6 +39,7 @@ import static org.opensearch.gradle.PropertyNormalization.IGNORE_VALUE apply plugin: 'opensearch.yaml-rest-test' apply plugin: 'opensearch.internal-cluster-test' +apply from: "$rootDir/gradle/fips.gradle" opensearchplugin { description = 'The S3 repository plugin adds S3 repositories' @@ -110,6 +111,7 @@ dependencies { api 'javax.xml.bind:jaxb-api:2.3.1' testImplementation project(':test:fixtures:s3-fixture') + testFipsRuntimeOnly libs.bundles.bouncycastle } restResources { 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 d54abb413c6fd..b33be14e596a6 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 @@ -147,7 +147,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 fa9e5580bdfa5..052d9a2a31913 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 @@ -359,6 +359,7 @@ private static SSLConnectionSocketFactory createSocksSslConnectionSocketFactory( try { final SSLContext sslCtx = SSLContext.getInstance("TLS"); sslCtx.init(SystemPropertyTlsKeyManagersProvider.create().keyManagers(), null, new SecureRandom()); + 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 4193609ac520d..06f0089a1cc5d 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 @@ -225,7 +225,10 @@ protected AsyncMultiStreamBlobContainer createBlobContainer( final MockSecureSettings secureSettings = new MockSecureSettings(); secureSettings.setString(S3ClientSettings.ACCESS_KEY_SETTING.getConcreteSettingForNamespace(clientName).getKey(), "access"); - secureSettings.setString(S3ClientSettings.SECRET_KEY_SETTING.getConcreteSettingForNamespace(clientName).getKey(), "secret"); + secureSettings.setString( + S3ClientSettings.SECRET_KEY_SETTING.getConcreteSettingForNamespace(clientName).getKey(), + "secret_password" + ); clientSettings.setSecureSettings(secureSettings); service.refreshAndClearCache(S3ClientSettings.load(clientSettings.build(), configPath())); asyncService.refreshAndClearCache(S3ClientSettings.load(clientSettings.build(), configPath())); diff --git a/plugins/transport-reactor-netty4/build.gradle b/plugins/transport-reactor-netty4/build.gradle index 6aa9aa1019753..33720a54b9a21 100644 --- a/plugins/transport-reactor-netty4/build.gradle +++ b/plugins/transport-reactor-netty4/build.gradle @@ -38,9 +38,7 @@ dependencies { testImplementation libs.log4jslf4jimpl javaRestTestImplementation libs.reactor.test testImplementation project(":modules:transport-netty4") - testFipsRuntimeOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" - testFipsRuntimeOnly "org.bouncycastle:bcpkix-fips:${versions.bouncycastle_pkix}" - testFipsRuntimeOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" + testFipsRuntimeOnly libs.bundles.bouncycastle } restResources { 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..2100edbca6900 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 @@ -62,6 +62,7 @@ import java.security.cert.X509Certificate; import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -79,6 +80,18 @@ public static void waitForOpenSearch(Installation installation) throws Exception waitForOpenSearch("green", null, installation, null, null); } + private static final Supplier inFipsMode = () -> { + try { + // Equivalent to: boolean approvedOnly = CryptoServicesRegistrar.isInApprovedOnlyMode() + var registrarClass = Class.forName("org.bouncycastle.crypto.CryptoServicesRegistrar"); + var isApprovedOnlyMethod = registrarClass.getMethod("isInApprovedOnlyMode"); + return (Boolean) isApprovedOnlyMethod.invoke(null); + } catch (ReflectiveOperationException e) { + return false; + } + }; + private static final String STORE_TYPE = inFipsMode.get() ? "BCFKS" : KeyStore.getDefaultType(); + /** * Executes the supplied request, optionally applying HTTP basic auth if the * username and pasword field are supplied. @@ -95,7 +108,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 = KeyStore.getInstance(STORE_TYPE); truststore.load(null, null); truststore.setCertificateEntry("myClusterCA", cert); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); diff --git a/qa/wildfly/build.gradle b/qa/wildfly/build.gradle index d3efb1cdf2eaf..1868edce65dd4 100644 --- a/qa/wildfly/build.gradle +++ b/qa/wildfly/build.gradle @@ -37,6 +37,7 @@ apply plugin: 'war' apply plugin: 'opensearch.build' apply plugin: 'opensearch.test.fixtures' apply plugin: 'opensearch.internal-distribution-download' +apply from: "$rootDir/gradle/fips.gradle" testFixtures.useFixture() @@ -66,6 +67,7 @@ dependencies { testImplementation(project(':test:framework')) { exclude module: 'jakarta.annotation-api' } + testFipsRuntimeOnly libs.bundles.bouncycastle } war { diff --git a/rest-api-spec/build.gradle b/rest-api-spec/build.gradle index 535749a5e0c14..a66a199718de3 100644 --- a/rest-api-spec/build.gradle +++ b/rest-api-spec/build.gradle @@ -1,3 +1,5 @@ +import org.opensearch.gradle.info.BuildParams + /* * SPDX-License-Identifier: Apache-2.0 * @@ -32,3 +34,10 @@ testClusters.all { test.enabled = false jarHell.enabled = false + +yamlRestTest { + if (BuildParams.inFipsJvm) { + systemProperty 'tests.rest.denylist', ['nodes.reload_secure_settings/11_no_password/*'] + .join(",") + } +} 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 c74803f683bbc..311bacda00fdc 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 @@ -19,16 +19,3 @@ setup: - match: { nodes.$node_id.reload_exception.reason: /^(Provided\skeystore\spassword\swas\sincorrect| Keystore\shas\sbeen\scorrupted\sor\stampered\swith)$/ } - ---- -"node_reload_secure_settings test correct(empty) password": - - - do: - nodes.reload_secure_settings: {} - - - set: - nodes._arbitrary_key_: node_id - - - is_true: nodes - - is_true: cluster_name - - is_false: nodes.$node_id.reload_exception diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/11_no_password.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/11_no_password.yml new file mode 100644 index 0000000000000..ee8b9f50276c1 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/nodes.reload_secure_settings/11_no_password.yml @@ -0,0 +1,14 @@ +setup: + - skip: + features: [arbitrary_key] +--- +"node_reload_secure_settings test correct(empty) password": + - do: + nodes.reload_secure_settings: {} + + - set: + nodes._arbitrary_key_: node_id + + - is_true: nodes + - is_true: cluster_name + - is_false: nodes.$node_id.reload_exception diff --git a/server/build.gradle b/server/build.gradle index 803d791295e71..24230247da9a7 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -125,6 +125,8 @@ dependencies { exclude group: 'org.opensearch', module: 'server' } testFipsRuntimeOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" + testFipsRuntimeOnly "org.bouncycastle:bctls-fips:${versions.bouncycastle_tls}" + testFipsRuntimeOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" } tasks.withType(JavaCompile).configureEach { 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 0da5e335b0081..9908e3f939aff 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/opensearch/bootstrap/Bootstrap.java @@ -200,6 +200,7 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot 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.removeNonCompliantFipsProviders(); + MultiProviderTrustStoreHandler.configureTrustStore(environment.tmpDir(), Path.of(System.getProperty("java.home"))); } // initialize probes before the security manager is installed diff --git a/server/src/main/java/org/opensearch/bootstrap/MultiProviderTrustStoreHandler.java b/server/src/main/java/org/opensearch/bootstrap/MultiProviderTrustStoreHandler.java new file mode 100644 index 0000000000000..75b0861e66651 --- /dev/null +++ b/server/src/main/java/org/opensearch/bootstrap/MultiProviderTrustStoreHandler.java @@ -0,0 +1,279 @@ +/* + * 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.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.common.SuppressForbidden; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.AccessController; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivilegedAction; +import java.security.Provider; +import java.security.Security; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +/** + * This class is responsible for handling and configuring trust stores from multiple providers. + * It facilitates the interception of trust store configurations, conversion of default + * system trust stores into desired formats, and provides utilities to manage the trust store + * configuration in runtime environments. + * The class supports handling PKCS#11 and BCFKS trust store types. If a custom trust store is + * not specified, it intercepts the default "cacerts" trust store and converts it to an alternative + * format if applicable. It uses security providers to dynamically determine and configure appropriate + * KeyStore services. + */ +public class MultiProviderTrustStoreHandler { + + private static final Logger logger = LogManager.getLogger(MultiProviderTrustStoreHandler.class); + private static final String TEMP_TRUSTSTORE_PREFIX = "converted-truststore-"; + private static final String TEMP_TRUSTSTORE_SUFFIX = ".bcfks"; + private static final String TRUST_STORE_PASSWORD = Security.getProperty("javax.net.ssl.trustStorePassword"); + private static final String JVM_DEFAULT_PASSWORD = Objects.requireNonNullElse(TRUST_STORE_PASSWORD, "changeit"); + private static final String SYSTEMSTORE_PASSWORD = Objects.requireNonNullElse(TRUST_STORE_PASSWORD, ""); + private static final String PKCS_11 = "PKCS11"; + private static final String BCFKS = "BCFKS"; + private static final String BCFIPS = "BCFIPS"; + private static final List KNOWN_JDK_TRUSTSTORE_TYPES = List.of("PKCS12", "JKS"); + + /** + * Encapsulates properties related to a TrustStore configuration. Is primarily used to manage and + * log details of TrustStore configurations within the system. + */ + protected record TrustStoreProperties(String trustStorePath, String trustStoreType, String trustStorePassword, + String trustStoreProvider) { + void logout() { + var detailLog = new StringWriter(); + var writer = new PrintWriter(detailLog); + var passwordSetStatus = trustStorePassword.isEmpty() ? "[NOT SET]" : "[SET]"; + + writer.printf(Locale.ROOT, "\njavax.net.ssl.trustStore: " + trustStorePath); + writer.printf(Locale.ROOT, "\njavax.net.ssl.trustStoreType: " + trustStoreType); + writer.printf(Locale.ROOT, "\njavax.net.ssl.trustStoreProvider: " + trustStoreProvider); + writer.printf(Locale.ROOT, "\njavax.net.ssl.trustStorePassword: " + passwordSetStatus); + writer.flush(); + + logger.info("\nChanged TrustStore configuration: {}", detailLog); + } + } + + /** + * Configures the Java TrustStore by setting up appropriate truststore properties. + * If a custom TrustStore path is provided, it will use it directly; + * otherwise, it will dynamically configure a TrustStore using a suitable provider. + * + * @param tmpDir the directory to store temporary TrustStore files + * @param javaHome the path to the Java Home, used for accessing default system TrustStore + */ + public static void configureTrustStore(Path tmpDir, Path javaHome) { + var existingTrustStorePath = existingTrustStorePath(); + if (existingTrustStorePath != null) { + logger.info("Custom truststore already specified: {}", existingTrustStorePath); + return; + } + + logger.info("No custom truststore specified - intercepting default cacerts usage"); + var pkcs11ProviderService = findPKCS11ProviderService(); + var properties = pkcs11ProviderService.map(MultiProviderTrustStoreHandler::configurePKCS11TrustStore) + .orElseGet(() -> MultiProviderTrustStoreHandler.configureBCFKSTrustStore(javaHome, tmpDir)); + + setProperties(properties); + logger.info("Converted system truststore to {} format", properties.trustStoreProvider); + properties.logout(); + printCurrentConfiguration(Level.TRACE); + } + + @SuppressForbidden(reason = "check system properties for TrustStore configuration") + private static Path existingTrustStorePath() { + var property = Optional.ofNullable(System.getProperty("javax.net.ssl.trustStore")); + if (property.isPresent() && Path.of(property.get()).toFile().exists()) { + return Path.of(property.get()); + } + return null; + } + + @SuppressForbidden(reason = "sets system properties for TrustStore configuration") + protected static void setProperties(TrustStoreProperties properties) { + System.setProperty("javax.net.ssl.trustStore", properties.trustStorePath()); + System.setProperty("javax.net.ssl.trustStorePassword", properties.trustStorePassword()); + System.setProperty("javax.net.ssl.trustStoreType", properties.trustStoreType()); + System.setProperty("javax.net.ssl.trustStoreProvider", properties.trustStoreProvider()); + } + + private static TrustStoreProperties configurePKCS11TrustStore(Provider.Service pkcs11ProviderService) { + logger.info("Configuring PKCS11 truststore..."); + return new TrustStoreProperties("NONE", PKCS_11, SYSTEMSTORE_PASSWORD, pkcs11ProviderService.getProvider().getName()); + } + + private static TrustStoreProperties configureBCFKSTrustStore(Path javaHome, Path tmpDir) { + logger.info("No PKCS11 provider found - converting system truststore to BCFKS..."); + var systemTrustStore = loadSystemDefaultTrustStore(javaHome); + var bcfksTrustStore = convertToBCFKS(systemTrustStore, tmpDir); + return new TrustStoreProperties(bcfksTrustStore.toAbsolutePath().toString(), BCFKS, JVM_DEFAULT_PASSWORD, BCFIPS); + } + + public static Optional findPKCS11ProviderService() { + return Arrays.stream(Security.getProviders()) + .filter(it -> it.getName().toUpperCase(Locale.ROOT).contains(PKCS_11)) + .map(it -> it.getService("KeyStore", PKCS_11)) + .filter(Objects::nonNull) + .findFirst(); + } + + private static KeyStore loadSystemDefaultTrustStore(Path javaHome) { + var cacertsPath = javaHome.resolve("lib").resolve("security").resolve("cacerts"); + if (!Files.exists(cacertsPath) && Files.isReadable(cacertsPath)) { + throw new IllegalStateException("System cacerts not found at: " + cacertsPath); + } + + logger.info("Loading system truststore from: {}", cacertsPath); + + KeyStore systemKs = null; + for (var type : KNOWN_JDK_TRUSTSTORE_TYPES) { + try { + systemKs = KeyStore.getInstance(type); + try (var is = readFileWithPriviledges(cacertsPath)) { + systemKs.load(is, JVM_DEFAULT_PASSWORD.toCharArray()); + } + int certCount = systemKs.size(); + logger.info("Loaded {} certificates from system truststore", certCount); + logger.info("Successfully loaded cacerts as {} format", type); + break; + } catch (Exception e) { + logger.info("Failed to load cacerts as {}: {}", type, e.getMessage()); + // continue + } + } + + if (systemKs == null) { + throw new IllegalStateException( + "Could not load system cacerts in any known format" + + KNOWN_JDK_TRUSTSTORE_TYPES.stream().collect(Collectors.joining(", ", "[", "]")) + ); + } + + return systemKs; + } + + @SuppressWarnings("removal") + private static InputStream readFileWithPriviledges(Path path) { + return AccessController.doPrivileged((PrivilegedAction) () -> { + try { + return Files.newInputStream(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + /** + * Creates a temporary BCFKS formatted trustStore + */ + private static Path convertToBCFKS(KeyStore sourceKeyStore, Path tmpDir) { + Path tempBcfksFile; + try { + tempBcfksFile = Files.createTempFile(tmpDir, TEMP_TRUSTSTORE_PREFIX, TEMP_TRUSTSTORE_SUFFIX); + } catch (IOException ex) { + throw new IllegalStateException("Could not create temporary truststore file", ex); + } + + // Register for deletion on JVM exit + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + Files.deleteIfExists(tempBcfksFile); + } catch (IOException e) { + logger.warn("Failed to delete temporary file: {}", e.getMessage()); + } + })); + + logger.info("Converting to BCFKS format: {}", tempBcfksFile.toAbsolutePath()); + + // Create new BCFKS keystore + int copiedCount = 0; + KeyStore bcfksKeyStore; + try { + bcfksKeyStore = KeyStore.getInstance(BCFKS, BCFIPS); + bcfksKeyStore.load(null, null); + + // Copy all certificates from source to BCFKS + Enumeration aliases = sourceKeyStore.aliases(); + + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + + if (sourceKeyStore.isCertificateEntry(alias)) { + Certificate cert = sourceKeyStore.getCertificate(alias); + if (cert != null) { + try { + bcfksKeyStore.setCertificateEntry(alias, cert); + copiedCount++; + } catch (Exception e) { + logger.warn("Failed to copy certificate '{}': {}", alias, e.getMessage()); + // Continue with other certificates + } + } + } + } + } catch (GeneralSecurityException | IOException e) { + throw new SecurityException(e); + } + + // Save BCFKS keystore to file using NIO.2 + try (var file = Files.newOutputStream(tempBcfksFile)) { + bcfksKeyStore.store(file, JVM_DEFAULT_PASSWORD.toCharArray()); + logger.info("Successfully converted {} certificates to BCFKS format", copiedCount); + } catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) { + throw new IllegalStateException("Failed to write BCFKS keystore", e); + } + + return tempBcfksFile; + } + + /** + * Utility method to check the current configuration. + */ + public static void printCurrentConfiguration(Level logLevel) { + if (logger.isEnabled(logLevel)) { + var detailLog = new StringWriter(); + var writer = new PrintWriter(detailLog); + var counter = new AtomicInteger(); + Arrays.stream(Security.getProviders()) + .peek( + provider -> writer.printf( + Locale.ROOT, + " %d. %s (version %s)\n".formatted(counter.incrementAndGet(), provider.getName(), provider.getVersionStr()) + ) + ) + .flatMap(provider -> provider.getServices().stream().filter(service -> "KeyStore".equals(service.getType()))) + .forEach(service -> writer.printf(Locale.ROOT, "\t\tKeyStore.%s\n".formatted(service.getAlgorithm()))); + + writer.flush(); + logger.log(logLevel, "\nAvailable Security Providers: \n{}", detailLog); + } + } +} 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 d5ec37516a678..b464ee8e62a48 100644 --- a/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java +++ b/server/src/main/java/org/opensearch/common/settings/KeyStoreWrapper.java @@ -84,6 +84,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import java.util.regex.Pattern; /** @@ -448,6 +449,10 @@ private byte[] encrypt(char[] password, byte[] salt, byte[] iv) throws GeneralSe } private void decryptLegacyEntries() throws GeneralSecurityException, IOException { + if (inFipsMode.get()) { + throw new SecurityException("Legacy KeyStore formats v1 & v2 are not supported in FIPS JVM"); + } + // v1 and v2 keystores never had passwords actually used, so we always use an empty password KeyStore keystore = KeyStore.getInstance("PKCS12", "SUN"); Map settingTypes = new HashMap<>(); @@ -575,6 +580,17 @@ public synchronized void save(Path configDir, char[] password) throws Exception } } + private static final Supplier inFipsMode = () -> { + try { + // Equivalent to: boolean approvedOnly = CryptoServicesRegistrar.isInApprovedOnlyMode() + var registrarClass = Class.forName("org.bouncycastle.crypto.CryptoServicesRegistrar"); + var isApprovedOnlyMethod = registrarClass.getMethod("isInApprovedOnlyMode"); + return (Boolean) isApprovedOnlyMethod.invoke(null); + } catch (ReflectiveOperationException e) { + return false; + } + }; + /** * It is possible to retrieve the setting names even if the keystore is closed. * This allows {@link SecureSetting} to correctly determine that a entry exists even though it cannot be read. Thus attempting to diff --git a/server/src/test/java/org/opensearch/bootstrap/MultiProviderTrustStoreHandlerTests.java b/server/src/test/java/org/opensearch/bootstrap/MultiProviderTrustStoreHandlerTests.java new file mode 100644 index 0000000000000..43a6b3f591b98 --- /dev/null +++ b/server/src/test/java/org/opensearch/bootstrap/MultiProviderTrustStoreHandlerTests.java @@ -0,0 +1,109 @@ +/* + * 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.common.SuppressForbidden; +import org.opensearch.fips.FipsMode; +import org.opensearch.test.OpenSearchTestCase; +import org.junit.Before; +import org.junit.BeforeClass; + +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import java.nio.file.Path; +import java.security.KeyStore; + +public class MultiProviderTrustStoreHandlerTests extends OpenSearchTestCase { + + private static final Path JAVA_HOME = Path.of(System.getProperty("java.home")); + + @BeforeClass + public static void beforeClass() throws Exception { + assumeTrue("Test should run in FIPS JVM", FipsMode.CHECK.isFipsEnabled()); + } + + @Before + @SuppressForbidden(reason = "clears system properties for clean setup") + public void setup() throws Exception { + System.clearProperty("javax.net.ssl.trustStore"); + System.clearProperty("javax.net.ssl.trustStorePassword"); + System.clearProperty("javax.net.ssl.trustStoreType"); + System.clearProperty("javax.net.ssl.trustStoreProvider"); + } + + @SuppressForbidden(reason = "set system properties for TrustStore configuration") + public void testPredefinedTrustStoreIsRespected() throws Exception { + // given + var trust = createTempFile("trust", ".bcfks"); + System.setProperty("javax.net.ssl.trustStore", trust.toString()); + + // when + MultiProviderTrustStoreHandler.configureTrustStore(createTempDir(), JAVA_HOME); + + // then + assertEquals(System.getProperty("javax.net.ssl.trustStore"), trust.toString()); + assertNull(System.getProperty("javax.net.ssl.trustStorePassword")); + assertNull(System.getProperty("javax.net.ssl.trustStoreType")); + assertNull(System.getProperty("javax.net.ssl.trustStoreProvider")); + } + + public void testPKCS11TrustStoreSetup() throws Exception { + // given + var pkcs11ProviderService = MultiProviderTrustStoreHandler.findPKCS11ProviderService(); + assumeTrue("Should only run when PKCS11 provider is installed.", pkcs11ProviderService.isPresent()); + var trustStoreProviderName = pkcs11ProviderService.get().getProvider().getName(); + + // when + MultiProviderTrustStoreHandler.configureTrustStore(createTempDir(), JAVA_HOME); + + // then + assertEquals("NONE", System.getProperty("javax.net.ssl.trustStore")); + assertEquals("", System.getProperty("javax.net.ssl.trustStorePassword")); + assertEquals("PKCS11", System.getProperty("javax.net.ssl.trustStoreType")); + assertEquals(trustStoreProviderName, System.getProperty("javax.net.ssl.trustStoreProvider")); + assertTrue(System.getProperty("javax.net.ssl.trustStorePassword").isEmpty()); + } + + public void testBCFKSTrustStoreCreation() throws Exception { + // given + var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); // Should use included truststore shipped by JVM + var totalDefaultCerts = ((X509TrustManager) tmf.getTrustManagers()[0]).getAcceptedIssuers(); + assumeTrue("JVMs truststore cannot be empty", totalDefaultCerts.length > 0); + var pkcs11ProviderService = MultiProviderTrustStoreHandler.findPKCS11ProviderService(); + assumeFalse( + "Should only run when NO PKCS11 provider is installed, otherwise it would be prioritised.", + pkcs11ProviderService.isPresent() + ); + var tmpDir = createTempDir(); + + // when + MultiProviderTrustStoreHandler.configureTrustStore(tmpDir, JAVA_HOME); + + // then + assertTrue(System.getProperty("javax.net.ssl.trustStore").endsWith(".bcfks")); + assertEquals("changeit", System.getProperty("javax.net.ssl.trustStorePassword")); + assertEquals("BCFKS", System.getProperty("javax.net.ssl.trustStoreType")); + assertEquals("BCFIPS", System.getProperty("javax.net.ssl.trustStoreProvider")); + + tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); // Should use configured truststore + var trustManagers = tmf.getTrustManagers(); + assertTrue(trustManagers.length > 0); + assertTrue(trustManagers[0] instanceof X509TrustManager); + + var totalMigratedCerts = ((X509TrustManager) trustManagers[0]).getAcceptedIssuers(); + assertEquals( + "number of accepted totalMigratedCerts matches the JVMs default truststore", + totalDefaultCerts.length, + totalMigratedCerts.length + ); + } +} diff --git a/server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java b/server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java index 90eefcfc38f44..815c5e6a92ddb 100644 --- a/server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java +++ b/server/src/test/java/org/opensearch/bootstrap/SecurityProviderManagerTests.java @@ -35,18 +35,16 @@ public class SecurityProviderManagerTests extends OpenSearchTestCase { @Override public void setUp() throws Exception { super.setUp(); - if (Arrays.stream(Security.getProviders()).noneMatch(provider -> SUN_JCE.equals(provider.getName()))) { - var sunJceClass = Class.forName("com.sun.crypto.provider.SunJCE"); - var originalSunProvider = (Provider) sunJceClass.getConstructor().newInstance(); - Security.addProvider(originalSunProvider); - } + addSunJceProvider(); } @AfterClass // restore the same state as before running the tests. - public static void removeSunJCE() { + public static void afterClass() throws Exception { if (inFipsJvm()) { SecurityProviderManager.removeNonCompliantFipsProviders(); + } else { + addSunJceProvider(); } } @@ -146,4 +144,12 @@ public void testGetPosition() { assertTrue(SUN_JCE + " is uninstalled", SecurityProviderManager.getPosition(SUN_JCE) < 0); } + private static void addSunJceProvider() throws Exception { + if (Arrays.stream(Security.getProviders()).noneMatch(provider -> SUN_JCE.equals(provider.getName()))) { + var sunJceClass = Class.forName("com.sun.crypto.provider.SunJCE"); + var originalSunProvider = (Provider) sunJceClass.getConstructor().newInstance(); + Security.addProvider(originalSunProvider); + } + } + } 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 de90ba90729d0..962d355e6709f 100644 --- a/test/framework/build.gradle +++ b/test/framework/build.gradle @@ -51,9 +51,7 @@ dependencies { api "org.mockito:mockito-core:${versions.mockito}" api "net.bytebuddy:byte-buddy:${versions.bytebuddy}" api "org.objenesis:objenesis:${versions.objenesis}" - fipsOnly "org.bouncycastle:bc-fips:${versions.bouncycastle_jce}" - fipsOnly "org.bouncycastle:bcpkix-fips:${versions.bouncycastle_pkix}" - fipsOnly "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" + api libs.bundles.bouncycastle compileOnly project(":libs:agent-sm:bootstrap") compileOnly "com.github.spotbugs:spotbugs-annotations:4.9.4" 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 36df28aae4e34..f54e48bd1fa3c 100644 --- a/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java +++ b/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java @@ -138,6 +138,7 @@ public class BootstrapForTesting { IfConfig.logIfNecessary(); if (FipsMode.CHECK.isFipsEnabled()) { SecurityProviderManager.removeNonCompliantFipsProviders(); + MultiProviderTrustStoreHandler.configureTrustStore(javaTmpDir, Path.of(System.getProperty("java.home"))); } // install security manager if requested diff --git a/test/framework/src/main/java/org/opensearch/fips/FipsConfig.java b/test/framework/src/main/java/org/opensearch/fips/FipsConfig.java new file mode 100644 index 0000000000000..f63d350f64867 --- /dev/null +++ b/test/framework/src/main/java/org/opensearch/fips/FipsConfig.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.fips; + +/** + * This configuration determines the keystore type, file extension, JCA (Java Cryptography Architecture) + * provider, and JSSE (Java Secure Socket Extension) provider based on whether a FIPS-compliant mode is enabled. + */ +record FipsConfig(String keyStoreType, String fileExtension, String jcaProvider, String jsseProvider) { + static FipsConfig detect() { + boolean fips = FipsMode.CHECK.isFipsEnabled(); + return new FipsConfig( + fips ? "BCFKS" : "JKS", // + fips ? ".bcfks" : ".jks", // + fips ? "BCFIPS" : "SUN", // + fips ? "BCJSSE" : "SunJSSE" // + ); + } +} diff --git a/test/framework/src/main/java/org/opensearch/fips/KeyManagerFipsAwareTestCase.java b/test/framework/src/main/java/org/opensearch/fips/KeyManagerFipsAwareTestCase.java new file mode 100644 index 0000000000000..0ce632cc8a16f --- /dev/null +++ b/test/framework/src/main/java/org/opensearch/fips/KeyManagerFipsAwareTestCase.java @@ -0,0 +1,22 @@ +/* + * 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.fips; + +import javax.net.ssl.KeyManagerFactory; + +public interface KeyManagerFipsAwareTestCase { + + default KeyManagerFactory createKeyManagerFactory() { + var cfg = FipsConfig.detect(); + return createKeyManagerFactory(cfg.keyStoreType(), cfg.fileExtension(), cfg.jcaProvider(), cfg.jsseProvider()); + } + + KeyManagerFactory createKeyManagerFactory(String keyStoreType, String fileExtension, String jcaProvider, String jsseProvider); + +} diff --git a/test/framework/src/main/java/org/opensearch/fips/TrustManagerFipsAwareTestCase.java b/test/framework/src/main/java/org/opensearch/fips/TrustManagerFipsAwareTestCase.java new file mode 100644 index 0000000000000..3ed6134807614 --- /dev/null +++ b/test/framework/src/main/java/org/opensearch/fips/TrustManagerFipsAwareTestCase.java @@ -0,0 +1,22 @@ +/* + * 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.fips; + +import javax.net.ssl.TrustManagerFactory; + +public interface TrustManagerFipsAwareTestCase { + + default TrustManagerFactory createTrustManagerFactory() { + var cfg = FipsConfig.detect(); + return createTrustManagerFactory(cfg.keyStoreType(), cfg.fileExtension(), cfg.jcaProvider(), cfg.jsseProvider()); + } + + TrustManagerFactory createTrustManagerFactory(String keyStoreType, String fileExtension, String jcaProvider, String jsseProvider); + +} diff --git a/test/framework/src/main/java/org/opensearch/test/KeyStoreUtils.java b/test/framework/src/main/java/org/opensearch/test/KeyStoreUtils.java index c90b2b872f8ba..7de3cda292ac9 100644 --- a/test/framework/src/main/java/org/opensearch/test/KeyStoreUtils.java +++ b/test/framework/src/main/java/org/opensearch/test/KeyStoreUtils.java @@ -12,6 +12,7 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.opensearch.fips.FipsMode; import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500PrivateCredential; @@ -22,14 +23,37 @@ import java.security.KeyStore; import java.security.cert.X509Certificate; import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class KeyStoreUtils { public static final char[] KEYSTORE_PASSWORD = "keystore_password".toCharArray(); + 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("PKCS12", List.of(".p12", ".pkcs12", ".pfx")); + TYPE_TO_EXTENSION_MAP.put("BCFKS", List.of(".bcfks")); // Bouncy Castle FIPS Keystore + } + + /** + * Make a best guess about the "type" (see {@link KeyStore#getType()}) of the keystore file located at the given {@code Path}. + * This method only references the file name of the keystore, it does not look at its contents. + */ + public static String 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 KeyStore createServerKeyStore() throws Exception { var serverCred = createCredential(); - var keyStore = KeyStore.getInstance("JKS"); + var keyStore = KeyStore.getInstance(FipsMode.CHECK.isFipsEnabled() ? "BCFKS" : "JKS"); keyStore.load(null, null); keyStore.setKeyEntry( serverCred.getAlias(), 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 4e1d56e3951de..dfbc607b058a5 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 @@ -197,6 +197,9 @@ private ReproduceErrorMessageBuilder appendESProperties() { if (FipsMode.CHECK.isFipsEnabled()) { appendProperties("org.bouncycastle.fips.approved_only"); } + if (System.getProperty("java.security.properties") != null) { + appendProperties("java.security.properties"); + } 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..632de49e76faf 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 @@ -79,6 +79,7 @@ import org.opensearch.index.IndexSettings; import org.opensearch.index.seqno.ReplicationTracker; import org.opensearch.snapshots.SnapshotState; +import org.opensearch.test.KeyStoreUtils; import org.opensearch.test.OpenSearchTestCase; import org.opensearch.test.rest.yaml.ObjectPath; import org.hamcrest.Matchers; @@ -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 = KeyStore.getInstance(KeyStoreUtils.inferStoreType(keystorePath)); try (InputStream is = Files.newInputStream(path)) { keyStore.load(is, keystorePass.toCharArray()); }