Тест Robolectri c, использующий OkHttp для реальных HTTP-запросов, выдает java .lang.NullPointerException: пароль не указан для PKCS # 12 KeyStore - PullRequest
2 голосов
/ 01 марта 2020

Я использую 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 мне пришлось обновить JVM модульного тестирования до JDK 9, в соответствии с инструкциями Robolectri c * 1010. *:

Выполнение тестов на Android API 29 теперь строго требует времени выполнения Java9 или новее. Если вы видите ошибки, связанные с неподдерживаемой версией Java при выполнении тестов API 29 через Android Studio; Вы можете использовать поле «JRE» в диалоговом окне «Run Configuration» для настройки более новой 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-вызовы?

1 Ответ

3 голосов
/ 01 марта 2020

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.

Я также попытался указать пароль по умолчанию для 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

Это была отличная подсказка. Я начал отлаживать исходный код JDK 9 с помощью Android Studio и заметил, что когда я работал с org.junit.runners.BlockJUnit4ClassRunner, мой KeyStore.keystoreSpi был sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12, но когда я работал с org.robolectric.RobolectricTestRunner, мой 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.

Таким образом, classi c $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 хранилища ключей, JDK DualFormatPKCS12 откатится к JKS если чтение файла как PKCS12 завершилось неудачно. Bouncycastle предполагает, что когда javax.net.ssl.trustStoreType равен pkcs12, это действительно то, что мы имеем в виду.

Таким образом, чтобы обойти эту проблему, установите системное свойство javax.net.ssl.trustStoreType равным JKS:

-Djavax.net.ssl.trustStoreType=JKS

Я подал проблемы с bouncycastle и robolectri c об этом.

...