Ошибка установления связи SSL Websocket

У меня есть сервер Tomcat с весенней загрузкой для безопасных подключений к веб-сокетам. Сервер принимает клиентов Android 4.4, iOS, Firefox и Chrome без сбоев с сертификатом, подписанным центром сертификации. Однако Android 5.0 не поддерживает квитирование 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)

Я думаю, что проблема связана с TLS или наборами шифров из-за изменений в Android 5.0 Lollipop, а не с сертификатами, потому что подключаются другие клиенты, но я не могу понять, как узнать, что происходит на стороне клиента соединения, потому что Отладка SSL не поддерживается на Android. Проблема, вероятно, очень похожа на этот, который также еще не решен, но предполагает проблема с наборами шифров. Ошибки Android 88313 81603 developer-preview-1989, похоже, указывает на то, что реализация Android верна, но конфигурация сервера или реализация наборов шифров могут быть неверными.

Я установил следующие комплекты серверных шифров

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

В частности, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA находится в списке поддерживаемых протоколов для Android для API 11+.

Я проверил, что сервер поддерживает это

openssl s_client -connect server:port

который возвращается

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

Существует небольшое несовпадение имен между openssl и java, но документация по openssl говорит, что это один и тот же набор шифров.

Мой сервер поддерживает и согласовывает сначала набор шифров с клиентом openssl, совместимым с Android 5.0. Я ожидаю, что Android 5.0 подключится без проблем, но это не удается.

Кто-нибудь успешно подключил безопасные веб-соединения Android 5.0 к Tomcat? Есть ли известные работающие комплекты шифров? Есть ли способ отладить реализацию SSL на стороне клиента Android?


ОБНОВЛЕНИЕ

Результаты трассировки сети:

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

Когда устройство Android 5.0 (Nexus 5) получает информацию о сертификате сервера, отправленную в 4-5 пакетах, оно отвечает переменным числом (2-4) ACK, затем FIN, ACK. При успешной трассировке клиент не отправляет FIN. Клиенту Android 5 не нравится то, что он получает от сервера.

В случае сбоя информация об отладке SSL сервера говорит:

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 

ОБНОВЛЕНИЕ 2

Вот простое приложение Tyrus для Android для использования

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

}

person mattm    schedule 18.01.2015    source источник
comment
Пожалуйста, проверьте журнал серверов на наличие сообщений об ошибках, чтобы решить проблему. Если вы ничего не нашли, сделайте захват пакетов успешного и неудачного соединения и сравните. Если вам нужна помощь в интерпретации перехваченных пакетов, опубликуйте их на сайте cloudshark.org.   -  person Steffen Ullrich    schedule 18.01.2015
comment
@mattm - какую версию OpenSSL использует сервер? Попробуйте $ openssl version с терминала.   -  person jww    schedule 18.01.2015
comment
OpenSSL 1.0.1e-fips, 11 февраля 2013 г., дата создания: четверг, 6 ноября, 12:33:36 UTC, 2014 г.   -  person mattm    schedule 18.01.2015
comment
@jww У меня есть второй сервер с OpenSSL 1.0.1j 15 октября 2014 года, который показывает такое же поведение.   -  person mattm    schedule 19.01.2015
comment
@mattm - извините за это (я не могу дублировать это с помощью s_client) ... Я думаю, нам понадобится PCAP. Можете ли вы собрать его на сервере Tomcat для сеанса работы с клиентом Android 5.0?   -  person jww    schedule 19.01.2015
comment
Позвольте нам продолжить это обсуждение в чате.   -  person mattm    schedule 19.01.2015
comment
@mattm - У меня есть второй сервер с OpenSSL 1.0.1j - теперь это интересно ...   -  person jww    schedule 19.01.2015
comment
@mattm - когда вы говорите с помощью WebSocket, вы имеете в виду, что первое соединение с сайтом в порядке (при загрузке страницы), а затем последующее использование соединения (через WebSocket) умирает?   -  person jww    schedule 19.01.2015
comment
@mattm - Я удалил материал о Nginx из вопроса. Это немного отвлекало, поскольку не усугубляло проблему. Возможно, вы захотите разместить свои PCAP где-нибудь, чтобы другие могли на них взглянуть. Но я подозреваю, что это ошибка Android, и способ разгадать загадку - получить s_client построенный, использующий исправленные источники OpenSSL Android.   -  person jww    schedule 19.01.2015
comment
@jww Отсутствует загрузка страницы на сервере из-за сбоя установления связи SSL. Клиенты открывают веб-соединения непосредственно с сервером (клиенты браузера загружают страницу с другого сервера, а затем открывают веб-сокет). Позже на этой неделе я попытаюсь установить сервер в общедоступном месте, который я могу сделать доступным для всех в течение длительного периода времени для устранения этой проблемы.   -  person mattm    schedule 19.01.2015
comment


