ssl websocket android-5.0-lollipop grizzly tyrus

Error de protocolo de enlace SSL de Websocket



android-5.0-lollipop grizzly (3)

Tengo un servidor Tomcat con arranque de resorte para conexiones seguras de websocket. El servidor acepta Android 4.4, iOS, Firefox y clientes de Chrome sin falla con un certificado firmado por la autoridad. Android 5.0, sin embargo, falla el protocolo de enlace SSL.

Caused by: javax.net.ssl.SSLHandshakeException: Handshake failed at com.android.org.conscrypt.OpenSSLEngineImpl.unwrap(OpenSSLEngineImpl.java:436) at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:1006) at org.glassfish.grizzly.ssl.SSLConnectionContext.unwrap(SSLConnectionContext.java:172) at org.glassfish.grizzly.ssl.SSLUtils.handshakeUnwrap(SSLUtils.java:263) at org.glassfish.grizzly.ssl.SSLBaseFilter.doHandshakeStep(SSLBaseFilter.java:603) at org.glassfish.grizzly.ssl.SSLFilter.doHandshakeStep(SSLFilter.java:312) at org.glassfish.grizzly.ssl.SSLBaseFilter.doHandshakeStep(SSLBaseFilter.java:552) at org.glassfish.grizzly.ssl.SSLBaseFilter.handleRead(SSLBaseFilter.java:273) at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119) at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:284) at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:201) at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:133) at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:112) at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77) at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:561) at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:112) at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:117) at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:56) at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:137) at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:565) at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:545) at java.lang.Thread.run(Thread.java:818) Caused by: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0xa1f34200: Failure in SSL library, usually a protocol error error:1408E0F4:SSL routines:SSL3_GET_MESSAGE:unexpected message (external/openssl/ssl/s3_both.c:498 0xac526e61:0x00000000) at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake_bio(Native Method) at com.android.org.conscrypt.OpenSSLEngineImpl.unwrap(OpenSSLEngineImpl.java:423)

Creo que el problema es con TLS o las suites de cifrado debido a los cambios en Android 5.0 Lollipop , y no con los certificados porque los otros clientes se conectan, pero no sé cómo decir qué está sucediendo en el lado del cliente de la conexión porque SSL la depuración no parece ser compatible con Android. El problema es muy similar a este , que tampoco se ha resuelto todavía, pero sugiere que el problema es con las suites de cifrado. Los errores de Android 88313 81603 developer-preview-1989 parecen indicar que la implementación de Android es correcta, pero la configuración del servidor o la implementación de las suites de cifrado pueden no serlo.

He configurado las siguientes suites de cifrado de servidor

server.ssl.ciphers = TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA

En particular, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA se encuentra en la lista de protocolos admitidos para Android para API 11+.

Verifiqué que el servidor admite esto

openssl s_client -connect server:port

que devuelve

SSL-Session: Protocol : TLSv1.2 Cipher : ECDHE-RSA-AES128-SHA

Hay una ligera falta de coincidencia en los nombres entre openssl y java, pero la documentación de openssl dice que estos son el mismo conjunto de cifrado.

Mi servidor admite y negocia primero una suite de cifrado con el cliente de openssl que es compatible con Android 5.0. Espero que Android 5.0 se conecte sin problemas, pero falla.

¿Alguien ha conectado con éxito las conexiones seguras de websocket de Android 5.0 con Tomcat? ¿Hay suites de cifrado que se sabe que funcionan? ¿Hay alguna forma de depurar la implementación de SSL del lado del cliente de Android?

ACTUALIZAR

Resultados de rastreo de red:

SYN --> <-- SYN, ACK ACK --> <-- Data ACK --> <-- certificates, SSL/TLS params? 1 <-- 2 <-- 3 <-- 4 ACK --> ACK --> ACK --> FIN(!), ACK -->

Cuando el dispositivo Android 5.0 (un Nexus 5) recibe la información del certificado del servidor enviada en 4-5 paquetes, responde con un número variable (2-4) de ACK y luego un FIN, ACK. En la traza exitosa, el cliente no envía un FIN. Al cliente de Android 5 no le gusta algo que obtiene del servidor.

Para la falla, la información de depuración de SSL del servidor dice:

http-nio-8080-exec-10, called closeOutbound() http-nio-8080-exec-10, closeOutboundInternal() http-nio-8080-exec-10, SEND TLSv1.2 ALERT: warning, description = close_notify http-nio-8080-exec-10, WRITE: TLSv1.2 Alert, length = 2 [Raw write]: length = 7 0000: 15 03 03 00 02 01 00

ACTUALIZACIÓN 2

Aquí hay una aplicación de Android Tyrus bare-bones para usar

