Initial support for running testsuite in BCFIPS approved mode
Closes #16429
This commit is contained in:
parent
6ac65f62d7
commit
79fa6bb3c9
8 changed files with 103 additions and 71 deletions
78
docs/fips.md
78
docs/fips.md
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
```
|
||||
|
|
|
@ -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).
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
|
@ -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"));
|
||||
|
|
Loading…
Reference in a new issue