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.
Se confirma que esto se debe a un error de Android 5.0 . Actualmente no me queda claro si también hay un problema en Tyrus websocket o Grizzly.
Ver también: 93740 y vista previa 328 .
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:
- Curvas binarias rotas en modo FIPS
- Bloqueo al usar TLS 1.2 causado por el uso de algoritmo hash incorrecto
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).