diff --git a/crypto/fips1402/pom.xml b/crypto/fips1402/pom.xml index 1ce9a822efc..cd5f1cd0b7d 100644 --- a/crypto/fips1402/pom.xml +++ b/crypto/fips1402/pom.xml @@ -89,19 +89,4 @@ - - - - - maven-surefire-plugin - - - ${basedir}/target/test-classes/kc.java.security - - - - - - - \ No newline at end of file diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java index 3e6b92d1d2d..cb0582ecd36 100644 --- a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java +++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java @@ -9,6 +9,7 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; +import java.security.SecureRandom; import java.security.spec.ECField; import java.security.spec.ECFieldF2m; import java.security.spec.ECFieldFp; @@ -83,7 +84,7 @@ public class FIPS1402Provider implements CryptoProvider { Security.insertProviderAt(new KeycloakFipsSecurityProvider(bcFipsProvider), 1); if (existingBcFipsProvider == null) { - Security.insertProviderAt(this.bcFipsProvider, 2); + checkSecureRandom(() -> Security.insertProviderAt(this.bcFipsProvider, 2)); Provider bcJsseProvider = new BouncyCastleJsseProvider("fips:BCFIPS"); Security.insertProviderAt(bcJsseProvider, 3); log.debugf("Inserted security providers: %s", Arrays.asList(this.bcFipsProvider.getName(),bcJsseProvider.getName())); @@ -191,11 +192,7 @@ public class FIPS1402Provider implements CryptoProvider { @Override public KeyStore getKeyStore(KeystoreFormat format) throws KeyStoreException, NoSuchProviderException { - if (format == KeystoreFormat.JKS) { - return KeyStore.getInstance(format.toString()); - } else { - return KeyStore.getInstance(format.toString(), BouncyIntegration.PROVIDER); - } + return KeyStore.getInstance(format.toString(), BouncyIntegration.PROVIDER); } @Override @@ -262,4 +259,34 @@ public class FIPS1402Provider implements CryptoProvider { }; } + + // BCFIPS require "SecureRandom.getInstanceStrong" to be available. But it may not be available on RHEL 8 on OpenJDK 17 due the https://bugzilla.redhat.com/show_bug.cgi?id=2155060 + private void checkSecureRandom(Runnable insertBcFipsProvider) { + try { + SecureRandom sr = SecureRandom.getInstanceStrong(); + log.debugf("Strong secure random available. Algorithm: %s, Provider: %s", sr.getAlgorithm(), sr.getProvider()); + insertBcFipsProvider.run(); + } catch (NoSuchAlgorithmException nsae) { + + // Fallback to regular SecureRandom + SecureRandom secRandom = new SecureRandom(); + String origStrongAlgs = Security.getProperty("securerandom.strongAlgorithms"); + String usedAlg = secRandom.getAlgorithm() + ":" + secRandom.getProvider().getName(); + log.debugf("Strong secure random not available. Tried algorithms: %s. Using algorithm as a fallback for strong secure random: %s", origStrongAlgs, usedAlg); + + String strongAlgs = origStrongAlgs == null ? usedAlg : usedAlg + "," + origStrongAlgs; + Security.setProperty("securerandom.strongAlgorithms", strongAlgs); + + try { + // Need to insert BCFIPS provider to security providers with "strong algorithm" available + insertBcFipsProvider.run(); + SecureRandom.getInstance("DEFAULT", "BCFIPS"); + log.debugf("Initialized BCFIPS secured random"); + } catch (NoSuchAlgorithmException | NoSuchProviderException nsaee) { + throw new IllegalStateException("Not possible to initiate BCFIPS secure random", nsaee); + } finally { + Security.setProperty("securerandom.strongAlgorithms", origStrongAlgs != null ? origStrongAlgs : ""); + } + } + } } diff --git a/crypto/fips1402/src/test/resources/kc.java.security b/crypto/fips1402/src/test/resources/kc.java.security deleted file mode 100644 index 9267176d29e..00000000000 --- a/crypto/fips1402/src/test/resources/kc.java.security +++ /dev/null @@ -1,51 +0,0 @@ -# Configuration file just with the security properties, which are supposed to be overriden. The properties, which are not mentioned in this file, -# are inherited from the default java.security file bundled within the Java distribution. -# -# NOTE: Each property is specified 2 times. This is so the same file can be used on both FIPS based RHEL host (which uses "fips" prefixed properties by default) -# and the non-fips based (EG. when running the tests on GH actions) - -# -# List of providers and their preference orders (see above). Used on the host without FIPS (EG. when running the tests on GH actions) -# Uses only BouncyCastle FIPS providers to make sure to use only FIPS compliant cryptography. -# -security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS -security.provider.3= - -# -# Security providers used when global crypto-policies are set to FIPS (Usually it is used when FIPS enabled on system/JVM level) -# -fips.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -fips.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS -fips.provider.3= - -# Commented this provider for now (and also other providers) as it uses lots of non-FIPS services. -# See https://access.redhat.com/documentation/en-us/openjdk/11/html-single/configuring_openjdk_11_on_rhel_with_fips/index#ref_openjdk-default-fips-configuration_openjdk -# fips.provider.2=SUN - -# -# Default keystore type. -# -keystore.type=PKCS12 -fips.keystore.type=PKCS12 - -# This is needed especially if we cannot add security provider "com.sun.net.ssl.internal.ssl.Provider BCFIPS" as a security provider. -# OpenJDK has "SunX509" as default algorithm, but that one is not supported by BCJSSE. So adding the Sun provider delegating to BCFIPS is needed (as above) -# or changing default algorithm as described here -ssl.KeyManagerFactory.algorithm=PKIX -fips.ssl.KeyManagerFactory.algorithm=PKIX - -ssl.TrustManagerFactory.algorithm=PKIX -fips.ssl.TrustManagerFactory.algorithm=PKIX - -# -# Controls compatibility mode for JKS and PKCS12 keystore types. -# -# When set to 'true', both JKS and PKCS12 keystore types support loading -# keystore files in either JKS or PKCS12 format. When set to 'false' the -# JKS keystore type supports loading only JKS keystore files and the PKCS12 -# keystore type supports loading only PKCS12 keystore files. -# -# This is set to false as BCFIPS providers don't support JKS -keystore.type.compat=false -fips.keystore.type.compat=false diff --git a/docs/fips.md b/docs/fips.md index e41c09d1d7a..6de167b765f 100644 --- a/docs/fips.md +++ b/docs/fips.md @@ -66,8 +66,7 @@ For the `fips-mode`, he alternative is to use `--fips-mode=strict` in which case which means even stricter security algorithms. As mentioned above, strict mode won't work with `pkcs12` keystore: ``` -./kc.sh build --fips-mode=enabled -./kc.sh start --optimized --hostname=localhost \ +./kc.sh start --fips-mode=enabled --hostname=localhost \ --https-key-store-file=$PWD/$KEYSTORE_FILE \ --https-key-store-type=$KEYSTORE_FORMAT \ --https-key-store-password=passwordpassword \ diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/AuthUtil.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/AuthUtil.java index dcebc8f09ed..125913fbe28 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/AuthUtil.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/AuthUtil.java @@ -191,7 +191,8 @@ public class AuthUtil { public static String getSignedRequestToken(String keystore, String storePass, String keyPass, String alias, int sigLifetime, String clientId, String realmInfoUrl) { - KeyPair keypair = KeystoreUtil.loadKeyPairFromKeystore(keystore, storePass, keyPass, alias, KeystoreUtil.KeystoreFormat.JKS); + KeystoreUtil.KeystoreFormat keystoreType = Enum.valueOf(KeystoreUtil.KeystoreFormat.class, KeystoreUtil.getKeystoreType(null, keystore, KeystoreUtil.KeystoreFormat.JKS.toString())); + KeyPair keypair = KeystoreUtil.loadKeyPairFromKeystore(keystore, storePass, keyPass, alias, keystoreType); JsonWebToken reqToken = new JsonWebToken(); reqToken.id(UUID.randomUUID().toString()); diff --git a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/util/AuthUtil.java b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/util/AuthUtil.java index 821271f96d4..be0107b2da4 100644 --- a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/util/AuthUtil.java +++ b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/util/AuthUtil.java @@ -31,7 +31,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.KeyPair; -import java.security.PrivateKey; import java.util.UUID; import static java.lang.System.currentTimeMillis; @@ -193,7 +192,8 @@ public class AuthUtil { public static String getSignedRequestToken(String keystore, String storePass, String keyPass, String alias, int sigLifetime, String clientId, String realmInfoUrl) { - KeyPair keypair = KeystoreUtil.loadKeyPairFromKeystore(keystore, storePass, keyPass, alias, KeystoreUtil.KeystoreFormat.JKS); + KeystoreUtil.KeystoreFormat keystoreType = Enum.valueOf(KeystoreUtil.KeystoreFormat.class, KeystoreUtil.getKeystoreType(null, keystore, KeystoreUtil.KeystoreFormat.JKS.toString())); + KeyPair keypair = KeystoreUtil.loadKeyPairFromKeystore(keystore, storePass, keyPass, alias, keystoreType); JsonWebToken reqToken = new JsonWebToken(); reqToken.id(UUID.randomUUID().toString()); diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java index 51368edfc14..c29b6eb3a15 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java @@ -34,7 +34,7 @@ final class ClassLoaderPropertyMappers { if (fipsEnabled != null && FipsMode.valueOf(fipsEnabled.getValue()).isFipsEnabled()) { return Optional.of( - "org.bouncycastle:bcprov-jdk15on,org.bouncycastle:bcpkix-jdk15on,org.keycloak:keycloak-crypto-default"); + "org.bouncycastle:bcprov-jdk15on,org.bouncycastle:bcpkix-jdk15on,org.bouncycastle:bcutil-jdk15on,org.keycloak:keycloak-crypto-default"); } return Optional.of( diff --git a/testsuite/integration-arquillian/servers/auth-server/common/fips/kc.java.security b/testsuite/integration-arquillian/servers/auth-server/common/fips/kc.java.security index 3828ad81c54..c9d2d7d866d 100644 --- a/testsuite/integration-arquillian/servers/auth-server/common/fips/kc.java.security +++ b/testsuite/integration-arquillian/servers/auth-server/common/fips/kc.java.security @@ -8,18 +8,19 @@ # List of providers and their preference orders (see above). Used on the host without FIPS (EG. when running the tests on GH actions) # Uses only BouncyCastle FIPS providers to make sure to use only FIPS compliant cryptography. # -security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS -security.provider.3= +#security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider +#security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS +#security.provider.3= # # Security providers used when global crypto-policies are set to FIPS (Usually it is used when FIPS enabled on system/JVM level) # -fips.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -fips.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS -fips.provider.3= -#fips.provider.3=SunJGSS -#fips.provider.4=XMLDSig +#fips.provider.1=SunPKCS11 ${java.home}/conf/security/nss.fips.cfg +#fips.provider.2=SUN +#fips.provider.3=SunEC +#fips.provider.4=com.sun.net.ssl.internal.ssl.Provider SunPKCS11-NSS-FIPS +#fips.provider.5=SunJGSS +#fips.provider.6=XMLDSig #fips.provider.5= # Commented this provider for now (and also other providers) as it uses lots of non-FIPS services. diff --git a/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml b/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml index 041692949c1..e14ff9bc55c 100644 --- a/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml @@ -352,7 +352,7 @@ copy-dependencies - ${auth.server.home}/lib/bootstrap + ${auth.server.home}/providers bc-fips,bctls-fips,bcpkix-fips diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java index f202a3b0f3d..6238b6bb5fb 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java @@ -5,8 +5,10 @@ import org.junit.Test; import org.keycloak.client.admin.cli.config.ConfigData; import org.keycloak.client.admin.cli.config.FileConfigHandler; import org.keycloak.client.admin.cli.config.RealmConfigData; +import org.keycloak.common.util.KeystoreUtil; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.testsuite.cli.KcAdmExec; +import org.keycloak.testsuite.util.KeystoreUtils; import org.keycloak.testsuite.util.TempFileResource; import org.keycloak.util.JsonSerialization; @@ -501,13 +503,24 @@ public class KcAdmTest extends AbstractAdmCliTest { } @Test - public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient() throws IOException { + public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_JKSKeystore() throws IOException { + KeystoreUtils.assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.JKS); + testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(KeystoreUtil.KeystoreFormat.JKS.getFileExtension()); + } + + @Test + public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_PKCS12Keystore() throws IOException { + KeystoreUtils.assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.PKCS12); + testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(KeystoreUtil.KeystoreFormat.PKCS12.getFileExtension()); + } + + private void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(String keystoreFileExtension) throws IOException { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using username, password, and client JWT signature. */ - File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcadm/admin-cli-keystore.jks"); - Assert.assertTrue("admin-cli-keystore.jks exists", keystore.isFile()); + File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcadm/admin-cli-keystore." + keystoreFileExtension); + Assert.assertTrue("admin-cli-keystore." + keystoreFileExtension + " must exist, but it does not exists", keystore.isFile()); // try client without direct grants enabled KcAdmExec exe = KcAdmExec.execute("get clients --no-config --server " + serverUrl + " --realm test" + @@ -536,7 +549,7 @@ public class KcAdmTest extends AbstractAdmCliTest { assertExitCodeAndStreamSizes(exe, 1, 0, 2); Assert.assertEquals("login message", "Logging into " + serverUrl + " as user user1 of realm test", exe.stderrLines().get(0)); - Assert.assertEquals("error message", "Failed to load private key: Keystore was tampered with, or password was incorrect", exe.stderrLines().get(exe.stderrLines().size() - 1)); + Assert.assertTrue("error message", exe.stderrLines().get(exe.stderrLines().size() - 1).startsWith("Failed to load private key:")); // try whole CRUD @@ -563,8 +576,8 @@ public class KcAdmTest extends AbstractAdmCliTest { * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using only client JWT signature - service account is used. */ - File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcadm/admin-cli-keystore.jks"); - Assert.assertTrue("admin-cli-keystore.jks exists", keystore.isFile()); + File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcadm/admin-cli-keystore.p12"); + Assert.assertTrue("admin-cli-keystore.p12 exists", keystore.isFile()); testCRUDWithOnTheFlyAuth(serverUrl, "--client admin-cli-jwt --keystore '" + keystore.getAbsolutePath() + "' --storepass storepass --keypass keypass --alias admin-cli", "", diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java index 83994d8f318..93a779c9935 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java @@ -6,8 +6,10 @@ import org.junit.Test; import org.keycloak.client.registration.cli.config.ConfigData; import org.keycloak.client.registration.cli.config.FileConfigHandler; import org.keycloak.client.registration.cli.config.RealmConfigData; +import org.keycloak.common.util.KeystoreUtil; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.testsuite.cli.KcRegExec; +import org.keycloak.testsuite.util.KeystoreUtils; import org.keycloak.testsuite.util.TempFileResource; import org.keycloak.util.JsonSerialization; @@ -502,13 +504,24 @@ public class KcRegTest extends AbstractRegCliTest { } @Test - public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient() throws IOException { + public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_JKSKeystore() throws IOException { + KeystoreUtils.assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.JKS); + testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(KeystoreUtil.KeystoreFormat.JKS.getFileExtension()); + } + + @Test + public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient_PKCS12Keystore() throws IOException { + KeystoreUtils.assumeKeystoreTypeSupported(KeystoreUtil.KeystoreFormat.PKCS12); + testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(KeystoreUtil.KeystoreFormat.PKCS12.getFileExtension()); + } + + private void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient(String keystoreFileExtension) throws IOException { /* * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using username, password, and client JWT signature. */ - File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcreg/reg-cli-keystore.jks"); - Assert.assertTrue("reg-cli-keystore.jks exists", keystore.isFile()); + File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcreg/reg-cli-keystore." + keystoreFileExtension); + Assert.assertTrue("reg-cli-keystore." + keystoreFileExtension + " exists", keystore.isFile()); // try client without direct grants enabled KcRegExec exe = execute("get test-client --no-config --server " + serverUrl + " --realm test" + @@ -537,7 +550,7 @@ public class KcRegTest extends AbstractRegCliTest { assertExitCodeAndStreamSizes(exe, 1, 0, 2); Assert.assertEquals("login message", "Logging into " + serverUrl + " as user user1 of realm test", exe.stderrLines().get(0)); - Assert.assertEquals("error message", "Failed to load private key: Keystore was tampered with, or password was incorrect", exe.stderrLines().get(exe.stderrLines().size() - 1)); + Assert.assertTrue("error message", exe.stderrLines().get(exe.stderrLines().size() - 1).startsWith("Failed to load private key: ")); // try whole CRUD @@ -564,8 +577,8 @@ public class KcRegTest extends AbstractRegCliTest { * Test create, get, update, and delete using on-the-fly authentication - without using any config file. * Login is performed by each operation again, and again using only client JWT signature - service account is used. */ - File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcreg/reg-cli-keystore.jks"); - Assert.assertTrue("reg-cli-keystore.jks exists", keystore.isFile()); + File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcreg/reg-cli-keystore.p12"); + Assert.assertTrue("reg-cli-keystore.p12 exists", keystore.isFile()); testCRUDWithOnTheFlyAuth(serverUrl, "--client reg-cli-jwt --keystore '" + keystore.getAbsolutePath() + "' --storepass storepass --keypass keypass --alias reg-cli", "", diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcadm/admin-cli-keystore.p12 b/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcadm/admin-cli-keystore.p12 new file mode 100644 index 00000000000..c05dd0f55f5 Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcadm/admin-cli-keystore.p12 differ diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcreg/reg-cli-keystore.p12 b/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcreg/reg-cli-keystore.p12 new file mode 100644 index 00000000000..1e165a5379a Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcreg/reg-cli-keystore.p12 differ