Bouncy Castle FIPS 2.1.0: Loading a PKCS12 throws NoSuchAlgorithmException unless BCFIPS is registered in the JVM
Problem
I'm working in a system where I'm unable to register BCFIPS as a JVM security provider, and I'm forced to use it dynamically by passing it to the Java Security factories. I tried to open a PKCS12 key store (truststore.zip) with Bouncy Castle FIPS 2.1.0. When specifying BCFIPS as the security provider to the KeyStore, loading the key store fails with an IOException. This code:
InputStream in = getClass().getResourceAsStream("/truststore.p12");
KeyStore inputKeyStore = KeyStore.getInstance("pkcs12", new BouncyCastleFipsProvider());
inputKeyStore.load(in, "password".toCharArray());
throws this exception:
org.bouncycastle.jcajce.provider.ProvIOException: exception decrypting data - java.security.NoSuchAlgorithmException: Cannot find any provider supporting 1.2.840.113549.3.7
at org.bouncycastle.jcajce.provider.ProvPKCS12$PKCS12KeyStoreSpi.cryptData(Unknown Source)
at org.bouncycastle.jcajce.provider.ProvPKCS12$PKCS12KeyStoreSpi.engineLoad(Unknown Source)
at java.base/java.security.KeyStore.load(KeyStore.java:1500)
at com.example.KeyStoreTest.testLoadWithExplicitProvider(KeyStoreTest.java:54)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.security.NoSuchAlgorithmException: Cannot find any provider supporting 1.2.840.113549.3.7
at java.base/javax.crypto.Cipher.getInstance(Cipher.java:574)
at org.bouncycastle.jcajce.provider.ProvPKCS12$PKCS12KeyStoreSpi.createPBES2Cipher(Unknown Source)
... 7 more
Analysis
BCFIPS already has a mapping for 1.2.840.113549.3.7, which is des-ede3-cbc. In fact, when installing BCFIPS as a security provider in the JVM, the code above does not fail.
Provider provider = new BouncyCastleFipsProvider();
InputStream in = getClass().getResourceAsStream("/truststore.p12");
KeyStore inputKeyStore = KeyStore.getInstance("pkcs12", provider);
Security.addProvider(provider);
inputKeyStore.load(in, "password".toCharArray());
Please note that my JVM (OpenJDK 21.0.7) is not able to understand that key store out of the box. In fact, this code:
InputStream in = getClass().getResourceAsStream("/truststore.p12");
KeyStore inputKeyStore = KeyStore.getInstance("pkcs12");
inputKeyStore.load(in, "password".toCharArray());
throws this exception:
java.io.IOException: PBE parameter parsing error: expecting the object identifier for AES cipher
at java.base/com.sun.crypto.provider.PBES2Parameters.parseES(PBES2Parameters.java:324)
at java.base/com.sun.crypto.provider.PBES2Parameters.engineInit(PBES2Parameters.java:240)
at java.base/java.security.AlgorithmParameters.init(AlgorithmParameters.java:311)
at java.base/sun.security.x509.AlgorithmId.decodeParams(AlgorithmId.java:149)
at java.base/sun.security.x509.AlgorithmId.<init>(AlgorithmId.java:131)
at java.base/sun.security.x509.AlgorithmId.parse(AlgorithmId.java:416)
at java.base/sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:2052)
at java.base/sun.security.util.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:228)
at java.base/java.security.KeyStore.load(KeyStore.java:1500)
at com.example.KeyStoreTest.testLoadWithDefaultProvider(KeyStoreTest.java:70)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
The only way to make the code from the example above work is to install BCFIPS as the highest priority security provider, so that its implementation is chosen both for the key store and for the cryptographic algorithm with OID 1.2.840.113549.3.7, as in the following code:
InputStream in = getClass().getResourceAsStream("/truststore.p12");
Security.insertProviderAt(new BouncyCastleFipsProvider(), 1);
KeyStore inputKeyStore = KeyStore.getInstance("pkcs12");
inputKeyStore.load(in, "password".toCharArray());
As stated at the beginning of this issue, this solution is unsatisfactory. I can't change the configuration of my JVM globally to insert a new security provider at runtime.
Possible solution
While I don't have access to the BCFIPS source code, I've noticed some inconsistencies from the decompiled version of ProvPKCS12. In one place, the Cipher for a specific entry is retrieved with:
Cipher cipher = Cipher.getInstance(algId.getAlgorithm().getId(), fipsProvider);
In another place in the same class, the Cipher is instead retrieved with:
Cipher cipher = Cipher.getInstance(encScheme.getAlgorithm().getId());
I suspect that retrieving the Cipher and consistently specifying the fipsProvider every time would make my problem disappear, but without access to the source code I can't create a debug build to prove my theory.
In bc-test.zip you can find a minimal project reproducing the problem, with some tests reproducing the four cases that I described in the issue.
So the diagnosis is correct. I've fixed this for the 2.1.X and 2.2.X streams. It'll need another minor release to make live though which for FIPS may take a while.
One note: the BCFIPS source code is also available on Maven Central, while there are fees for Enterprise Level Support (it's how we fund this) and some restrictions we need to follow relating to publishing, the BCFIPS project is as open-source as the non-FIPS one once a release to Maven Central has been done. If you're not worried about compliance directly, you're welcome to put together your own patched version following the suggestion you made above.