Устройства Android pre-lollipop выдают ошибку. SSL-рукопожатие прервано: ssl = 0x618d9c18: ошибка ввода-вывода во время системного вызова, сброс соединения одноранговым узлом

У меня есть эта странная проблема, в которой модернизация продолжает бросать меня

«Подтверждение SSL прервано: ssl = 0x618d9c18: ошибка ввода-вывода во время системного вызова, сброс соединения партнером»

в kitkat, тогда как тот же код отлично работает в устройствах с леденцами. Я использую клиент OkHttpClient, как показано ниже.

public OkHttpClient getUnsafeOkHttpClient() {
    try {
        final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            @Override
            public void checkClientTrusted(
                    java.security.cert.X509Certificate[] chain,
                    String authType) {
            }
            @Override
            public void checkServerTrusted(
                    java.security.cert.X509Certificate[] chain,
                    String authType) {
            }
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[0];
            }
        } };

        int cacheSize = 10 * 1024 * 1024; // 10 MB
        Cache cache = new Cache(getCacheDir(), cacheSize);
        final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(null, trustAllCerts,
                new java.security.SecureRandom());
        final SSLSocketFactory sslSocketFactory = sslContext
                .getSocketFactory();
        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient = okHttpClient.newBuilder()
                .cache(cache)
                .sslSocketFactory(sslSocketFactory)
                .hostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER).build();
        return okHttpClient;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

}

Я использую этот клиент в модификации, как это

Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(URL)
            .client(getUnsafeOkHttpClient())
            .addConverterFactory(GsonConverterFactory.create())
            .build();

РЕДАКТИРОВАТЬ: добавление getUnsafeOkHttpClient() здесь не имеет никакого эффекта, и вообще не рекомендуется обходить проверку ssl с помощью getUnsafeOkHttpClient()

К вашему сведению: проблема заключалась в том, что конечная точка API поддерживает только TLS 1.2, который был отключен по умолчанию на устройствах Android 16<device<20. Итак, для 16<device<20 создайте собственный SSLSocketFactory


person Navneet Krishna    schedule 31.08.2017    source источник
comment
Обязательно ли использовать TLSv1.2? Что делать, если вы используете значение по умолчанию?   -  person algrid    schedule 03.09.2017
comment
@algrid нет никакого эффекта, если я изменю экземпляр ssl, поэтому я удалил весь небезопасный клиент из модификации, но он работает только на леденце, а не на киткат   -  person Navneet Krishna    schedule 03.09.2017
comment
Что у вас на стороне сервера? Может быть, вы можете получить некоторые журналы с вашего сервера?   -  person algrid    schedule 03.09.2017
comment
на самом деле это бесплатный API от api.data.gov.in и у меня нет доступа к журналам сервера   -  person Navneet Krishna    schedule 03.09.2017
comment
@algrid возможная проблема на стороне сервера?   -  person Navneet Krishna    schedule 03.09.2017


Ответы (6)


Наконец нашел решение этой проблемы, это не полное решение, так как это взлом, упомянутый Джесси Уилсон из okhttp, Square здесь. Как я уже упоминал, это был простой хак, когда мне пришлось переименовать переменную SSLSocketFactory в

private SSLSocketFactory delegate;

обратите внимание, что это вызовет ошибку, если вы укажете любое имя, кроме делегата. Я публикую свое полное решение ниже

Это мой класс TLSSocketFactory.

public class TLSSocketFactory extends SSLSocketFactory {

private SSLSocketFactory delegate;
private TrustManager[] trustManagers;

public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
    generateTrustManagers();
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, trustManagers, null);
    delegate = context.getSocketFactory();
}

private void generateTrustManagers() throws KeyStoreException, NoSuchAlgorithmException {
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init((KeyStore) null);
    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

    if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException("Unexpected default trust managers:"
                + Arrays.toString(trustManagers));
    }

    this.trustManagers = trustManagers;
}


