繁体   English   中英

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

[英]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

对我来说,这似乎是:

  • 客户端连接。
  • 服务器接受。
  • 在以下情况下会发生TLS握手:
    • 密码套件已成功协商。
    • 客户端验证服务器证书
  • 服务器任意关闭套接字。
  • 客户端挂了。

我已经经历了几个小时的变化:

  • 没有抛出异常。
  • 调试输出中没有明显的错误。
  • 没有解释为什么服务器任意关闭套接字。

这三个类(连接,服务器,客户端)的完整代码大约为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关闭。

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()如果关闭了套接字返回true。 不是同行。 结论:您已关闭插座。

注意setUseClientMode()是不必要的。 您要做的只是声明默认值。 如果必须调用,则必须在握手之前调用它 startHandshake()也不是必需的,它是自动的。

暂无
暂无

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

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