Initial integration tests with BCFIPS distribution (#14895)

Closes #14886
This commit is contained in:
Marek Posolda 2022-10-17 23:33:22 +02:00 committed by GitHub
parent 19ee00ff54
commit 0756ef9a75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 743 additions and 96 deletions

View file

@ -274,6 +274,77 @@ jobs:
path: reports-${{ matrix.server }}-base-tests-${{ matrix.tests }}.zip path: reports-${{ matrix.server }}-base-tests-${{ matrix.tests }}.zip
if-no-files-found: ignore if-no-files-found: ignore
test-fips:
name: Base testsuite (fips)
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
server: ['bcfips-nonapproved-pkcs12']
tests: ['group1']
fail-fast: false
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: cache-2-${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: cache-1-${{ runner.os }}-m2
- name: Download built keycloak
id: download-keycloak
uses: actions/download-artifact@v3
with:
path: ~/.m2/repository/org/keycloak/
name: keycloak-artifacts.zip
# - name: List M2 repo
# run: |
# find ~ -name *dist*.zip
# ls -lR ~/.m2/repository
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: ${{ env.DEFAULT_JDK_VERSION }}
- name: Update maven settings
run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/
- name: Prepare quarkus distribution with BCFIPS
run: ./mvnw clean install -nsu -B -e -f testsuite/integration-arquillian/servers/auth-server/quarkus -Pauth-server-quarkus,auth-server-fips140-2
- name: Run base tests
run: |
declare -A PARAMS TESTGROUP
PARAMS["bcfips-nonapproved-pkcs12"]="-Pauth-server-quarkus,auth-server-fips140-2"
TESTGROUP["group1"]="-Dtest=org.keycloak.testsuite.forms.**" # Tests in the package "forms"
./mvnw clean install -nsu -B ${PARAMS["${{ matrix.server }}"]} ${TESTGROUP["${{ matrix.tests }}"]} -f testsuite/integration-arquillian/tests/base/pom.xml | misc/log/trimmer.sh
TEST_RESULT=${PIPESTATUS[0]}
find . -path '*/target/surefire-reports/*.xml' | zip -q reports-${{ matrix.server }}-base-tests-${{ matrix.tests }}.zip -@
exit $TEST_RESULT
- name: Analyze Test and/or Coverage Results
uses: runforesight/foresight-test-kit-action@v1
if: always() && github.repository == 'keycloak/keycloak'
with:
api_key: ${{ secrets.FORESIGHT_API_KEY }}
test_format: JUNIT
test_framework: JUNIT
test_path: 'testsuite/integration-arquillian/tests/base/target/surefire-reports/*.xml'
- name: Base test reports
uses: actions/upload-artifact@v3
if: failure()
with:
name: reports-${{ matrix.server }}-base-tests-${{ matrix.tests }}
retention-days: 14
path: reports-${{ matrix.server }}-base-tests-${{ matrix.tests }}.zip
if-no-files-found: ignore
test-posgres: test-posgres:
name: Base testsuite (postgres) name: Base testsuite (postgres)
needs: build needs: build

View file

@ -1,10 +1,12 @@
package org.keycloak.common.crypto; package org.keycloak.common.crypto;
import java.security.KeyStore;
import java.security.Provider; import java.security.Provider;
import java.security.Security; import java.security.Security;
import java.util.List; import java.util.List;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@ -33,6 +35,7 @@ public class CryptoIntegration {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracef(dumpJavaSecurityProviders()); logger.tracef(dumpJavaSecurityProviders());
logger.tracef(dumpSecurityProperties());
} }
} }
@ -68,7 +71,19 @@ public class CryptoIntegration {
return builder.append("]").toString(); return builder.append("]").toString();
} }
public static String dumpSecurityProperties() {
StringBuilder builder = new StringBuilder("Security properties: [ \n")
.append(" Java security properties file: " + System.getProperty("java.security.properties") + "\n")
.append(" Default keystore type: " + KeyStore.getDefaultType() + "\n")
.append(" keystore.type.compat: " + Security.getProperty("keystore.type.compat") + "\n");
Stream.of("javax.net.ssl.trustStoreType", "javax.net.ssl.trustStore", "javax.net.ssl.trustStoreProvider",
"javax.net.ssl.keyStoreType", "javax.net.ssl.keyStore", "javax.net.ssl.keyStoreProvider")
.forEach(propertyName -> builder.append(" " + propertyName + ": " + System.getProperty(propertyName) + "\n"));
return builder.append("]").toString();
}
public static void setProvider(CryptoProvider provider) { public static void setProvider(CryptoProvider provider) {
logger.debugf("Using the crypto provider: %s", provider.getClass().getName());
cryptoProvider = provider; cryptoProvider = provider;
} }
} }

View file

@ -0,0 +1,21 @@
package org.keycloak.common.crypto;
public enum FipsMode {
enabled("org.keycloak.crypto.fips.FIPS1402Provider"),
strict("org.keycloak.crypto.fips.Fips1402StrictCryptoProvider"),
disabled("org.keycloak.crypto.def.DefaultCryptoProvider");
private String providerClassName;
FipsMode(String providerClassName) {
this.providerClassName = providerClassName;
}
public boolean isFipsEnabled() {
return this.equals(enabled) || this.equals(strict);
}
public String getProviderClassName() {
return providerClassName;
}
}

View file

@ -17,7 +17,6 @@
package org.keycloak.common.util; package org.keycloak.common.util;
import org.jboss.logging.Logger;
import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.crypto.CryptoIntegration;
import java.security.Provider; import java.security.Provider;
@ -29,8 +28,6 @@ import java.security.Security;
*/ */
public class BouncyIntegration { public class BouncyIntegration {
private static final Logger log = Logger.getLogger(BouncyIntegration.class);
public static final String PROVIDER = loadProvider(); public static final String PROVIDER = loadProvider();
private static String loadProvider() { private static String loadProvider() {
@ -39,12 +36,6 @@ public class BouncyIntegration {
return Security.getProviders()[0].getName(); return Security.getProviders()[0].getName();
// throw new RuntimeException("Failed to load required security provider: BouncyCastleProvider or BouncyCastleFipsProvider"); // throw new RuntimeException("Failed to load required security provider: BouncyCastleProvider or BouncyCastleFipsProvider");
} }
if (Security.getProvider(provider.getName()) == null) {
Security.addProvider(provider);
log.debugv("Loaded {0} security provider", provider.getClass().getName());
} else {
log.debugv("Security provider {0} already loaded", provider.getClass().getName());
}
return provider.getName(); return provider.getName();
} }

View file

@ -27,6 +27,7 @@ import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec; import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jboss.logging.Logger;
import org.keycloak.common.crypto.CryptoProvider; import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.common.crypto.CryptoConstants; import org.keycloak.common.crypto.CryptoConstants;
import org.keycloak.common.crypto.ECDSACryptoProvider; import org.keycloak.common.crypto.ECDSACryptoProvider;
@ -42,6 +43,8 @@ import org.keycloak.crypto.JavaAlgorithm;
*/ */
public class DefaultCryptoProvider implements CryptoProvider { public class DefaultCryptoProvider implements CryptoProvider {
private static final Logger log = Logger.getLogger(DefaultCryptoProvider.class);
private final Provider bcProvider; private final Provider bcProvider;
private Map<String, Object> providers = new ConcurrentHashMap<>(); private Map<String, Object> providers = new ConcurrentHashMap<>();
@ -55,6 +58,13 @@ public class DefaultCryptoProvider implements CryptoProvider {
providers.put(CryptoConstants.RSA1_5, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/PKCS1Padding")); providers.put(CryptoConstants.RSA1_5, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/PKCS1Padding"));
providers.put(CryptoConstants.RSA_OAEP, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding")); providers.put(CryptoConstants.RSA_OAEP, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"));
providers.put(CryptoConstants.RSA_OAEP_256, new DefaultRsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")); providers.put(CryptoConstants.RSA_OAEP_256, new DefaultRsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"));
if (existingBc == null) {
Security.addProvider(this.bcProvider);
log.debugv("Loaded {0} security provider", this.bcProvider.getClass().getName());
} else {
log.debugv("Security provider {0} already loaded", this.bcProvider.getClass().getName());
}
} }

