Как отключить SSLv3 в Android для HttpsUrlConnection?
Мы написали клиентское приложение на Android, которое соединяется с серверами https с помощью API HttpsUrlConnection. Из-за уязвимости Poodle нам нужно отключить SSLv3 из списка включенных протоколов при вызове любого запроса.
Мы следовали рекомендациям, записанным oracle как http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html
и добавил следующую строку перед вызовом URL-соединения
java.lang.System.setProperty("https.protocols", "TLSv1");
Это решение прекрасно работает с обычной программой Java. Мы получили SSLHandShakeException, когда пытались соединиться с сервером, который работает только по протоколу SSLv3.
Но проблема в том, что такое же исправление не работает для Android. Я что-то упустил или я должен попробовать другой подход для Android? Пожалуйста, предложите.
8 ответов
Я нашел решение для этого путем анализа пакетов данных с помощью Wireshark. Что я обнаружил, так это то, что при создании безопасного соединения андроид откатывался на SSLv3 от TLSv1. Это ошибка в версиях Android < 4.4, и ее можно устранить, удалив протокол SSLv3 из списка включенных протоколов. Я создал собственный класс socketFactory с именем NoSSLv3SocketFactory.java. Используйте это, чтобы сделать сокет фактори.
/*Copyright 2015 Bhavit Singh Sengar
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class NoSSLv3SocketFactory extends SSLSocketFactory{
private final SSLSocketFactory delegate;
public NoSSLv3SocketFactory() {
this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
}
public NoSSLv3SocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
private Socket makeSocketSafe(Socket socket) {
if (socket instanceof SSLSocket) {
socket = new NoSSLv3SSLSocket((SSLSocket) socket);
}
return socket;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return makeSocketSafe(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return makeSocketSafe(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return makeSocketSafe(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort));
}
private class NoSSLv3SSLSocket extends DelegateSSLSocket {
private NoSSLv3SSLSocket(SSLSocket delegate) {
super(delegate);
}
@Override
public void setEnabledProtocols(String[] protocols) {
if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {
List<String> enabledProtocols = new ArrayList<String>(Arrays.asList(delegate.getEnabledProtocols()));
if (enabledProtocols.size() > 1) {
enabledProtocols.remove("SSLv3");
System.out.println("Removed SSLv3 from enabled protocols");
} else {
System.out.println("SSL stuck with protocol available for " + String.valueOf(enabledProtocols));
}
protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
}
super.setEnabledProtocols(protocols);
}
}
public class DelegateSSLSocket extends SSLSocket {
protected final SSLSocket delegate;
DelegateSSLSocket(SSLSocket delegate) {
this.delegate = delegate;
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public String[] getEnabledCipherSuites() {
return delegate.getEnabledCipherSuites();
}
@Override
public void setEnabledCipherSuites(String[] suites) {
delegate.setEnabledCipherSuites(suites);
}
@Override
public String[] getSupportedProtocols() {
return delegate.getSupportedProtocols();
}
@Override
public String[] getEnabledProtocols() {
return delegate.getEnabledProtocols();
}
@Override
public void setEnabledProtocols(String[] protocols) {
delegate.setEnabledProtocols(protocols);
}
@Override
public SSLSession getSession() {
return delegate.getSession();
}
@Override
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
delegate.addHandshakeCompletedListener(listener);
}
@Override
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
delegate.removeHandshakeCompletedListener(listener);
}
@Override
public void startHandshake() throws IOException {
delegate.startHandshake();
}
@Override
public void setUseClientMode(boolean mode) {
delegate.setUseClientMode(mode);
}
@Override
public boolean getUseClientMode() {
return delegate.getUseClientMode();
}
@Override
public void setNeedClientAuth(boolean need) {
delegate.setNeedClientAuth(need);
}
@Override
public void setWantClientAuth(boolean want) {
delegate.setWantClientAuth(want);
}
@Override
public boolean getNeedClientAuth() {
return delegate.getNeedClientAuth();
}
@Override
public boolean getWantClientAuth() {
return delegate.getWantClientAuth();
}
@Override
public void setEnableSessionCreation(boolean flag) {
delegate.setEnableSessionCreation(flag);
}
@Override
public boolean getEnableSessionCreation() {
return delegate.getEnableSessionCreation();
}
@Override
public void bind(SocketAddress localAddr) throws IOException {
delegate.bind(localAddr);
}
@Override
public synchronized void close() throws IOException {
delegate.close();
}
@Override
public void connect(SocketAddress remoteAddr) throws IOException {
delegate.connect(remoteAddr);
}
@Override
public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
delegate.connect(remoteAddr, timeout);
}
@Override
public SocketChannel getChannel() {
return delegate.getChannel();
}
@Override
public InetAddress getInetAddress() {
return delegate.getInetAddress();
}
@Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
@Override
public boolean getKeepAlive() throws SocketException {
return delegate.getKeepAlive();
}
@Override
public InetAddress getLocalAddress() {
return delegate.getLocalAddress();
}
@Override
public int getLocalPort() {
return delegate.getLocalPort();
}
@Override
public SocketAddress getLocalSocketAddress() {
return delegate.getLocalSocketAddress();
}
@Override
public boolean getOOBInline() throws SocketException {
return delegate.getOOBInline();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public int getPort() {
return delegate.getPort();
}
@Override
public synchronized int getReceiveBufferSize() throws SocketException {
return delegate.getReceiveBufferSize();
}
@Override
public SocketAddress getRemoteSocketAddress() {
return delegate.getRemoteSocketAddress();
}
@Override
public boolean getReuseAddress() throws SocketException {
return delegate.getReuseAddress();
}
@Override
public synchronized int getSendBufferSize() throws SocketException {
return delegate.getSendBufferSize();
}
@Override
public int getSoLinger() throws SocketException {
return delegate.getSoLinger();
}
@Override
public synchronized int getSoTimeout() throws SocketException {
return delegate.getSoTimeout();
}
@Override
public boolean getTcpNoDelay() throws SocketException {
return delegate.getTcpNoDelay();
}
@Override
public int getTrafficClass() throws SocketException {
return delegate.getTrafficClass();
}
@Override
public boolean isBound() {
return delegate.isBound();
}
@Override
public boolean isClosed() {
return delegate.isClosed();
}
@Override
public boolean isConnected() {
return delegate.isConnected();
}
@Override
public boolean isInputShutdown() {
return delegate.isInputShutdown();
}
@Override
public boolean isOutputShutdown() {
return delegate.isOutputShutdown();
}
@Override
public void sendUrgentData(int value) throws IOException {
delegate.sendUrgentData(value);
}
@Override
public void setKeepAlive(boolean keepAlive) throws SocketException {
delegate.setKeepAlive(keepAlive);
}
@Override
public void setOOBInline(boolean oobinline) throws SocketException {
delegate.setOOBInline(oobinline);
}
@Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
}
@Override
public synchronized void setReceiveBufferSize(int size) throws SocketException {
delegate.setReceiveBufferSize(size);
}
@Override
public void setReuseAddress(boolean reuse) throws SocketException {
delegate.setReuseAddress(reuse);
}
@Override
public synchronized void setSendBufferSize(int size) throws SocketException {
delegate.setSendBufferSize(size);
}
@Override
public void setSoLinger(boolean on, int timeout) throws SocketException {
delegate.setSoLinger(on, timeout);
}
@Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
delegate.setSoTimeout(timeout);
}
@Override
public void setTcpNoDelay(boolean on) throws SocketException {
delegate.setTcpNoDelay(on);
}
@Override
public void setTrafficClass(int value) throws SocketException {
delegate.setTrafficClass(value);
}
@Override
public void shutdownInput() throws IOException {
delegate.shutdownInput();
}
@Override
public void shutdownOutput() throws IOException {
delegate.shutdownOutput();
}
@Override
public String toString() {
return delegate.toString();
}
@Override
public boolean equals(Object o) {
return delegate.equals(o);
}
}
}
Используйте этот класс, как это при подключении:
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null,
null,
null);
SSLSocketFactory NoSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(NoSSLv3Factory);
l_connection = (HttpsURLConnection) l_url.openConnection();
l_connection.connect();
ОБНОВИТЬ:
Теперь правильным решением было бы установить нового поставщика безопасности с помощью Google Play Services:
ProviderInstaller.installIfNeeded(getApplicationContext());
Это эффективно дает вашему приложению доступ к более новой версии OpenSSL и провайдера безопасности Java, который включает поддержку TLSv1.2 в SSLEngine. После установки нового провайдера вы можете создать SSLEngine, который поддерживает SSLv3, TLSv1, TLSv1.1 и TLSv1.2 обычным способом:
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
SSLEngine engine = sslContext.createSSLEngine();
Или вы можете ограничить включенные протоколы, используя engine.setEnabledProtocols
,
Не забудьте добавить следующую зависимость ( последняя версия найдена здесь):
compile 'com.google.android.gms:play-services-auth:11.8.0'
Для получения дополнительной информации, перейдите по этой ссылке.
Вдохновленный ответом Бхавита С. Сенгара, он объединил эту технику в мертвый простой вызов метода. Вы можете использовать библиотеку NetCipher, чтобы получить современную конфигурацию TLS при использовании Android HttpsURLConnection
, NetCipher настраивает HttpsURLConnection
экземпляр, чтобы использовать лучшую поддерживаемую версию TLS, удаляет поддержку SSLv3 и настраивает лучший набор шифров для этой версии TLS. Сначала добавьте его в свой build.gradle:
compile 'info.guardianproject.netcipher:netcipher:1.2'
Или вы можете скачать netcipher-1.2.jar и включить его прямо в ваше приложение. Тогда вместо звонка:
HttpURLConnection connection = (HttpURLConnection) sourceUrl.openConnection();
Позвоните это:
HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);
Сначала я попробовал ответ Бхавита С. Сенгара, и он работал в большинстве случаев. Но иногда возникают проблемы, даже когда протокол SSLv3 был удален из включенных протоколов на устройстве Android 4.4.4. Таким образом, библиотека NetCipher от Hans-Christoph Steiner идеально подходит для решения этой проблемы, насколько я мог ее протестировать.
Мы используем jsoup, чтобы сделать кучу веб-скребков на разных серверах, поэтому мы не можем установить HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);
, Я предполагаю, что это та же проблема, если вы используете OkHttp.
Лучшее решение, к которому мы пришли, это установить info.guardianproject.netcipher.client.TlsOnlySocketFactory
из NetCipher как DefaultSSLSocketFactory
в статическом блоке. Так что он установлен для всего времени выполнения нашего приложения:
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null, null, null);
SSLSocketFactory noSSLv3Factory = new TlsOnlySocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(noSSLv3Factory);
Если вы хотите проверить полную информацию (с trustAllCertificates
Вы можете сделать это здесь.
Используйте этот фрагмент кода, если на сервере включен SSLv3, произойдет сбой при установлении связи.
SocketFactory sf = SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) sf.createSocket("host-name", 443);
socket.setEnabledProtocols(new String[] { "TLSv1"});
socket.startHandshake();
Соединяется с https сервером, нам нужен сертификат для подтверждения связи со стороны клиента. 1 год назад я решил похожую проблему, используя сертификат собственной подписи следующим образом:
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class HttpsTrustManager implements X509TrustManager {
private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
public boolean isClientTrusted(X509Certificate[] chain) {
return true;
}
public boolean isServerTrusted(X509Certificate[] chain) {
return true;
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return _AcceptedIssuers;
}
public static void allowAllSSL() {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
SSLContext context = null;
if (trustManagers == null) {
trustManagers = new TrustManager[]{new HttpsTrustManager()};
}
try {
context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
HttpsURLConnection.setDefaultSSLSocketFactory(context
.getSocketFactory());
}
}
Использование на стороне клиента до HttpsUrlConnection
HttpsTrustManager.allowAllSSL();
надеюсь, это будет работать:)
При использовании клиентских библиотек издателя PlayService на Android я столкнулся с той же проблемой при запуске образца.
Исправлено с помощью @bhavit-s-sengar's awnser выше. Пришлось также изменить AndroidPublisherHelper.newTrustedTransport()
к этому:
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null, null, null);
// NoSSLv3SocketFactory is @bhavit-s-sengar's http://stackru.com/a/29946540/8524
SSLSocketFactory noSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());
NetHttpTransport.Builder netTransportBuilder = new NetHttpTransport.Builder();
netTransportBuilder.setSslSocketFactory(noSSLv3Factory);
HTTP_TRANSPORT = netTransportBuilder.build();
На самом деле нам не нужно отключать SSLV3 или TLSV1.0, что нам просто нужно, чтобы включить TLSV1.1 или TLSv1.2 на устройствах с Android <5.
Проблема в том, что TLSv1.1 и TLSv1.2 не включены на Android <5 по умолчанию, и для подключения с использованием этого последнего безопасного протокола нам необходимо включить на устройствах Android <5.
Это решение решило мою проблему: /questions/23807545/okhttp-nevozmozhno-najti-priemlemyie-protokolyi-android/23807549#23807549
SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(null, null, null);
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
httpURLConnection.setSSLSocketFactory(socketFactory);
HttpsURLConnection с использованием TSL создать безопасность не удалось, реализация Android вернется к SSLV3 для подключения.
Пожалуйста, ознакомьтесь с этим http://code.google.com/p/android/issues/detail?id=78431