Sunday, August 1. 2021
Configuring BCFIPS in wildfly via elytron
More or less one year ago an unsuccessful try to configure Bouncy Castle FIPS in wildfly and openjdk 11 was presented in the blog. At that time only the direct JVM setup was performed (adding the BCFIPS at java.security file level). I already knew that the elytron subsystem also had the ability of loading and enabling JCE providers for applications (registering the providers in the Security class). But I did not realize that, using this feature, a new option was at our disposal for adding the BCFIPS to the application server. Recently I discovered this elytron bug and it gave me this idea. So today's entry is going to configure again the BCFIPS inside a wildfly and openjdk 11 server, but this time, via elytron.
As always the steps followed to perform the full configuration are presented one by one.
The last openjdk 11 is installed in the system. The path for the java installation will be ${JAVA_HOME} from now on.
wget https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.11%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.11_9.tar.gz tar zxvf OpenJDK11U-jdk_x64_linux_hotspot_11.0.11_9.tar.gz
Next wildfly 24.0.1 is installed. The directory where the server is unzipped will be called ${WILDFLY_HOME}.
wget https://download.jboss.org/wildfly/24.0.1.Final/wildfly-24.0.1.Final.zip unzip wildfly-24.0.1.Final.zip cd wildfly-24.0.1.Final/bin/ ./add-user.sh -u admin -p admin export JAVA_HOME=/path/to/jdk-11.0.11+9 ./standalone
Now from the Bouncy Castle page download the FIPS (bc-fips-1.0.2.1.jar) and the JSSE (bctls-fips-1.0.12.1.jar) provider files. This try the BCJSSE implementation from Bouncy Castle will be used instead of the default SunJSSE used in the previous entry. The jars are downloaded to ${FIPS_HOME} folder.
Time to create the stores with the server certificate and the trusted CAs. The BCFKS custom format is used to show that the BCFIPS provider is really in place.
cd ${WILDFLY_HOME}/standalone/configuration/ ${JAVA_HOME}/bin/keytool -providername BCFIPS -providerpath ${FIPS_HOME}/bc-fips-1.0.2.1.jar -providerclass org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -genkeypair -alias localhost -keyalg RSA -keysize 2048 -validity 365 -keystore localhost.bcfks -storetype bcfks -dname "CN=localhost" -storepass secret -keypass secret -ext "SAN=DNS:localhost,IP:127.0.0.1" cp ${JAVA_HOME}/lib/security/cacerts . ${JAVA_HOME}/bin/keytool -providername BCFIPS -providerpath ${FIPS_HOME}/bc-fips-1.0.2.1.jar -providerclass org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -importkeystore -srckeystore cacerts -destkeystore cacerts.bcfks -srcstoretype JKS -deststoretype bcfks -srcstorepass changeit -deststorepass changeit
The elytron magic starts here. Both jar files are registered as modules inside the server using CLI. Please note that the JSSE module has the main one as a dependency.
module add --name=org.bouncycastle.fips --resources=${FIPS_HOME}/bc-fips-1.0.2.1.jar module add --name=org.bouncycastle.tls.fips --resources=${FIPS_HOME}/bctls-fips-1.0.12.1.jar --dependencies=org.bouncycastle.fips
The BC documentation (which only explains direct JVM configuration using the java.security file) says that the providers should be configured like this:
security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS
So the main FIPS provider is defined first and then the JSSE one needs the argument fips:BCFIPS. This means that the latter is in fips mode and it will use the main BCFIPS provider defined previously to perform the security operations. But, how do we do this inside elytron? This is the only tricky point needed for the configuration. Passing one provider to another provider cannot be configured in wildfly. But the BCJSSE also understands a fully qualified class name. Therefore this setup works:
/subsystem=elytron/provider-loader=bc-fips:add(module=org.bouncycastle.fips) /subsystem=elytron/provider-loader=bc-fips-tls:add(module=org.bouncycastle.tls.fips, class-names=[org.bouncycastle.jsse.provider.BouncyCastleJsseProvider], argument="fips:org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider")
It is extremely important that the BCJSSE provider is registered passing the argument parameter. The value should be fips:org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider, fips mode and the BCFIPS provider but using the class name.
The common configuration for stores and ssl context is performed now, but using the appropriate provider for each step. First the key store and manager:
/subsystem=elytron/key-store=fipsKS:add(path=localhost.bcfks, relative-to=jboss.server.config.dir, credential-reference={clear-text=secret}, type=bcfks, providers=bc-fips) /subsystem=elytron/key-manager=fipsKM:add(key-store=fipsKS, algorithm="PKIX", credential-reference={clear-text=secret}, providers=bc-fips-tls)
Then the same thing for the trust store and manager:
/subsystem=elytron/key-store=fipsCAKS:add(type=bcfks, relative-to=jboss.server.config.dir, path=cacerts.bcfks, credential-reference={clear-text=changeit}, providers=bc-fips) /subsystem=elytron/trust-manager=fipsTM:add(key-store=fipsCAKS, providers=bc-fips-tls)
The server ssl context is created with the previous elements. Please note that only TLSv1.2 is configured. TLSv1.3 was tried first but it seems that currently the BCFIPS implementation has no ciphers for this version.
/subsystem=elytron/server-ssl-context=fipsSSC:add(key-manager=fipsKM, trust-manager=fipsTM, protocols=["TLSv1.2"], providers=bc-fips-tls)
Finally the ssl context is assigned to the https listener.
batch /subsystem=undertow/server=default-server/https-listener=https:undefine-attribute(name=security-realm) /subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=ssl-context, value=fipsSSC) run-batch
And that is all. The Bouncy Castle FIPS is configured and used for the default 8443 secure port. Using for example curl you can see that the server is working and our certificate is displayed.
curl -k -v -I https://localhost:8443/ Trying 127.0.0.1:8443... Connected to localhost (127.0.0.1) port 8443 (#0) ALPN, offering h2 ALPN, offering http/1.1 successfully set certificate verify locations: CAfile: /etc/pki/tls/certs/ca-bundle.crt CApath: none TLSv1.3 (OUT), TLS handshake, Client hello (1): TLSv1.3 (IN), TLS handshake, Server hello (2): TLSv1.2 (IN), TLS handshake, Certificate (11): TLSv1.2 (IN), TLS handshake, Server key exchange (12): TLSv1.2 (IN), TLS handshake, Server finished (14): TLSv1.2 (OUT), TLS handshake, Client key exchange (16): TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): TLSv1.2 (OUT), TLS handshake, Finished (20): TLSv1.2 (IN), TLS handshake, Finished (20): SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 ALPN, server accepted to use h2 Server certificate: subject: CN=localhost start date: Jul 31 10:30:19 2021 GMT expire date: Jul 31 10:30:19 2022 GMT issuer: CN=localhost SSL certificate verify result: self signed certificate (18), continuing anyway. Using HTTP2, server supports multi-use Connection state changed (HTTP/2 confirmed) Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 Using Stream ID: 1 (easy handle 0x55e78bb10aa0) > HEAD / HTTP/2 > Host: localhost:8443 > user-agent: curl/7.71.1 > accept: / > Connection state changed (MAX_CONCURRENT_STREAMS == 4294967295)! < HTTP/2 200 HTTP/2 200 < last-modified: Tue, 27 Jul 2021 08:03:32 GMT last-modified: Tue, 27 Jul 2021 08:03:32 GMT < content-length: 1504 content-length: 1504 < content-type: text/html content-type: text/html < accept-ranges: bytes accept-ranges: bytes < date: Sat, 31 Jul 2021 11:13:42 GMT date: Sat, 31 Jul 2021 11:13:42 GMT
At this point there are more options you can be interested in configuring. For example elytron allows to include the JCE providers into the JVM. This way the applications can use the provider as usual (getProvider("BCFIPS") for example). Check the final-providers and the default combined-providers to see how elytron and openssl providers are exposed by default. Another interesting feature is setting the default-ssl-context at JVM level, allowing to configure a BCFIPS ssl context as the default one for applications.
So today's entry finally shows a working BCFIPS configuration for wildfly and openjdk 11. The removal of the extension mechanism in java complicated a lot the installation of custom JCE providers inside the wildfly server (a thing that was very easy with version 8). But finally elytron was the right component for the goal. The new security system allows the installation of JCE providers and let us configure the different security elements with them (key and trust stores, managers, ssl contexts,...). The only trick in the setup was passing the BCFIPS provider to the BCJSSE provider, but luckily it accepts a class name and that can be configured inside elytron. Note that in this entry the BCJSSE implementation was used instead of the default SunJSSE, elytron is used and not the java.security file, so using an external JSSE implementation is much easier.
Best regards!
Comments