View file

@ -21,6 +21,7 @@ import java.security.cert.CertStore;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters; import java.security.cert.CollectionCertStoreParameters;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -33,7 +34,9 @@ import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.fips.FipsRSA; import org.bouncycastle.crypto.fips.FipsRSA;
import org.bouncycastle.crypto.fips.FipsSHS; import org.bouncycastle.crypto.fips.FipsSHS;
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECCurve;
import org.jboss.logging.Logger;
import org.keycloak.common.crypto.CryptoProvider; import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.common.crypto.ECDSACryptoProvider; import org.keycloak.common.crypto.ECDSACryptoProvider;
import org.keycloak.common.crypto.CryptoConstants; import org.keycloak.common.crypto.CryptoConstants;
@ -52,6 +55,8 @@ import org.keycloak.crypto.JavaAlgorithm;
*/ */
public class FIPS1402Provider implements CryptoProvider { public class FIPS1402Provider implements CryptoProvider {
private static final Logger log = Logger.getLogger(FIPS1402Provider.class);
private final BouncyCastleFipsProvider bcFipsProvider; private final BouncyCastleFipsProvider bcFipsProvider;
private final Map<String, Object> providers = new ConcurrentHashMap<>(); private final Map<String, Object> providers = new ConcurrentHashMap<>();
@ -66,6 +71,14 @@ public class FIPS1402Provider implements CryptoProvider {
providers.put(CryptoConstants.RSA_OAEP_256, new FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WRAP_OAEP.withDigest(FipsSHS.Algorithm.SHA256))); providers.put(CryptoConstants.RSA_OAEP_256, new FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WRAP_OAEP.withDigest(FipsSHS.Algorithm.SHA256)));
Security.insertProviderAt(new KeycloakFipsSecurityProvider(bcFipsProvider), 1); Security.insertProviderAt(new KeycloakFipsSecurityProvider(bcFipsProvider), 1);
if (existingBcFipsProvider == null) {
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()));
} else {
log.debugf("Security provider %s already loaded", existingBcFipsProvider.getName());
}
} }

View file

@ -0,0 +1,142 @@
package org.keycloak.crypto.fips.test;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSessionContext;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.Environment;
import org.keycloak.rule.CryptoInitRule;
import static org.hamcrest.Matchers.greaterThan;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class FIPS1402SslTest {
protected static final Logger logger = Logger.getLogger(FIPS1402SslTest.class);
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
@BeforeClass
public static void dumpSecurityProviders() {
logger.info(CryptoIntegration.dumpJavaSecurityProviders());
logger.info(CryptoIntegration.dumpSecurityProperties());
}
@Before
public void before() {
// Run this test just if java is in FIPS mode
Assume.assumeTrue("Java is not in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
}
@Test
public void testPkcs12KeyStoreWithPKIXKeyMgrFactory() throws Exception {
// PKCS12 keystore works just in non-approved mode
Assume.assumeFalse(CryptoServicesRegistrar.isInApprovedOnlyMode());
String type = "PKCS12";
String password = "passwordpassword";
KeyStore keystore = loadKeystore(type, password);
String keyMgrDefaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyMgrFact = getKeyMgrFactory(password, keystore, keyMgrDefaultAlgorithm);
testSSLContext(keyMgrFact);
}
// This works with BCFIPS, but requires addition of security provider "com.sun.net.ssl.internal.ssl.Provider BCFIPS" to Java Security providers
@Test
@Ignore("Skip for now and keep it just for the reference. We can check if we want to test this path with SunX509 algorithm withadditional security provider")
public void testPkcs12KeyStoreWithSunX509KeyMgrFactory() throws Exception {
// PKCS12 keystore works just in non-approved mode
Assume.assumeFalse(CryptoServicesRegistrar.isInApprovedOnlyMode());
String type = "PKCS12";
String password = "passwordpassword";
KeyStore keystore = loadKeystore(type, password);
String keyMgrDefaultAlgorithm = "SunX509";
KeyManagerFactory keyMgrFact = getKeyMgrFactory(password, keystore, keyMgrDefaultAlgorithm);
testSSLContext(keyMgrFact);
}
@Test
public void testBcfksKeyStoreWithPKIXKeyMgrFactory() throws Exception {
String type = "BCFKS";
String password = "passwordpassword";
KeyStore keystore = loadKeystore(type, password);
String keyMgrDefaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory keyMgrFact = getKeyMgrFactory(password, keystore, keyMgrDefaultAlgorithm);
testSSLContext(keyMgrFact);
}
// This works with BCFIPS, but requires addition of security provider "com.sun.net.ssl.internal.ssl.Provider BCFIPS" to Java Security providers
@Test
@Ignore("Skip for now and keep it just for the reference. We can check if we want to test this path with SunX509 algorithm withadditional security provider")
public void testBcfksKeyStoreWithSunX509KeyMgrFactory() throws Exception {
String type = "BCFKS";
String password = "passwordpassword";
KeyStore keystore = loadKeystore(type, password);
String keyMgrDefaultAlgorithm = "SunX509";
KeyManagerFactory keyMgrFact = getKeyMgrFactory(password, keystore, keyMgrDefaultAlgorithm);
testSSLContext(keyMgrFact);
}
private KeyStore loadKeystore(String type, String password) throws Exception {
KeyStore keystore = KeyStore.getInstance(type);
InputStream in = FIPS1402SslTest.class.getClassLoader().getResourceAsStream("bcfips-keystore." + type.toLowerCase());
keystore.load(in, password != null ? password.toCharArray() : null);
logger.infof("Keystore loaded successfully. Type: %s, provider: %s", keystore.getProvider().getName());
return keystore;
}
private KeyManagerFactory getKeyMgrFactory(String password, KeyStore keystore, String keyMgrAlgorithm) throws Exception {
KeyManagerFactory keyMgrFact = KeyManagerFactory.getInstance(keyMgrAlgorithm);
char[] keyPassword = password.toCharArray();
keyMgrFact.init(keystore, keyPassword);
logger.infof("KeyManagerFactory loaded for algorithm: %s", keyMgrAlgorithm);
return keyMgrFact;
}
private void testSSLContext(KeyManagerFactory keyMgrFact) throws Exception {
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyMgrFact.getKeyManagers(), null, null);
SSLEngine engine = context.createSSLEngine();
List<String> enabledCipherSuites = Arrays.asList(engine.getEnabledCipherSuites());
List<String> supportedProtocols = Arrays.asList(context.getDefaultSSLParameters().getProtocols());
List<String> supportedCiphers = Arrays.asList(engine.getSupportedCipherSuites());
logger.infof("Enabled ciphersuites: %s", enabledCipherSuites.size());
logger.infof("Supported protocols: %s", supportedProtocols);
logger.infof("Supported ciphers size: %d", supportedCiphers.size());
Assert.assertThat(enabledCipherSuites.size(), greaterThan(0));
Assert.assertThat(supportedProtocols.size(), greaterThan(0));
Assert.assertThat(supportedCiphers.size(), greaterThan(0));
SSLSessionContext sslServerCtx = context.getServerSessionContext();
Assert.assertNotNull(sslServerCtx);
}
}