package edu.umd.mindlab.androidssldebug; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; import org.glassfish.tyrus.client.ClientManager; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.net.URI; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; @ClientEndpoint public class MainActivity extends ActionBarActivity { public static final String TAG = "edu.umd.mindlab.androidssldebug"; final Object annotatedClientEndpoint = this; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onStart(){ super.onStart(); final Object annotatedClientEndpoint = this; new Thread(new Runnable(){ @Override public void run() { try { URI connectionURI = new URI("wss://mind7.cs.umd.edu:8080/test"); ClientManager client = ClientManager.createClient(); Object clientEndpoint = annotatedClientEndpoint; client.connectToServer(clientEndpoint, connectionURI); } catch(Exception e){ ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); PrintStream printStream = new PrintStream(byteStream); e.printStackTrace(printStream); final String message = byteStream.toString(); Log.e(TAG, message); e.printStackTrace(); runOnUiThread(new Runnable() { public void run() { TextView outputTextView = (TextView) findViewById(R.id.outputTextView); outputTextView.setText(message); } }); } } }).start(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } @OnOpen public void onOpen(Session session) { Log.i(TAG, "opened"); runOnUiThread(new Runnable() { public void run() { TextView outputTextView = (TextView) findViewById(R.id.outputTextView); outputTextView.setText("opened"); } }); } @OnMessage public void onMessage(String message, Session session) { Log.i(TAG, "message: " + message); } @OnClose public void onClose(Session session, CloseReason closeReason) { Log.i(TAG, "close: " + closeReason.toString() ); } @OnError public void onError(Session session, Throwable t) { final String message = "error: " + t.toString(); Log.e(TAG, message); runOnUiThread(new Runnable() { public void run() { TextView outputTextView = (TextView) findViewById(R.id.outputTextView); outputTextView.setText(message); } }); } }


La solución sugerida en TYRUS-402 resuelve esto. He abierto un correspondiente Grizzly Bug GRIZZLY-1827 que tiene el parche correspondiente.

Actualización: El error GRIZZLY-1827 ha sido reparado.



error:1408E0F4:SSL routines:SSL3_GET_MESSAGE:unexpected message (external/openssl/ssl/s3_both.c:498 0xac526e61:0x00000000) at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake_bio(Native Method) at com.android.org.conscrypt.OpenSSLEngineImpl.unwrap(OpenSSLEngineImpl.java:423)

0x1408E0F4 es:

$ openssl errstr 0x1408E0F4 error:1408E0F4:SSL routines:SSL3_GET_MESSAGE:unexpected message

Aparece en las fuentes de OpenSSL en un par de lugares:

$ cd openssl-1.0.1l $ grep -R SSL3_GET_MESSAGE * ssl/s3_both.c: SSLerr(SSL_F_SSL3_GET_MESSAGE,SSL_R_UNEXPECTED_MESSAGE); ssl/s3_both.c: SSLerr(SSL_F_SSL3_GET_MESSAGE,SSL_R_UNEXPECTED_MESSAGE); ssl/s3_both.c: SSLerr(SSL_F_SSL3_GET_MESSAGE,SSL_R_EXCESSIVE_MESSAGE_SIZE); ssl/s3_both.c: SSLerr(SSL_F_SSL3_GET_MESSAGE,SSL_R_EXCESSIVE_MESSAGE_SIZE); ssl/s3_both.c: SSLerr(SSL_F_SSL3_GET_MESSAGE,ERR_R_BUF_LIB);

Este es el código que creo está causando el problema (los números de línea han cambiado, y SSLerr está en 491):

/* Obtain handshake message of message type ''mt'' (any if mt == -1), * maximum acceptable body length ''max''. * The first four bytes (msg_type and length) are read in state ''st1'', * the body is read in state ''stn''. */ long ssl3_get_message(SSL *s, int st1, int stn, int mt, long max, int *ok) { ... /* s->init_num == 4 */ if ((mt >= 0) && (*p != mt)) { al=SSL_AD_UNEXPECTED_MESSAGE; SSLerr(SSL_F_SSL3_GET_MESSAGE,SSL_R_UNEXPECTED_MESSAGE); goto f_err; } ...

Pero no estoy seguro de qué causa ese problema en particular. Consulte esta pregunta en la lista de usuarios de OpenSSL en SSL_F_SSL3_GET_MESSAGE y SSL_R_UNEXPECTED_MESSAGE .

EDITAR : de acuerdo con la fuente de Android para s3_both.c , ese es el código que está desencadenando el problema.

-----

OK, mirando el archivo successful.pcap y unsuccessful.pcap , el buen cliente está utilizando TLS 1.0 mientras que el cliente que está haciendo mal uso está usando TLS 1.2. Pero no veo nada ofensivo que haga que el cliente cierre la conexión mientras procesa los cuatro mensajes (Hola del servidor, Certificado, Intercambio de claves del servidor, Hola del servidor realizado) en el registro.

-----

Basado en el mensaje ServerKeyExchange :

El servidor seleccionó la oferta del cliente de secp521r1. Es posible que desee utilizar secp256 . Eso es más interoperable en este momento. Ver también ¿El soporte de curva elíptica limitado en rhel / centos / redhat openssl es lo suficientemente robusto? .

-----

OpenSSL 1.0.1e FIPS utilizado por el servidor ha sufrido algunos problemas. Ver, por ejemplo:

Si es posible, es posible que desee actualizarlo a algo más nuevo.

-----

¿Hay alguna forma de depurar la implementación de SSL del lado del cliente de Android?

Creo que esta es una pregunta más fácil. Use una SSLSocketFactory personalizada como SSLSocketFactoryEx . Le permitirá probar diferentes protocolos, conjuntos de cifrado y configuraciones. Pero es prueba y error.

De lo contrario, necesitaría obtener una copia del código fuente de OpenSSL utilizado por Android 5.0 (incluidos los parches). No sé cómo conseguirlo y asegurarme de que se construye como OpenSSL principal (de hecho, necesitas compilar s_client usando fuentes de Android con información de depuración).

Esto podría ser útil: OpenSSL en Android . Desde el aspecto de las diferencias, parece que Android está utilizando OpenSSL 1.0.0. (Algunos de los parches en el directorio patch/ específicamente llaman 1.0.0b).