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)

6 ответов

Причина этого заключается в том, что JVM/Dalvik не доверяют сертификатам CA в системе или хранилищам пользовательских сертификатов.

Чтобы исправить это с помощью Retrofit, если вы используете okhttp, с другим клиентом это очень похоже.
Вы должны сделать:

А). Создайте хранилище сертификатов, содержащее открытый ключ CA. Для этого вам нужно запустить следующий скрипт для *nix. Вам нужно установить openssl на свой компьютер и загрузить с https://www.bouncycastle.org/ the 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..."

Б). Скопируйте файл truststore mytruststore.bks в res/raw вашего проектарасположение склада

С). Настройка SSLC по тексту соединения:

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

По сути, у вас есть четыре возможных решения для устранения исключения "javax.net.ssl.SSLHandshakeException: " на Android.

  1. Доверяйте всем сертификатам. Не делайте этого, если вы действительно не знаете, что делаете.
  2. Создайте собственный SSLSocketFactory, который доверяет только вашему сертификату. Это работает до тех пор, пока вы точно знаете, к каким серверам собираетесь подключиться, но как только вам потребуется подключиться к новому серверу с другим сертификатом SSL, вам потребуется обновить приложение.
  3. Создайте файл хранилища ключей, содержащий "главный список" сертификатов Android, а затем добавьте свой собственный. Если срок действия какого-либо из этих сертификатов истекает в будущем, вы несете ответственность за их обновление в своем приложении. Я не могу придумать причины для этого.
  4. Создайте настраиваемый SSLSocketFactory, который использует встроенное хранилище ключей сертификата, но использует альтернативное хранилище ключей для всего, что не удается проверить с использованием по умолчанию. Это хорошо объяснено здесь.

Кроме того, я хочу более подробно остановиться на пункте № 1. Мы можем выборочно пропустить какой-либо домен, используя конфигурацию сети манифеста, как описано:

  1. Создайте файл "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>
    
  2. Добавьте "network_security_config.xml" в тег приложения в манифесте как:

    android:networkSecurityConfig="@xml/network_security_config"

Вот оно.. сделано!!. Вы успешно пропустили сертификат SSL.

Это может произойти по нескольким причинам, в том числе:

  1. CA, выдавший сертификат сервера, был неизвестен
  2. Сертификат сервера не был подписан ЦС, но был самоподписан
  3. В конфигурации сервера отсутствует промежуточный ЦС

пожалуйста, проверьте эту ссылку для решения: https://developer.android.com/training/articles/security-ssl.html

Исправление для Android N и выше: у меня была аналогичная проблема, и мне удалось ее решить, выполнив шаги, описанные в https://developer.android.com/training/articles/security-config

Но изменения конфигурации без какой-либо сложной логики кода будут работать только на Android версии 24 и выше.

Исправление для всех версий, включая версию Итак, для Android ниже N (версия 24) решение заключается в изменении кода, как указано выше. Если вы используете OkHttp, следуйте customTrust:https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java

Привет та же проблема, которую я решил, вы можете попробовать это

java.security.cert.CertPathValidatorException: доверенная привязка для пути сертификации не найдена.NETWORK

 // SET SSL
public static OkClient setSSLFactoryForClient(OkHttpClient client) {
    try {
        // Create a trust manager that does not validate certificate chains
        final TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                    }

                    @Override
                    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                    }

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                }
        };

        // Install the all-trusting trust manager
        final SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        // Create an ssl socket factory with our all-trusting manager
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();


        client.setSslSocketFactory(sslSocketFactory);
        client.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });

    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return new OkClient(client);
}

Есть 4 способа, которые я знаю:

  • импортируйте сертификат в ваше приложение и используйте его для подключения
  • отключить проверку сертификата
  • добавьте свой сертификат в доверенные системные сертификаты в Android
  • купить проверенный сертификат, который принимается Android

Я предполагаю, что вы не хотите платить за это, поэтому я думаю, что самое элегантное решение - это первое, что может быть достигнуто следующим образом:

http://blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html