@Override
public String[] getDefaultCipherSuites() {
    return delegate.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
    return delegate.getSupportedCipherSuites();
}

@Override
public Socket createSocket() throws IOException {
    return enableTLSOnSocket(delegate.createSocket());
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
    return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose));
}

@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    return enableTLSOnSocket(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
    return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort));
}

@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
    return enableTLSOnSocket(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
    return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort));
}

private Socket enableTLSOnSocket(Socket socket) {
    if(socket != null && (socket instanceof SSLSocket)) {
        ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
    }
    return socket;
}

@Nullable
public X509TrustManager getTrustManager() {
    return  (X509TrustManager) trustManagers[0];
}

}

и вот как я использовал его с okhttp и модификацией

 OkHttpClient client=new OkHttpClient();
    try {
        TLSSocketFactory tlsSocketFactory=new TLSSocketFactory();
        if (tlsSocketFactory.getTrustManager()!=null) {
            client = new OkHttpClient.Builder()
                    .sslSocketFactory(tlsSocketFactory, tlsSocketFactory.getTrustManager())
                    .build();
        }
    } catch (KeyManagementException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    }

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(URL)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

EDIT: метод public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory) теперь устарел, и мы должны использовать public Builder sslSocketFactory( SSLSocketFactory sslSocketFactory, X509TrustManager trustManager), как я обновил в ответе. Это связано с тем, что X509TrustManager — это поле, которое необходимо OkHttp для создания чистой цепочки сертификатов, которая не была приостановлена ​​в устаревшем методе.

Вы также можете проверить это для получения дополнительной информации

person Navneet Krishna    schedule 03.09.2017

Я изменил ответ @Navneet Krishna, потому что метод OkHttpClient.Builder. builder.sslSocketFactory(tlsSocketFactory) устарел.

public class TLSSocketFactory extends SSLSocketFactory {

private final SSLSocketFactory delegate;
private TrustManager[] trustManagers;

public TLSSocketFactory() throws KeyStoreException, KeyManagementException, NoSuchAlgorithmException {
    generateTrustManagers();
    SSLContext context = SSLContext.getInstance("TLS");
    context.init(null, trustManagers, null);
    delegate = context.getSocketFactory();
}

private void generateTrustManagers() throws KeyStoreException, NoSuchAlgorithmException {
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init((KeyStore) null);
    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

    if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException("Unexpected default trust managers:"
                + Arrays.toString(trustManagers));
    }

    this.trustManagers = trustManagers;
}

@Override
public String[] getDefaultCipherSuites() {
    return delegate.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
    return delegate.getSupportedCipherSuites();
}

@Override
public Socket createSocket() throws IOException {
    return enableTLSOnSocket(delegate.createSocket());
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
    return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose));
}

@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    return enableTLSOnSocket(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
    return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort));
}

@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
    return enableTLSOnSocket(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
    return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort));
}

private Socket enableTLSOnSocket(Socket socket) {
    if (socket instanceof SSLSocket) {
        ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.1", "TLSv1.2"});
    }
    return socket;
}

@Nullable
public X509TrustManager getTrustManager() {
    return  (X509TrustManager) trustManagers[0];
}
}

Вам нужно назначить его следующим образом:

TLSSocketFactory tlsTocketFactory = new TLSSocketFactory();
client = new OkHttpClient.Builder()
            .sslSocketFactory(tlsSocketFactory, tlsSocketFactory.getTrustManager());
            .build();
person Paweł Dedio    schedule 16.01.2019
comment
Мне пришлось удалить @Inject в конструкторе TLSSocketFactory, чтобы это работало. Возможно, это был неправильный импорт (для меня был возможен только 1 вариант: импортировать javax.inject.Inject, что для меня не имеет смысла). пс. включение импорта в ваш ответ действительно помогает! - person P Kuijpers; 18.10.2019
comment
Извините, эта аннотация использовалась в Dagger DI. Я отредактировал свой ответ. Спасибо, что заметили это. - person Paweł Dedio; 19.10.2019

