versión verified una type studio servidor protocolo network enabled conjunto conexion común cliente cifrado check certificado admiten android ssl amazon-web-services poodle-attack sslsocketfactory

android - verified - Deshabilite SSL como protocolo en HttpsURLConnection



retrofit ssl pinning (5)

Debido a la vulnerabilidad de POODLE , mi servidor alojado en Amazon AWS ya no es compatible con SSLv3.

Como resultado, la primera conexión HTTPS que mi aplicación de Android hace contra el servidor da como resultado un error cuando se estableció la conexión.

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)

El error ocurre solo en la primera solicitud. Las solicitudes posteriores funcionan durante un tiempo.

Para solucionarlo, intento eliminar SSL de la lista de protocolos aceptados por el cliente de Android, y asegurarme de que voy solo con TLS. Para hacer esto, configuré una SSLSocketFactory personalizada que elimina SSL de la lista de protocolos habilitados y conjuntos de cheros compatibles.

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

Como ve, mi SSLSocketFactory delega en otra SSLSocketFactory y lo que hace es simplemente eliminar SSL de la lista de protocolos habilitados.

Establezco esta fábrica como

final TLSOnlySocketFactory tlsOnlySocketFactory = new TLSOnlySocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory()); HttpsURLConnection.setDefaultSSLSocketFactory(tlsOnlySocketFactory);

Esto NO soluciona el problema. De vez en cuando, todavía veo el error cuando se estableció la conexión. Curiosamente, esto no lo soluciona, pero minimiza claramente las apariciones del problema.

¿Cómo podría forzar a HttpsUrlConnection en mi cliente de Android a usar solo TLS?

Gracias.


Creo que he resuelto esto. La idea fundamental es la misma que en el código de la pregunta (evite SSLv3 como el único protocolo disponible), pero el código que lo realiza es diferente:

import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; 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.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; /** * {@link javax.net.ssl.SSLSocketFactory} that doesn''t allow {@code SSLv3} only connections * <p>fixes https://github.com/koush/ion/issues/386</p> * * <p> see https://code.google.com/p/android/issues/detail?id=78187 </p> */ public class NoSSLv3Factory extends SSLSocketFactory { private final SSLSocketFactory delegate; public NoSSLv3Factory() { this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory(); } @Override public String[] getDefaultCipherSuites() { return delegate.getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { return delegate.getSupportedCipherSuites(); } private static 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)); } /** * Created by robUx4 on 25/10/2014. */ private static 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 setSSLParameters(SSLParameters p) { delegate.setSSLParameters(p); } @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); } } /** * An {@link javax.net.ssl.SSLSocket} that doesn''t allow {@code SSLv3} only connections * <p>fixes https://github.com/koush/ion/issues/386</p> */ private static class NoSSLv3SSLSocket extends DelegateSSLSocket { private NoSSLv3SSLSocket(SSLSocket delegate) { super(delegate); String canonicalName = delegate.getClass().getCanonicalName(); if (!canonicalName.equals("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl")) { // try replicate the code from HttpConnection.setupSecureSocket() try { Method msetUseSessionTickets = delegate.getClass().getMethod("setUseSessionTickets", boolean.class); if (null != msetUseSessionTickets) { msetUseSessionTickets.invoke(delegate, true); } } catch (NoSuchMethodException ignored) { } catch (InvocationTargetException ignored) { } catch (IllegalAccessException ignored) { } } } @Override public void setEnabledProtocols(String[] protocols) { if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) { // no way jose // see issue https://code.google.com/p/android/issues/detail?id=78187 List<String> enabledProtocols = new ArrayList<String>(Arrays.asList(delegate.getEnabledProtocols())); if (enabledProtocols.size() > 1) { enabledProtocols.remove("SSLv3"); } protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]); } super.setEnabledProtocols(protocols); } } }

y en algún lugar de tu código, antes de crear la conexión:

static { HttpsURLConnection.setDefaultSSLSocketFactory(new NoSSLv3Factory()); }

Este código está tomado de https://code.google.com/p/android/issues/detail?id=78187 , donde puede encontrar una explicación completa de por qué sucede esto en Android 4.X.

He tenido esto en producción desde una semana y parece haber hecho el truco.


Además de la respuesta de @GaRRaPeTa, asegúrese de que el método makeSocketsafe determine si el socket aún no se ha convertido en NoSSLv3SSLSocket para evitar problemas de :

private static Socket makeSocketSafe(Socket socket) { if (socket instanceof SSLSocket && !(socket instanceof NoSSLv3SSLSocket)) { socket = new NoSSLv3SSLSocket((SSLSocket) socket); } return socket; }

PD. No puedo comentar para que esté en una publicación separada.


Tomé la respuesta de @GraRRaPeTa y la incluí en una llamada a un método simple y muerto. Puede usar la biblioteca de NetCipher para obtener una configuración de TLS moderna al usar HttpsURLConnection de Android. NetCipher configura la instancia `HttpsURLConnection para usar la mejor versión soportada 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);


Las soluciones anteriores no me funcionaron, por lo tanto, esto es lo que aprendí e hice para superar este problema.

Para dispositivos más antiguos que Android 5.0, el proveedor de seguridad predeterminado tenía esas propiedades:

  1. Los protocolos TSLv1 y TSLv2 no fueron habilitados por defecto
  2. El protocolo SSLv3 no se desactiva por defecto.

Una solución que funcionó para mí aquí es parchear el "Proveedor" si es necesario al iniciar la aplicación, por lo que ya no tendrá SSLv3 en su lista de protocolos. Una forma sencilla de parchar Android desde su aplicación es la siguiente: (teniendo en cuenta que tiene acceso a los servicios de Google Play Store).

private void updateAndroidSecurityProvider(Activity callingActivity) { try { ProviderInstaller.installIfNeeded(this); } catch (GooglePlayServicesRepairableException e) { // Thrown when Google Play Services is not installed, up-to-date, or enabled // Show dialog to allow users to install, update, or otherwise enable Google Play services. GooglePlayServicesUtil.getErrorDialog(e.getConnectionStatusCode(), callingActivity, 0); } catch (GooglePlayServicesNotAvailableException e) { Log.e("SecurityException", "Google Play Services not available."); } }

Eche un vistazo a: https://developer.android.com/training/articles/security-gms-provider.html?#patching para obtener más información.


Recientemente probé esto usando SSLContext (ya que necesitaba acceso a Trustmanager) en lugar de implementar mi propio NoSSLv3Factory y hasta ahora no he tenido ningún problema.

private getSSLContext() { /* Load the keyStore that includes self-signed cert as a "trusted" entry. */ KeyStore keyStore = ... //optional TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); //optional tmf.init(keyStore); //optional //This is the important line, specifying the cipher to use and cipher provider SSLContext sslContext = SSLContext.getInstance("TLSv1","AndroidOpenSSL"); ctx.init(null, tmf.getTrustManagers(), null); //if trustmanager not used pass null as the second parameter return sslContext; }

A continuación, puede usar esto en su objeto HttpsURLConnection de la siguiente manera:

... URL url = new URL("https://yourwebapp.com/"); HttpsURLConnection webConnection = (HttpsURLConnection)url.openConnection(); webConnection.setSSLSocketFactory(getSSLContext()) ...

Sin embargo, esto significa que tendrá que estar al tanto de las vulnerabilidades de TLS y modificar el cifrado especificado si se divulgan públicamente las vulnerabilidades de SSL / TLS.

Aquí se enumera una lista de cifradores y proveedores compatibles que puede usar

El primer bloque de código, menor que el cambio clave para este escenario, se tomó principalmente de esta respuesta SO