Thanks for reporting the bug and the detailed analysis.
I have filed https://bugs.openjdk.java.net/browse/JDK-8246383 to keep
track of this. Will aim to fix this for 15 and have it backported
accordingly.
Is it possible to get hold of an test provider to reproduce and
verifying the fix?
Regards,
Valerie
On 6/2/2020 1:18 PM, John Gray wrote:
Hello,
At Entrust Datacard, we produce a Java based toolkit that contains our
own Security Provider. This toolkit and provider has been around
for about 19 years.
In JDK version 11.07 (and I also think Java 12 and beyond), our
toolkit reports the following error:
java.lang.RuntimeException: java.security.NoSuchAlgorithmException:
Error constructing implementation (algorithm: X9_31usingAES256,
provider: Entrust, class:
com.entrust.toolkit.security.crypto.random.X9_31usingAES256)
at
java.base/java.security.SecureRandom.getDefaultPRNG(SecureRandom.java:281)
at java.base/java.security.SecureRandom.<init>(SecureRandom.java:219)
at java.base/javax.crypto.JceSecurity.<clinit>(JceSecurity.java:80)
... 41 more
Caused by: java.security.NoSuchAlgorithmException: Error constructing
implementation (algorithm: X9_31usingAES256, provider: Entrust, class:
com.entrust.toolkit.security.crypto.random.X9_31usingAES256)
at
java.base/java.security.Provider$Service.newInstance(Provider.java:1825)
at
java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:236)
at
java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:164)
at java.base/java.security.SecureRandom.getInstance(SecureRandom.java:365)
at
java.base/java.security.SecureRandom.getDefaultPRNG(SecureRandom.java:273)
... 43 more
Caused by: java.lang.NullPointerException
at
java.base/javax.crypto.JceSecurity.getVerificationResult(JceSecurity.java:203)
at java.base/javax.crypto.Cipher.getInstance(Cipher.java:690)
at java.base/javax.crypto.Cipher.getInstance(Cipher.java:625)
at
com.entrust.toolkit.security.crypto.random.X9_31usingAES256.initialize(X9_31usingAES256.java:524)
at
com.entrust.toolkit.security.crypto.random.X9_31usingAES256.<init>(X9_31usingAES256.java:102)
at
java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native
Method)
at
java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at
java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at
java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at java.base/java.security.Provider.newInstanceUtil(Provider.java:176)
at
java.base/java.security.Provider$Service.newInstance(Provider.java:1818)
I investigated this error, and found it was made possible because of
the following change in Java 11.07 which unmasked a bug in the JVM
that has probably been around for years.
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8228613
It is a problem inside the JceSecurity class. When the class is being
loaded, the call to setup a default SecureRandom() instance is
invoked. That seems to invoke the JVM to find the first available
SecureRandom() instance. This error happens when our Entrust
provider is in first position. In previous versions of the JDK it
honoured the order of algorithms specified in the providers. In our
Entrust Security provider, we have a number of SecureRandom
implementations. Now because of the above change, it picks a
different SecureRandom instance (the X9_31usingAES256). That should
be fine, however the problem is that the SecureRandom() setup calls
Cipher.getInstance() and as you can see below, that calls
JceSecurity.getVerificationResult() which is static, and uses the
verificationResuts Map that has not yet been initialized (becasuse
it’s declaration is after the SecureRandom setup). That is why
there is a NullPointerException.
*public**static**final*Cipher getInstance(String transformation,
Provider provider)
*throws*NoSuchAlgorithmException, NoSuchPaddingException
{
*if*((transformation == *null*) || transformation.equals("")) {
*throw**new*NoSuchAlgorithmException("Null or empty transformation");
}
*if*(provider == *null*) {
*throw**new*IllegalArgumentException("Missing provider");
}
Exception failure = *null*;
List<Transform> transforms = getTransforms(transformation);
*boolean*providerChecked = *false*;
String paddingError = *null*;
*for*(Transform tr : transforms) {
Service s = provider.getService("Cipher", tr.transform);
*if*(s == *null*) {
*continue*;
}
*if*(providerChecked == *false*) {
// for compatibility, first do the lookup and then verify
// the provider. this makes the difference between a NSAE
// and a SecurityException if the
// provider does not support the algorithm.
Exception ve = JceSecurity.getVerificationResult(provider);
*if*(ve != *null*) {
String msg = "JCE cannot authenticate the provider "
+ provider.getName();
*throw**new*SecurityException(msg, ve);
}
providerChecked = *true*;
}
The JceSecurity.getVerificationResult(provider) method is used when
initializing the SecureRandom (first highlighted line below) when the
classLoader is loading the JceSecurity class itself.
From the JceSecurity class:
*static**final*SecureRandom RANDOM = *new*SecureRandom();
// The defaultPolicy and exemptPolicy will be set up
// in the static initializer.
*private**static*CryptoPermissions defaultPolicy = *null*;
*private**static*CryptoPermissions exemptPolicy = *null*;
// Map<Provider,?> of the providers we already have verified
// value == PROVIDER_VERIFIED is successfully verified
// value is failure cause Exception in error case
*private**static**final*Map<Provider, Object> verificationResults =
*new*IdentityHashMap<>();
It fails when it calls the following code in JceSecurity.java because
the verificationResults Map<Provider, Object> has not been initialized
because the SecureRandom() constructor ends up calling the
JceSecurity.getVerificationResult() static method that makes use of
the Map! That explains the NullPointerException.
The fix to the issue should be simple, just move the initialization of
the verificationResults Map BEFORE the SecureRandom initialization in
JceSecurity.java
Because verificationResults is not initialized, the line highlighted
below fails (Because the Map has not been initialized).
/*
* Verify that the provider JAR files are signed properly, which
* means the signer's certificate can be traced back to a
* JCE trusted CA.
* Return null if ok, failure Exception if verification failed.
*/
*static**synchronized*Exception getVerificationResult(Provider p) {
Object o = verificationResults.get(p);
*if*(o == PROVIDER_VERIFIED) {
*return**null*;
} *else**if*(o != *null*) {
*return*(Exception)o;
}
*if*(verifyingProviders.get(p) != *null*) {
// this method is static synchronized, must be recursion
// return failure now but do not save the result
*return**new*NoSuchProviderException("Recursion during verification");
}
*try*{
verifyingProviders.put(p, Boolean.FALSE);
URL providerURL = getCodeBase(p.getClass());
verifyProvider(providerURL, p);
// Verified ok, cache result
verificationResults.put(p, PROVIDER_VERIFIED);
*return**null*;
} *catch*(Exception e) {
verificationResults.put(p, e);
*return*e;
} *finally*{
verifyingProviders.remove(p);
}
}
Cheers,
John Gray