Saturday, November 28. 2020
Can wildfly use BCFIPS provider for SSL running with jdk-11?
This time I am going to talk about the Bouncy Castle FIPS, wildfly and JDK version 11. It is known that BCFIPS can be configured with wildfly and jdk version 8 (this link is the EAP documentation but it also works for upstream wildfly). There is a step in which you need to add the bouncy castle jar files inside the ext directory of the JDK. But this is not possible since version 9, because the extension mechanism was removed. On the other hand, the certification of BCFIPS for jdk-11 is not absolutely clear to me, because in the faq it says that only jdk 7 and 8 are certified but in the roadmap version 1.0.2 certifies jre 11. So I assume that jdk 11 is valid for 1.0.2 but the faq page was not updated.
In general there is a big problem with wildfly if the extension mechanism is not present. The JDK needs to access to the BC classes in order to configure the provider, and in wildfly the jboss-modules project disallows adding jars globally. But let's go step by step, first just try a simple standalone java class. This is the sample.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpURLConnectionExample {
public static void main(String[] args) throws Exception {
URL obj = new URL(args[0]);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();ยก
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
System.out.println("GET Response Code: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
StringBuffer response = new StringBuffer();
String inputLine;
while ((inputLine = in.readLine()) != null) {
response.append(inputLine).append(System.getProperty("line.separator"));
}
in.close();
System.out.println(response.toString());
} else {
System.out.println("GET request not worked");
}
}
}
The idea is configuring a JDK-11 to use the BCFIPS provider at JVM level. So just download a JDK-11 from adopt-openjdk and install it.
wget https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.9.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.9.1_1.tar.gz
tar zxvf OpenJDK11U-jdk_x64_linux_hotspot_11.0.9.1_1.tar.gz
After that the JDK needs to be configured to use the BCFIPS provider. For that, and following the documentation, the file ${JAVA_HOME}/conf/security/java.security is modified to add the new providers.
security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
security.provider.2=com.sun.net.ssl.internal.ssl.Provider BCFIPS
security.provider.3=SUN
security.provider.4=SunRsaSign
security.provider.5=SunEC
security.provider.6=SunJCE
security.provider.7=SunJGSS
security.provider.8=SunSASL
security.provider.9=XMLDSig
security.provider.10=SunPCSC
security.provider.11=JdkLDAP
security.provider.12=JdkSASL
security.provider.13=SunPKCS11
The BCFIPS provider is added at first position and the ssl one is configured to use it at second position. The rest of the providers present in the default configuration are just moved after them (SunJSSE is removed because now is at position 2 and configured to use the BCFIPS). In order to execute the previous java file we need the default cacerts file converted to PKCS format, as BCFIPS only accepts this format.
cp ${JAVA_HOME}/lib/security/cacerts .
keytool -importkeystore -srckeystore cacerts -destkeystore cacerts.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass changeit -deststorepass changeit
Now compile and execute the sample class just adding the BC jar inside the classpath.
${JAVA_HOME}/bin/javac HttpURLConnectionExample.java
${JAVA_HOME}/bin/java -cp bc-fips-1.0.2.jar:. -Djavax.net.debug=all -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStore=cacerts.p12 HttpURLConnectionExample https://blogs.nologin.es/rickyepoderi/
And it works. The BCFIPS can be executed successfully as a client and we can connect to retrieve the HTTPS page with it. But, as commented before, this will not work with wildfly. Because the JavaEE server uses jboss-modules and it avoids the access from normal java.base module to the BC jar. There is no way of adding the file to the JDK module classpath. In version 8 this was solved using the EXT directory but now I do not know how to overcome this. My only idea was adding the jar as boot classpath, adding the following option.
-Xbootclasspath/a:/path/to/bc-fips-1.0.2.jar
Sadly this idea does not work. The BC provider code uses in several places a call similar to XXX.class.getClassLoader().load("a.internal.jdk.class") to load classes from the SUN provider. If using the boot classpath the getClassLoader method returns null and it breaks the provider. I did a quick hack just changing the previous call with Class.forName. More or less I changed the following classes.
find . -type f -exec grep "Class.forName(" {} \; -print
Class def = Class.forName(className);
Class provClass = Class.forName("sun.security.jca.Providers");
./org/bouncycastle/jcajce/provider/BouncyCastleFipsProvider.java
Class def = Class.forName("sun.security.internal.spec.TlsPrfParameterSpec");
Class def = Class.forName("sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec");
Class def = Class.forName("sun.security.internal.spec.TlsPrfParameterSpec");
Class def = Class.forName("sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec");
./org/bouncycastle/jcajce/provider/ProvSunTLSKDF.java
Class def = Class.forName(className);
./org/bouncycastle/jcajce/provider/ClassUtil.java
return Class.forName(className);
./org/bouncycastle/jcajce/provider/GcmSpecUtil.java
Class def = Class.forName(className);
./org/bouncycastle/jcajce/provider/BaseSingleBlockCipher.java
Class.forName(className);
With the hack present the previous java works successfully using the BCFIPS provider configured inside the boot classpath. Note that I changed to use the sources directory with the modified classes.
${JAVA_HOME}/bin/java -Xbootclasspath/a:/path/to/bc-fips-1.0.2-sources -Djavax.net.debug=all -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStore=cacerts.p12 HttpURLConnectionExample https://blogs.nologin.es/rickyepoderi/
Moving the same hack to wildfly also works. These are the configuring steps:
Modify the standalone.conf to include the modified provider:
JAVA_OPTS="$JAVA_OPTS -Xbootclasspath/a:/path/to/bc-fips-1.0.2-sources"
Create a certificate in the ${WILDFLY_HOME}/standalone/configuration folder and copy the previous cacerts.p12 to it:
keytool -genkeypair -alias localhost -keyalg RSA -keysize 2048 -validity 365 -keystore ${WILDFLY_HOME}/standalone/configuration/keystore.p12 -storetype PKCS12 -dname "CN=localhost" -storepass changeit -ext SAN=dns:localhost cp cacerts.p12 ${WILDFLY_HOME}/standalone/configuration/
Start the server using the configured JAVA_HOME.
export JAVA_HOME="/path/to/jdk-11.0.9.1+1" ./standalone.sh
Under the CLI command line, configure elytron to use all the previous stuff.
/subsystem=elytron/key-store=localhost:add(type=pkcs12, relative-to=jboss.server.config.dir, path=keystore.p12, credential-reference={clear-text=changeit}) /subsystem=elytron/key-store=ca:add(type=pkcs12, relative-to=jboss.server.config.dir, path=cacerts.p12, credential-reference={clear-text=changeit}) /subsystem=elytron/key-manager=localhost-manager:add(key-store=localhost, credential-reference={clear-text=changeit}) /subsystem=elytron/trust-manager=ca-manager:add(key-store=ca) /subsystem=elytron/server-ssl-context=localhost-context:add(key-manager=localhost-manager, trust-manager=ca-manager) 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=localhost-context) run-batch
Change the ApplicationRealm to also use the p12 certificates:
/core-service=management/security-realm=ApplicationRealm/server-identity=ssl:write-attribute(name=keystore-provider, value=PKCS12) /core-service=management/security-realm=ApplicationRealm/server-identity=ssl:write-attribute(name=alias, value=localhost) /core-service=management/security-realm=ApplicationRealm/server-identity=ssl:write-attribute(name=keystore-path, value=keystore.p12) /core-service=management/security-realm=ApplicationRealm/server-identity=ssl:write-attribute(name=keystore-password, value=changeit) /core-service=management/security-realm=ApplicationRealm/server-identity=ssl:write-attribute(name=key-password, value=changeit)
And it works. Now the wildfly server is using BCFIPS. But obviously the solution is extremely hacky and unmanageable.
So the summary is that I could not add Bouncy Castle FIPS to wildfly using jdk 11 at the moment (I am talking about adding BCFIPS for SSL, using the provider programmatically is valid). BCFIPS seems to be certified with JRE version 11, but the necessary jars cannot be added to classpath when using wildfly and jboss-modules. My idea of using the boot classpath failed because of how BCFIPS loads the sun internal classes. So no luck, sometimes you get the bear and sometimes the bear gets you, and today it was the second option . Anyway I wanted to backup my tests in detail here. if I do not summarize them in the blog I will forget the steps that were tried and, more important, what was the root problem.
Regards!
Comments