diff --git a/build.gradle b/build.gradle index 502d0fc58f673..993f86bb9c529 100644 --- a/build.gradle +++ b/build.gradle @@ -465,8 +465,8 @@ gradle.projectsEvaluated { } } -// test retry configuration subprojects { + // test retry configuration tasks.withType(Test).configureEach { develocity.testRetry { if (BuildParams.isCi()) { @@ -552,6 +552,15 @@ subprojects { } } } + + // test with FIPS-140-2 enabled + plugins.withType(JavaPlugin).configureEach { + tasks.withType(Test).configureEach { testTask -> + if (System.getenv('OPENSEARCH_CRYPTO_STANDARD') == 'FIPS-140-2') { + testTask.jvmArgs += "-Dorg.bouncycastle.fips.approved_only=true" + } + } + } } // eclipse configuration diff --git a/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java index 3161343ee1148..cf2cad686f3ac 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java @@ -164,9 +164,10 @@ public void execute(Task t) { test.systemProperty("tests.seed", BuildParams.getTestSeed()); } + var securityFile = BuildParams.isInFipsJvm() ? "fips_java.security" : "java.security"; test.systemProperty( "java.security.properties", - project.getRootProject().getLayout().getProjectDirectory() + "/distribution/src/config/fips_java.security" + project.getRootProject().getLayout().getProjectDirectory() + "/distribution/src/config/" + securityFile ); // don't track these as inputs since they contain absolute paths and break cache relocatability diff --git a/distribution/src/config/fips_java.security b/distribution/src/config/fips_java.security index fc1608c8df1bd..bf160a0055a59 100644 --- a/distribution/src/config/fips_java.security +++ b/distribution/src/config/fips_java.security @@ -1,27 +1,34 @@ -# Security Properties for JDK 11 and higher, with BouncyCastle FIPS provider and BouncyCastleJsseProvider in FIPS mode +# Security Properties for JDK 11 and higher, with BouncyCastle FIPS provider and BouncyCastleJsseProvider in approved-only mode security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:HYBRID;ENABLE{All}; security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS security.provider.3=SUN security.provider.4=SunJGSS + securerandom.source=file:/dev/urandom securerandom.strongAlgorithms=NativePRNGBlocking:SUN,DRBG:SUN securerandom.drbg.config= + login.configuration.provider=sun.security.provider.ConfigFile policy.provider=sun.security.provider.PolicyFile policy.expandProperties=true policy.allowSystemProperty=true policy.ignoreIdentityScope=false keystore.type.compat=true + package.access=sun.misc.,\ sun.reflect. package.definition=sun.misc.,\ sun.reflect. + security.overridePropertiesFile=true + ssl.KeyManagerFactory.algorithm=PKIX ssl.TrustManagerFactory.algorithm=PKIX + networkaddress.cache.negative.ttl=10 krb5.kdc.bad.policy = tryLast + jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1, jdkCA&usageTLSServer, RSA keySize < 2048, DSA keySize < 2048, EC keySize < 224 jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 2048, DSA keySize < 2048 jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, MD5withRSA, DH keySize < 2048, EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC @@ -31,7 +38,9 @@ jdk.tls.legacyAlgorithms= \ RC4_128, RC4_40, DES_CBC, DES40_CBC, \ 3DES_EDE_CBC jdk.tls.keyLimits=AES/GCM/NoPadding KeyUpdate 2^37 + crypto.policy=unlimited + jdk.xml.dsig.secureValidationPolicy=\ disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\ disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\ @@ -45,5 +54,6 @@ jdk.xml.dsig.secureValidationPolicy=\ minKeySize EC 224,\ noDuplicateIds,\ noRetrievalMethodLoops + jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep;\ java.base/java.security.KeyRep$Type;java.base/javax.crypto.spec.SecretKeySpec;!* diff --git a/distribution/src/config/java.security b/distribution/src/config/java.security new file mode 100644 index 0000000000000..5ddbbde240f8f --- /dev/null +++ b/distribution/src/config/java.security @@ -0,0 +1,58 @@ +# Security Properties for JDK 11 and higher, with BouncyCastle FIPS provider and BouncyCastleJSSEProvider in non-approved mode + +security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider +security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +security.provider.3=SUN +security.provider.4=SunJGSS + +securerandom.source=file:/dev/urandom +securerandom.strongAlgorithms=NativePRNGBlocking:SUN,DRBG:SUN +securerandom.drbg.config= + +login.configuration.provider=sun.security.provider.ConfigFile +policy.provider=sun.security.provider.PolicyFile +policy.expandProperties=true +policy.allowSystemProperty=true +policy.ignoreIdentityScope=false +keystore.type.compat=true + +package.access=sun.misc.,\ + sun.reflect. +package.definition=sun.misc.,\ + sun.reflect. + +security.overridePropertiesFile=true + +ssl.KeyManagerFactory.algorithm=PKIX +ssl.TrustManagerFactory.algorithm=PKIX + +networkaddress.cache.negative.ttl=10 +krb5.kdc.bad.policy = tryLast + +jdk.certpath.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySize < 1024 +jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, DSA keySize < 1024 +jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 1024, DES40_CBC, RC4_40 +jdk.tls.legacyAlgorithms= \ + K_NULL, C_NULL, M_NULL, \ + DH_anon, ECDH_anon, \ + RC4_128, RC4_40, DES_CBC, DES40_CBC, \ + 3DES_EDE_CBC +jdk.tls.keyLimits=AES/GCM/NoPadding KeyUpdate 2^37 + +crypto.policy=unlimited + +jdk.xml.dsig.secureValidationPolicy=\ + disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\ + disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\ + disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\ + maxTransforms 5,\ + maxReferences 30,\ + disallowReferenceUriSchemes file http https,\ + minKeySize RSA 1024,\ + minKeySize DSA 1024,\ + minKeySize EC 224,\ + noDuplicateIds,\ + noRetrievalMethodLoops + +jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep;\ + java.base/java.security.KeyRep$Type;java.base/javax.crypto.spec.SecretKeySpec;!* diff --git a/distribution/tools/keystore-cli/src/test/java/org/opensearch/common/settings/KeyStoreWrapperTests.java b/distribution/tools/keystore-cli/src/test/java/org/opensearch/common/settings/KeyStoreWrapperTests.java index 5effcd8b84a33..dee8843c58d62 100644 --- a/distribution/tools/keystore-cli/src/test/java/org/opensearch/common/settings/KeyStoreWrapperTests.java +++ b/distribution/tools/keystore-cli/src/test/java/org/opensearch/common/settings/KeyStoreWrapperTests.java @@ -69,8 +69,13 @@ import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -363,31 +368,24 @@ public void testIllegalSettingName() throws Exception { assertTrue(e.getMessage().contains("does not match the allowed setting name pattern")); } - public void testBackcompatV1() throws Exception { - assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); - Path configDir = env.configDir(); - NIOFSDirectory directory = new NIOFSDirectory(configDir); - try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) { - CodecUtil.writeHeader(output, "opensearch.keystore", 1); - output.writeByte((byte) 0); // hasPassword = false - output.writeString("PKCS12"); - output.writeString("PBE"); + public void testFailLoadV1KeystoresInFipsJvm() throws Exception { + assumeTrue("Test in FIPS JVM", inFipsJvm()); - SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); - KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); - keystore.load(null, null); - SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); - KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); - keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); + Exception e = assertThrows(SecurityException.class, () -> generateV1()); + assertThat(e.getMessage(), containsString("Only PKCS_11, BCFKS, JKS keystores are allowed in FIPS JVM")); + } - ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream(); - keystore.store(keystoreBytesStream, new char[0]); - byte[] keystoreBytes = keystoreBytesStream.toByteArray(); - output.writeInt(keystoreBytes.length); - output.writeBytes(keystoreBytes, keystoreBytes.length); - CodecUtil.writeFooter(output); - } + public void testFailLoadV2KeystoresInFipsJvm() throws Exception { + assumeTrue("Test in FIPS JVM", inFipsJvm()); + + Exception e = assertThrows(SecurityException.class, () -> generateV2()); + assertThat(e.getMessage(), containsString("Only PKCS_11, BCFKS, JKS keystores are allowed in FIPS JVM")); + } + public void testBackcompatV1() throws Exception { + assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); + generateV1(); + Path configDir = env.configDir(); KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir); keystore.decrypt(new char[0]); SecureString testValue = keystore.getString("string_setting"); @@ -396,47 +394,8 @@ public void testBackcompatV1() throws Exception { public void testBackcompatV2() throws Exception { assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); + byte[] fileBytes = generateV2(); Path configDir = env.configDir(); - NIOFSDirectory directory = new NIOFSDirectory(configDir); - byte[] fileBytes = new byte[20]; - random().nextBytes(fileBytes); - try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) { - - CodecUtil.writeHeader(output, "opensearch.keystore", 2); - output.writeByte((byte) 0); // hasPassword = false - output.writeString("PKCS12"); - output.writeString("PBE"); // string algo - output.writeString("PBE"); // file algo - - output.writeVInt(2); // num settings - output.writeString("string_setting"); - output.writeString("STRING"); - output.writeString("file_setting"); - output.writeString("FILE"); - - SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); - KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); - keystore.load(null, null); - SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); - KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); - keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); - - byte[] base64Bytes = Base64.getEncoder().encode(fileBytes); - char[] chars = new char[base64Bytes.length]; - for (int i = 0; i < chars.length; ++i) { - chars[i] = (char) base64Bytes[i]; // PBE only stores the lower 8 bits, so this narrowing is ok - } - secretKey = secretFactory.generateSecret(new PBEKeySpec(chars)); - keystore.setEntry("file_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); - - ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream(); - keystore.store(keystoreBytesStream, new char[0]); - byte[] keystoreBytes = keystoreBytesStream.toByteArray(); - output.writeInt(keystoreBytes.length); - output.writeBytes(keystoreBytes, keystoreBytes.length); - CodecUtil.writeFooter(output); - } - KeyStoreWrapper keystore = KeyStoreWrapper.load(configDir); keystore.decrypt(new char[0]); SecureString testValue = keystore.getString("string_setting"); @@ -496,6 +455,77 @@ public void testLegacyV3() throws GeneralSecurityException, IOException { assertThat(toByteArray(wrapper.getFile("file_setting")), equalTo("file_value".getBytes(StandardCharsets.UTF_8))); } + private void generateV1() throws IOException, NoSuchAlgorithmException, NoSuchProviderException, CertificateException, + InvalidKeySpecException, KeyStoreException { + Path configDir = env.configDir(); + NIOFSDirectory directory = new NIOFSDirectory(configDir); + try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) { + CodecUtil.writeHeader(output, "opensearch.keystore", 1); + output.writeByte((byte) 0); // hasPassword = false + output.writeString("PKCS12"); + output.writeString("PBE"); + + SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); + KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); + keystore.load(null, null); + SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); + KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); + keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); + + ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream(); + keystore.store(keystoreBytesStream, new char[0]); + byte[] keystoreBytes = keystoreBytesStream.toByteArray(); + output.writeInt(keystoreBytes.length); + output.writeBytes(keystoreBytes, keystoreBytes.length); + CodecUtil.writeFooter(output); + } + } + + private byte[] generateV2() throws Exception { + Path configDir = env.configDir(); + NIOFSDirectory directory = new NIOFSDirectory(configDir); + byte[] fileBytes = new byte[20]; + random().nextBytes(fileBytes); + try (IndexOutput output = EndiannessReverserUtil.createOutput(directory, "opensearch.keystore", IOContext.DEFAULT)) { + + CodecUtil.writeHeader(output, "opensearch.keystore", 2); + output.writeByte((byte) 0); // hasPassword = false + output.writeString("PKCS12"); + output.writeString("PBE"); // string algo + output.writeString("PBE"); // file algo + + output.writeVInt(2); // num settings + output.writeString("string_setting"); + output.writeString("STRING"); + output.writeString("file_setting"); + output.writeString("FILE"); + + SecretKeyFactory secretFactory = SecretKeyFactory.getInstance("PBE", "SunJCE"); + KeyStore keystore = KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN"); + keystore.load(null, null); + SecretKey secretKey = secretFactory.generateSecret(new PBEKeySpec("stringSecretValue".toCharArray())); + KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection(new char[0]); + keystore.setEntry("string_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); + + byte[] base64Bytes = Base64.getEncoder().encode(fileBytes); + char[] chars = new char[base64Bytes.length]; + for (int i = 0; i < chars.length; ++i) { + chars[i] = (char) base64Bytes[i]; // PBE only stores the lower 8 bits, so this narrowing is ok + } + secretKey = secretFactory.generateSecret(new PBEKeySpec(chars)); + keystore.setEntry("file_setting", new KeyStore.SecretKeyEntry(secretKey), protectionParameter); + + ByteArrayOutputStream keystoreBytesStream = new ByteArrayOutputStream(); + keystore.store(keystoreBytesStream, new char[0]); + byte[] keystoreBytes = keystoreBytesStream.toByteArray(); + output.writeInt(keystoreBytes.length); + output.writeBytes(keystoreBytes, keystoreBytes.length); + CodecUtil.writeFooter(output); + } + + return fileBytes; + } + private byte[] toByteArray(final InputStream is) throws IOException { final ByteArrayOutputStream os = new ByteArrayOutputStream(); final byte[] buffer = new byte[1024]; diff --git a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java index 2d10fac45efda..45e10b461d1db 100644 --- a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java +++ b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java @@ -40,6 +40,9 @@ final class SystemJvmOptions { + protected static final String OPENSEARCH_CRYPTO_STANDARD = "OPENSEARCH_CRYPTO_STANDARD"; + protected static final String FIPS_140_2 = "FIPS-140-2"; + static List systemJvmOptions(final Path config) { return Collections.unmodifiableList( Arrays.asList( @@ -88,16 +91,22 @@ static List systemJvmOptions(final Path config) { } private static String enableFips() { - var cryptoStandard = System.getenv("OPENSEARCH_CRYPTO_STANDARD"); - if (cryptoStandard != null && cryptoStandard.equals("FIPS-140-2")) { + var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); + if (cryptoStandard != null && cryptoStandard.equals(FIPS_140_2)) { return "-Dorg.bouncycastle.fips.approved_only=true"; } return ""; } private static String loadJavaSecurityProperties(final Path config) { - var securityFile = config.resolve("fips_java.security"); - return "-Djava.security.properties=" + securityFile.toAbsolutePath(); + String securityFile; + var cryptoStandard = System.getenv(OPENSEARCH_CRYPTO_STANDARD); + if (cryptoStandard != null && cryptoStandard.equals(FIPS_140_2)) { + securityFile = "fips_java.security"; + } else { + securityFile = "java.security"; + } + return "-Djava.security.properties=" + config.resolve(securityFile).toAbsolutePath(); } private static String allowSecurityManagerOption() { diff --git a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java index 2b458d431215f..2c1ba85e8235d 100644 --- a/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java +++ b/libs/common/src/main/java/org/opensearch/common/crypto/KeyStoreType.java @@ -21,15 +21,13 @@ public enum KeyStoreType { JKS("JKS"), PKCS_12("PKCS12"), PKCS_11("PKCS11"), - BKS("BKS"), BCFKS("BCFKS"); - private static final Map> TYPE_TO_EXTENSION_MAP = new HashMap<>(); + static final Map> TYPE_TO_EXTENSION_MAP = new HashMap<>(); static { TYPE_TO_EXTENSION_MAP.put(JKS, List.of(".jks", ".ks")); TYPE_TO_EXTENSION_MAP.put(PKCS_12, List.of(".p12", ".pkcs12", ".pfx")); - TYPE_TO_EXTENSION_MAP.put(BKS, List.of(".bks")); // Bouncy Castle Keystore TYPE_TO_EXTENSION_MAP.put(BCFKS, List.of(".bcfks")); // Bouncy Castle FIPS Keystore } diff --git a/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java b/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java new file mode 100644 index 0000000000000..2ed3a2fafa867 --- /dev/null +++ b/libs/common/src/test/java/org/opensearch/common/crypto/KeyStoreFactoryTests.java @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.crypto; + +import com.carrotsearch.randomizedtesting.generators.RandomStrings; + +import org.opensearch.test.OpenSearchTestCase; + +import static org.hamcrest.Matchers.equalTo; + +public class KeyStoreFactoryTests extends OpenSearchTestCase { + + public void testPKCS12KeyStore() { + assumeFalse("Can't run in a FIPS JVM as PBE is not available", inFipsJvm()); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12).getType(), equalTo("PKCS12")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12).getProvider().getName(), equalTo("BCFIPS")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "BCFIPS").getType(), equalTo("PKCS12")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "BCFIPS").getProvider().getName(), equalTo("BCFIPS")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN").getType(), equalTo("PKCS12")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.PKCS_12, "SUN").getProvider().getName(), equalTo("SUN")); + + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(KeyStoreType.PKCS_12).forEach(extension -> { + var keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName(extension)); + assertThat(keyStore.getType(), equalTo(KeyStoreType.PKCS_12.getJcaName())); + }); + } + + public void testJKSKeyStore() { + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS).getType(), equalTo("JKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS).getProvider().getName(), equalTo("SUN")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS, "SUN").getType(), equalTo("JKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.JKS, "SUN").getProvider().getName(), equalTo("SUN")); + + assertThrows("BCFKS not found", SecurityException.class, () -> KeyStoreFactory.getInstance(KeyStoreType.JKS, "BCFIPS")); + + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(KeyStoreType.JKS).forEach(extension -> { + var keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName(extension)); + assertThat(keyStore.getType(), equalTo(KeyStoreType.JKS.getJcaName())); + }); + } + + public void testBCFIPSKeyStore() { + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS).getType(), equalTo("BCFKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS).getProvider().getName(), equalTo("BCFIPS")); + + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "BCFIPS").getType(), equalTo("BCFKS")); + assertThat(KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "BCFIPS").getProvider().getName(), equalTo("BCFIPS")); + + assertThrows("BCFKS not found", SecurityException.class, () -> KeyStoreFactory.getInstance(KeyStoreType.BCFKS, "SUN")); + + KeyStoreType.TYPE_TO_EXTENSION_MAP.get(KeyStoreType.BCFKS).forEach(extension -> { + var keyStore = KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName(extension)); + assertThat(keyStore.getType(), equalTo(KeyStoreType.BCFKS.getJcaName())); + }); + } + + public void testUnknownKeyStoreType() { + assertThrows( + "Unknown keystore type for file path: keystore.unknown", + IllegalArgumentException.class, + () -> KeyStoreFactory.getInstanceBasedOnFileExtension(createRandomFileName("unknown")) + ); + } + + private String createRandomFileName(String extension) { + return RandomStrings.randomAsciiAlphanumOfLengthBetween(random(), 0, 10) + "." + extension; + } +} diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java index 3a1b369c64902..dd5b67cbcbbb8 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/PemUtils.java @@ -62,7 +62,7 @@ final class PemUtils { private static final String BCFIPS = "BCFIPS"; - private PemUtils() { + PemUtils() { throw new IllegalStateException("Utility class should not be instantiated"); } @@ -87,7 +87,7 @@ static List readCertificates(Collection certPaths) throws Cer try (InputStream input = Files.newInputStream(path)) { final Collection parsed = certFactory.generateCertificates(input); if (parsed.isEmpty()) { - throw new SslConfigException("failed to parse any certificates from [" + path.toAbsolutePath() + "]"); + throw new SslConfigException("Failed to parse any certificate from [" + path.toAbsolutePath() + "]"); } certificates.addAll(parsed); } diff --git a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java index 909b8facd68b5..224699660b65b 100644 --- a/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java +++ b/libs/ssl-config/src/main/java/org/opensearch/common/ssl/SslConfiguration.java @@ -164,9 +164,6 @@ public SSLContext createSslContext() { * {@link #getSupportedProtocols() configured protocols}. */ private String contextProtocol() { - if (supportedProtocols.isEmpty()) { - throw new SslConfigException("no SSL/TLS protocols have been configured"); - } if (CryptoServicesRegistrar.isInApprovedOnlyMode()) { if (!new HashSet<>(SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS).containsAll(supportedProtocols)) { throw new SslConfigException( diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemTrustConfigTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemTrustConfigTests.java index 543021e0b1252..3d66c2157b754 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemTrustConfigTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemTrustConfigTests.java @@ -138,14 +138,14 @@ private void assertEmptyFile(PemTrustConfig trustConfig, Path file) { final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); logger.info("failure", exception); assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); - assertThat(exception.getMessage(), Matchers.containsString("failed to parse any certificates")); + assertThat(exception.getMessage(), Matchers.containsString("Failed to parse any certificate from")); } private void assertFailedToParse(PemTrustConfig trustConfig, Path file) { final SslConfigException exception = expectThrows(SslConfigException.class, trustConfig::createTrustManager); logger.info("failure", exception); assertThat(exception.getMessage(), Matchers.containsString(file.toAbsolutePath().toString())); - assertThat(exception.getMessage(), Matchers.containsString("failed to parse any certificates")); + assertThat(exception.getMessage(), Matchers.containsString("Failed to parse any certificate from")); } private void assertFileNotFound(PemTrustConfig trustConfig, Path file) { diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemUtilsTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemUtilsTests.java index 7ab684fea0670..0aa0213d54111 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemUtilsTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/PemUtilsTests.java @@ -59,6 +59,10 @@ public class PemUtilsTests extends OpenSearchTestCase { private static final Supplier TESTNODE_PASSWORD = "testnode"::toCharArray; private static final Supplier STRONG_PRIVATE_SECRET = "6!6428DQXwPpi7@$ggeg/="::toCharArray; // has to be at least 112 bit long. + public void testInstantiateWithDefaultConstructor() { + assertThrows("Utility class should not be instantiated", IllegalStateException.class, PemUtils::new); + } + public void testReadPKCS8RsaKey() throws Exception { assumeFalse("Can't use JKS/PKCS12 keystores in a FIPS JVM", inFipsJvm()); Key key = getKeyFromKeystore("RSA", KeyStoreType.JKS); diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java index bb46c086fbedc..f4c42231ab96a 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationLoaderTests.java @@ -100,6 +100,14 @@ public void testBasicConfigurationOptions() { if (verificationMode == SslVerificationMode.NONE) { final SslTrustConfig trustConfig = configuration.getTrustConfig(); assertThat(trustConfig, instanceOf(TrustEverythingConfig.class)); + + if (inFipsJvm()) { + assertThrows( + "The use of TrustEverythingConfig is not permitted in FIPS mode. This issue may be caused by the 'ssl.verification_mode=NONE' setting.", + IllegalStateException.class, + trustConfig::createTrustManager + ); + } } } diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java index 9281d9612d806..ce846381c6e5e 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslConfigurationTests.java @@ -42,10 +42,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import org.mockito.Mockito; import static org.opensearch.common.ssl.SslConfigurationLoader.DEFAULT_CIPHERS; +import static org.opensearch.common.ssl.SslConfigurationLoader.FIPS_APPROVED_PROTOCOLS; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -204,4 +206,80 @@ public void testBuildSslContext() { Mockito.verifyNoMoreInteractions(trustConfig, keyConfig); } + public void testCreateSslContextWithUnsupportedProtocols() { + assumeFalse("Test not in FIPS JVM", inFipsJvm()); + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + SslConfiguration configuration = new SslConfiguration( + trustConfig, + keyConfig, + randomFrom(SslVerificationMode.values()), + randomFrom(SslClientAuthenticationMode.values()), + DEFAULT_CIPHERS, + Collections.singletonList("DTLSv1.2") + ); + + Exception e = assertThrows(SslConfigException.class, configuration::createSslContext); + assertThat( + e.getMessage(), + containsString("no supported SSL/TLS protocol was found in the configured supported protocols: [DTLSv1.2]") + ); + } + + public void testNotSupportedProtocolsInFipsJvm() { + assumeTrue("Test in FIPS JVM", inFipsJvm()); + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + final String protocol = randomFrom(List.of("TLSv1.1", "TLSv1", "SSLv3", "SSLv2Hello", "SSLv2")); + final SslConfiguration configuration = new SslConfiguration( + trustConfig, + keyConfig, + randomFrom(SslVerificationMode.values()), + randomFrom(SslClientAuthenticationMode.values()), + DEFAULT_CIPHERS, + Collections.singletonList(protocol) + ); + + Mockito.when(trustConfig.createTrustManager()).thenReturn(null); + Mockito.when(keyConfig.createKeyManager()).thenReturn(null); + var exception = assertThrows(SslConfigException.class, configuration::createSslContext); + assertThat( + exception.getMessage(), + equalTo( + String.format(Locale.ROOT, "in FIPS mode only the following SSL/TLS protocols are allowed: [%s]", FIPS_APPROVED_PROTOCOLS) + ) + ); + } + + public void testInitValuesExist() { + final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); + final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); + + assertThrows( + "cannot configure SSL/TLS without any supported cipher suites", + SslConfigException.class, + () -> new SslConfiguration( + trustConfig, + keyConfig, + SslVerificationMode.CERTIFICATE, + SslClientAuthenticationMode.REQUIRED, + Collections.emptyList(), + List.of("SSLv2") + ) + ); + + assertThrows( + "cannot configure SSL/TLS without any supported protocols", + SslConfigException.class, + () -> new SslConfiguration( + trustConfig, + keyConfig, + SslVerificationMode.CERTIFICATE, + SslClientAuthenticationMode.REQUIRED, + DEFAULT_CIPHERS, + Collections.emptyList() + ) + ); + } + } diff --git a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java index b733b6536f5cb..31a4082f0609a 100644 --- a/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java +++ b/libs/ssl-config/src/test/java/org/opensearch/common/ssl/SslDiagnosticsTests.java @@ -52,6 +52,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -70,6 +71,17 @@ public class SslDiagnosticsTests extends OpenSearchTestCase { private static final byte[] MOCK_ENCODING_4 = { 0x64, 0x65, 0x66, 0x67, 0x68, 0x69 }; private static final String MOCK_FINGERPRINT_4 = "5d96965bfae50bf2be0d6259eb87a6cc9f5d0b26"; + public void testTrustEmptyStore() { + var fileName = "cert-all/empty.jks"; + var exception = assertThrows(SslConfigException.class, () -> loadCertificate(fileName)); + assertThat( + exception.getMessage(), + Matchers.equalTo( + String.format(Locale.ROOT, "Failed to parse any certificate from [%s]", getDataPath("/certs/" + fileName).toAbsolutePath()) + ) + ); + } + public void testDiagnosticMessageWhenServerProvidesAFullCertChainThatIsTrusted() throws Exception { X509Certificate[] chain = loadCertChain("cert1/cert1.crt", "ca1/ca.crt"); final SSLSession session = session("192.168.1.1"); diff --git a/libs/ssl-config/src/test/resources/certs/README.md b/libs/ssl-config/src/test/resources/certs/README.md index dbbe840dd5431..e363e287c9521 100644 --- a/libs/ssl-config/src/test/resources/certs/README.md +++ b/libs/ssl-config/src/test/resources/certs/README.md @@ -153,3 +153,20 @@ done opensearch-certutil ca --pem --out ${PWD}/ca1-b.zip --days 9999 --ca-dn "CN=Test CA 1" unzip ca1-b.zip mv ca ca1-b + +# 16. Create empty KeyStore + +```bash +keytool -genkeypair \ + -alias temp \ + -storetype JKS \ + -keyalg rsa \ + -storepass storePassword \ + -keypass secretPassword \ + -keystore cert-all/empty.jks \ + -dname "CN=foo,DC=example,DC=com" +keytool -delete \ + -alias temp \ + -storepass storePassword \ + -keystore cert-all/empty.jks +``` diff --git a/libs/ssl-config/src/test/resources/certs/cert-all/empty.jks b/libs/ssl-config/src/test/resources/certs/cert-all/empty.jks new file mode 100644 index 0000000000000..1327d550f61fd Binary files /dev/null and b/libs/ssl-config/src/test/resources/certs/cert-all/empty.jks differ diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java index a1945bab6a4ed..45bf634de1ec1 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcher.java @@ -38,7 +38,6 @@ public class BCryptPasswordMatcher implements CredentialsMatcher { public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { final UsernamePasswordToken userToken = (UsernamePasswordToken) token; return check(userToken.getPassword(), (String) info.getCredentials()); - } private boolean check(char[] password, String hash) { diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java index 91e88ed1bf701..304903e27c953 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/realm/BCryptPasswordMatcherTests.java @@ -41,4 +41,23 @@ public void testCredentialDoNotMatch() { assertThat(result, equalTo(false)); } + + public void testEmptyPassword() { + final UsernamePasswordToken token = mock(UsernamePasswordToken.class); + when(token.getPassword()).thenReturn(randomFrom("".toCharArray(), null)); + final AuthenticationInfo info = mock(AuthenticationInfo.class); + + Exception e = assertThrows(IllegalStateException.class, () -> new BCryptPasswordMatcher().doCredentialsMatch(token, info)); + assertThat(e.getMessage(), equalTo("Password cannot be empty or null")); + } + + public void testEmptyHash() { + final UsernamePasswordToken token = mock(UsernamePasswordToken.class); + when(token.getPassword()).thenReturn("HashedPassword".toCharArray()); + final AuthenticationInfo info = mock(AuthenticationInfo.class); + when(info.getCredentials()).thenReturn(randomFrom("", null)); + + Exception e = assertThrows(IllegalStateException.class, () -> new BCryptPasswordMatcher().doCredentialsMatch(token, info)); + assertThat(e.getMessage(), equalTo("Hash cannot be empty or null")); + } }