Search code examples
javaclassloaderaspectjjava-11jar-signing

Exception when using '-Djava.system.class.loader' on JDK 11.0.11, if class loader is in signed JAR


I have problems to start/run an Java application with AspectJ Load-Time-Weaving in Spring Tool Suite 4.11 (based on Eclipse 2021-06) on a Java 11.0.11 Runtime (tested with AdoptOpenJDK 11.0.11.9-hotspot or Oracle JDK build 11.0.11+9-LTS-194 or Zulu11.48+21-CA (build 11.0.11+9-LTS) on Windows 10) when javax.net.ssl.SSLContext is accessed. With older Java 11 versions this issue does not occur. I have reduced the problem to a small application:

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
public class Main {
    public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException {
        String cp = System.getProperty("java.class.path");
        System.err.println(cp);
        String cn = System.getProperty("java.system.class.loader");
        System.err.println(cn);
        final SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, null, null);
    }
}   

The app is started in Eclipse with an AspectJ LTW Launch Configuration. The output and exception I get is this:

D:\sts-4.11.0.RELEASE\plugins\org.aspectj.weaver_1.9.6.202103162301.jar;D:\sts-4.11.0.RELEASE\plugins\org.aspectj.runtime_1.9.6.202103162301.jar
org.aspectj.weaver.loadtime.WeavingURLClassLoader

Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class sun.security.jca.ProviderConfig$ProviderLoader
    at java.base/sun.security.jca.ProviderConfig$3.run(ProviderConfig.java:248)
    at java.base/sun.security.jca.ProviderConfig$3.run(ProviderConfig.java:242)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/sun.security.jca.ProviderConfig.doLoadProvider(ProviderConfig.java:242)
    at java.base/sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:222)
    at java.base/sun.security.jca.ProviderList.getProvider(ProviderList.java:266)
    at java.base/sun.security.jca.ProviderList$ServiceList.tryGet(ProviderList.java:511)
    at java.base/sun.security.jca.ProviderList$ServiceList$1.hasNext(ProviderList.java:565)
    at java.base/java.security.Signature.getInstance(Signature.java:266)
    at java.base/sun.security.ssl.JsseJce.getSignature(JsseJce.java:202)
    at java.base/sun.security.ssl.JsseJce$EcAvailability.<clinit>(JsseJce.java:394)
    at java.base/sun.security.ssl.JsseJce.isEcAvailable(JsseJce.java:175)
    at java.base/sun.security.ssl.CipherSuite$KeyExchange.isAvailable(CipherSuite.java:1079)
    at java.base/sun.security.ssl.CipherSuite.isAvailable(CipherSuite.java:941)
    at java.base/sun.security.ssl.SSLContextImpl.getApplicableCipherSuites(SSLContextImpl.java:384)
    at java.base/sun.security.ssl.SSLContextImpl.getApplicableSupportedCipherSuites(SSLContextImpl.java:347)
    at java.base/sun.security.ssl.SSLContextImpl$AbstractTLSContext.<clinit>(SSLContextImpl.java:580)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:315)
    at java.base/java.security.Provider$Service.getImplClass(Provider.java:1918)
    at java.base/java.security.Provider$Service.newInstance(Provider.java:1894)
    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/javax.net.ssl.SSLContext.getInstance(SSLContext.java:168)
    at org.pmd.bug.Main.main(Main.java:18)

Here is the full output when I enable Security debug log with VM argument "-Djava.security.debug="jca":