SSL не настроен должным образом. Эти ошибки trustAnchor обычно означают, что хранилище доверия не может быть найдено. Проверьте свою конфигурацию и убедитесь, что вы на самом деле указываете на хранилище доверенных сертификатов и на его месте.

Убедитесь, что у вас есть -Djavax.net.ssl.trustStore установить системное свойство и затем проверить, что путь действительно ведет к хранилищу доверия.

Вы также можете включить отладку SSL, установив это системное свойство -Djavax.net.debug=all, В выводе отладки вы заметите, что он утверждает, что не может найти хранилище доверенных сертификатов.

После некоторого исследования я нашел способ обойти ошибку ssl. Якорь доверия для пути сертификации не найден. Возможно, это не лучший способ, но вы можете использовать его для тестирования.

 private HttpsURLConnection httpsUrlConnection( URL urlDownload) throws Exception {
  HttpsURLConnection connection=null;
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @SuppressLint("TrustAllX509TrustManager")
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }

            @SuppressLint("TrustAllX509TrustManager")
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        }
        };
        SSLContext sc = SSLContext.getInstance("SSL"); // Add in try catch block if you get error.
        sc.init(null, trustAllCerts, new java.security.SecureRandom()); // Add in try catch block if you get error.
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        HostnameVerifier usnoHostnameVerifier = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        SSLSocketFactory sslSocketFactory = sc.getSocketFactory();

        connection = (HttpsURLConnection) urlDownload.openConnection();
        connection.setHostnameVerifier(usnoHostnameVerifier);
        connection.setSSLSocketFactory(sslSocketFactory);

        return connection;
    }

Это проблема на стороне сервера.

На стороне сервера есть файл .crt для HTTPS, здесь нам нужно объединить

cat your_domain.**crt** your_domain.**ca-bundle** >> ssl_your_domain_.crt 

затем перезапустите.

sudo service nginx restart

У меня работает нормально.

Мой ответ может не быть решением вашего вопроса, но он наверняка поможет другим, ищущим подобную проблему, подобную этой: javax.net.ssl.SSLHandshakeException: проверка цепи не удалась

Вам просто нужно проверить дату и время вашего Android-устройства, это должно решить проблему. Это решило мою проблему.

Хорошо, поэтому я столкнулся с той же проблемой для моего приложения для Android, которое имеет защищенный домен, то есть HTTPS,

Вот шаги:

  1. Для вашего домена вам понадобится файл сертификата SSL, т.е. файл .pem.
  2. поместите этот файл в папку с ресурсами
  3. Просто скопируйте и вставьте этот класс в свой проект

public class SSlUtilsw {

public static SSLContext getSslContextForCertificateFile(Context context, String fileName){

    try {
        KeyStore keyStore = SSlUtilsw.getKeyStore(context, fileName);
        SSLContext sslContext = SSLContext.getInstance("SSL");
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);
        sslContext.init(null,trustManagerFactory.getTrustManagers(),new SecureRandom());
        return sslContext;

    }catch (Exception e){
        String msg = "Error during creating SslContext for certificate from assets";
        e.printStackTrace();
        throw new RuntimeException(msg);
    }
}

public static KeyStore getKeyStore(Context context,String fileName){
    KeyStore keyStore = null;
    try {
        AssetManager assetManager=context.getAssets();
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream caInput=assetManager.open(fileName);
        Certificate ca;
        try {
            ca=cf.generateCertificate(caInput);

        }finally {
            caInput.close();
        }
        String keyStoreType=KeyStore.getDefaultType();
        keyStore=KeyStore.getInstance(keyStoreType);
        keyStore.load(null,null);
        keyStore.setCertificateEntry("ca",ca);
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return keyStore;
}}
  1. В своем классе модернизации http-клиента добавьте это

        val trustManagerFactory: TrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
    trustManagerFactory.init(null as KeyStore?)
    val trustManagers: Array<TrustManager> = trustManagerFactory.trustManagers
    if (trustManagers.size != 1 || trustManagers[0] !is X509TrustManager) {
        throw IllegalStateException("Unexpected default trust managers:" + trustManagers.contentToString())
    }
    val trustManager = trustManagers[0] as X509TrustManager
    
    httpClient.sslSocketFactory(SSlUtils.getSslContextForCertificateFile(
            applicationContextHere, "yourcertificate.pem").socketFactory, trustManager)
    