В дополнение к Navneet Krishna мне нужно было сделать следующее в классе моего приложения:

ProviderInstaller.installIfNeededAsync

Согласно https://developer.android.com/training/articles/security-gms-provider, и это потому, что мне нужно было обновить провайдера безопасности для защиты от эксплойтов SSL.

Класс моего приложения:

public class AppClass extends MultiDexApplication {

private static final String TAG = AppClass.class.getName();

private static Context context;
private static AuthAPI authAPI;
private static RestAPI buyersAPI;

@Override
public void onCreate() {
    super.onCreate();
    /* enable SSL compatibility in pre-lollipop devices */
    upgradeSecurityProvider();

    createAuthAPI();
    createRestAPI();
}

private void upgradeSecurityProvider() {
    try{
        ProviderInstaller.installIfNeededAsync(this, new ProviderInstaller.ProviderInstallListener() {
            @Override
            public void onProviderInstalled() {
                Log.e(TAG, "New security provider installed.");
            }

            @Override
            public void onProviderInstallFailed(int errorCode, Intent recoveryIntent) {
                GooglePlayServicesUtil.showErrorNotification(errorCode, BuyersApp.this);
                Log.e(TAG, "New security provider install failed.");
            }
        });
    }catch (Exception ex){
        Log.e(TAG, "Unknown issue trying to install a new security provider", ex);
    }

}

private void createAuthAPI() {
    OkHttpClient.Builder authAPIHttpClientBuilder = new OkHttpClient.Builder();

    Retrofit retrofit = new Retrofit.Builder()
            .client(enableTls12OnPreLollipop(authAPIHttpClientBuilder).build())
            .baseUrl(DomainLoader.getInstance(context).getAuthDomain())
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    authAPI = retrofit.create(AuthAPI.class);
}

private static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) {
    if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 22) {
        try {
            SSLContext sc = SSLContext.getInstance("TLSv1.2");
            sc.init(null, null, null);

            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }
            X509TrustManager trustManager = (X509TrustManager) trustManagers[0];

            client.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()), trustManager);

            ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                    .tlsVersions(TlsVersion.TLS_1_2)
                    .build();

            List<ConnectionSpec> specs = new ArrayList<>();
            specs.add(cs);
            specs.add(ConnectionSpec.COMPATIBLE_TLS);
            specs.add(ConnectionSpec.CLEARTEXT);

            client.connectionSpecs(specs);
        } catch (Exception exc) {
            Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc);
        }
    }

    return client;
}

private void createRestAPI() {
    OkHttpClient.Builder restAPIHttpClientBuilder = new OkHttpClient.Builder();
    buyersAPIHttpClientBuilder.readTimeout(60, TimeUnit.SECONDS);
    buyersAPIHttpClientBuilder.connectTimeout(60, TimeUnit.SECONDS);
    buyersAPIHttpClientBuilder.writeTimeout(600, TimeUnit.SECONDS);
    buyersAPIHttpClientBuilder.addInterceptor(new NetworkErrorInterceptor());
    buyersAPIHttpClientBuilder.addInterceptor(new TokenVerificationInterceptor());

    Retrofit retrofit = new Retrofit.Builder()
            .client(enableTls12OnPreLollipop(restAPIHttpClientBuilder).build())
            .baseUrl(DomainLoader.getInstance(context).getDomain())
            .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()))
            .addConverterFactory(ScalarsConverterFactory.create())
            .build();

    buyersAPI = retrofit.create(RestAPI.class);
}
}

И мой класс Tls12SocketFactory:

