Make Keycloak FIPS working with OpenJDK 17 on FIPS enabled RHEL
Closes #15721
This commit is contained in:
parent
44715fe397
commit
36bd76957d
13 changed files with 87 additions and 99 deletions
|
@ -89,19 +89,4 @@
|
|||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<java.security.properties>${basedir}/target/test-classes/kc.java.security</java.security.properties>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -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,12 +192,8 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CertificateFactory getX509CertFactory() throws CertificateException, NoSuchProviderException {
|
||||
|
@ -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 : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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 \
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -352,7 +352,7 @@
|
|||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${auth.server.home}/lib/bootstrap</outputDirectory>
|
||||
<outputDirectory>${auth.server.home}/providers</outputDirectory>
|
||||
<includeArtifactIds>bc-fips,bctls-fips,bcpkix-fips</includeArtifactIds>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
|
|
@ -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", "",
|
||||
|
|
|
@ -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", "",
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue