SSLSocket a través de otro SSLSocket

Estoy intentando crear un SSLSocket encima de otro SSLSocket en una aplicación de Android. La conexión inferior es una conexión protegida por SSL a un proxy web seguro (proxy HTTP sobre SSL), la conexión superior es para HTTP sobre SSL (HTTPS).

Para esto, estoy usando la función createSocket() de createSocket() que permite pasar un Socket existente sobre el cual ejecutar la conexión SSL como esto:

 private Socket doSSLHandshake(Socket socket, String host, int port) throws IOException { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager(){ public X509Certificate[] getAcceptedIssuers(){ return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) {} public void checkServerTrusted(X509Certificate[] certs, String authType) {} } }; try { SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new SecureRandom()); SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(socket, host, port, true); sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols()); sslSocket.setEnableSessionCreation(true); sslSocket.startHandshake(); return sslSocket; } catch (KeyManagementException | NoSuchAlgorithmException e) { throw new IOException("Could not do handshake: " + e); } } 

Este código funciona bien cuando el socket subyacente es un socket tcp normal, pero cuando utilizo como socket subyacente un SSLSocket que se ha creado utilizando el código anterior antes, el handshake falla con la siguiente excepción:

 javax.net.ssl.SSLHandshakeException: Handshake failed at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:429) at com.myapp.MyThreadClass.doSSLHandshake(MyThreadClass.java:148) at com.myapp.MyThreadClass.run(MyThreadClass.java:254) Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x7374d56e80: Failure in SSL library, usually a protocol error error:100000e3:SSL routines:OPENSSL_internal:UNKNOWN_ALERT_TYPE (external/boringssl/src/ssl/s3_pkt.c:618 0x738418ce7e:0x00000000) at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method) at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357) ... 2 more 

Estoy probando en Android 7.1.1. La aplicación está orientada al nivel 23 del SDK.

  • ¿Qué podría estar haciendo mal?
  • ¿Cómo podría depurar más el problema?
  • ¿Alguien tiene un ejemplo de trabajo de un SSLSocket que pasa sobre otro SSLSocket en una versión reciente de Android?

¡Cualquier ayuda es muy apreciada!


Actualización : El mismo código funciona en JRE 1.8 en una Mac, pero no en Android.


Actualización 2 : Conceptualmente, estos son los pasos a los que pasa la conexión:

  1. Desde la aplicación de Android, establezca una conexión con el servidor Secure Proxy (Socket)
  2. Hacer un handshake SSL / TLS con el servidor proxy (SSLSocket over Socket)
  3. A través del SSLSocket, envíe el mensaje CONNECT al servidor Proxy
  4. Proxy se conecta al servidor de destino (https) y sólo copia bytes a partir de ahora
  5. Hacer un handshake SSL / TLS con el servidor de destino (https) (SSLSocket sobre SSLSocket sobre Socket)
  6. Enviar el mensaje GET al servidor de destino y leer la respuesta

El problema surge en el paso 5, al hacer el apretón de manos en un SSLSocket que pasa por un SSLSocket (que pasa por un Socket).


Actualización 3 : He abierto un repo GitHub con un proyecto de ejemplo y tcpdumps: https://github.com/FD-/SSLviaSSL


Nota : He encontrado y leído una pregunta con título muy similar , pero no contenía mucha ayuda útil por desgracia.

No creo que estés haciendo nada malo. Parece que hay un error en la negociación del protocolo durante su segundo apretón de manos. Un buen candidato estaría fallando en una extensión NPN TLS handshake.

Eche un vistazo a sus protocolos en esta llamada: sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());

Puede recorrer los protocolos listados y probarlos individualmente. Vea si puede bloquear lo que está fallando y si necesita ese protocolo específico o extensión compatible.

Así que traté de averiguar qué pasa mal en el caso de Android, pero hasta ahora no he encontrado nada malo con su código. También, puesto que el código funciona para JRE, afirma la hipótesis.

Desde el tcpdump proporcionado, hay información importante para concluir cómo se comporta Android con el mismo conjunto de API que JRE.

Echemos un vistazo a JRE tcpdump:

Introduzca aquí la descripción de la imagen

  • Ver los mensajes iniciales de handshake (Cliente Hola, Servidor Hola, Cambiar especificación de cifrado). Esto muestra el apretón de manos entre el cliente JRE y el servidor proxy. Esto es exitoso.
  • Ahora no vemos el segundo apretón de manos entre el cliente JRE y http://www.google.com (el servidor final), ya que está encriptado como lo estamos haciendo SSL a través de SSL. El servidor proxy los está copiando poco a poco al servidor final. Así que este es un comportamiento correcto.

Ahora veamos el android tcpdump:

Introduzca aquí la descripción de la imagen

  • Ver los mensajes iniciales de handshake (Cliente Hola, Servidor Hola, Cambiar especificación de cifrado). Esto muestra el apretón de manos entre el cliente android y el servidor proxy. Esto es exitoso.
  • Ahora idealmente no deberíamos ver el segundo apretón de manos como debería ser cifrado. Pero aquí podemos ver que android cliente está enviando un "cliente hola" y lo está enviando a "www.google.com" a pesar de que el paquete se envía al servidor proxy. Introduzca aquí la descripción de la imagen
  • Lo anterior está obligado a fallar, ya que el paquete se supone que debe escribirse a través de socket SSL y no sobre el socket inicial llano. He revisado su código y veo que está haciendo el segundo apretón de manos sobre SSLSocket y no sobre socket llano.

Análisis de proxy / stunnel wireshark:

JRE Caso:

En el caso de JRE, el cliente realiza el protocolo de enlace SSL inicial con el servidor stunnel / proxy. Lo mismo puede verse a continuación: Introduzca aquí la descripción de la imagen

