在 JDK 11.0.11 上使用“-Djava.system.class.loader”时的异常,如果类加载器在签名的 JAR

问题描述

我在 Java 11.0.11 运行时(使用 AdoptOpenJDK 11.0.11.9-hotspot 或访问 javax.net.ssl.SSLContext 时,Windows 10 上的 Oracle JDK 版本 11.0.11+9-LTS-194 或 Zulu11.48+21-CA(版本 11.0.11+9-LTS)。使用较旧的 Java 11 版本不会发生此问题。 我已将问题简化为一个小应用程序:

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);
    }
}   

该应用程序在 Eclipse 中使用 AspectJ LTW 启动配置启动。 我得到的输出和异常是这样的:

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)

这是我使用 VM 参数 "-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,SunJCE]
ProviderList: Disabling ThreadLocal providers
ProviderList: ThreadLocal providers: [SUN,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,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)

在我看来,当系统类加载器在 org.aspectj.weaver.loadtime.WeavingURLClassLoader 中初始化为 Classloader#initSystemClassLoader 时,稍后会以某种方式触发所有安全提供程序的某些步骤加载,这反过来又需要访问系统类加载器(其中尚未完全初始化)。 这导致了日志的这一部分:

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)

稍后在应用程序中调用 SSLContext.getInstance("TLS") 时,再次需要 sun.security.jca.ProviderConfig$ProviderLoader ,但这次失败并显示 NoClassDefFoundError。

您认为我的分析正确吗?

这是 OpenJDK 11.0.11 中的(潜在)错误吗?

是否有可能解决此问题的方法?

更新 (06.07.21):

I found out 如何查看由 Eclipse 启动配置产生的完整命令行调用。这是:

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

当我像这样(使用 java 而不是 javaw)从命令行运行它时,我也遇到了异常。当我使用较旧的 Java 版本 (11.0.10) 时,例如

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

它有效。

所以 Eclipse 似乎取代了 LTW 的系统类加载器。有没有办法告诉 Eclipse 将 LTW 与 javaagent 解决方案一起使用?也许这会规避这个问题?

我想我找到了一个解决方法:不要使用“AspectJ Load-Time Weaving Application”类型的 Eclipse 启动配置,而是使用普通的 Java 应用程序启动配置并传递“-javaagent”标志以启用LTW,例如

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

这对我有用。

解决方法

我做了一些实验,发现问题所在

  • 与 AspectJ 完全无关,
  • 通常与 Java 代理完全无关,
  • 发生当且仅当
    • -Djava.system.class.loader 被使用并且
    • 有问题的系统类加载器位于签名的 JAR 中,并且
    • 该应用程序在 JDK 11.0.11(不是 11.0.10 或我测试过的任何其他 JDK 9-16)上运行。

一旦我从 META-INF 目录中删除 JAR 签名,它也适用于 JDK 11.0.11。 IMO,这是 JDK 中的回归错误,应该修复。这是一个最小的测试用例:

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,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;
  }
}

请确保两个类都在不同的包中,否则稍后 JVM 会抱怨两个类都没有签名。

我们在基本目录中,源代码在文件夹 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

改变 java.exe 的路径以查看问题确实只发生在 JDK 11.0.11 上。


更新:我刚刚向 Oracle 提交了一份 JDK 错误报告(内部审查 ID 9070863)。一旦我听到他们的消息并且错误在公共错误数据库中,我将再次更新此答案。

更新 2:错误报告 JDK-8270170 在经过验证后现在可见。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...