Initial support for running testsuite in BCFIPS approved mode

Closes #16429
This commit is contained in:
mposolda 2023-01-12 18:24:23 +01:00 committed by Pedro Igor
parent 6ac65f62d7
commit 79fa6bb3c9
8 changed files with 103 additions and 71 deletions

View file

@ -1,6 +1,11 @@
FIPS 140-2 Integration
======================
Environment
-----------
All the steps below were tested on RHEL 8.6 with FIPS mode enabled (See [this page](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/security_hardening/assembly_installing-a-rhel-8-system-with-fips-mode-enabled_security-hardening#doc-wrapper)
for the details) and with OpenJDK 17.0.5 on that host.
Run the server with FIPS
------------------------
@ -21,8 +26,8 @@ running the unit tests below):
cd $KEYCLOAK_HOME/bin
export MAVEN_REPO_HOME=$HOME/.m2/repository
export BCFIPS_VERSION=1.0.2.3
export BCTLSFIPS_VERSION=1.0.12.2
export BCPKIXFIPS_VERSION=1.0.5
export BCTLSFIPS_VERSION=1.0.14
export BCPKIXFIPS_VERSION=1.0.7
cp $MAVEN_REPO_HOME/org/bouncycastle/bc-fips/$BCFIPS_VERSION/bc-fips-$BCFIPS_VERSION.jar ../providers/
cp $MAVEN_REPO_HOME/org/bouncycastle/bctls-fips/$BCTLSFIPS_VERSION/bctls-fips-$BCTLSFIPS_VERSION.jar ../providers/
cp $MAVEN_REPO_HOME/org/bouncycastle/bcpkix-fips/$BCPKIXFIPS_VERSION/bcpkix-fips-$BCPKIXFIPS_VERSION.jar ../providers/
@ -38,14 +43,17 @@ Note that for keystore generation, it is needed to use the BouncyCastle FIPS lib
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.
```
export KEYSTORE_FILE=keycloak-server.pkcs12
export KEYSTORE_FILE=keycloak-server.p12
#export KEYSTORE_FILE=keycloak-server.bcfks
export KEYCLOAK_SOURCES=$HOME/IdeaProjects/keycloak
export KEYSTORE_FORMAT=$(echo $KEYSTORE_FILE | cut -d. -f2)
if [ "$KEYSTORE_FORMAT" == "p12" ]; then
export KEYSTORE_FORMAT=pkcs12;
fi;
# Removing old keystore file to start from fresh
rm keycloak-server.pkcs12
rm keycloak-server.p12
rm keycloak-server.bcfks
keytool -keystore $KEYSTORE_FILE \
@ -57,13 +65,10 @@ keytool -keystore $KEYSTORE_FILE \
-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
-J-Djava.security.properties=$KEYCLOAK_SOURCES/testsuite/integration-arquillian/servers/auth-server/common/fips/kc.keystore-create.java.security
```
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:
3) Run "build" to re-augment with `enabled` fips mode and start the server. This will run the server with BCFIPS in non-approved mode
```
./kc.sh start --fips-mode=enabled --hostname=localhost \
@ -73,48 +78,24 @@ which means even stricter security algorithms. As mentioned above, strict mode w
--log-level=INFO,org.keycloak.common.crypto:TRACE,org.keycloak.crypto:TRACE
```
4) The approach above will run the Keycloak JVM with all the default java security providers and will add also
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
4) For the `fips-mode` option, the more secure alternative is to use `--fips-mode=strict` in which case BouncyCastle FIPS will use "approved mode",
which means even stricter security requirements on cryptography and security algorithms. Few more points:
- As mentioned above, strict mode won't work with `pkcs12` keystore. So it is needed to use other keystore (probably `bcfks`).
- User passwords must be 14 characters or longer. Keycloak uses PBKDF2 based password encoding by default. BCFIPS approved mode requires passwords to be at least 112 bits
- (effectively 14 characters). If you want to allow shorter password, you need to set property `max-padding-length` of
provider `pbkdf2-sha256` of SPI `password-hashing` to value 14, so there will be some additional padding used when verifying hash created by this algorithm.
This is also backwards compatible with previously stored passwords (if you had your user's DB in non-FIPS environment and you have shorter passwords and you
want to verify them now with Keycloak using BCFIPS in approved mode, it should work fine). So effectively, you can use option like this when starting the server:
```
--spi-password-hashing-pbkdf2-sha256-max-padding-length=14
```
- RSA keys of 1024 bits don't work (2048 is the minimum)
- Also `jks` and `pkcs12` keystores/trustores are not supported.
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:
When starting server at startup, you can 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 CLI on the FIPS host
----------------------------
@ -150,5 +131,4 @@ 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)
See the FIPS section in the [HOW-TO-RUN.md](../testsuite/integration-arquillian/HOW-TO-RUN.md)

View file