View file

@ -1,29 +1,51 @@
# Configuration file just with the security properties, which are supposed to be overriden. The properties, which are not mentioned in this file, # 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 distribution. # 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) # 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) # 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) # List of providers and their preference orders (see above). Used on the host without FIPS (EG. when running the tests on GH actions)
# NOTE: List is empty for now, so we test just with BCFIPS provider, which is registered programatically # Uses only BouncyCastle FIPS providers to make sure to use only FIPS compliant cryptography.
# #
security.provider.1= 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. # Security providers used when global crypto-policies are set to FIPS (Usually it is used when FIPS enabled on system/JVM level)
# NOTE: List is empty for now, so we test just with BCFIPS provider, which is registered programatically
# #
fips.provider.1= fips.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
#fips.provider.1=SunPKCS11 ${java.home}/conf/security/nss.fips.cfg fips.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS
#fips.provider.2=SunEC fips.provider.3=
#fips.provider.3=com.sun.net.ssl.internal.ssl.Provider SunPKCS11-NSS-FIPS
# Commented this provider for now 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 # 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 # fips.provider.2=SUN
# #
# Default keystore type. # Default keystore type.
# #
keystore.type=PKCS11 keystore.type=PKCS12
fips.keystore.type=PKCS11 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

View file

@ -1,57 +1,140 @@
FIPS 140-2 Integration FIPS 140-2 Integration
====================== ======================
Build with FIPS Run the server with FIPS
--------------- ------------------------
With OpenJDK 11 on the classpath, run this from the project root directory: To run Keycloak quarkus distribution, on the FIPS enabled host and FIPS enabled OpenJDK, you need to:
- Make sure that Keycloak will use the BouncyCastle FIPS dependencies instead of the normal BouncyCastle dependencies
- Make sure to start the server with the FIPS mode.
1) Copy BCFIPS dependencies to your Keycloak distribution.
You can either download them from BouncyCastle page and add it manually to the directory `KEYCLOAK_HOME/providers`(make sure to
use proper versions compatible with BouncyCastle Keycloak dependencies).
Or you can use for example commands like this to copy the appropriate BCFIPS jars to the Keycloak distribution. Again, replace
the BCFIPS versions with the appropriate versions from pom.xml. Assumption is that you have already these BCFIPS in
your local maven repository, which can be achieved for example by building `crypto/fips1402` module (See the section for
running the unit tests below):
``` ```
mvn clean install -DskipTests=true -Dfips140-2 -Pquarkus cd $KEYCLOAK_HOME/bin
``` export MAVEN_REPO_HOME=$HOME/.m2/repository
The property `fips140-2` is used to trigger maven profile to build keycloak+quarkus distribution with `bouncycastle-fips` dependencies instead of plain `bouncycastle` cp $MAVEN_REPO_HOME/org/bouncycastle/bc-fips/1.0.2.3/bc-fips-1.0.2.3.jar ../providers/
and also with `keycloak-crypto-fips1402` module containing some security code dependent on bouncycastle-fips APIs. cp $MAVEN_REPO_HOME/org/bouncycastle/bctls-fips/1.0.12.2/bctls-fips-1.0.12.2.jar ../providers/
cp $MAVEN_REPO_HOME/org/bouncycastle/bcpkix-fips/1.0.5/bcpkix-fips-1.0.5.jar ../providers/
Note, that if you ommit the `fips140-2` property from the command above, then the quarkus distribution will be built
with the plain non-fips bouncycastle dependencies and with `keycloak-crypto-default` module.
Then unzip and check only bouncycastle-fips libraries are inside "lib" directory:
```
tar xf $KEYCLOAK_SOURCES/quarkus/dist/target/keycloak-999-SNAPSHOT.tar.gz
ls keycloak-999-SNAPSHOT/lib/lib/main/org.bouncycastle.bc*
```
Output should be something like:
```
keycloak-999-SNAPSHOT/lib/lib/main/org.bouncycastle.bc-fips-1.0.2.jar keycloak-999-SNAPSHOT/lib/lib/main/org.bouncycastle.bctls-fips-1.0.11.jar
keycloak-999-SNAPSHOT/lib/lib/main/org.bouncycastle.bcpkix-fips-1.0.3.jar
``` ```
Similarly the JAR keycloak-fips-integration should be available: 2) Now create either pkcs12 or bcfks keystore. The pkcs12 works just in BCFIPS non-approved mode.
Please choose either `bcfips` or `pkcs12` and use the appropriate value of `KEYSTORE_FILE` variable according to your choice:
Also make sure to set `KEYCLOAK_SOURCES` to the location with your Keycloak codebase.
Note that for keystore generation, it is needed to use the BouncyCastle FIPS libraries and use custom security file, which
will remove default SUN and SunPKCS11 providers as it doesn't work to create keystore with them on FIPS enabled OpenJDK11 due
the limitation described here https://access.redhat.com/solutions/6954451 and in the related bugzilla https://bugzilla.redhat.com/show_bug.cgi?id=2048582.
``` ```
ls keycloak-999-SNAPSHOT/lib/lib/main/org.keycloak.keycloak-fips-integration-999-SNAPSHOT.jar export KEYSTORE_FILE=keycloak-server.pkcs12
#export KEYSTORE_FILE=keycloak-server.bcfks
export KEYCLOAK_SOURCES=$HOME/IdeaProjects/keycloak
export KEYSTORE_FORMAT=$(echo $KEYSTORE_FILE | cut -d. -f2)
# Removing old keystore file to start from fresh
rm keycloak-server.pkcs12
rm keycloak-server.bcfks
keytool -keystore $KEYSTORE_FILE \
-storetype $KEYSTORE_FORMAT \
-providername BCFIPS \
-providerclass org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \
-providerpath $MAVEN_REPO_HOME/org/bouncycastle/bc-fips/1.0.2.3/bc-fips-1.0.2.3.jar \
-alias localhost \
-genkeypair -sigalg SHA512withRSA -keyalg RSA -storepass passwordpassword \
-dname CN=localhost -keypass passwordpassword \
-J-Djava.security.properties=$KEYCLOAK_SOURCES/crypto/fips1402/src/test/resources/kc.java.security
``` ```
Now run the server on the FIPS enabled machine with FIPS-enabled OpenJDK (Tested on RHEL 8.6): 3) Run "build" to re-augment with `enabled` fips mode and start the server.
For the `fips-mode`, he alternative is to use `--fips-mode=strict` in which case BouncyCastle FIPS will use "approved mode",
which means even stricter security algorithms. As mentioned above, strict mode won't work with `pkcs12` keystore:
``` ```
cd keycloak-999-SNAPSHOT/bin ./kc.sh build --fips-mode=enabled
./kc.sh start-dev ./kc.sh start --optimized --hostname=localhost \
--https-key-store-file=$PWD/$KEYSTORE_FILE \
--https-key-store-type=$KEYSTORE_FORMAT \
--https-key-store-password=passwordpassword \
--log-level=INFO,org.keycloak.common.crypto:TRACE,org.keycloak.crypto:TRACE
``` ```
NOTE: Right now, server should start, and you should be able to use `http://localhost:8080` and login to admin console etc. 4) The approach above will run the Keycloak JVM with all the default java security providers and will add also
Keycloak will now use bouncycastle-fips libraries and the `CryptoIntegration` will use `FIPS1402Provider`. BouncyCastle FIPS security providers on top of that in runtime. This works fine, however it may not be guaranteed that
all the crypto algorithms are used in the FIPS compliant way as the default providers like "Sun" potentially allow non-FIPS
usage in the Java. Some more details here: 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
Run the tests in the FIPS environment To ensure that Java strictly allows to use only FIPS-compliant crypto, it can be good to rely solely just on the BCFIPS.
------------------------------------- This is possible by using custom java security file, which adds just the BouncyCastle FIPS security providers. This requires
BouncyCastle FIPS dependencies to be available in the bootstrap classpath instead of adding them in runtime.
So for this approach, it is needed to move the BCFIPS jars from the `providers` directory to bootstrap classpath.
```
mkdir ../lib/bootstrap
mv ../providers/bc*.jar ../lib/bootstrap/
```
Then run `build` and `start` commands as above, but with additional property for the alternative security file like
```
-Djava.security.properties=$KEYCLOAK_SOURCES/crypto/fips1402/src/test/resources/kc.java.security
```
At the server startup, you should see the message like this in the log and you can check if correct providers are present and not any others:
```
2022-10-10 08:23:07,097 TRACE [org.keycloak.common.crypto.CryptoIntegration] (main) Java security providers: [
KC(BCFIPS version 1.000203) version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider,
BCFIPS version 1.000203 - class org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider,
BCJSSE version 1.001202 - class org.bouncycastle.jsse.provider.BouncyCastleJsseProvider,
]
```
NOTE: If you want to use BouncyCastle approved mode, then it is recommended to change/add these properties into the `kc.java.security`
file:
```
keystore.type=BCFKS
fips.keystore.type=BCFKS
org.bouncycastle.fips.approved_only=true
```
and then check that startup log contains `KC` provider contains KC provider with the note about `Approved Mode` like this:
```
KC(BCFIPS version 1.000203 Approved Mode) version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider,
```
Note that in approved mode, there are few limitations at the moment like for example:
- User passwords must be at least 14 characters long
- Keystore/truststore must be of type bcfks due the both of `jks` and `pkcs12` don't work
- Some warnings in the server.log at startup
Run the unit tests in the FIPS environment
------------------------------------------
This instruction is about running automated tests on the FIPS enabled RHEL 8.6 system with the FIPS enabled OpenJDK 11. This instruction is about running automated tests on the FIPS enabled RHEL 8.6 system with the FIPS enabled OpenJDK 11.
So far only the unit tests inside the `crypto` module are supported. More effort is needed to have whole testsuite passing. So far only the unit tests inside the `crypto` module are supported. More effort is needed to have whole testsuite passing.
First it is needed to build the project (See above). Then run the tests in the `crypto` module. First it is needed to build the project (See above). Then run the tests in the `crypto` module.
``` ```
mvn clean install -f crypto mvn clean install -f common -DskipTests=true
mvn clean install -f core -DskipTests=true
mvn clean install -f server-spi -DskipTests=true
mvn clean install -f server-spi-private -DskipTests=true
mvn clean install -f crypto/fips1402
``` ```
The tests should work also with the BouncyCastle approved mode, which is more strict in the used crypto algorithms The tests should work also with the BouncyCastle approved mode, which is more strict in the used crypto algorithms
``` ```
mvn clean install -f crypto -Dorg.bouncycastle.fips.approved_only=true mvn clean install -f crypto/fips1402 -Dorg.bouncycastle.fips.approved_only=true
``` ```
Run the integration tests in the FIPS environment
-------------------------------------------------
See the FIPS section in the [MySQL docker image](../testsuite/integration-arquillian/HOW-TO-RUN.md)

