7

安全な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)

Android 5.0 Lollipop の変更による TLS または暗号スイートの問題であり、他のクライアントが接続するための証明書ではないと思いますが、接続のクライアント側で何が起こっているかを知る方法がわかりません。デバッグは 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 は、API 11以降の Android でサポートされているプロトコルのリストに含まれています。

サーバーがこれをサポートしていることを確認しました

openssl s_client -connect server:port

返す

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

openssl と java の名前には若干の不一致がありますが、openssl のドキュメントには、これらは同じ暗号スイートであると記載されています。

私のサーバーは、最初に Android 5.0 と互換性のある openssl クライアントとの暗号スイートをサポートし、ネゴシエートします。Android 5.0 なら問題なく接続できると思いますが、失敗します。

Android 5.0 の安全な WebSocket 接続を Tomcat に正常に接続した人はいますか? 動作することが知られている暗号スイートはありますか? Android クライアント側の SSL 実装をデバッグする方法はありますか?


アップデート

ネットワーク トレースの結果:

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

}
4

3 に答える 3

3
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 は次のとおりです。

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

これが問題を引き起こしていると思われるコードです(行番号が変更され、SSLerr491にあります):

/* 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;
        }
    ...

しかし、その特定の問題の原因が何であるかはわかりません。SSL_F_SSL3_GET_MESSAGE および SSL_R_UNEXPECTED_MESSAGEの OpenSSL ユーザー リストでこの質問を参照してください。

編集: s3_both.cの Android ソースによると、これが問題を引き起こしているコードです。

-----

OK、ファイルsuccessful.pcapとを見るとunsuccessful.pcap、正常なクライアントは TLS 1.0 を使用していますが、動作の悪いクライアントは TLS 1.2 を使用しています。しかし、Record 内の 4 つのメッセージ (Server Hello、Certificate、Server Key Exchange、Server Hello Done) の処理中にクライアントが接続を閉じる原因となる攻撃的なものは見当たりません。

-----

ServerKeyExchangeメッセージに基づいて:

ここに画像の説明を入力

サーバーは、secp521r1 のクライアントの提供を選択しました。を使用することもできますsecp256。これは現在、最も相互運用性があります。また、rhel/centos/redhat openssl での限定的な楕円曲線のサポートは十分に堅牢ですか?も参照してください。.

-----

サーバーが使用する OpenSSL 1.0.1e FIPS には、いくつかの問題があります。たとえば、次を参照してください。

可能であれば、新しいものにアップグレードすることをお勧めします。

-----

Android クライアント側の SSL 実装をデバッグする方法はありますか?

これは簡単な質問だと思います。SSLSocketFactoryのようなカスタムを使用しますSSLSocketFactoryEx。さまざまなプロトコル、暗号スイート、および設定を試すことができます。しかし、その試行錯誤。

それ以外の場合は、Android 5.0 で使用されている OpenSSL ソース コード (パッチを含む) のコピーを取得する必要があります。それを取得して、メインラインの OpenSSL のようにビルドする方法がわかりません (事実上、デバッグ情報を含むs_clientAndroid ソースを使用してビルドする必要があります)。

これは役立つかもしれません: OpenSSL on Android . 差分を見ると、Android は OpenSSL 1.0.0 を使用しているようです。(ディレクトリ内の一部のパッチでは、patch/1.0.0b が明確に呼び出されます)。

于 2015-01-18T20:16:55.853 に答える
1

これは、Android 5.0 のバグが原因であることが確認されています。Tyrus websocket または Grizzly にも問題があるかどうかは、現時点では不明です。

参照: 93740およびプレビュー 328

于 2015-01-23T04:53:56.003 に答える
0

TYRUS-402で提案されている修正により、これが解決されます。対応するパッチが適用された対応する Grizzly Bug GRIZZLY-1827 を開きました。

更新: バグGRIZZLY-1827が修正されました。

于 2016-03-24T22:13:06.530 に答える