public class Tls12SocketFactory extends SSLSocketFactory {
private static final String[] TLS_V12_ONLY = {"TLSv1.2"};

final SSLSocketFactory delegate;

public Tls12SocketFactory(SSLSocketFactory base) {
    this.delegate = base;
}

@Override
public String[] getDefaultCipherSuites() {
    return delegate.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
    return delegate.getSupportedCipherSuites();
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
    return patch(delegate.createSocket(s, host, port, autoClose));
}

@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    return patch(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
    return patch(delegate.createSocket(host, port, localHost, localPort));
}

@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
    return patch(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
    return patch(delegate.createSocket(address, port, localAddress, localPort));
}

private Socket patch(Socket s) {
    if (s instanceof SSLSocket) {
        ((SSLSocket) s).setEnabledProtocols(TLS_V12_ONLY);
    }
    return s;
}
}

И это прекрасно работает на всех устройствах с KitKat и выше.

person Carlos Daniel    schedule 04.05.2018

Я получил информацию SSL/TLS для api.data.gov.in здесь — https://www.ssllabs.com/ssltest/analyze.html?d=api.data.gov.in

Похоже, он поддерживает только TLSv1.2. Старые версии Android действительно имеют проблемы с новейшими версиями TLS. В разделе «Моделирование рукопожатия» на странице ssllabs вы даже можете увидеть свои проблемы.

См. Как включить поддержку TLS 1.2 в приложении Android (работающем на Android 4.1 JB) для доступных решений.

person algrid    schedule 03.09.2017
comment
у меня не работает, теперь вылетает с Caused by: java.lang.IllegalStateException: Unable to extract the trust manager on okhttp3.internal.platform.AndroidPlatform@422663d0, sslSocketFactory is class navneet.com.devinered.service.TLSSocketFactory - person Navneet Krishna; 03.09.2017

Я думаю, что мое решение может помочь кому-то.

В моем проекте мне нужно было выполнить запрос JSON через SSL на более старой версии Android (4.4), и я продолжал сталкиваться с проблемой, как упоминалось в верхней части темы.

Чтобы это исправить, мне нужно было добавить класс Tls12SocketFactory точно так же, как указано выше.

Однако я добавил измененный код в свой класс проекта.

Я добавил это в свой oncreate

upgradeSecurityProvider();

и изменил функцию для контекста, как показано ниже, и все. Больше никаких проблем с SSL-соединением

private void upgradeSecurityProvider() {
        try{
            ProviderInstaller.installIfNeededAsync(this, new ProviderInstaller.ProviderInstallListener() {
                @Override
                public void onProviderInstalled() {
                    Log.e("SSLFix", "New security provider installed.");
                }

                @Override
                public void onProviderInstallFailed(int errorCode, Intent recoveryIntent) {
                   // GooglePlayServicesUtil.showErrorNotification(errorCode, BuyersApp.this);
                    Log.e("SSLFix", "New security provider install failed.");
                }
            });
        }catch (Exception ex){
            Log.e("SSLFix", "Unknown issue trying to install a new security provider", ex);
        }

}

Это все и больше никаких вопросов.

person Waqas Ahmed    schedule 17.11.2018
comment
Две вещи: 1) безопасно ли это делать. 2) Можно рабочий код? - person Vibhanshu Biswas; 31.08.2019

В моем случае я просто добавляю зависимость в build.gradle (модульное приложение) и добавляю код к своему первому действию, решившему мою проблему. Зависимость:

implementation 'com.google.android.gms:play-services-base:11.0.0'

Мой код:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
        && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
    try {
        ProviderInstaller.installIfNeeded(this);
    } catch (GooglePlayServicesRepairableException e) {
        GooglePlayServicesUtil.showErrorNotification(e.getConnectionStatusCode(), this);
        return;
    } catch (GooglePlayServicesNotAvailableException e) {
        return;
    }
}

Эта проблема возникает в нижеприведенной версии леденца. По умолчанию TLS не включен, поэтому его нужно включить программно.

person Mahmud Jerry    schedule 08.12.2020
comment
Это работает для меня очень хорошо. Большое спасибо! - person Saran; 29.12.2020