View file

@ -1,27 +1,9 @@
package org.keycloak.config; package org.keycloak.config;
import org.keycloak.common.crypto.FipsMode;
public class SecurityOptions { public class SecurityOptions {
public enum FipsMode {
enabled("org.keycloak.crypto.fips.FIPS1402Provider"),
strict("org.keycloak.crypto.fips.Fips1402StrictCryptoProvider"),
disabled("org.keycloak.crypto.def.DefaultCryptoProvider");
private String providerClassName;
FipsMode(String providerClassName) {
this.providerClassName = providerClassName;
}
public boolean isFipsEnabled() {
return this.equals(enabled) || this.equals(strict);
}
public String getProviderClassName() {
return providerClassName;
}
}
public static final Option<FipsMode> FIPS_MODE = new OptionBuilder<>("fips-mode", FipsMode.class) public static final Option<FipsMode> FIPS_MODE = new OptionBuilder<>("fips-mode", FipsMode.class)
.category(OptionCategory.SECURITY) .category(OptionCategory.SECURITY)
.buildTime(true) .buildTime(true)

View file

@ -89,6 +89,7 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import org.jboss.resteasy.spi.ResteasyDeployment; import org.jboss.resteasy.spi.ResteasyDeployment;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.crypto.FipsMode;
import org.keycloak.config.SecurityOptions; import org.keycloak.config.SecurityOptions;
import org.keycloak.config.StorageOptions; import org.keycloak.config.StorageOptions;
import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.connections.jpa.JpaConnectionProvider;
@ -305,10 +306,9 @@ class KeycloakProcessor {
* *
* @param recorder * @param recorder
*/ */
@Consume(BootstrapConfigSetupCompleteBuildItem.class)
@Record(ExecutionTime.STATIC_INIT) @Record(ExecutionTime.STATIC_INIT)
@BuildStep @BuildStep
KeycloakSessionFactoryPreInitBuildItem configureProviders(KeycloakRecorder recorder, List<PersistenceXmlDescriptorBuildItem> descriptors) { KeycloakSessionFactoryPreInitBuildItem configureKeycloakSessionFactory(KeycloakRecorder recorder, List<PersistenceXmlDescriptorBuildItem> descriptors) {
Profile.setInstance(new QuarkusProfile()); Profile.setInstance(new QuarkusProfile());
Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories = new HashMap<>(); Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories = new HashMap<>();
Map<Class<? extends Provider>, String> defaultProviders = new HashMap<>(); Map<Class<? extends Provider>, String> defaultProviders = new HashMap<>();
@ -572,13 +572,13 @@ class KeycloakProcessor {
})); }));
} }
@Consume(KeycloakSessionFactoryPreInitBuildItem.class) @Consume(BootstrapConfigSetupCompleteBuildItem.class)
@BuildStep @BuildStep
@Record(ExecutionTime.STATIC_INIT) @Record(ExecutionTime.STATIC_INIT)
void setCryptoProvider(KeycloakRecorder recorder) { void setCryptoProvider(KeycloakRecorder recorder) {
SecurityOptions.FipsMode fipsMode = Configuration.getOptionalValue( FipsMode fipsMode = Configuration.getOptionalValue(
MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + SecurityOptions.FIPS_MODE.getKey()).map( MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + SecurityOptions.FIPS_MODE.getKey()).map(
SecurityOptions.FipsMode::valueOf).orElse(SecurityOptions.FipsMode.disabled); FipsMode::valueOf).orElse(FipsMode.disabled);
recorder.setCryptoProvider(fipsMode); recorder.setCryptoProvider(fipsMode);
} }