ProviderList: provider configuration: [SUN, SunRsaSign, SunEC, SunJSSE, SunJCE, SunJGSS, SunSASL, XMLDSig, SunPCSC, JdkLDAP, JdkSASL, SunMSCAPI, SunPKCS11]
ProviderList: config configuration: null
ProviderList: ThreadLocal providers: [SUN, SunRsaSign, SunEC, SunJCE]
ProviderList: Disabling ThreadLocal providers
ProviderList: ThreadLocal providers: [SUN, SunRsaSign, SunEC, SunJCE]
ProviderList: Loading all providers
java.lang.Exception: Debug Info. Call trace:
    at java.base/sun.security.jca.ProviderList.loadAll(ProviderList.java:311)
    at java.base/sun.security.jca.ProviderList.removeInvalid(ProviderList.java:332)
    at java.base/sun.security.jca.Providers.getFullProviderList(Providers.java:165)
    at java.base/java.security.Security.getProviders(Security.java:457)
    at java.base/sun.security.x509.AlgorithmId.computeOidTable(AlgorithmId.java:637)
    at java.base/sun.security.x509.AlgorithmId.oidTable(AlgorithmId.java:627)
    at java.base/sun.security.x509.AlgorithmId.algOID(AlgorithmId.java:609)
    at java.base/sun.security.x509.AlgorithmId.get(AlgorithmId.java:441)
    at java.base/sun.security.pkcs.SignerInfo.verify(SignerInfo.java:380)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:578)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:595)
    at java.base/sun.security.pkcs.SignerInfo.getTimestamp(SignerInfo.java:545)
    at java.base/sun.security.pkcs.SignerInfo.verify(SignerInfo.java:318)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:578)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:595)
    at java.base/sun.security.util.SignatureFileVerifier.processImpl(SignatureFileVerifier.java:283)
    at java.base/sun.security.util.SignatureFileVerifier.process(SignatureFileVerifier.java:259)
    at java.base/java.util.jar.JarVerifier.processEntry(JarVerifier.java:316)
    at java.base/java.util.jar.JarVerifier.update(JarVerifier.java:230)
    at java.base/java.util.jar.JarFile.initializeVerifier(JarFile.java:759)
    at java.base/java.util.jar.JarFile.ensureInitialization(JarFile.java:1038)
    at java.base/java.util.jar.JavaUtilJarAccessImpl.ensureInitialization(JavaUtilJarAccessImpl.java:69)
    at java.base/jdk.internal.loader.URLClassPath$JarLoader$2.getManifest(URLClassPath.java:870)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:786)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:698)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:621)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:579)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:398)
    at java.base/java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1976)
    at java.base/java.lang.System.initPhase3(System.java:2074)
ProviderConfig: Loading provider SunEC
ProviderConfig: Error loading provider SunEC
java.lang.ExceptionInInitializerError
    at java.base/sun.security.jca.ProviderConfig$3.run(ProviderConfig.java:248)
    at java.base/sun.security.jca.ProviderConfig$3.run(ProviderConfig.java:242)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/sun.security.jca.ProviderConfig.doLoadProvider(ProviderConfig.java:242)
    at java.base/sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:222)
    at java.base/sun.security.jca.ProviderList.loadAll(ProviderList.java:315)
    at java.base/sun.security.jca.ProviderList.removeInvalid(ProviderList.java:332)
    at java.base/sun.security.jca.Providers.getFullProviderList(Providers.java:165)
    at java.base/java.security.Security.getProviders(Security.java:457)
    at java.base/sun.security.x509.AlgorithmId.computeOidTable(AlgorithmId.java:637)
    at java.base/sun.security.x509.AlgorithmId.oidTable(AlgorithmId.java:627)
    at java.base/sun.security.x509.AlgorithmId.algOID(AlgorithmId.java:609)
    at java.base/sun.security.x509.AlgorithmId.get(AlgorithmId.java:441)
    at java.base/sun.security.pkcs.SignerInfo.verify(SignerInfo.java:380)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:578)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:595)
    at java.base/sun.security.pkcs.SignerInfo.getTimestamp(SignerInfo.java:545)
    at java.base/sun.security.pkcs.SignerInfo.verify(SignerInfo.java:318)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:578)
    at java.base/sun.security.pkcs.PKCS7.verify(PKCS7.java:595)
    at java.base/sun.security.util.SignatureFileVerifier.processImpl(SignatureFileVerifier.java:283)
    at java.base/sun.security.util.SignatureFileVerifier.process(SignatureFileVerifier.java:259)
    at java.base/java.util.jar.JarVerifier.processEntry(JarVerifier.java:316)
    at java.base/java.util.jar.JarVerifier.update(JarVerifier.java:230)
    at java.base/java.util.jar.JarFile.initializeVerifier(JarFile.java:759)
    at java.base/java.util.jar.JarFile.ensureInitialization(JarFile.java:1038)
    at java.base/java.util.jar.JavaUtilJarAccessImpl.ensureInitialization(JavaUtilJarAccessImpl.java:69)
    at java.base/jdk.internal.loader.URLClassPath$JarLoader$2.getManifest(URLClassPath.java:870)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:786)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:698)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:621)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:579)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:398)
    at java.base/java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1976)
    at java.base/java.lang.System.initPhase3(System.java:2074)
