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
|
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
|
Run the server with FIPS
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
@ -21,8 +26,8 @@ running the unit tests below):
|
||||||
cd $KEYCLOAK_HOME/bin
|
cd $KEYCLOAK_HOME/bin
|
||||||
export MAVEN_REPO_HOME=$HOME/.m2/repository
|
export MAVEN_REPO_HOME=$HOME/.m2/repository
|
||||||
export BCFIPS_VERSION=1.0.2.3
|
export BCFIPS_VERSION=1.0.2.3
|
||||||
export BCTLSFIPS_VERSION=1.0.12.2
|
export BCTLSFIPS_VERSION=1.0.14
|
||||||
export BCPKIXFIPS_VERSION=1.0.5
|
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/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/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/
|
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
|
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.
|
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 KEYSTORE_FILE=keycloak-server.bcfks
|
||||||
export KEYCLOAK_SOURCES=$HOME/IdeaProjects/keycloak
|
export KEYCLOAK_SOURCES=$HOME/IdeaProjects/keycloak
|
||||||
|
|
||||||
export KEYSTORE_FORMAT=$(echo $KEYSTORE_FILE | cut -d. -f2)
|
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
|
# Removing old keystore file to start from fresh
|
||||||
rm keycloak-server.pkcs12
|
rm keycloak-server.p12
|
||||||
rm keycloak-server.bcfks
|
rm keycloak-server.bcfks
|
||||||
|
|
||||||
keytool -keystore $KEYSTORE_FILE \
|
keytool -keystore $KEYSTORE_FILE \
|
||||||
|
@ -57,13 +65,10 @@ keytool -keystore $KEYSTORE_FILE \
|
||||||
-alias localhost \
|
-alias localhost \
|
||||||
-genkeypair -sigalg SHA512withRSA -keyalg RSA -storepass passwordpassword \
|
-genkeypair -sigalg SHA512withRSA -keyalg RSA -storepass passwordpassword \
|
||||||
-dname CN=localhost -keypass 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.
|
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
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
./kc.sh start --fips-mode=enabled --hostname=localhost \
|
./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
|
--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
|
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",
|
||||||
BouncyCastle FIPS security providers on top of that in runtime. This works fine, however it may not be guaranteed that
|
which means even stricter security requirements on cryptography and security algorithms. Few more points:
|
||||||
all the crypto algorithms are used in the FIPS compliant way as the default providers like "Sun" potentially allow non-FIPS
|
- As mentioned above, strict mode won't work with `pkcs12` keystore. So it is needed to use other keystore (probably `bcfks`).
|
||||||
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
|
- 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.
|
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:
|
||||||
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,
|
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
|
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
|
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) {
|
if (hash == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
PasswordCredentialModel credentialModel = hash.encodedCredential(password, policy.getHashIterations());
|
try {
|
||||||
credentialModel.setCreatedDate(Time.currentTimeMillis());
|
PasswordCredentialModel credentialModel = hash.encodedCredential(password, policy.getHashIterations());
|
||||||
createCredential(realm, user, credentialModel);
|
credentialModel.setCreatedDate(Time.currentTimeMillis());
|
||||||
|
createCredential(realm, user, credentialModel);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new ModelException(t.getMessage(), t);
|
||||||
|
}
|
||||||
return true;
|
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());
|
logger.debugv("PasswordHashProvider {0} not found for user {1} ", password.getPasswordCredentialData().getAlgorithm(), user.getUsername());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!hash.verify(input.getChallengeResponse(), password)) {
|
try {
|
||||||
logger.debugv("Failed password validation for user {0} ", user.getUsername());
|
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;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -968,3 +968,16 @@ there should be messages similar to those:
|
||||||
BCJSSE version 1.001202 - class org.bouncycastle.jsse.provider.BouncyCastleJsseProvider,
|
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-file=" + configuration.getTruststoreFile());
|
||||||
commands.add("--spi-truststore-file-password=" + configuration.getTruststorePassword());
|
commands.add("--spi-truststore-file-password=" + configuration.getTruststorePassword());
|
||||||
commands.add("--spi-truststore-file-type=" + configuration.getTruststoreType());
|
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");
|
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"));
|
configuration.appendJavaOpts("-Djava.security.properties=" + System.getProperty("auth.server.java.security.file"));
|
||||||
|
|
Loading…
Reference in a new issue