android - 使用 OkHttp 进行真实 HTTP 请求的 Robolectric 测试抛出 java.lang.NullPointerException:没有为 PKCS#12 KeyStore 提供密码
问题描述
我正在使用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 调用?
解决方案
TLDR
要解决此问题,请将javax.net.ssl.trustStoreType
系统属性设置为 JKS
:
-Djavax.net.ssl.trustStoreType=JKS
细节
我找到了这个解决方法:
在尝试了很多事情之后,我终于找到了一种解决方法:
System.setProperty("javax.net.ssl.trustStore", "NONE") MockWebServer()
测试通过了这个额外的配置。
但是,当我尝试该解决方法时,我的所有 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
.
changeit
我还尝试通过以下解决方法为 java 信任库指定默认密码:
解决方法:
-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
,myKeyStore.keystoreSpi
是 a sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12
,但是当我使用org.robolectric.RobolectricTestRunner
my运行时KeyStore.keystoreSpi
是org.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 Key Store,我可以验证:
$ 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
密钥库,如果读取文件失败,JDKDualFormatPKCS12
将退回到 a 。Bouncycastle 假设当是我们的意思时。JKS
PKCS12
javax.net.ssl.trustStoreType
pkcs12
因此,要解决此问题,请将javax.net.ssl.trustStoreType
系统属性设置为 JKS
:
-Djavax.net.ssl.trustStoreType=JKS
我就此向bouncycastle和robolectric提出了问题。
推荐阅读
- python - 如何修复 django 中的“不可排序类型:NoneType() > float()”错误
- javascript - 无法读取数组中未定义的属性“情节”[vuejs]
- java - 为什么模拟对象没有得到方法调用?
- jquery - 房间:362 未捕获的类型错误:无法读取未定义的属性“长度”
- kubernetes - kubelet 处理事件时出错 /sys/fs/cgroup/memory/libcontainer_10010_systemd_test_default.slice
- docker - 使用 Docker 映像的神秘 Filebeat 7 X-Pack 问题
- r - R:从多个文件夹中读取一个 csv 文件并写入一个 xslx 文件,保留工作表名称
- c++ - 有没有办法使用数组填充向量?
- javascript - 我可以使用表单弹出创建动态菜单项吗?
- sql - SQL 时间序列作业