El apretón de manos es exitoso y la conexión se realiza

A continuación, el cliente intenta conectarse al servidor remoto (www.google.com) e inicia el apretón de manos. Así que el cliente hello enviado por el cliente se ve como mensaje encriptado en el paquete # 34 y cuando stunnel descifra el mismo, se ve en el "cliente hola" que se envía al servidor proxy por stunnel Introduzca aquí la descripción de la imagen

Ahora veamos el caso del cliente android. Introduzca aquí la descripción de la imagen

El protocolo inicial SSL de cliente a stunnel / proxy es satisfactorio como se ha visto anteriormente.

Entonces cuando el cliente de android comienza el apretón de manos con el telecontrol (www.google.com), idealmente debe utilizar el zócalo del SSL para el mismo. Si este fuera el caso, deberíamos ver el tráfico cifrado de android a stunnel (similar al paquete # 34 del caso JRE), stunnel debería descifrar y enviar "hello" al cliente. Sin embargo, como se puede ver a continuación, cliente android está enviando un "cliente hola" sobre llano socket.

Introduzca aquí la descripción de la imagen

Si comparas el paquete # 24 con el paquete # 34 de JRE, podemos detectar esta diferencia.

Conclusión:

Esto es un error con la implementación de SSL de android ( factory.createsocket() con socket SSL) y siento que no puede haber una solución mágica para el mismo usando el mismo conjunto de API. De hecho, encontré este problema en la lista de errores de Android. Consulta el siguiente enlace: https://code.google.com/p/android/issues/detail?id=204159

Este problema sigue sin resolverse y es probable que pueda seguir con el equipo de desarrollo de Android para arreglar el mismo.

Soluciones posibles:

Si llegamos a la conclusión de que el mismo conjunto de API no puede funcionar, entonces se queda con una sola opción:

  1. Escriba su propio envoltorio SSL a través del socket SSL. Puede realizar manualmente un apretón de manos o utilizar una implementación de terceros. Esto podría tomar un rato pero parece la única manera.

Desde HTTPS asegurarse de que ningún intermediario interrumpir la comunicación entre los dos. Thats porqué usted es incapaz de hacer tan. Así que el segundo apretón de manos falla.

Aquí está el enlace puede ayudar.

Conexiones HTTPS a través de servidores proxy

No sé si esto ayuda pero:
He construido su entorno de prueba de repo, y mi error es ligeramente diferente:

 I/SurfaceTextureClient(20733): [0x52851b98] frames:2, duration:1.005000, fps:1.989805 I/System.out(20733): [socket][2] connection /192.168.1.100:10443;LocalPort=35380(0) I/System.out(20733): [CDS]connect[/192.168.1.100:10443] tm:90 I/System.out(20733): [socket][/192.168.1.123:35380] connected I/System.out(20733): Doing SSL handshake with 192.168.1.100:10443 I/System.out(20733): Supported protocols are: [SSLv3, TLSv1, TLSv1.1, TLSv1.2] E/NativeCrypto(20733): ssl=0x53c96268 cert_verify_callback x509_store_ctx=0x542e0a80 arg=0x0 E/NativeCrypto(20733): ssl=0x53c96268 cert_verify_callback calling verifyCertificateChain authMethod=RSA I/System.out(20733): Doing SSL handshake with 192.168.1.100:443 I/System.out(20733): Supported protocols are: [SSLv3, TLSv1, TLSv1.1, TLSv1.2] E/NativeCrypto(20733): Unknown error during handshake I/System.out(20733): Shutdown rx/tx I/System.out(20733): [CDS]close[35380] I/System.out(20733): close [socket][/0.0.0.0:35380] W/System.err(20733): javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x53c9c1d8: Failure in SSL library, usually a protocol error W/System.err(20733): error:140770FC:SSL routines: SSL23_GET_SERVER_HELLO: unknown protocol (external/openssl/ssl/s23_clnt.c:766 0x4e7cb3ad:0x00000000) W/System.err(20733): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:413) W/System.err(20733): at com.bugreport.sslviassl.SecureWebProxyThread.doSSLHandshake(SecureWebProxyThread.java:147) W/System.err(20733): at com.bugreport.sslviassl.SecureWebProxyThread.run(SecureWebProxyThread.java:216) W/System.err(20733): Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x53c9c1d8: Failure in SSL library, usually a protocol error W/System.err(20733): error:140770FC:SSL routines: SSL23_GET_SERVER_HELLO:unknown protocol (external/openssl/ssl/s23_clnt.c:766 0x4e7cb3ad:0x00000000) W/System.err(20733): at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method) W/System.err(20733): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:372) W/System.err(20733): ... 2 more I/SurfaceTextureClient(20733): [0x52851b98] frames:5, duration:1.010000, fps:4.946089 

Un pensamiento sobre los hilos: ¿qué hilo estás ejecutando tu método doSSLHandshake () en?

 public void policy() { int SDK_INT = android.os.Build.VERSION.SDK_INT; if (SDK_INT > 8) { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); } } 
  • Javax.net.ssl.sslpeerunverifiedexception sin certificado de igualdad
  • Android / Java - ¿Cómo crear una conexión HTTPS?
  • Utilice DefaultHttpClient simple o un almacén de claves creado con BouncyCastle incluido en la aplicación.
  • Android y HTTPS con SSL
  • Verificando servidores Verisign certificates throws No es una excepción de certificado de servidor de confianza
  • Javax.net.ssl.SSLHandshakeException: Conexión cerrada por peer en com.android.org.conscrypt.NativeCrypto.SSL_do_handshake (Native Method)
  • El proxy Charles falla en el método de conexión SSL
  • Android: streaming de video por servidor HTTPS local: certificado SSL rechazado
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.