Попытаться заставить приложение на Android 4.x (API ниже 20) использовать TLSv1.2
Наш сервер обновлен с SHA-1 и TLS 1.0 до сертификата SHA-2 и TLS 1.2. Он нарушает связь HTTPS нашего приложения Android на платформах с уровнем API ниже 20 (Android 4.1-4.4).
(Наш проект Android использует Retrofit 2.4.0 и okhttp 3.10.0)
Я пытаюсь решить вышеуказанную проблему, заставив наше приложение на Android 4.x с помощью TLS 1.2, мой код вдохновлен этим учебником (но я исключил закрепление открытого ключа учебного кода):
Я сначала создал TLSSocketFactory
:
public class TLSSocketFactory extends SSLSocketFactory {
private SSLSocketFactory internalSSLSocketFactory;
public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { systemDefaultTrustManager() }, null);
internalSSLSocketFactory = sslContext.getSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.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;
}
public X509TrustManager systemDefaultTrustManager() {
try {
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));
}
return (X509TrustManager) trustManagers[0];
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
}
Затем примените выше TLSSocketFactory
в OkHttpClient
:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_1, TlsVersion.TLS_1_2)
.cipherSuites(
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
.build();
builder.connectionSpecs(Collections.singletonList(spec));
builder.readTimeout(10, TimeUnit.SECONDS);
builder.connectTimeout(10, TimeUnit.SECONDS);
builder.writeTimeout(10, TimeUnit.SECONDS);
// apply TLSSocketFactory
try {
TLSSocketFactory socketFactory = new TLSSocketFactory();
builder.sslSocketFactory(socketFactory, socketFactory.systemDefaultTrustManager());
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
OkHttpClient client = builder.build();
Затем создайте экземпляр Retrofit
используя выше OkHttpClient
:
Retrofit retrofit = new Retrofit.Builder().baseUrl(myUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client) // my OkHttpClient
.build();
Но когда я запускаю свое приложение на Android 4.x для связи с нашим бэкэндом, я все равно получаю сообщение об ошибке:
OkHttp: <-- HTTP FAILED: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Зачем? Что мне не хватает?
(В TLSSocketFactory
конструктор я тоже пробовал sslContext.init(null, null, null);
но это не помогает, та же ошибка.)
(Новый сертификат в порядке, он работает с Android 5.0+, но новый сертификат и старый сертификат от разных эмитентов)
2 ответа
Попробуйте предложения, размещенные здесь на Github библиотеки okHttp - это хорошо известная проблема
Я не понимаю, почему нужно вызывать этот метод systemDefaultTrustManager
Чтобы использовать системные настройки по умолчанию, попробуйте заменить
sslContext.init(null, new TrustManager[] { systemDefaultTrustManager() }, null);
от
sslContext.init(null, null, null);