Sunday, September 3, 2023

Few Gotchas! While Making Your Keycloak FIPS compliant

Keycloak is a very popular Java based open source Identity Access Management product. The recent versions (17.x onward) have gone server-less using Quarkus. When dealing with government, your SSO/ IAM system required to be FedRAMP and FIPS compliant.  Keycloak version 21.x+ is FIPS-2 certified, but additional configurations are needed to make the default Keycloak FIPS 140-2 enabled. 

Enabling FIPs mode in Keycloak is documented here. The documentation covers the steps in a greater detail. However, I have not seen any IAM deployed out of box, so the documented steps are not enough. I went thru series of hair pulling hours to make it work in my environment.  I am jotting down those gotchas here hoping this will help someone.

The FIPS standard revolves around the use of approved crypto module. Keycloak uses only FIPS approved crypto algorithm for it's functionalities when fips-mode is enabled.  

Keycloak recommends to use FIPs validated BouncyCastle library when strict fips-mode is enabled.  So the starting step is downloading BouncyCastle-FIPS bits and adding them to the Keycloak distribution as a crypto Service Provider, so BCFIPS bits will be used instead of the default bits.

(1) Download BouncyCastle jars

The Keycloak documentation instructs to download the following BouncyCastle jars and add them to KEYCPLAO_HOME/providers directory.

bc-fips-1.0.2.3.jar, bctls-fips-1.0.14.jar, bcpkix-fips-1.0.7.jar

However, by copying these jars to "providers" directory does not register it as providers unless you build the Keycloak again. Here is an example of the code snippets from my Dockerfile.

:

ENV KC_FEATURES=fips

ENV KC_FIPS_MODE=strict

ENV KC_HTTPS_PORT=8443

ENV KC_HTTPS_KEY_STORE_TYPE=BCFKS

ENV KC_HOSTNAME="sso.poc.abc.com"

:

ARG BOUNCYCASTLE_FIPS_JAR_LOC='https://downloads.bouncycastle.org/fips-java'

RUN curl -qsSL --retry 3 --retry-delay 0 ${BOUNCYCASTLE_FIPS_JAR_LOC}/bc-fips-1.0.2.3.jar -O --output-dir /opt/keycloak/providers &&\

    curl -qsSL --retry 3 --retry-delay 0 ${BOUNCYCASTLE_FIPS_JAR_LOC}/bctls-fips-1.0.14.jar -O --output-dir /opt/keycloak/providers &&\

    curl -qsSL --retry 3 --retry-delay 0 ${BOUNCYCASTLE_FIPS_JAR_LOC}/bcpkix-fips-1.0.7.jar -O --output-dir  /opt/keycloak/providers &&\

    chmod +x /opt/keycloak/providers/*.jar


RUN /opt/keycloak/bin/kc.sh -v build


The build (last line is) needed to register the providers

(2) Use of BouncyCastle Keystore :

By default, Keycloak uses "PKCS12" keystore and the same is also supported for BCFIPS non-approved mode. However "BCFKS" (BouncyCastle key store) is a must when keycloak runs in strict fips mode. BCFKS can be generated various way (from scratch or from existing PKCS12 etc) depending on your use case.

> Use case I: Creating BCfKS from scratch
Set the secure random algorithm. This will be used in the next step.

$ echo "securerandom.strongAlgorithms=PKCS11:SunPKCS11-NSS-FIPS" > kc.keystore-create.java.security


$ keytool -genkeypair -noprompt\

  -sigalg SHA512withRSA \

  -keyalg RSA \

  -alias sso.poc.server \

  -storetype bcfks \

  -dname "CN=sso.poc.volterra.us, OU=f5xc, O=f5, L=San Jose, S=CA, C=US" \

  -keystore server.keystore.bcfks \

  -storepass ChangeMe! \

  -keypass ChangeMe! \

  -providername BCFIPS \

  -providerclass org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \

  -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \

  -providerpath ./lib/bc-fips-*.jar \

  -J-Djava.security.properties=kc.keystore-create.java.security


$ cp server.keystore.bcfks /opt/keycloak/conf/server.keystore


> Use case || : Converting an existing PKCS12 file to BCfKS
This is a typical use case when you already have a PKCS12 keystore delivered by your PKI team ot some other means. The PKCS12 may have been generated using opnenssl or java keytool. 

Example of generating PKCS12 using openssl

$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -node -subj "/C=US/ST=CA/L=SanJose/O=security/OU=sso/CN=sso.poc.abc.com"


$ openssl pkcs12 -export -nodes -password pass:"" \

 -inkey key.pem -in cert.pem \

 -name "server" \

 -out server.keystore.pkcs12


Example of generating PKCS12 using Java keytool

$ keytool -genkey -noprompt \

 -keyalg RSA \

 -alias server \

 -storetype PKCS12 -validity 3650 -keysize 2048 \

 -dname "CN=sso.poc.abc.com, OU=sso, O=security, L=San Jose, S=CA, C=US" \

 -keystore server.keystore.pkcs12 \

 -storepass ChangeMe! \

 -keypass ChangeMe!


The aboves are just example of PKCS12 keycstore creation. The assumption here is, you already have a PKCS12 keystore. Now convert the PKCS12 created above to a BCFKS. 

$ keytool -importkeystore -v \

    -srckeystore server.keystore.pkcs12 \

    -srcstorepass "" \

    -srcstoretype PKCS12 \

    -srcalias "server" \

    -srckeypass "" \

    -destkeystore server.keystore \

    -deststoretype bcfks \

    -deststorepass "password" \

    -destalias "server" \

    -destkeypass "password" \

    -providername BCFIPS \

    -providerpath ./lib/bc-fips-*.jar \

    -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \

    -J-Djava.security.properties=kc.keystore-create.java.security


# test the BCFKS Keystore that is just created by listing it"

$ keytool -list -v -keystore server.keystore \

  -storepass "password" -storetype bcfks \

  -providerpath ./lib/bc-fips-*.jar -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider


$ cp server.keystore.bcfks /opt/keycloak/conf/server.keystore


My use case was similar to above, but little twisted.  My PKCS12 file was delivered by another centralized PKI/ Keystore service through a side car, which runs in my container. So my BCFKS keystore was build from this sidecar provided PKCS12 during my Keycloak container building process.  In this scenario, the above code did not work. I get this error while building BCFKS using keytool.

Importing keystore server.keystore.pkcs12 to server.keystore...
Exception in thread "main" java.lang.IllegalAccessError: class org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$CoreSecureRandom (in unnamed module @0x4f2d995e) cannot access class sun.security.provider.SecureRandom (in module java.base) because module java.base does not export sun.security.provider to unnamed module @0x4f2d995e
	at org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$CoreSecureRandom.<init>(Unknown Source)
	at org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$4.run(Unknown Source)
	at org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$4.run(Unknown Source)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)

The reason is, the Bouncycastle (bc-fips-1.0.2.3.jar) is not certified for JDK 18 (As of Sep 2023,).  So I had to rebuild my image with JDK 11.

:

RUN microdnf install -y java-11-openjdk-headless ....

:    

RUN /opt/keycloak/bin/kc.sh -v build

:


This fix works in Keycloak version 21.X, but may break in version 22.X. I did not cover all detailed steps as the deployment architectures varies. I tried to jot down the critical blockers that slowed me down in this project.  

Thanks for stopping by my blog!