Caused by: java.lang.IllegalStateException: getSystemClassLoader cannot be called during the system class loader instantiation
    at java.base/java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1932)
    at java.base/sun.security.jca.ProviderConfig$ProviderLoader.<init>(ProviderConfig.java:323)
    at java.base/sun.security.jca.ProviderConfig$ProviderLoader.<clinit>(ProviderConfig.java:313)
    ... 38 more
ProviderList: Disabling ThreadLocal providers
ProviderList: ThreadLocal providers: [SUN, SunRsaSign, SunEC, SunJCE]
ProviderList: Disabling ThreadLocal providers
ProviderList: ThreadLocal providers: [SUN, SunRsaSign, SunEC, SunJCE]
ProviderList: Disabling ThreadLocal providers
D:\sts-4.11.0.RELEASE\plugins\org.aspectj.weaver_1.9.6.202103162301.jar;D:\sts-4.11.0.RELEASE\plugins\org.aspectj.runtime_1.9.6.202103162301.jar
org.aspectj.weaver.loadtime.WeavingURLClassLoader
Hello World!
ProviderConfig: Loading provider SunJGSS
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class sun.security.jca.ProviderConfig$ProviderLoader
    at java.base/sun.security.jca.ProviderConfig$3.run(ProviderConfig.java:248)
    at java.base/sun.security.jca.ProviderConfig$3.run(ProviderConfig.java:242)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/sun.security.jca.ProviderConfig.doLoadProvider(ProviderConfig.java:242)
    at java.base/sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:222)
    at java.base/sun.security.jca.ProviderList.getProvider(ProviderList.java:266)
    at java.base/sun.security.jca.ProviderList$ServiceList.tryGet(ProviderList.java:511)
    at java.base/sun.security.jca.ProviderList$ServiceList$1.hasNext(ProviderList.java:565)
    at java.base/java.security.Signature.getInstance(Signature.java:266)
    at java.base/sun.security.ssl.JsseJce.getSignature(JsseJce.java:202)
    at java.base/sun.security.ssl.JsseJce$EcAvailability.<clinit>(JsseJce.java:394)
    at java.base/sun.security.ssl.JsseJce.isEcAvailable(JsseJce.java:175)
    at java.base/sun.security.ssl.CipherSuite$KeyExchange.isAvailable(CipherSuite.java:1079)
    at java.base/sun.security.ssl.CipherSuite.isAvailable(CipherSuite.java:941)
    at java.base/sun.security.ssl.SSLContextImpl.getApplicableCipherSuites(SSLContextImpl.java:384)
    at java.base/sun.security.ssl.SSLContextImpl.getApplicableSupportedCipherSuites(SSLContextImpl.java:347)
    at java.base/sun.security.ssl.SSLContextImpl$AbstractTLSContext.<clinit>(SSLContextImpl.java:580)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:315)
    at java.base/java.security.Provider$Service.getImplClass(Provider.java:1918)
    at java.base/java.security.Provider$Service.newInstance(Provider.java:1894)
    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/javax.net.ssl.SSLContext.getInstance(SSLContext.java:168)
    at org.pmd.bug.Main.main(Main.java:18)

It seems to me that when the system class loader is initialized to org.aspectj.weaver.loadtime.WeavingURLClassLoader in Classloader#initSystemClassLoader some steps later loading of all Security providers is triggered somehow which in turn needs access to the system class loader (which isn't fully initialized yet). This leeds to this part of the log:

Caused by: java.lang.IllegalStateException: getSystemClassLoader cannot be called during the system class loader instantiation
    at java.base/java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1932)
    at java.base/sun.security.jca.ProviderConfig$ProviderLoader.<init>(ProviderConfig.java:323)
    at java.base/sun.security.jca.ProviderConfig$ProviderLoader.<clinit>(ProviderConfig.java:313)

Later with the call to SSLContext.getInstance("TLS") in the app, the sun.security.jca.ProviderConfig$ProviderLoader is needed again but this time fails with the NoClassDefFoundError.

Do you think my analysis is correct?

Is this a (potential) bug in OpenJDK 11.0.11?

Is there a possible workaround for this problem?

Update (06.07.21):

I found out how to see the complete command-line invocation resulting from the Eclipse launch configuration. Here it is:

C:\Programme\Java\jdk-11.0.11.9-hotspot\bin\javaw.exe -Djava.security.debug=jca -Djava.system.class.loader=org.aspectj.weaver.loadtime.WeavingURLClassLoader -Daj.class.path=D:\workspace\sts490\edrewemaster\example-demo\target\classes;D:\workspace\sts490\edrewemaster\example-demo\target\test-classes;D:\repo\org\aspectj\aspectjrt\1.9.6\aspectjrt-1.9.6.jar;D:\workspace\sts490\edrewemaster\example-demo\target\classes -Dfile.encoding=Cp1252 -classpath D:\sts-4.11.0.RELEASE\plugins\org.aspectj.weaver_1.9.6.202103162301.jar;D:\sts-4.11.0.RELEASE\plugins\org.aspectj.runtime_1.9.6.202103162301.jar org.pmd.bug.Main

When I run it from the command line like this (with java instead of javaw), I also get the exception. When I use an older Java version (11.0.10) like

C:\Programme\Java\11.0.10_9_adopt\jdk-11.0.10+9\bin\java ...

it works.

So Eclipse seems to replace the system class loader for LTW. Is there a way to tell Eclipse to use LTW with the javaagent solution? Perhaps this would circumvent the issue?

I think I found a workaround: Don't use the Eclipse launch configuration of type "AspectJ Load-Time Weaving Application", instead use a normal Java Application launch config and pass the "-javaagent" flag to enable LTW, e.g.

-javaagent:D:\repo\org\aspectj\aspectjweaver\1.9.6\aspectjweaver-1.9.6.jar

That did the trick for me.


Solution

  • I made a few experiments and found out that the problem

    • is completely unrelated to AspectJ,
    • is completely unrelated to Java agents in general,
    • occurs if and only if
      • -Djava.system.class.loader is used and
      • the system class loader in question is found in a signed JAR and
      • the application runs on JDK 11.0.11 (not 11.0.10 or any other JDK 9-16 I tested).

    As soon as I remove the JAR signature from the META-INF directory, it also works on JDK 11.0.11. IMO, this is a regression bug in the JDK and should be fixed. Here is a minimal test case:

    package org.acme.app;
    
    public class Main {
      public static void main(String[] args) {}
    }
    
    package org.acme.loader;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    
    public class CustomClassLoader extends ClassLoader {
    
      public CustomClassLoader(ClassLoader parent) {
        super(parent);
      }
    
      @Override
      public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromFile(name);
        return defineClass(name, b, 0, b.length);
      }
    
      private byte[] loadClassFromFile(String fileName) {
        InputStream inputStream = getClass().getClassLoader()
          .getResourceAsStream(fileName.replace('.', File.separatorChar) + ".class");
        byte[] buffer;
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        int nextValue = 0;
        try {
          while ((nextValue = inputStream.read()) != -1) {
            byteStream.write(nextValue);
          }
        } catch (IOException e) {
          e.printStackTrace();
        }
        buffer = byteStream.toByteArray();
        return buffer;
      }
    }
    

    Please make sure that both classes are in different packages, otherwise later the JVM will complain about not both of them being signed.

    We are in the base directory, the source code is in folder src.

    # Compile source files (JDK used for compilation is unimportant)
    javac --release 8 src\org\acme\app\Main.java src\org\acme\loader\CustomClassLoader.java
    
    # Create JAR containing custom class loader
    jar cf CustomClassLoader.jar -C src org\acme\loader\CustomClassLoader.class
    
    # Create signing key (default keystore has password 'changeit')
    keytool -genkeypair -keyalg RSA -alias test-user
    
    # Sign JAR (default keystore has password 'changeit')
    jarsigner CustomClassLoader.jar test-user
    
    # Run dummy application, setting custom class loader from JAR as system class loader
    "c:\Program Files\Java\jdk-11.0.11\bin\java.exe" -Djava.security.debug="jca" -Djava.system.class.loader=org.acme.loader.CustomClassLoader -cp "CustomClassLoader.jar;src" org.acme.app.Main
    

    Vary the path to java.exe in order to see that the problem really only occurs on JDK 11.0.11.


    Update: I just filed a JDK bug report with Oracle (internal review ID 9070863). As soon as I hear from them and the bug is in the public bug database, I will update this answer again.

    Update 2: Bug report JDK-8270170 is now visible, after it was verified.