Вот и все.

Я использую этот класс, и у меня нет проблем.

public class WCFs
{
    //  https://192.168.30.8/myservice.svc?wsdl
private static final String NAMESPACE = "http://tempuri.org/";
private static final String URL = "192.168.30.8";
private static final String SERVICE = "/myservice.svc?wsdl";
private static String SOAP_ACTION = "http://tempuri.org/iWCFserviceMe/";


public static Thread myMethod(Runnable rp)
{
    String METHOD_NAME = "myMethod";

    SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);

    request.addProperty("Message", "Https WCF Running...");
    return _call(rp,METHOD_NAME, request);
}

protected static HandlerThread _call(final RunProcess rp,final String METHOD_NAME, SoapObject soapReq)
{
    final SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
    int TimeOut = 5*1000;

    envelope.dotNet = true;
    envelope.bodyOut = soapReq;
    envelope.setOutputSoapObject(soapReq);

    final HttpsTransportSE httpTransport_net = new HttpsTransportSE(URL, 443, SERVICE, TimeOut);

    try
    {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() // use this section if crt file is handmake
        {
            @Override
            public boolean verify(String hostname, SSLSession session)
            {
                return true;
            }
        });

        KeyStore k = getFromRaw(R.raw.key, "PKCS12", "password");
        ((HttpsServiceConnectionSE) httpTransport_net.getServiceConnection()).setSSLSocketFactory(getSSLSocketFactory(k, "SSL"));


    }
    catch(Exception e){}

    HandlerThread thread = new HandlerThread("wcfTd"+ Generator.getRandomNumber())
    {
        @Override
        public void run()
        {
            Handler h = new Handler(Looper.getMainLooper());
            Object response = null;

            for(int i=0; i<4; i++)
            {
                response = send(envelope, httpTransport_net , METHOD_NAME, null);

                try
                {if(Thread.currentThread().isInterrupted()) return;}catch(Exception e){}

                if(response != null)
                    break;

                ThreadHelper.threadSleep(250);
            }

            if(response != null)
            {
                if(rp != null)
                {
                    rp.setArguments(response.toString());
                    h.post(rp);
                }
            }
            else
            {
                if(Thread.currentThread().isInterrupted())
                    return;

                if(rp != null)
                {
                    rp.setExceptionState(true);
                    h.post(rp);
                }
            }

            ThreadHelper.stopThread(this);
        }
    };

    thread.start();

    return thread;
}


private static Object send(SoapSerializationEnvelope envelope, HttpTransportSE androidHttpTransport, String METHOD_NAME, List<HeaderProperty> headerList)
{
    try
    {
        if(headerList != null)
            androidHttpTransport.call(SOAP_ACTION + METHOD_NAME, envelope, headerList);
        else
            androidHttpTransport.call(SOAP_ACTION + METHOD_NAME, envelope);

        Object res = envelope.getResponse();

        if(res instanceof SoapPrimitive)
            return (SoapPrimitive) envelope.getResponse();
        else if(res instanceof SoapObject)
            return ((SoapObject) envelope.getResponse());
    }
    catch(Exception e)
    {}

    return null;
}

public static KeyStore getFromRaw(@RawRes int id, String algorithm, String filePassword)
{
    try
    {
        InputStream inputStream = ResourceMaster.openRaw(id);
        KeyStore keystore = KeyStore.getInstance(algorithm);
        keystore.load(inputStream, filePassword.toCharArray());
        inputStream.close();

        return keystore;
    }
    catch(Exception e)
    {}

    return null;
}

public static SSLSocketFactory getSSLSocketFactory(KeyStore trustKey, String SSLAlgorithm)
{
    try
    {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustKey);

        SSLContext context = SSLContext.getInstance(SSLAlgorithm);//"SSL" "TLS"
        context.init(null, tmf.getTrustManagers(), null);

        return context.getSocketFactory();
    }
    catch(Exception e){}

    return null;
}

}

Другие вопросы по тегам