我有用于安全websocket连接的spring-boot 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或由于changes in Android 5.0 Lollipop引起的密码套件,而不是因为其他客户端连接而没有证书,但我无法弄清楚如何告诉客户端端的情况连接是因为SSL debugging does not appear to be supported on Android.问题可能与this one非常相似,但问题尚未得到解决,但表明问题在于密码套件。 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}的list of supported protocols for Android上。
我验证了服务器支持此
openssl s_client -connect server:port
返回
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-SHA
openssl和java之间的名称略有不匹配,但the openssl documentation表示这些是相同的密码套件。
我的服务器首先使用与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);
}
});
}
}
答案 0 :(得分: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);
以下是我认为导致问题的代码(行号已更改,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;
}
...
但我不确定是什么导致了这个特殊问题。请在SSL_F_SSL3_GET_MESSAGE and SSL_R_UNEXPECTED_MESSAGE上的OpenSSL用户列表中查看此问题。
编辑:根据s3_both.c的Android来源,这是触发此问题的代码。
好的,查看文件successful.pcap
和unsuccessful.pcap
,好的客户端正在使用TLS 1.0,而行为不端的客户端正在使用TLS 1.2。但是我没有看到任何令人反感的行为,这会导致客户端在处理记录中的四条消息(服务器Hello,证书,服务器密钥交换,服务器Hello完成)时关闭连接。
基于ServerKeyExchange
消息:
服务器选择了客户端提供的secp521r1。您可能想要使用secp256
。现在, 大多数 可互操作。另请参阅Is the limited elliptic curve support in rhel/centos/redhat openssl robust enough?。
OpenSSL 1.0.1e服务器使用的FIPS遇到了一些问题。例如,见:
如果可能,您可能希望将其升级为更新版本。
有没有办法调试Android客户端SSL实现?
我认为这是一个更容易的问题。使用SSLSocketFactoryEx
之类的自定义SSLSocketFactory
。它允许您尝试不同的协议,密码套件和设置。但它的反复试验。
否则,您需要获取Android 5.0使用的OpenSSL源代码的副本(包括修补程序)。我不知道如何获得它并确保它像主线OpenSSL一样构建(实际上,你需要使用Android源和调试信息来构建s_client
。
这可能会有所帮助:OpenSSL on Android。从差异的外观来看,Android似乎正在使用OpenSSL 1.0.0。 (patch/
目录中的某些补丁专门调用1.0.0b。)
答案 1 :(得分:1)
确认这是由Android 5.0 bug引起的。目前我不清楚Tyrus websocket或Grizzly是否也存在问题。
另请参阅:93740和preview 328。
答案 2 :(得分:0)
TYRUS-402的建议修正案解决了这个问题。我已经打开了相应的Grizzly Bug GRIZZLY-1827,它有相应的补丁。
更新:错误GRIZZLY-1827已修复。