Как заставить якоря доверия работать правильно на уровнях Android API 16-19

Я работаю над проектом, который должен поддерживать уровень API 16 и требует, чтобы я выполнял сетевые вызовы для динамических URL-адресов. Проблема, с которой я сталкиваюсь, заключается в том, что серверу недавно пришлось обновить TLS до 1.2 для соответствия PCI, который по умолчанию отключен в Android API 16-19. Хотя включить это несложно, у меня возникла проблема с сертификатами X509 только для этих уровней API.

Я провел довольно много исследований по этому вопросу, и хотя все эти ссылки были полезны:

1) Как включить поддержку TLS 1.2 в приложении Android (работающем на Android 4.1 JB)

2) Как использовать самоподписанный сертификат для подключения к серверу Mqtt в Android (клиент paho)?

3) SSLSocketFactory в java

4) Разрешение Java использовать недоверенный сертификат для SSL/HTTPS-соединение

5) Якорь доверия не найден для SSL-соединения Android

6) SSLHandshakeException: якорь доверия для пути сертификации не нашел. Только для Android API ‹ 19

7) доверие всем сертификатам с использованием HttpClient через HTTPS

Они не решают мою основную проблему, заключающуюся в том, что я вижу это исключение:

 com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
     at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:151)
     at com.android.volley.NetworkDispatcher.run(NetworkDispatcher.java:112)
 Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
     at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:409)
     at com.android.okhttp.Connection.upgradeToTls(Connection.java:146)
     at com.android.okhttp.Connection.connect(Connection.java:107)
     at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:294)
     at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255)
     at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206)
     at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345)
     at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:296)
     at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:503)
     at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:136)
     at com.android.volley.toolbox.HurlStack.performRequest(HurlStack.java:110)
     at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:96)
    ... 1 more

Когда я пытаюсь подключиться к URL-адресу, для которого требуется TLS1.2, например https://www.ssllabs.com/ssltest/viewMyClient.html.

По сути, это сводится к проблеме с TrustManager.

Вот 2 разных способа получить объект TrustManager (первый более безопасен, чем второй, поскольку второй доверяет всем сертификатам, что делает SSL спорным вопросом):

Первый метод:

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
        TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];

Второй метод:

X509TrustManager trustManager = new X509TrustManager() {
    public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {}
    public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {}
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
};

Затем они используются с этим кодом:

SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, new TrustManager[] { trustManager }, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

Если я использую первый TrustManager на уровне API 20+, указанный выше URL-адрес будет работать нормально, и страница загрузится. Если я использую первый на уровнях API 16-19, он выдаст исключение, указанное выше.

Если я использую второй TrustManager на любом уровне API 16+, вызов будет работать, но он оставит брешь в безопасности моего приложения, потому что теперь я доверяю всем сертификатам и оставляю себя открытым для всех видов атак.

Еще одно замечание заключается в том, что, хотя приведенный выше пример ошибки относится к библиотеке Volley, он все еще возникает при использовании Retrofit с теми же результатами.

Поэтому мой вопрос заключается в том, как использовать X509TrustManager для работы с такими веб-сайтами, как This One на Уровни API 16-19 такие же, как на уровнях API 20+?

Спасибо за ваше время.


person PGMacDesign    schedule 03.10.2018    source источник
comment
Я сталкиваюсь с точно такой же проблемой в проекте, над которым я сейчас работаю, используя сокеты. Все работает нормально на API 20+, но на чем-то ниже я получаю ту же проблему с якорем доверия, что и вы описали, даже когда я сам предоставляю правильный сертификат. Удалось ли вам решить эту проблему в конце концов?   -  person Sander van't Veer    schedule 14.11.2018
comment
@Sandervan'tVeer Нет, к сожалению. У меня даже была награда за неделю без успешных решений, так что это может быть одна из тех проблем, которые могут остаться без ответа.   -  person PGMacDesign    schedule 14.11.2018


Ответы (1)


Вы можете использовать этот метод для этой ошибки

Эта ошибка возникает из «https://», мы используем «s» в нашем базовом URL-адресе.

и измените время connectTimeout на ваше согласно

public static OkHttpClient getUnsafeOkHttpClient() {
    try {

        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 new java.security.cert.X509Certificate[]{};
                    }
                }
        };

        // 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();

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
        builder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
        builder.connectTimeout(6000, TimeUnit.SECONDS)
                .readTimeout(6000, TimeUnit.SECONDS)
                .writeTimeout(6000, TimeUnit.SECONDS).connectionPool(new ConnectionPool(50, 50000, TimeUnit.SECONDS));
        return builder.build();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
person Vijendra patidar    schedule 15.10.2018
comment
Насколько мне известно, изменение моего TrustManager в соответствии с вашим кодом выше просто разрешит все, что, как я уже упоминал в своем вопросе, является серьезным недостатком безопасности, которого я пытаюсь избежать. - person PGMacDesign; 16.10.2018