javax.net.ssl.SSLHandshakeException: java.security.cert.Исключение CertPathValidatorException: привязка доверия для пути сертификации не найдена
Я использую Retrofit для доступа к моему REST API. Однако, когда я помещаю свой API за ssl и получаю к нему доступ с помощью http://myhost/myapi
, я получаю эту ошибку:
Нужно ли мне делать что-то дополнительное теперь, когда мой API поддерживает SSL?
Вот как я подключаюсь:
private final String API = "https://myhost/myapi";
private final RestAdapter REST_ADAPTER = new RestAdapter.Builder()
.setServer(API)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
01-10 09:49:55.621 2076-2100/com.myapp.mobile D/Retrofit﹕ javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:401)
at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:497)
at libcore.net.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:134)
at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:90)
at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:48)
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:287)
at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:222)
at $Proxy12.signin(Native Method)
at com.myapp.loginactivity$3.doInBackground(LoginActivity.java:143)
at com.myapp.loginactivity$3.doInBackground(LoginActivity.java:136)
at android.os.AsyncTask$2.call(AsyncTask.java:287)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:841)
Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:282)
at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:202)
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:595)
at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:398)
at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:497)
at libcore.net.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:134)
at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:90)
at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:48)
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:287)
at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:222)
at $Proxy12.signin(Native Method)
at com.myapp.LoginActivity$3.doInBackground(LoginActivity.java:143)
at com.myapp.LoginActivity$3.doInBackground(LoginActivity.java:136)
at android.os.AsyncTask$2.call(AsyncTask.java:287)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:841)
Переведено автоматически
Ответ 1
Причина, по которой это происходит, заключается в том, что JVM / Dalvik не уверены в сертификатах CA в системе или в хранилищах пользовательских сертификатов.
Чтобы исправить это с помощью Retrofit, если вы используете okhttp, с другим клиентом это очень похоже.
Вам нужно сделать:
A). Создайте хранилище сертификатов, содержащее открытый ключ CA. Для этого вам нужно запустить следующий скрипт для * nix. Вам необходимо установить openssl на свой компьютер и загрузить с https://www.bouncycastle.org / jar bcprov-jdk16-1.46.jar. Загружайте эту версию, а не другую, версия 1.5x несовместима с Android 4.0.4.
#!/bin/bash
if [ -z $1 ]; then
echo "Usage: cert2Android<CA cert PEM file>"
exit 1
fi
CACERT=$1
BCJAR=bcprov-jdk16-1.46.jar
TRUSTSTORE=mytruststore.bks
ALIAS=`openssl x509 -inform PEM -subject_hash -noout -in $CACERT`
if [ -f $TRUSTSTORE ]; then
rm $TRUSTSTORE || exit 1
fi
echo "Adding certificate to $TRUSTSTORE..."
keytool -import -v -trustcacerts -alias $ALIAS \
-file $CACERT \
-keystore $TRUSTSTORE -storetype BKS \
-providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath $BCJAR \
-storepass secret
echo ""
echo "Added '$CACERT' with alias '$ALIAS' to $TRUSTSTORE..."
B). Скопируйте файл truststore mytruststore.bks в res / raw вашего проекта.
C). Установка SSLContext соединения:
.............
okHttpClient = new OkHttpClient();
try {
KeyStore ksTrust = KeyStore.getInstance("BKS");
InputStream instream = context.getResources().openRawResource(R.raw.mytruststore);
ksTrust.load(instream, "secret".toCharArray());
// TrustManager decides which certificate authorities to use.
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ksTrust);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | KeyManagementException e) {
e.printStackTrace();
}
.................
Ответ 2
Это может произойти по нескольким причинам, в том числе:
- Центр сертификации, выдавший сертификат сервера, был неизвестен
- Сертификат сервера не был подписан центром сертификации, но был подписан самостоятельно
- В конфигурации сервера отсутствует промежуточный центр сертификации
пожалуйста, проверьте эту ссылку для решения: https://developer.android.com/training/articles/security-ssl.html#CommonProblems
Ответ 3
В принципе, у вас есть четыре потенциальных решения для исправления исключения "javax.net.ssl.SSLHandshakeException: " на Android
- Доверяйте всем сертификатам. Не делайте этого, если вы действительно не знаете, что делаете.
- Создайте пользовательский SSLSocketFactory, который доверяет только вашему сертификату. Это работает до тех пор, пока вы точно знаете, к каким серверам собираетесь подключаться, но как только вам потребуется подключиться к новому серверу с другим SSL-сертификатом, вам потребуется обновить ваше приложение.
- Создайте файл хранилища ключей, содержащий "основной список" сертификатов Android, затем добавьте свой собственный. Если срок действия какого-либо из этих сертификатов истекает в будущем, вы несете ответственность за их обновление в своем приложении. Я не могу придумать причину для этого.
- Создайте пользовательский SSLSocketFactory, который использует встроенное хранилище ключей сертификата, но использует альтернативное хранилище ключей для всего, что не удается проверить по умолчанию. Это хорошо объяснено в нажмите здесь
Кроме того, я хочу подробнее остановиться на пункте № 1. Мы можем выборочно пропустить некоторый домен, используя manifest network config в качестве объяснения:
Создайте файл "network_security_config.xml" в папке xml в папке res со следующим содержимым.
<network-security-config xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<domain-config>
<domain includeSubdomains="true">191.1.1.0</domain>
<domain includeSubdomains="true">your_domain</domain>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</domain-config>
</network-security-config>Добавьте "network_security_config.xml" в тег приложения в манифесте как:
android:networkSecurityConfig="@xml/network_security_config"
Вот и все .. готово !!. Вы успешно пропустили SSL-сертификат.
Ответ 4
Исправление для Android и выше: У меня была похожая проблема, и я попытался решить ее, выполнив шаги, описанные в https://developer.android.com/training/articles/security-config
Но изменения конфигурации без какой-либо сложной логики кода будут работать только на Android версии 24 и выше.
Исправлено для всех версий, включая версию < N: Поэтому для Android ниже N (версия 24) решение заключается в изменении кода, как упоминалось выше. Если вы используете OkHttp, то соблюдайте customTrust: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java