Ответы (3)


Предлагаемое исправление на TYRUS-402 решает эту проблему. Я открыл соответствующий Grizzly Bug GRIZZLY-1827 с соответствующим патчем.

Обновление: исправлена ​​ошибка GRIZZLY-1827.

person dgoel    schedule 24.03.2016
comment
Чтобы повысить качество этого ответа, кратко опишите предлагаемое исправление, на которое вы ссылаетесь. - person David; 25.03.2016

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 is:

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

Он появляется в источниках OpenSSL в нескольких местах:

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

Вот код, который, как мне кажется, вызывает проблемы (номера строк изменились, а SSLerr находится на 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;
        }
    ...

Но я не уверен, что вызывает эту конкретную проблему. См. Этот вопрос в списке пользователей OpenSSL на странице SSL_F_SSL3_GET_MESSAGE / SSL_F_SSL3_GET_MESSAGE и SSL_F_SSL3_GET_MESSAGE а>.

ИЗМЕНИТЬ: согласно источнику Android для s3_both.c, это код, вызывающий проблему.

-----

Хорошо, посмотрев на файл successful.pcap и unsuccessful.pcap, хороший клиент использует TLS 1.0, а некорректный клиент использует TLS 1.2. Но я не вижу ничего оскорбительного, что могло бы заставить клиента закрыть соединение при обработке четырех сообщений (Server Hello, Certificate, Server Key Exchange, Server Hello Done) в записи.

-----

На основании сообщения ServerKeyExchange:

введите описание изображения здесь

Сервер выбрал предложение клиента secp521r1. Возможно, вы захотите использовать secp256. Это наиболее на данный момент совместимость. Также см. Достаточно ли надежна ограниченная поддержка эллиптических кривых в rhel / centos / redhat openssl?.

-----

OpenSSL 1.0.1e FIPS, используемый сервером, имеет несколько проблем. См., Например:

Если возможно, вы можете обновить его до чего-то более нового.

-----

Есть ли способ отладить реализацию SSL на стороне клиента Android?

Я думаю, это более простой вопрос. Используйте собственный SSLSocketFactory, например SSLSocketFactoryEx. Это позволит вам попробовать разные протоколы, наборы шифров и настройки. Но это метод проб и ошибок.

В противном случае вам нужно будет получить копию исходного кода OpenSSL, используемого Android 5.0 (включая исправления). Я не знаю, как это получить и убедиться, что он строится как основной OpenSSL (по сути, вам нужно собрать s_client с использованием источников Android с отладочной информацией).

Это может быть полезно: OpenSSL на Android. Судя по разностям, Android использует OpenSSL 1.0.0. (Некоторые патчи в каталоге patch/ относятся к версии 1.0.0b).

person jww    schedule 18.01.2015
comment
Эта конкретная трассировка предназначена для OpenSSL 1.0.1j. 192.168.0.109 - это сервер, а 192.168.0.100 - клиент. Разве это не клиент закрывает соединение в кадре 14, когда он отправляет первый FIN? Я установил шифр TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA в конфигурации сервера, потому что он находится на пересечении шифров, поддерживаемых Android 4.4 и Android 5. Моя первая цель - добиться успешного согласования любого набора шифров между Android 5 и сервером. Я бы предпочел набор на основе SHA-256 или SHA-384, но я не нашел ни одной конфигурации сервера, которая успешно согласовывала бы какой-либо шифр. - person mattm; 19.01.2015
comment
@mattm - Разве это не клиент, закрывающий соединение в 14-м кадре ... - да, плохо. Я неправильно понял, какая сторона послала закрытие. - person jww; 19.01.2015

Подтверждено, что это вызвано ошибкой Android 5.0. В настоящее время мне неясно, есть ли проблема также в Tyrus websocket или Grizzly.

См. Также: 93740 и предварительный просмотр 328.

person mattm    schedule 23.01.2015