JDK-8246383 : NullPointerException in JceSecurity.getVerificationResult when using Entrust provider
  • Type: Bug
  • Component: security-libs
  • Sub-Component: java.security
  • Affected Version: 11.0.7,14.0.1,15
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2020-06-02
  • Updated: 2024-01-24
  • Resolved: 2020-08-21
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 16
11.0.20Fixed 16 b13Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Description
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);
        }
    }
Comments
Fix request [11u] This backport is a prerequisite to easily backport JDK-8260274 (required for 11.0.20-oracle parity). The patch applies cleanly except for copyright header mismatches. GitHub commit URL with full hash: https://github.com/openjdk/jdk17u-dev/commit/03f2ab325d30b74095f8abec8dbe09def27950c7 Tests (GHA and SAP internal) successful.
26-05-2023

A pull request was submitted for review. URL: https://git.openjdk.org/jdk11u-dev/pull/1901 Date: 2023-05-25 12:50:36 +0000
25-05-2023

URL: https://hg.openjdk.java.net/jdk/jdk/rev/1d9c98f27461 User: valeriep Date: 2020-08-21 03:10:06 +0000
21-08-2020

JceSecurity.RANDOM is used by other classes in javax.crypto package to avoid repeated calling new SecureRandom() when the caller did not specify a SecureRandom object. Found out that these classes could use sun.security.jca.JCAUtil.getSecureRandom() call instead. Then JceSecurity class can remove this RANDOM field.
23-07-2020

mail thread: http://mail.openjdk.java.net/pipermail/security-dev/2020-June/021958.html
03-06-2020