使用OkHttp进行真实HTTP请求的Robolectric测试引发了java.lang.NullPointerException错误:未为PKCS#12 KeyStore提供密码。

9
我正在使用Robolectric 4.3.1(testImplementation "org.robolectric:robolectric:4.3.1")为我的集成测试创建一个Android sqlite环境。我的系统使用OkHttp(implementation 'com.squareup.okhttp3:okhttp:3.14.7')进行真实的HTTP请求,而不是使用MockWebServer。在升级到Android 10 SDK之后,我必须按照Robolectric的说明将单元测试JVM升级到JDK 9,如下

运行Android API 29上的测试现在严格要求Java9或更高版本的运行时。如果在通过Android Studio运行API 29时出现有关不支持的Java版本的错误,则可以使用“运行配置”对话框中的“JRE”字段来配置更新的Java运行时。有关更多背景信息,请参见https://developer.android.com/studio/run/rundebugconfig

然而,现在我的集成测试失败了,显示如下:

java.lang.NullPointerException: No password supplied for PKCS#12 KeyStore.

    at org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.engineLoad(Unknown Source)
    at java.base/java.security.KeyStore.load(KeyStore.java:1479)
    at java.base/sun.security.ssl.TrustStoreManager$TrustAnchorManager.loadKeyStore(TrustStoreManager.java:367)
    at java.base/sun.security.ssl.TrustStoreManager$TrustAnchorManager.getTrustedCerts(TrustStoreManager.java:315)
    at java.base/sun.security.ssl.TrustStoreManager.getTrustedCerts(TrustStoreManager.java:59)
    at java.base/sun.security.ssl.TrustManagerFactoryImpl.engineInit(TrustManagerFactoryImpl.java:51)
    at java.base/javax.net.ssl.TrustManagerFactory.init(TrustManagerFactory.java:278)
    at okhttp3.internal.Util.platformTrustManager(Util.java:640)
    at okhttp3.OkHttpClient.<init>(OkHttpClient.java:228)
    at okhttp3.OkHttpClient.<init>(OkHttpClient.java:202)

如何再次进行真正的HTTP调用?

2个回答

8

简述

为了解决这个问题,将 javax.net.ssl.trustStoreType 系统属性设置为 JKS

-Djavax.net.ssl.trustStoreType=JKS

详情

我找到了这个解决方法

After trying many things I finally found one workaround:

System.setProperty("javax.net.ssl.trustStore", "NONE")

MockWebServer()

The tests are passing with this additional configuration.

然而,当我尝试使用这个解决方法时,所有的HTTP请求都失败了,并显示以下ConnectException错误:
java.net.ConnectException: Failed to connect to myhost.com:443
    at okhttp3.internal.connection.RealConnection.connectSocket(RealConnection.java:265)
    at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:183)
    at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:224)
    at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:108)
    at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:88)
    at okhttp3.internal.connection.Transmitter.newExchange(Transmitter.java:169)
    at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:41)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
    at okhttp3.RealCall.execute(RealCall.java:81)

我怀疑 OkHttp 正确地拒绝所有 TLS 连接,因为解决方法建议将 javax.net.ssl.trustStore 设置为 NONE 以增强安全性。
我还尝试通过 此解决方法 指定 Java 信任库的默认密码 changeit

解决方法:-Djavax.net.ssl.trustStorePassword=changeit

但是,当我尝试实例化一个 OkHttpClient 时,出现了以下异常:
java.lang.AssertionError: No System TLS

    at okhttp3.internal.Util.platformTrustManager(Util.java:648)
    at okhttp3.OkHttpClient.<init>(OkHttpClient.java:228)
    at okhttp3.OkHttpClient.<init>(OkHttpClient.java:202)

由以下原因引起:

Caused by: java.security.KeyStoreException: problem accessing trust store
    at java.base/sun.security.ssl.TrustManagerFactoryImpl.engineInit(TrustManagerFactoryImpl.java:75)
    at java.base/javax.net.ssl.TrustManagerFactory.init(TrustManagerFactory.java:278)
    at okhttp3.internal.Util.platformTrustManager(Util.java:640)
    ... 22 more
Caused by: java.io.IOException: stream does not represent a PKCS12 key store
    at org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.engineLoad(Unknown Source)
    at java.base/java.security.KeyStore.load(KeyStore.java:1479)
    at java.base/sun.security.ssl.TrustStoreManager$TrustAnchorManager.loadKeyStore(TrustStoreManager.java:367)
    at java.base/sun.security.ssl.TrustStoreManager$TrustAnchorManager.getTrustedCerts(TrustStoreManager.java:315)
    at java.base/sun.security.ssl.TrustStoreManager.getTrustedCerts(TrustStoreManager.java:59)
    at java.base/sun.security.ssl.TrustManagerFactoryImpl.engineInit(TrustManagerFactoryImpl.java:51)
    ... 24 more

这是一个很好的线索。我开始使用Android Studio调试JDK 9源代码,注意到当我使用org.junit.runners.BlockJUnit4ClassRunner运行时,我的KeyStore.keystoreSpisun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12,但当我使用org.robolectric.RobolectricTestRunner运行时,我的KeyStore.keystoreSpiorg.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi$BCPKCS12KeyStore
根据JEP-229
这个功能将默认密钥库类型从JKS更改为PKCS12。默认情况下,新的密钥库将以PKCS12密钥库格式创建。现有的密钥库不会改变,密钥库应用程序可以继续明确指定所需的密钥库类型。
不能破坏现有的应用程序。密钥库往往存在很长时间,因此我们需要支持跨几个JDK版本的访问。访问早期JDK版本创建的密钥库的应用程序必须在JDK 9上运行不变。同样,访问由JDK 9创建的密钥库的应用程序应该在早期的JDK版本上运行不变。
通过引入一个密钥库检测机制来实现此要求,该机制了解JKS和PKCS12格式。在加载之前检查密钥库的格式以确定其类型,然后使用适当的密钥库实现来访问它。该机制默认启用,但如果需要,可以禁用它。
这个密钥库检测机制的支持可能被回溯到早期的JDK版本。
因此,经典的$JAVA_HOME/lib/security/cacerts仍然是Java密钥库,我可以验证:
$ file /Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home/lib/security/cacerts
/Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home/lib/security/cacerts: Java KeyStore

然而,由于JDK想要默认使用PKCS12密钥库,所以当以PKCS12格式读取文件失败时,JDK的DualFormatPKCS12会退回到JKS。Bouncycastle假定当javax.net.ssl.trustStoreType为pkcs12时,我们真正想要的是PKCS12格式。
因此,为了解决这个问题,请将javax.net.ssl.trustStoreType系统属性设置为JKS:
-Djavax.net.ssl.trustStoreType=JKS

我向bouncycastlerobolectric提交了与此相关的问题。


在Gradle中,我应该在哪里设置-Djavax.net.ssl.trustStoreType=JKS? - kilg
尝试将以下内容添加到您的gradle.properties文件中:org.gradle.jvmargs=-Xms4g -Xmx4096m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Djavax.net.ssl.trustStoreType=JKS - bsautner
好消息,这个问题已经在 Robolectric 版本 4.4 中得到解决了! - Keval Langalia

4
如果您在使用 Robolectric 时遇到此问题,请将版本升级至4.4,因为该问题已在4.4中得到解决。

testImplementation ('org.robolectric:robolectric:4.4')


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接