@ -64,9 +64,13 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
if (hash == null) {
return false;
}
PasswordCredentialModel credentialModel = hash.encodedCredential(password, policy.getHashIterations());
credentialModel.setCreatedDate(Time.currentTimeMillis());
createCredential(realm, user, credentialModel);
try {
PasswordCredentialModel credentialModel = hash.encodedCredential(password, policy.getHashIterations());
credentialModel.setCreatedDate(Time.currentTimeMillis());
createCredential(realm, user, credentialModel);
} catch (Throwable t) {
throw new ModelException(t.getMessage(), t);
}
return true;
}
@ -174,27 +178,32 @@ public class PasswordCredentialProvider implements CredentialProvider<PasswordCr
logger.debugv("PasswordHashProvider {0} not found for user {1} ", password.getPasswordCredentialData().getAlgorithm(), user.getUsername());
return false;
}
if (!hash.verify(input.getChallengeResponse(), password)) {
logger.debugv("Failed password validation for user {0} ", user.getUsername());
try {
if (!hash.verify(input.getChallengeResponse(), password)) {
logger.debugv("Failed password validation for user {0} ", user.getUsername());
return false;
}
PasswordPolicy policy = realm.getPasswordPolicy();
if (policy == null) {
return true;
}
hash = getHashProvider(policy);
if (hash == null) {
return true;
}
if (hash.policyCheck(policy, password)) {
return true;
}
PasswordCredentialModel newPassword = hash.encodedCredential(input.getChallengeResponse(), policy.getHashIterations());
newPassword.setId(password.getId());
newPassword.setCreatedDate(password.getCreatedDate());
newPassword.setUserLabel(password.getUserLabel());
user.credentialManager().updateStoredCredential(newPassword);
} catch (Throwable t) {
logger.warn("Error when validating user password", t);
return false;
}
PasswordPolicy policy = realm.getPasswordPolicy();
if (policy == null) {
return true;
}
hash = getHashProvider(policy);
if (hash == null) {
return true;
}
if (hash.policyCheck(policy, password)) {
return true;
}
PasswordCredentialModel newPassword = hash.encodedCredential(input.getChallengeResponse(), policy.getHashIterations());
newPassword.setId(password.getId());
newPassword.setCreatedDate(password.getCreatedDate());
newPassword.setUserLabel(password.getUserLabel());
user.credentialManager().updateStoredCredential(newPassword);
return true;
}

View file

@ -968,3 +968,16 @@ there should be messages similar to those:
BCJSSE version 1.001202 - class org.bouncycastle.jsse.provider.BouncyCastleJsseProvider,
]
```
### BCFIPS approved mode
For running testsuite with server using BCFIPS approved mode, those additional properties should be added when running tests:
```
-Dauth.server.fips.mode=strict \
-Dauth.server.supported.keystore.types=BCFKS \
-Dauth.server.keystore.type=bcfks
```
The log should contain `KeycloakFipsSecurityProvider` mentioning "Approved mode". Something like:
```
KC(BCFIPS version 1.000203 Approved Mode) version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider,
```

View file

@ -0,0 +1,18 @@
How to convert keystores and truststores
----------------------------------------
Magic command to import PKCS12 keystore to BCFKS
```
keytool -importkeystore -srckeystore keycloak-fips.keystore.pkcs12 -destkeystore keycloak-fips.keystore.bcfks \
-srcstoretype PKCS12 -deststoretype BCFKS -deststorepass passwordpassword \
-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 \
-J-Djava.security.properties=$KEYCLOAK_SOURCES/testsuite/integration-arquillian/servers/auth-server/common/fips/kc.keystore-create.java.security
```
Default password is `passwordpassword`.
When converting from `JKS` to `PKCS12` on non-FIPS host, only first 2 lines from this command are needed (no need to use BCFIPS provider).
Original JKS keystore, which was used to create `PKCS12` (and transitively also `BCFKS`) keystore is [keycloak.jks](../keystore/keycloak.jks).
Original JKS truststore is [keycloak.truststore](../keystore/keycloak.truststore).

View file

@ -0,0 +1,3 @@
# This property is needed when creating keystore with BCFIPS provider on FIPS enabled RHEL 8.6 due the issue https://bugzilla.redhat.com/show_bug.cgi?id=2155060
# once that is fixed in the OpenJDK, we should be good.
securerandom.strongAlgorithms=PKCS11:SunPKCS11-NSS-FIPS

View file

@ -294,6 +294,15 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
commands.add("--spi-truststore-file-file=" + configuration.getTruststoreFile());
commands.add("--spi-truststore-file-password=" + configuration.getTruststorePassword());
commands.add("--spi-truststore-file-type=" + configuration.getTruststoreType());
// BCFIPS approved mode requires passwords of at least 112 bits (14 characters) to be used. To bypass this, we use this by default
// as testsuite uses shorter passwords everywhere
if (FipsMode.strict == configuration.getFipsMode()) {
commands.add("--spi-password-hashing-pbkdf2-max-padding-length=14");
commands.add("--spi-password-hashing-pbkdf2-sha256-max-padding-length=14");
commands.add("--spi-password-hashing-pbkdf2-sha512-max-padding-length=14");
}
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"));