Last week I talked about the requirements for implementing TLS and a certified encryption module (specifically Bouncy Castle FIPS or BC-FIPS). Today I'm going to tell you a bit more about technically how one my go about this, and the specific technical challenges that you may run into.
First of all, BC-FIPS provides some installation instructions that a) no longer work with JDK-11, and b) also don't play well with Spring Boot uber-jar class loading using standard Classpath override mechanisms. I never found root cause for this problem, all I wound up doing was simply dynamically loaded the BC-FIPS security modules at application startup.
These (non-working) instructions include modifications needed to the JDK, specifically the java.security file and lib/ext folders.
There are three aspects of this configuration:
- Creating a compliant SecureRandom (this is described in the BC-FIPS documentation).
- Installing the BC FIPS Security Provider
- Installing the BC JSSE Security Provider
I do this in a static method BEFORE database initialization. The reason for this is that DB initialization code needs to be able to get a FIPS compliant socket factory to initialize the connection pool.
private static void init() {
// This is necessary initialization to use BCFKS module
CryptoServicesRegistrar.setSecureRandom(getSecureRandom());
Security.insertProviderAt(new BouncyCastleFipsProvider(), 1);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 2);
}
/**
* Generate a a NIST SP 800-90A compliant secure random number
* generator.
*
* @return A compliant generator.
*/
private static SecureRandom getSecureRandom() {
/*
* According to NIST Special Publication 800-90A, a Nonce is
* A time-varying value that has at most a negligible chance of
* repeating, e.g., a random value that is generated anew for each
* use, a timestamp, a sequence number, or some combination of
* these.
*
* The nonce is combined with the entropy input to create the initial
* DRBG seed.
*/
byte [] nonce = ByteBuffer.allocate(8).putLong(System.nanoTime()).array();
EntropySourceProvider entSource = new BasicEntropySourceProvider(new SecureRandom(), true);
FipsDRBG.Builder drgbBldr = FipsDRBG.SHA512
.fromEntropySource(entSource).setSecurityStrength(256)
.setEntropyBitsRequired(256);
return drgbBldr.build(nonce, true);
}
The code above effectively does what making changes to the JDK's java.security (as recommended by BC-FIPS documentation). I make all the recommended changes except the ones that initialize the security providers, because I cannot configure the JDK to load the BC classes from the lib/ext folder since that is no longer supported in JDK-11. The alternative suggested is to put the location of those classes on your classpath during application startup. However, I also discovered that doesn't work, likely due to conflicts with how uber-jar classloading works (as in fact, those classes technically are on the classpath in the uber-jar). I also swap out the default keystore format from JKS to BCFKS to ensure compliance with BC-FIPS KeyStore requirements. Technically JKS is fine for Certificate stores, but frankly, I didn't even want to enable JKS support in case something broke somewhere else.
If your database is in the cloud (e.g., AWS or Azure), you may need to add a certificate to cacerts to enable the database connection using JSSE (BC-FIPS or native Java JSSE code). I just do this to the cacerts file in the deployed JDK.
keytool -keystore cacerts -storepass SECRET -noprompt -trustcacerts -importcert -alias awscert -file certificate.der
keytool -importkeystore -srckeystore cacerts -srcstoretype JKS -srcstorepass changeit \
-destkeystore jssecacerts -deststorepass changeit -deststoretype BCFKS -providername BCFIPS \
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -providerpath lib/bc-fips-1.0.2.3.jar
A simpler way to do this conversion is with KeyStore Explorer, I tool I often use to inspect/modify key and trust store content. This tool already has BCFKS support built in, even if it may not be BCFIPS compliant (straight BC also supports the BCFKS format, it's just not a certified component).
Finally, you'll have to change how you configure SSL/TLS for your server and/or client components. Our system programatically configures using beans for KeyStore, TrustStore, et cetera, but other servers may just use property or configuration values (e.g., server.xml for Tomcat).
Anywhere the default keystore type is present, you'll need to change the type of keystore to BCFKS, and if the provider type is specified, you'd use BCFIPS (as for keytool commands above).
If you want to get a KeyManagerFactory, TrustManagerFactory, or SSLContext programatically, here's how you'd get those:
KeyManagerFactory keyMgrFact = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
TrustManagerFactory trustMgrFact = TrustManagerFactory.getInstance("PKIX", "BCJSSE");
SSLContext sslContext = SSLContext.getInstance("TLS", "BCJSSE");
Anywhere the default keystore type is present, you'll need to change the type of keystore to BCFKS, and if you need to specify the KeyStore provider, you'd specify BCFIPS as the provider.
This ensures that all encryption used to protect key and trust material is FIPS compliantly encrypted. Sadly, the encryption used for JKS nor PKCS12 formats are themselves compliant.
In the continuing saga of this effort, I just recently completed another set of code changes that ensures that I can just drop in new key and trust stores on a shared file system, and all my servers will automatically reconfigure themselves with the latest and greatest. This greatly simplifies updating certificates for annual renewals or for other reasons, with zero downtime. More on that later.