简体   繁体   English

握手完成后服务器关闭套接字

[英]Server closes socket after handshake completed

I'm trying to implement a basic TLS client/server framework. 我正在尝试实现基本的TLS客户端/服务器框架。 Everything seems to go fine until after the handshake, at which point the test echo server is supposed to read and write back whatever it receives. 一切似乎都很好,直到握手之后为止,此时测试回显服务器应该读取和回写接收到的所有内容。

I've run this from both sides with -Djavax.net.debug=all and the protocol downgraded to TLSv1 (but strarting from TLSv1.2). 我已经使用-Djavax.net.debug=all从两端运行了该协议,并且该协议降级到TLSv1(但从TLSv1.2开始)。 This is Oracle 8 on GNU/Linux x86-64 with security packs installed. 这是安装了安全包的GNU / Linux x86-64上的Oracle 8。

There are no exceptions thrown from either side until the server calls BufferedInputReader.read() using the stream obtained from SSLSocket.getInputStream() . 在服务器使用从SSLSocket.getInputStream()获得的流调用BufferedInputReader.read()之前,没有任何一方抛出异常。 At this point the test client is sitting waiting for console input to send to the echoing test server. 此时,测试客户端正在等待控制台输入发送到回显测试服务器。

I've previously implemented TLS 1.2 servers in C and C++ using GnuTLS, so I have a basic understanding of the fundamentals involved. 我以前已经使用GnuTLS在C和C ++中实现了TLS 1.2服务器,因此我对所涉及的基础知识有基本的了解。 With regard to debugging this java implementation, I've read through stuff like like this and this and my description of the problem will follow that pattern. 对于调试这个java实现,我读过通过这样的东西像这个这个 ,我的问题的说明将遵循这一模式。

On the server side things begin with: 在服务器端,事情始于:

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

A "RandomCookie" is sent and then the server cert. 发送“ RandomCookie”,然后发送服务器证书。 At this point the client has gone through: 此时,客户已经通过:

*** ClientHello, TLSv1
[...]
[write] MD5 and SHA1 hashes:  len = 151
main, WRITE: TLSv1 Handshake, length = 151
[...]
main, READ: TLSv1 Handshake, length = 1683
*** ServerHello, TLSv1

...reads an identical "RandomCookie" sent from the server, then... ...读取服务器发送的相同“ 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

Then reads the server cert. 然后读取服务器证书。 The client has the CA cert with which this is signed, so eventually there's a Found trusted certificate , then 客户端具有用于签名的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

...some raw reads totalling 53 bytes... ...一些原始读取总计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

And then reports that it's "Connected", having passed through this relevant code: 然后通过以下相关代码报告它是“已连接”:

sock.setUseClientMode(true); 
sock.startHandshake();
in = new BufferedInputStream(sock.getInputStream());
out = new BufferedOutputStream(sock.getOutputStream()); 

The server set-up being much the same, except the socket came from SSLServerSocket.accept() (instead of an appropriate factory) and setUseClientMode(false) is used before the handshake. 除了套接字来自SSLServerSocket.accept() (而不是适当的工厂)和在握手之前使用setUseClientMode(false)之外,服务器设置基本相同。 Initially I wasn't using setUseClientMode() at all on either side, then noticed javadoc says, "This method must be called before any handshaking occurs", although doing so made no difference at all to the debug output or result. 最初我根本不在任何一方使用setUseClientMode() ,然后注意到javadoc说:“必须在发生任何握手之前调用此方法”,尽管这样做对调试输出或结果完全没有影响。

What is interesting is that after the call to startHandshake() and the creation of the in/out BufferedStreams, isClosed() reports false for both client and server, but the next move by the server is in here (this is from a "TLSconnection" class used by both the server and client class): 有趣的是,在调用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;
}          

Where isClosed() now returns true, which makes sense since the debugging trace on the server concludes: 现在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)

I've also done this using a handshakeCompletedListener() that simply writes to standard error and here's what it does to that: 我还使用了一个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

To me, this appears that: 对我来说,这似乎是:

  • The client connects. 客户端连接。
  • The server accepts. 服务器接受。
  • A TLS handshake occurs during which: 在以下情况下会发生TLS握手:
    • Cipher suites are successfully negotiated. 密码套件已成功协商。
    • The client verifies the server certificate 客户端验证服务器证书
  • The server arbitrarily closes the socket. 服务器任意关闭套接字。
  • The client is left hanging. 客户端挂了。

I've been through variations of this for hours and again: 我已经经历了几个小时的变化:

  • There are no exceptions thrown. 没有抛出异常。
  • There are no apparent errors in the debug output. 调试输出中没有明显的错误。
  • There is no explanation for why the server arbitrarily closes the socket. 没有解释为什么服务器任意关闭套接字。

The complete code for the three classes (connection, server, client) is about 250 lines, plus about another hundred each for the test instances (client and server), so I haven't posted it all here, but what's shown is what is in play during the debug session. 这三个类(连接,服务器,客户端)的完整代码大约为250行,加上测试实例(客户端和服务器)的每个代码大约为一百,所以我没有在此处全部张贴,但是显示的是什么在调试会话中发挥作用。

I'm assuming this can't really have anything to do with cert or key errors since there is nothing to indicate such, or that the handshake failed, or that anybody rejected anybody else for any reason. 我假设这与证书或密钥错误实际上没有任何关系,因为没有迹象表明此类错误,握手失败或任何人出于任何原因拒绝了其他任何人。


In response to Steffen Ullrich's comment that "It looks like the server is doing a clean shutdown (sends close_notify) so I would suggest that something in your code is actually doing an explicit close" -- which is the same thing I would think first -- here's the section of the test server code which leads up to sock.isClosed() returning true: 回应斯特芬·乌尔里希(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.");   

What literally happens in relation to this is the "Accepted" bit is printed out; 与此相关的字面意思是打印出“ Accepted”位; the describeConnection() method is simply: 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();
} 

Then the con.recv() method is the one shown completely earlier, where isClosed() now returns true. 然后, con.recv()方法是前面完全显示的方法,其中isClosed()现在返回true。 Again, there's no nullPointerExceptions that occur in the interim, etc. In other words, somewhere between that accept and recv the socket is being closed by the JVM. 同样,在过渡期间等中不存在nullPointerExceptions。换句话说,在该接受和接收之间的某个位置,套接字被JVM关闭。

The acceptConnection() method (of TLSserver ) is: TLSserveracceptConnection()方法为:

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() returns true if you closed the socket. Socket.isClosed()如果关闭了套接字返回true。 Not the peer. 不是同行。 Conclusion: you closed the socket. 结论:您已关闭插座。

NB setUseClientMode() isn't necessary. 注意setUseClientMode()是不必要的。 All you're doing is asserting the defaults. 您要做的只是声明默认值。 It must be called before the handshake if called at all. 如果必须调用,则必须在握手之前调用它 startHandshake() isn't necessary either, it is automatic. startHandshake()也不是必需的,它是自动的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM