Skip to content

Commit

Permalink
Initial support for running testsuite in BCFIPS approved mode
Browse files Browse the repository at this point in the history
  • Loading branch information
mposolda authored and pedroigor committed Jan 13, 2023
1 parent 6ac65f6 commit 79fa6bb
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 70 deletions.
76 changes: 28 additions & 48 deletions docs/fips.md
Original file line number Diff line number Diff line change
@@ -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
------------------------

Expand All @@ -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/
Expand All @@ -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 \
Expand All @@ -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 \
Expand All @@ -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

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,
]
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.

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
----------------------------
Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@ public boolean createCredential(RealmModel realm, UserModel user, String passwor
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;
}

Expand Down Expand Up @@ -174,27 +178,32 @@ public boolean isValid(RealmModel realm, UserModel user, CredentialInput input)
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;
}
Expand Down
13 changes: 13 additions & 0 deletions testsuite/integration-arquillian/HOW-TO-RUN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
```
Original file line number Diff line number Diff line change
@@ -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).
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,15 @@ private void addFipsOptions(List<String> commands) {
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"));
Expand Down

0 comments on commit 79fa6bb

Please sign in to comment.