View file

@ -124,7 +124,7 @@ if "x%JAVA_HOME%" == "x" (
) )
) )
set "CLASSPATH_OPTS=%DIRNAME%..\lib\quarkus-run.jar" set "CLASSPATH_OPTS=%DIRNAME%..\lib\quarkus-run.jar:%DIRNAME%..\lib\bootstrap\*"
set "JAVA_RUN_OPTS=%JAVA_OPTS% -Dkc.home.dir="%DIRNAME%.." -Djboss.server.config.dir="%DIRNAME%..\conf" -Dkeycloak.theme.dir="%DIRNAME%..\themes" %SERVER_OPTS% -cp "%CLASSPATH_OPTS%" io.quarkus.bootstrap.runner.QuarkusEntryPoint %CONFIG_ARGS%" set "JAVA_RUN_OPTS=%JAVA_OPTS% -Dkc.home.dir="%DIRNAME%.." -Djboss.server.config.dir="%DIRNAME%..\conf" -Dkeycloak.theme.dir="%DIRNAME%..\themes" %SERVER_OPTS% -cp "%CLASSPATH_OPTS%" io.quarkus.bootstrap.runner.QuarkusEntryPoint %CONFIG_ARGS%"

View file

@ -36,7 +36,7 @@ SERVER_OPTS="-Dkc.home.dir='$(abs_path '..')'"
SERVER_OPTS="$SERVER_OPTS -Djboss.server.config.dir='$(abs_path '../conf')'" SERVER_OPTS="$SERVER_OPTS -Djboss.server.config.dir='$(abs_path '../conf')'"
SERVER_OPTS="$SERVER_OPTS -Djava.util.logging.manager=org.jboss.logmanager.LogManager" SERVER_OPTS="$SERVER_OPTS -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
SERVER_OPTS="$SERVER_OPTS -Dquarkus-log-max-startup-records=10000" SERVER_OPTS="$SERVER_OPTS -Dquarkus-log-max-startup-records=10000"
CLASSPATH_OPTS="'$(abs_path "../lib/quarkus-run.jar")'" CLASSPATH_OPTS="'$(abs_path "../lib/quarkus-run.jar"):$(abs_path "../lib/bootstrap/*")'"
DEBUG_MODE="${DEBUG:-false}" DEBUG_MODE="${DEBUG:-false}"
DEBUG_PORT="${DEBUG_PORT:-8787}" DEBUG_PORT="${DEBUG_PORT:-8787}"

View file

@ -40,7 +40,7 @@ import org.keycloak.Config;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.CryptoProvider; import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.config.SecurityOptions; import org.keycloak.common.crypto.FipsMode;
import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory; import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
@ -166,7 +166,7 @@ public class KeycloakRecorder {
}; };
} }
public void setCryptoProvider(SecurityOptions.FipsMode fipsMode) { public void setCryptoProvider(FipsMode fipsMode) {
String cryptoProvider = fipsMode.getProviderClassName(); String cryptoProvider = fipsMode.getProviderClassName();
try { try {

View file

@ -3,6 +3,8 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.util.Optional; import java.util.Optional;
import org.keycloak.common.crypto.FipsMode;
import org.keycloak.config.ClassLoaderOptions; import org.keycloak.config.ClassLoaderOptions;
import org.keycloak.config.SecurityOptions; import org.keycloak.config.SecurityOptions;
import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.Environment;
@ -30,7 +32,7 @@ final class ClassLoaderPropertyMappers {
ConfigValue fipsEnabled = Configuration.getConfigValue( ConfigValue fipsEnabled = Configuration.getConfigValue(
MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + SecurityOptions.FIPS_MODE.getKey()); MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + SecurityOptions.FIPS_MODE.getKey());
if (fipsEnabled != null && SecurityOptions.FipsMode.valueOf(fipsEnabled.getValue()).isFipsEnabled()) { if (fipsEnabled != null && FipsMode.valueOf(fipsEnabled.getValue()).isFipsEnabled()) {
return Optional.of( 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.keycloak:keycloak-crypto-default");
} }

View file

@ -4,6 +4,8 @@ import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.util.Optional; import java.util.Optional;
import org.keycloak.common.crypto.FipsMode;
import org.keycloak.config.SecurityOptions; import org.keycloak.config.SecurityOptions;
import io.smallrye.config.ConfigSourceInterceptorContext; import io.smallrye.config.ConfigSourceInterceptorContext;
@ -23,16 +25,16 @@ final class SecurityPropertyMappers {
private static Optional<String> resolveFipsMode(Optional<String> value, ConfigSourceInterceptorContext context) { private static Optional<String> resolveFipsMode(Optional<String> value, ConfigSourceInterceptorContext context) {
if (value.isEmpty()) { if (value.isEmpty()) {
return of(SecurityOptions.FipsMode.disabled.toString()); return of(FipsMode.disabled.toString());
} }
return of(SecurityOptions.FipsMode.valueOf(value.get()).toString()); return of(FipsMode.valueOf(value.get()).toString());
} }
private static Optional<String> resolveSecurityProvider(Optional<String> value, private static Optional<String> resolveSecurityProvider(Optional<String> value,
ConfigSourceInterceptorContext configSourceInterceptorContext) { ConfigSourceInterceptorContext configSourceInterceptorContext) {
SecurityOptions.FipsMode fipsMode = value.map(SecurityOptions.FipsMode::valueOf) FipsMode fipsMode = value.map(FipsMode::valueOf)
.orElse(SecurityOptions.FipsMode.disabled); .orElse(FipsMode.disabled);
if (fipsMode.isFipsEnabled()) { if (fipsMode.isFipsEnabled()) {
return of("BCFIPS"); return of("BCFIPS");

View file

@ -38,8 +38,6 @@ import javax.mail.internet.MimeMultipart;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
@ -49,9 +47,8 @@ import java.util.Properties;
*/ */
public class DefaultEmailSenderProvider implements EmailSenderProvider { public class DefaultEmailSenderProvider implements EmailSenderProvider {
private static final String SUPPORTED_SSL_PROTOCOLS = getSupportedSslProtocols();
private static final Logger logger = Logger.getLogger(DefaultEmailSenderProvider.class); private static final Logger logger = Logger.getLogger(DefaultEmailSenderProvider.class);
private static final String SUPPORTED_SSL_PROTOCOLS = getSupportedSslProtocols();
private final KeycloakSession session; private final KeycloakSession session;

View file

@ -19,6 +19,7 @@ package org.keycloak.truststore;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
@ -44,6 +45,8 @@ import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
/** /**
@ -66,6 +69,7 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
String storepath = config.get("file"); String storepath = config.get("file");
String pass = config.get("password"); String pass = config.get("password");
String policy = config.get("hostname-verification-policy"); String policy = config.get("hostname-verification-policy");
String configuredType = config.get("type");
// if "truststore" . "file" is not configured then it is disabled // if "truststore" . "file" is not configured then it is disabled
if (storepath == null && pass == null && policy == null) { if (storepath == null && pass == null && policy == null) {
@ -82,10 +86,11 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
throw new RuntimeException("Attribute 'password' missing in 'truststore':'file' configuration"); throw new RuntimeException("Attribute 'password' missing in 'truststore':'file' configuration");
} }
String type = getTruststoreType(storepath, configuredType);
try { try {
truststore = loadStore(storepath, pass == null ? null :pass.toCharArray()); truststore = loadStore(storepath, type, pass == null ? null :pass.toCharArray());
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Failed to initialize TruststoreProviderFactory: " + new File(storepath).getAbsolutePath(), e); throw new RuntimeException("Failed to initialize TruststoreProviderFactory: " + new File(storepath).getAbsolutePath() + ", truststore type: " + type, e);
} }
if (policy == null) { if (policy == null) {
verificationPolicy = HostnameVerificationPolicy.WILDCARD; verificationPolicy = HostnameVerificationPolicy.WILDCARD;
@ -101,11 +106,11 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
provider = new FileTruststoreProvider(truststore, verificationPolicy, Collections.unmodifiableMap(certsLoader.trustedRootCerts) provider = new FileTruststoreProvider(truststore, verificationPolicy, Collections.unmodifiableMap(certsLoader.trustedRootCerts)
, Collections.unmodifiableMap(certsLoader.intermediateCerts)); , Collections.unmodifiableMap(certsLoader.intermediateCerts));
TruststoreProviderSingleton.set(provider); TruststoreProviderSingleton.set(provider);
log.debug("File truststore provider initialized: " + new File(storepath).getAbsolutePath()); log.debugf("File truststore provider initialized: %s, Truststore type: %s", new File(storepath).getAbsolutePath(), type);
} }
private KeyStore loadStore(String path, char[] password) throws Exception { private KeyStore loadStore(String path, String type, char[] password) throws Exception {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore ks = KeyStore.getInstance(type);
InputStream is = new FileInputStream(path); InputStream is = new FileInputStream(path);
try { try {
ks.load(is, password); ks.load(is, password);
@ -154,6 +159,25 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
.build(); .build();
} }
private String getTruststoreType(String path, String configuredType) {
// Configured type has precedence
if (configuredType != null) return configuredType;
// Fallback to detected tyoe from the file format (EG. my-keystore.pkcs12 will return "pkcs12")
int lastDotIndex = path.lastIndexOf('.');
if (lastDotIndex > -1) {
String ext = path.substring(lastDotIndex).toUpperCase();
Optional<String> detectedType = Arrays.stream(KeystoreUtil.KeystoreFormat.values())
.map(KeystoreUtil.KeystoreFormat::toString)
.filter(ksFormat -> ksFormat.equals(ext))
.findFirst();
if (detectedType.isPresent()) return detectedType.get();
}
// Fallback to default JVM
return KeyStore.getDefaultType();
}
private static class TruststoreCertificatesLoader { private static class TruststoreCertificatesLoader {
private Map<X500Principal, X509Certificate> trustedRootCerts = new HashMap<>(); private Map<X500Principal, X509Certificate> trustedRootCerts = new HashMap<>();

View file

@ -923,3 +923,25 @@ DefaultHostnameTest.java:226)
when running these tests on your local machine. This happens when something on your machine or network is blocking DNS queries to [nip.io](https://nip.io) when running these tests on your local machine. This happens when something on your machine or network is blocking DNS queries to [nip.io](https://nip.io)
One possible workaround is to add a commonly used public dns server (e.g. 8.8.8.8 for google dns server) to your local One possible workaround is to add a commonly used public dns server (e.g. 8.8.8.8 for google dns server) to your local
networks dns configuration and run the tests. networks dns configuration and run the tests.
## FIPS 140-2 testing
On the FIPS enabled platform with FIPS enabled OpenJDK 11, you can run this to test against Keycloak server on Quarkus
with FIPS 140.2 integration enabled
```
mvn -B -f testsuite/integration-arquillian/pom.xml \
clean install \
-Pauth-server-quarkus,auth-server-fips140-2 \
-Dcom.redhat.fips=false
```
NOTE 1: The property `com.redhat.fips` is needed so that testsuite itself is executed in the JVM with FIPS disabled. However
most important part is that Keycloak itself is running on the JVM with FIPS enabled. You can check log from server startup and
there should be messages similar to those:
```
2022-10-11 19:34:29,521 DEBUG [org.keycloak.common.crypto.CryptoIntegration] (main) Using the crypto provider: org.keycloak.crypto.fips.FIPS1402Provider
2022-10-11 19:34:31,072 TRACE [org.keycloak.common.crypto.CryptoIntegration] (main) Java security providers: [
KC(BCFIPS version 1.000203) version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider,
BCFIPS version 1.000203 - class org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider,
BCJSSE version 1.001202 - class org.bouncycastle.jsse.provider.BouncyCastleJsseProvider,
]
```

View file

@ -0,0 +1,51 @@
# 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

View file

@ -21,6 +21,7 @@
<auth.server.quarkus.cluster.config>local</auth.server.quarkus.cluster.config> <auth.server.quarkus.cluster.config>local</auth.server.quarkus.cluster.config>
<!-- Path to testsuite/test resources : Themes--> <!-- Path to testsuite/test resources : Themes-->
<keycloak.base.test.resource>${project.parent.basedir}/../../tests/base/src/test/resources</keycloak.base.test.resource> <keycloak.base.test.resource>${project.parent.basedir}/../../tests/base/src/test/resources</keycloak.base.test.resource>
<auth.server.fips.mode>disabled</auth.server.fips.mode>
</properties> </properties>
<dependencies> <dependencies>
@ -300,5 +301,68 @@
</build> </build>
</profile> </profile>
<profile>
<id>auth-server-fips140-2</id>
<properties>
<auth.server.fips.mode>enabled</auth.server.fips.mode>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-crypto-fips1402</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-fips-pki-conf</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${auth.server.home}/conf</outputDirectory>
<resources>
<resource>
<directory>${common.resources}/fips</directory>
<includes>
<include>**</include>
</includes>
</resource>
</resources>
<overwrite>true</overwrite>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-bcfips-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${auth.server.home}/lib/bootstrap</outputDirectory>
<includeArtifactIds>bc-fips,bctls-fips,bcpkix-fips</includeArtifactIds>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles> </profiles>
</project> </project>

View file

@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import org.jboss.arquillian.container.spi.ConfigurationException; import org.jboss.arquillian.container.spi.ConfigurationException;
import org.jboss.arquillian.container.spi.client.container.ContainerConfiguration; import org.jboss.arquillian.container.spi.client.container.ContainerConfiguration;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.common.crypto.FipsMode;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import java.io.IOException; import java.io.IOException;
@ -23,6 +24,20 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
private int bindHttpPort = 8080; private int bindHttpPort = 8080;
private int bindHttpsPortOffset = 0; private int bindHttpsPortOffset = 0;
private int bindHttpsPort = Integer.getInteger("auth.server.https.port", 8543); private int bindHttpsPort = Integer.getInteger("auth.server.https.port", 8543);
private String keystoreFile = System.getProperty("auth.server.keystore");
private String keystorePassword = System.getProperty("auth.server.keystore.password");
private String keystoreType = System.getProperty("auth.server.keystore.type");
private String truststoreFile = System.getProperty("auth.server.truststore");
private String truststorePassword = System.getProperty("auth.server.truststore.password");
private String truststoreType = System.getProperty("auth.server.truststore.type");
private int debugPort = -1; private int debugPort = -1;
private Path providersPath = Paths.get(System.getProperty("auth.server.home")); private Path providersPath = Paths.get(System.getProperty("auth.server.home"));
private int startupTimeoutInSeconds = 300; private int startupTimeoutInSeconds = 300;
@ -34,6 +49,8 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
private boolean reaugmentBeforeStart; private boolean reaugmentBeforeStart;
private String importFile = System.getProperty("migration.import.file.name"); private String importFile = System.getProperty("migration.import.file.name");
private FipsMode fipsMode = FipsMode.valueOf(System.getProperty("auth.server.fips.mode"));
@Override @Override
public void validate() throws ConfigurationException { public void validate() throws ConfigurationException {
int basePort = getBindHttpPort(); int basePort = getBindHttpPort();
@ -88,6 +105,54 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
this.bindHttpPort = bindHttpPort; this.bindHttpPort = bindHttpPort;
} }
public String getKeystoreFile() {
return keystoreFile;
}
public void setKeystoreFile(String keystoreFile) {
this.keystoreFile = keystoreFile;
}
public String getKeystorePassword() {
return keystorePassword;
}
public void setKeystorePassword(String keystorePassword) {
this.keystorePassword = keystorePassword;
}
public String getKeystoreType() {
return keystoreType;
}
public void setKeystoreType(String keystoreType) {
this.keystoreType = keystoreType;
}
public String getTruststoreFile() {
return truststoreFile;
}
public void setTruststoreFile(String truststoreFile) {
this.truststoreFile = truststoreFile;
}
public String getTruststorePassword() {
return truststorePassword;
}
public void setTruststorePassword(String truststorePassword) {
this.truststorePassword = truststorePassword;
}
public String getTruststoreType() {
return truststoreType;
}
public void setTruststoreType(String truststoreType) {
this.truststoreType = truststoreType;
}
public Path getProvidersPath() { public Path getProvidersPath() {
return providersPath; return providersPath;
} }
@ -140,6 +205,14 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
return javaOpts; return javaOpts;
} }
public void appendJavaOpts(String javaOpts) {
if (javaOpts == null) {
setJavaOpts(javaOpts);
} else {
setJavaOpts(this.javaOpts + " " + javaOpts);
}
}
public boolean isReaugmentBeforeStart() { public boolean isReaugmentBeforeStart() {
return reaugmentBeforeStart; return reaugmentBeforeStart;
} }
@ -163,4 +236,12 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
public void setImportFile(String importFile) { public void setImportFile(String importFile) {
this.importFile = importFile; this.importFile = importFile;
} }
public FipsMode getFipsMode() {
return fipsMode;
}
public void setFipsMode(FipsMode fipsMode) {
this.fipsMode = fipsMode;
}
} }

View file

@ -47,6 +47,7 @@ import org.jboss.logging.Logger;
import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.exporter.ZipExporter; import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.descriptor.api.Descriptor; import org.jboss.shrinkwrap.descriptor.api.Descriptor;
import org.keycloak.common.crypto.FipsMode;
import org.keycloak.testsuite.arquillian.SuiteContext; import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.model.StoreProvider; import org.keycloak.testsuite.model.StoreProvider;
@ -227,8 +228,10 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
final Supplier<Boolean> shouldSetUpDb = () -> !restart.get() && !storeProvider.equals(StoreProvider.DEFAULT); final Supplier<Boolean> shouldSetUpDb = () -> !restart.get() && !storeProvider.equals(StoreProvider.DEFAULT);
final Supplier<String> getClusterConfig = () -> System.getProperty("auth.server.quarkus.cluster.config", "local"); final Supplier<String> getClusterConfig = () -> System.getProperty("auth.server.quarkus.cluster.config", "local");
log.debugf("FIPS Mode: %s", configuration.getFipsMode());
// only run build during first execution of the server (if the DB is specified), restarts or when running cluster tests // only run build during first execution of the server (if the DB is specified), restarts or when running cluster tests
if (restart.get() || shouldSetUpDb.get() || "ha".equals(getClusterConfig.get())) { if (restart.get() || shouldSetUpDb.get() || "ha".equals(getClusterConfig.get()) || configuration.getFipsMode() != FipsMode.disabled) {
commands.removeIf("--optimized"::equals); commands.removeIf("--optimized"::equals);
commands.add("--http-relative-path=/auth"); commands.add("--http-relative-path=/auth");
@ -241,6 +244,10 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
commands.add("--cache-config-file=cluster-" + cacheMode + ".xml"); commands.add("--cache-config-file=cluster-" + cacheMode + ".xml");
} }
} }
if (configuration.getFipsMode() != FipsMode.disabled) {
addFipsOptions(commands);
}
} }
addStorageOptions(storeProvider, commands); addStorageOptions(storeProvider, commands);
@ -257,6 +264,26 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
storeProvider.addStoreOptions(commands); storeProvider.addStoreOptions(commands);
} }
private void addFipsOptions(List<String> commands) {
commands.add("--fips-mode=" + configuration.getFipsMode().toString());
log.debugf("Keystore file: %s, keystore type: %s, truststore file: %s, truststore type: %s",
configuration.getKeystoreFile(), configuration.getKeystoreType(),
configuration.getTruststoreFile(), configuration.getTruststoreType());
commands.add("--https-key-store-file=" + configuration.getKeystoreFile());
commands.add("--https-key-store-type=" + configuration.getKeystoreType());
commands.add("--https-key-store-password=" + configuration.getKeystorePassword());
commands.add("--https-trust-store-file=" + configuration.getTruststoreFile());
commands.add("--https-trust-store-type=" + configuration.getTruststoreType());
commands.add("--https-trust-store-password=" + configuration.getTruststorePassword());
commands.add("--spi-truststore-file-file=" + configuration.getTruststoreFile());
commands.add("--spi-truststore-file-password=" + configuration.getTruststorePassword());
commands.add("--spi-truststore-file-type=" + configuration.getTruststoreType());
commands.add("--log-level=INFO,org.keycloak.common.crypto:TRACE,org.keycloak.crypto:TRACE,org.keycloak.truststore:TRACE");
configuration.appendJavaOpts("-Djava.security.properties=" + System.getProperty("auth.server.java.security.file"));
}
private void waitForReadiness() throws MalformedURLException, LifecycleException { private void waitForReadiness() throws MalformedURLException, LifecycleException {
SuiteContext suiteContext = this.suiteContext.get(); SuiteContext suiteContext = this.suiteContext.get();
//TODO: not sure if the best endpoint but it makes sure that everything is properly initialized. Once we have //TODO: not sure if the best endpoint but it makes sure that everything is properly initialized. Once we have

View file

@ -73,8 +73,10 @@
<auth.server.adapter.impl.class>org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</auth.server.adapter.impl.class> <auth.server.adapter.impl.class>org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</auth.server.adapter.impl.class>
<auth.server.truststore>${auth.server.config.dir}/keycloak.truststore</auth.server.truststore> <auth.server.truststore>${auth.server.config.dir}/keycloak.truststore</auth.server.truststore>
<auth.server.truststore.password>secret</auth.server.truststore.password> <auth.server.truststore.password>secret</auth.server.truststore.password>
<auth.server.truststore.type>jks</auth.server.truststore.type>
<auth.server.keystore>${auth.server.config.dir}/keycloak.jks</auth.server.keystore> <auth.server.keystore>${auth.server.config.dir}/keycloak.jks</auth.server.keystore>
<auth.server.keystore.password>secret</auth.server.keystore.password> <auth.server.keystore.password>secret</auth.server.keystore.password>
<auth.server.keystore.type>jks</auth.server.keystore.type>
<auth.server.jvm.args.extra/> <auth.server.jvm.args.extra/>
<auth.server.jboss.artifactId>integration-arquillian-servers-auth-server-${auth.server}</auth.server.jboss.artifactId> <auth.server.jboss.artifactId>integration-arquillian-servers-auth-server-${auth.server}</auth.server.jboss.artifactId>
@ -260,6 +262,7 @@
<auth.server.ocsp.responder.enabled>false</auth.server.ocsp.responder.enabled> <auth.server.ocsp.responder.enabled>false</auth.server.ocsp.responder.enabled>
<keycloak.x509cert.lookup.provider>default</keycloak.x509cert.lookup.provider> <keycloak.x509cert.lookup.provider>default</keycloak.x509cert.lookup.provider>
<auth.server.quarkus.cluster.config>local</auth.server.quarkus.cluster.config> <auth.server.quarkus.cluster.config>local</auth.server.quarkus.cluster.config>
<auth.server.fips.mode>disabled</auth.server.fips.mode>
</properties> </properties>
<build> <build>
@ -522,8 +525,11 @@
<auth.server.jboss.jvm.debug.args>${auth.server.jboss.jvm.debug.args}</auth.server.jboss.jvm.debug.args> <auth.server.jboss.jvm.debug.args>${auth.server.jboss.jvm.debug.args}</auth.server.jboss.jvm.debug.args>
<auth.server.truststore>${auth.server.truststore}</auth.server.truststore> <auth.server.truststore>${auth.server.truststore}</auth.server.truststore>
<auth.server.truststore.password>${auth.server.truststore.password}</auth.server.truststore.password> <auth.server.truststore.password>${auth.server.truststore.password}</auth.server.truststore.password>
<auth.server.truststore.type>${auth.server.truststore.type}</auth.server.truststore.type>
<auth.server.keystore>${auth.server.keystore}</auth.server.keystore> <auth.server.keystore>${auth.server.keystore}</auth.server.keystore>
<auth.server.keystore.password>${auth.server.keystore.password}</auth.server.keystore.password> <auth.server.keystore.password>${auth.server.keystore.password}</auth.server.keystore.password>
<auth.server.keystore.type>${auth.server.keystore.type}</auth.server.keystore.type>
<auth.server.java.security.file>${auth.server.java.security.file}</auth.server.java.security.file>
<auth.server.jvm.args.extra>${auth.server.jvm.args.extra}</auth.server.jvm.args.extra> <auth.server.jvm.args.extra>${auth.server.jvm.args.extra}</auth.server.jvm.args.extra>
<auth.server.profile>${auth.server.profile}</auth.server.profile> <auth.server.profile>${auth.server.profile}</auth.server.profile>
@ -676,6 +682,10 @@
<keycloak.connectionsJpa.user>${keycloak.connectionsJpa.user}</keycloak.connectionsJpa.user> <keycloak.connectionsJpa.user>${keycloak.connectionsJpa.user}</keycloak.connectionsJpa.user>
<keycloak.connectionsJpa.password>${keycloak.connectionsJpa.password}</keycloak.connectionsJpa.password> <keycloak.connectionsJpa.password>${keycloak.connectionsJpa.password}</keycloak.connectionsJpa.password>
<!-- FIPS 140-2 -->
<auth.server.fips.mode>${auth.server.fips.mode}</auth.server.fips.mode>
<auth.server.fips.keystore.type>${auth.server.fips.keystore.type}</auth.server.fips.keystore.type>
<!-- <!--
~ Used for Wildfly Elytron 1.13.0.CR3+ RESTEasy client SSL truststore configuration. ~ Used for Wildfly Elytron 1.13.0.CR3+ RESTEasy client SSL truststore configuration.
~ See KEYCLOAK-15692, ELY-1891 issues & PRs of EAP7-1219 issue for details. ~ See KEYCLOAK-15692, ELY-1891 issues & PRs of EAP7-1219 issue for details.
@ -1572,6 +1582,23 @@
</properties> </properties>
</profile> </profile>
<profile>
<id>auth-server-fips140-2</id>
<properties>
<auth.server.fips.mode>enabled</auth.server.fips.mode>
<auth.server.keystore.type>pkcs12</auth.server.keystore.type>
<auth.server.keystore>${auth.server.config.dir}/keycloak-fips.keystore.${auth.server.keystore.type}</auth.server.keystore>
<auth.server.keystore.password>passwordpassword</auth.server.keystore.password>
<auth.server.truststore.type>${auth.server.keystore.type}</auth.server.truststore.type>
<auth.server.truststore>${auth.server.config.dir}/keycloak-fips.truststore.${auth.server.truststore.type}</auth.server.truststore>
<auth.server.truststore.password>passwordpassword</auth.server.truststore.password>
<auth.server.java.security.file>${auth.server.config.dir}/kc.java.security</auth.server.java.security.file>
</properties>
</profile>
<profile> <profile>
<id>common-test-dependencies</id> <id>common-test-dependencies</id>
<activation> <activation>