Из-за POODLE мой сервер, размещенный в Amazon AWS, больше не поддерживает SSLv3.
В результате первое соединение HTTPS, которое приложение Android делает с сервером, приводит к ошибке при установлении соединения.
Error reading server response: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x77d8ab68: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:741 0x7339ad74:0x00000000)
[....]
Caused by: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x77d8ab68: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:741 0x7339ad74:0x00000000)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:448)
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)
Ошибка происходит только в первом запросе. Последующие запросы работают некоторое время.
Чтобы исправить это, я пытаюсь удалить SSL из списка протоколов, принятых клиентом Android, и убедитесь, что я иду только с TLS. Для этого я устанавливаю настраиваемый SSLSocketFactory, который удаляет SSL из списка разрешенных протоколов и поддерживает комплекты cypher.
/**
* SSLSocketFactory that wraps one existing SSLSocketFactory and delegetes into it adding
* a new cipher suite
*/
public class TLSOnlySocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public TLSOnlySocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
@Override
public String[] getDefaultCipherSuites() {
return getPreferredDefaultCipherSuites(this.delegate);
}
@Override
public String[] getSupportedCipherSuites() {
return getPreferredSupportedCipherSuites(this.delegate);
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
final Socket socket = this.delegate.createSocket(s, host, port, autoClose);
((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
((SSLSocket)socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));
return socket;
}
[.....]
((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
((SSLSocket) socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));
return socket;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
final Socket socket = this.delegate.createSocket(host, port);
((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
((SSLSocket) socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));
return socket;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
final Socket socket = this.delegate.createSocket(address, port, localAddress, localPort);
((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
((SSLSocket) socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));
return socket;
}
private String[] getPreferredDefaultCipherSuites(SSLSocketFactory sslSocketFactory) {
return getCipherSuites(sslSocketFactory.getDefaultCipherSuites());
}
private String[] getPreferredSupportedCipherSuites(SSLSocketFactory sslSocketFactory) {
return getCipherSuites(sslSocketFactory.getSupportedCipherSuites());
}
private String[] getCipherSuites(String[] cipherSuites) {
final ArrayList<String> suitesList = new ArrayList<String>(Arrays.asList(cipherSuites));
final Iterator<String> iterator = suitesList.iterator();
while (iterator.hasNext()) {
final String cipherSuite = iterator.next();
if (cipherSuite.contains("SSL")) {
iterator.remove();
}
}
return suitesList.toArray(new String[suitesList.size()]);
}
private String[] getEnabledProtocols(SSLSocket socket) {
final ArrayList<String> protocolList = new ArrayList<String>(Arrays.asList(socket.getSupportedProtocols()));
final Iterator<String> iterator = protocolList.iterator();
while (iterator.hasNext()) {
final String protocl = iterator.next();
if (protocl.contains("SSL")) {
iterator.remove();
}
}
return protocolList.toArray(new String[protocolList.size()]);
}
}
Как вы видите, мой SSLSocketFactory делегирует в другой SSLSocketFactory, и что он делает, просто удаляет SSL из списка разрешенных протоколов.
Я устанавливаю этот factory как
final TLSOnlySocketFactory tlsOnlySocketFactory = new TLSOnlySocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(tlsOnlySocketFactory);
Это НЕ исправляет проблему. Время от времени я все еще вижу ошибку при установлении соединения. Как ни странно, это не исправляет, но это явно минимизирует возникновение проблемы.
Как я могу заставить HttpsUrlConnection на моем Android-клиенте использовать только TLS?
Спасибо.