¿Cómo deshabilitar SSLv3 en Android para HttpsUrlConnection?

Escribimos la aplicación cliente en Android que se conecta con los servidores https usando HttpsUrlConnection apis. Debido a la vulnerabilidad de Poodle, debemos deshabilitar SSLv3 de la lista de protocolos habilitados al invocar cualquier solicitud.

Seguimos las pautas capturadas por Oracle como http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html

y agregué la siguiente línea antes de invocar la conexión a la url

java.lang.System.setProperty("https.protocols", "TLSv1"); 

Esta solución funciona bien con el progtwig Java normal. Obtuvimos SSLHandShakeException cuando intentamos conectarnos con un servidor que solo funciona con el protocolo SSLv3.

Pero la preocupación es : la misma solución no funciona para Android. ¿Me estoy perdiendo algo o debería intentar otro enfoque para Android? Por favor recomiende.

Encontré la solución analizando los paquetes de datos usando wireshark. Lo que encontré es que, al hacer una conexión segura, Android estaba recurriendo a SSLv3 desde TLSv1 . Es un error en las versiones de Android <4.4, y se puede resolver eliminando el protocolo SSLv3 de la lista de Protocolos habilitados. Hice una clase de socketFactory personalizada llamada NoSSLv3SocketFactory.java. Use esto para hacer un socketfactory.

 /*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 enabledProtocols = new ArrayList(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); } } } 

Usa esta clase así mientras conectas:

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

ACTUALIZAR:

Ahora, la solución correcta sería instalar un proveedor de seguridad más nuevo usando Google Play Services :

  ProviderInstaller.installIfNeeded(getApplicationContext()); 

Esto efectivamente le da acceso a su aplicación a una versión más nueva de OpenSSL y Java Security Provider, que incluye soporte para TLSv1.2 en SSLEngine. Una vez que se instala el nuevo proveedor, puede crear un SSLEngine que admita SSLv3, TLSv1, TLSv1.1 y TLSv1.2 de la forma habitual:

  SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, null, null); SSLEngine engine = sslContext.createSSLEngine(); 

O puede restringir los protocolos habilitados utilizando engine.setEnabledProtocols .

No olvide agregar la siguiente dependencia (la última versión se encuentra aquí ):

 compile 'com.google.android.gms:play-services-auth:11.8.0' 

Para obtener más información, consulte este enlace .

Inspirado por la respuesta de Bhavit S. Sengar, incluyó esa técnica en una llamada a método simple muerto. Puede usar la biblioteca de NetCipher para obtener una configuración de TLS moderna al usar HttpsURLConnection de Android. NetCipher configura la instancia de HttpsURLConnection para usar la mejor versión compatible de TLS, elimina la compatibilidad con SSLv3 y configura el mejor conjunto de cifras para esa versión de TLS. Primero, agrégalo a tu build.gradle :

 compile 'info.guardianproject.netcipher:netcipher:1.2' 

O puede descargar netcipher-1.2.jar e incluirlo directamente en su aplicación. Entonces, en lugar de llamar:

 HttpURLConnection connection = (HttpURLConnection) sourceUrl.openConnection(); 

Llama esto:

 HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl); 

Al principio probé la respuesta de Bhavit S. Sengar y funcionó en la mayoría de los casos. Pero a veces hubo problemas incluso cuando se eliminó el protocolo SSLv3 de los Protocolos Habilitados en un dispositivo con Android 4.4.4. Así que la biblioteca NetCipher de Hans-Christoph Steiner es perfecta para resolver ese problema en la medida en que pude probarlo.

Usamos jsoup para hacer un montón de raspado web en diferentes servidores, por lo que no podemos establecer la HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl); . Supongo que es el mismo problema si usa OkHttp.

La mejor solución que hemos encontrado es establecer info.guardianproject.netcipher.client.TlsOnlySocketFactory de NetCipher como DefaultSSLSocketFactory en un bloque estático. Por lo tanto, está configurado para todo el tiempo de ejecución de nuestra aplicación:

 SSLContext sslcontext = SSLContext.getInstance("TLSv1"); sslcontext.init(null, null, null); SSLSocketFactory noSSLv3Factory = new TlsOnlySocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultSSLSocketFactory(noSSLv3Factory); 

Si desea inspeccionar los detalles completos (con trustAllCertificates ) puede hacerlo aquí .

use este fragmento de código, si el servidor está habilitado para SSLv3, fallará el intercambio de información.

  SocketFactory sf = SSLSocketFactory.getDefault(); SSLSocket socket = (SSLSocket) sf.createSocket("host-name", 443); socket.setEnabledProtocols(new String[] { "TLSv1"}); socket.startHandshake(); 

Se conecta con el servidor https. Necesitamos un certificado en handshaking desde el lado del cliente. Hace 1 año resolví un problema similar usando el certificado de auto-señal de la siguiente manera-

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

Uso en el lado del cliente antes de HttpsUrlConnection

 HttpsTrustManager.allowAllSSL(); 

espero que funcione 🙂

  SSLContext sslContext = SSLContext.getInstance("TLSv1"); sslContext.init(null, null, null); SSLSocketFactory socketFactory = sslContext.getSocketFactory(); httpURLConnection.setSSLSocketFactory(socketFactory); 

HttpsURLConnection utilizando TSL crea una falla de seguridad, la implementación de Android se volverá a conectar a SSLV3.

Consulte este http://code.google.com/p/android/issues/detail?id=78431

Utilizando las bibliotecas de cliente editor de PlayService que se ejecutan en Android, experimenté el mismo problema al ejecutar la muestra .

Se corrigió con la respuesta de @ bhavit-s-sengar anterior. Tuvo que cambiar también AndroidPublisherHelper.newTrustedTransport() a esto:

 SSLContext sslcontext = SSLContext.getInstance("TLSv1"); sslcontext.init(null, null, null); // NoSSLv3SocketFactory is @bhavit-s-sengar's http://stackoverflow.com/a/29946540/8524 SSLSocketFactory noSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory()); NetHttpTransport.Builder netTransportBuilder = new NetHttpTransport.Builder(); netTransportBuilder.setSslSocketFactory(noSSLv3Factory); HTTP_TRANSPORT = netTransportBuilder.build(); 

En realidad, no es necesario que desactivemos SSLV3 o TLSV1.0, lo que necesitamos para habilitar TLSV1.1 o TLSv1.2 en dispositivos Android <5.

El problema es que TLSv1.1 y TLSv1.2 no están habilitados en Android <5 de manera predeterminada y para conectarse utilizando estos últimos protocolos seguros debemos habilitarlos en dispositivos Android <5.

Esta solución solucionó mi problema: https://stackoverflow.com/a/45853669/3448003