[英]Server closes socket after handshake completed
我正在尝试实现基本的TLS客户端/服务器框架。 一切似乎都很好,直到握手之后为止,此时测试回显服务器应该读取和回写接收到的所有内容。
我已经使用-Djavax.net.debug=all
从两端运行了该协议,并且该协议降级到TLSv1(但从TLSv1.2开始)。 这是安装了安全包的GNU / Linux x86-64上的Oracle 8。
在服务器使用从SSLSocket.getInputStream()
获得的流调用BufferedInputReader.read()
之前,没有任何一方抛出异常。 此时,测试客户端正在等待控制台输入发送到回显测试服务器。
我以前已经使用GnuTLS在C和C ++中实现了TLS 1.2服务器,因此我对所涉及的基础知识有基本的了解。 对于调试这个java实现,我读过通过这样的东西像这个和这个 ,我的问题的说明将遵循这一模式。
在服务器端,事情始于:
main, READ: TLSv1 Handshake, length = 151
*** ClientHello, TLSv1
[...]
[read] MD5 and SHA1 hashes: len = 151
[...]
%% Initialized: [Session-1, SSL_NULL_WITH_NULL_NULL]
matching alias: tls_server_key
%% Negotiating: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]
*** ServerHello, TLSv1
发送“ RandomCookie”,然后发送服务器证书。 此时,客户已经通过:
*** ClientHello, TLSv1
[...]
[write] MD5 and SHA1 hashes: len = 151
main, WRITE: TLSv1 Handshake, length = 151
[...]
main, READ: TLSv1 Handshake, length = 1683
*** ServerHello, TLSv1
...读取服务器发送的相同“ RandomCookie”,然后...
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
Compression Method: 0
Extension renegotiation_info, renegotiated_connection: <empty>
***
%% Initialized: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]
** TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
[read] MD5 and SHA1 hashes: len = 81
然后读取服务器证书。 客户端具有用于签名的CA证书,因此最终有一个Found trusted certificate
,然后
*** ECDH ServerKeyExchange
Server key: Sun EC public key, 256 bits
public x coord: 99333168850581082484902625735441572937781175927498004126728233464162626469072
public y coord: 8153267160158506973550759395885968557147147981466758879486894574711221505422
parameters: secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)
[read] MD5 and SHA1 hashes: len = 459
[...]
*** ServerHelloDone
[read] MD5 and SHA1 hashes: len = 4
0000: 0E 00 00 00 ....
*** ECDHClientKeyExchange
[...]
[write] MD5 and SHA1 hashes: len = 70
[...]
main, WRITE: TLSv1 Handshake, length = 70
[Raw write]: length = 75
[...]
*** Finished
[...]
main, WRITE: TLSv1 Handshake, length = 48
[Raw write]: length = 53
[...]
main, READ: TLSv1 Change Cipher Spec, length = 1
...一些原始读取总计53个字节...
main, READ: TLSv1 Handshake, length = 48
Padded plaintext after DECRYPTION: len = 48
[...]
*** Finished
verify_data: { 188, 99, 75, 234, 162, 27, 147, 7, 173, 51, 170, 34 }
***
%% Cached client session: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]
[read] MD5 and SHA1 hashes: len = 16
然后通过以下相关代码报告它是“已连接”:
sock.setUseClientMode(true);
sock.startHandshake();
in = new BufferedInputStream(sock.getInputStream());
out = new BufferedOutputStream(sock.getOutputStream());
除了套接字来自SSLServerSocket.accept()
(而不是适当的工厂)和在握手之前使用setUseClientMode(false)
之外,服务器设置基本相同。 最初我根本不在任何一方使用setUseClientMode()
,然后注意到javadoc说:“必须在发生任何握手之前调用此方法”,尽管这样做对调试输出或结果完全没有影响。
有趣的是,在调用startHandshake()
和创建in / out BufferedStreams之后, isClosed()
报告客户端和服务器均为false,但是服务器的下一步是在此处(这来自“ TLSconnection “服务器类和客户端类都使用的类):
public int recv (byte[] data) throws IOException
{
if (sock.isClosed()) return -1;
int len = in.read(data, 0, data.length);
if (len == -1) shut();
return len;
}
现在isClosed()
返回true,这是有道理的,因为服务器上的调试跟踪已结束:
[write] MD5 and SHA1 hashes: len = 16
main, WRITE: TLSv1 Handshake, length = 48
[Raw write]: length = 53
%% Cached server session: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]
main, called close()
main, called closeInternal(true)
main, SEND TLSv1 ALERT: warning, description = close_notify
[...]
main, WRITE: TLSv1 Alert, length = 32
[...]
main, called closeSocket(true)
我还使用了一个handshakeCompletedListener()
来完成此操作,该handshakeCompletedListener()
简单地写入标准错误,这是它的作用:
%% Cached server session: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]
main, called close()
main, called closeInternal(true)
main
HANDSHAKE COMPLETED
, SEND TLSv1 ALERT: warning, description = close_notify
对我来说,这似乎是:
我已经经历了几个小时的变化:
这三个类(连接,服务器,客户端)的完整代码大约为250行,加上测试实例(客户端和服务器)的每个代码大约为一百,所以我没有在此处全部张贴,但是显示的是什么在调试会话中发挥作用。
我假设这与证书或密钥错误实际上没有任何关系,因为没有迹象表明此类错误,握手失败或任何人出于任何原因拒绝了其他任何人。
回应斯特芬·乌尔里希(Steffen Ullrich)的评论“看起来服务器正在执行干净的关闭(发送close_notify),因此我建议您的代码中的某些内容实际上正在执行显式关闭” –我首先想到的是同一件事- -这是测试服务器代码的一部分,导致sock.isClosed()
返回true:
while (true) {
TLSconnection con = null;
try { con = server.acceptConnection(true); }
catch (Throwable ex) {
ex.printStackTrace();
continue;
}
if (con != null) {
System.out.println (
"Accepted: "
+TestCommon.describeConnection(con.getDetails())
);
}
while (con != null) {
// Echo.
try {
int check = con.recv(buffer);
if (check == -1) {
System.out.println("Disconnected.");
与此相关的字面意思是打印出“ Accepted”位; describeConnection()
方法很简单:
static String describeConnection (TLSconnection.Details details)
{
if (details.addr == null) return "Not connected.";
StringBuffer sb = new StringBuffer(details.addr.getHostAddress()+":")
.append(details.remotePort).append(" (local ")
.append(details.localPort).append(")");
return sb.toString();
}
然后, con.recv()
方法是前面完全显示的方法,其中isClosed()
现在返回true。 同样,在过渡期间等中不存在nullPointerExceptions。换句话说,在该接受和接收之间的某个位置,套接字被JVM关闭。
( TLSserver
) acceptConnection()
方法为:
public TLSconnection acceptConnection (boolean handshake)
throws TLSexception {
try (SSLSocket client = (SSLSocket)listenSock.accept()) {
client.setUseClientMode(false);
return new TLSconnection(client, handshake);
} catch (Throwable ex) {
throw new TLSexception (
"Accept failed:",
ex
);
}
}
Socket.isClosed()
如果你关闭了套接字返回true。 不是同行。 结论:您已关闭插座。
注意setUseClientMode()
是不必要的。 您要做的只是声明默认值。 如果必须调用,则必须在握手之前调用它。 startHandshake()
也不是必需的,它是自动的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.