Netty tcnative 的 OpenSSL SSL_connect SYSCALL 错误

OpenSSL SSL_connect SYSCALL error with Netty tcnative

我正在使用 C++ 在 Windows 上使用 Berkeley 套接字和 OpenSSL 1.1.1k 连接到 Java (netty-tcnative) 服务器。有时在连接阶段,我会收到来自 SSL_connectSSL_ERROR_SYSCALL。如果我尝试使用 ERR_get_error 从这个 SYSCALL 错误中获取更多信息(根据 OpenSSL 文档),我会得到一个 return 值 0。TLS 连接的状态是 SSLv3/TLS write client hello。经过我这边的一些测试,我发现问题只在我重新连接套接字时重现(可能是 TLS 会话恢复?)

这里有 3 个问题:

我在 Debian 10 上使用 2.0.40 版本的 netty-tcnative。

我的 C++ 客户端代码是 运行 on MSVC 2017 和 Windows 10.

用于创建 SSL_ctx:

的 C++ 代码
auto ssl_ctx = SSL_CTX_new(TLS_client_method());
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); // Use TLS 1.3 if possible
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION); // Use TLS 1.2 at a minimum

客户端连接的C++代码:

const int sslErr = SSL_connect(ssl);
if (sslErr <= 0) {
    const int sslErrCode = SSL_get_error(ssl, sslErr);
    if (sslErrorCode == SSL_ERROR_SYSCALL) {
        const unsigned long errGetErr = ERR_get_error();
        //const OSSL_HANDSHAKE_STATE state = SSL_get_state(ssl);
        const char* stateStr = SSL_state_string_long(ssl);
        std::cout << "SSL_connect error: " << sslErrCode << std::endl;
        std::cout << "ERR_get_error error: " << errGetErr << std::endl;
        std::cout << "state: " << stateStr << std::endl;
        if (sslErrCode == SSL_ERROR_SYSCALL) {
            char buffer[256] = { 0 };
            ERR_error_string_n(errGetErr, buffer, sizeof(buffer));
            printf_s(buffer);
            printf_s("\n");
        }
    }
}

程序输出:

SSL_connect error: 5
ERR_get_error error: 0
state: SSLv3/TLS write client hello
error:00000000:lib(0):func(0):reason(0)

服务器端代码(netty 管道):

@Override
protected final void initChannel(Channel ch) throws Exception {
    ChannelPipeline pipe = ch.pipeline();
    SslContext sslCtx = SslContextBuilder.forServer(keyCertChainFile, keyFile).sslProvider(SslProvider.OPENSSL).build();
    pipe.addLast("ssl", sslCtx.newHandler(ch.alloc()));
}

经过大量调试,我终于找到了这个问题的答案。

  • 我是否可以设置一个标志或布尔值来启用 OpenSSL 端的调试日志记录,或者是否有一种方法可以让我验证我对 OpenSSL 试图恢复 TLS 会话的怀疑?

如果您使用的是 BoringSSL 而不是 Netty-tcnative OpenSSL 动态库,则 支持 TLS 重新协商。事实上,BoringSSL 根本不支持 TLS 重新协商。他们的文档中提到了 here

  • 是否有一个标志让我在 Netty tcnative 端设置以记录类似于 -Djavax.net.debug=all 的 TLS 握手?

对于阅读本文的 reader,如果您碰巧 运行 遇到这个问题,我强烈建议您确保在服务器处理程序中记录所有 TLS 握手错误。请参考以下代码段,因为这会在发生握手错误时提醒您。

@Override
public final void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if (evt instanceof SslHandshakeCompletionEvent) {
        SslHandshakeCompletionEvent hsEvt = (SslHandshakeCompletionEvent) evt;
        if (hsEvt.isSuccess()) {
            // handshake success
        } else {
            Throwable cause = hsEvt.cause();
            LOGGER.error("SSL Handshake Failure: {}", cause.getLocalizedMessage(), cause);
        }
    }
}
  • 这是客户端问题还是服务器端问题?

就我个人而言,这是一个客户端问题。在深入了解版本 1.1.1l 的 OpenSSL 文档后,有一个快速的 side-note 说明当 SSL_ERROR_SYSCALL 出现时错误号为 0(使用 ERR_get_error 获取)表示 EOF 来自同行。从这个注释中,您应该看到谁在关闭连接并检查套接字的状态。因为我使用的是 winsock,所以我得到的错误是 WSAENOTCONN,这意味着当我尝试 SSL_connect 时连接无效。 SSL_connect 在引擎盖下执行套接字 writes/reads ,因为它在这里执行 TLS 握手。如果在套接字未连接时发生任何套接字writes/reads,则WSAGetLastError将returnWSAENOTCONN。我没有看到任何表明 Netty 端连接关闭的日志,所以我决定使用 Wireshark 深入研究这个问题,以确定 TLS 握手在路径中的哪个位置失败,以及连接是在客户端还是服务器端关闭。在翻阅了一些在线文档后,我发现如果 SSL_readSSL_write 在 OpenSSL 中失败,则 SSL_ERROR_SYSCALL 是一个通用错误代码 returned。用Wireshark分析后,发现客户端一直没有给服务器发送的SYN-ACK发送ACK,说明连接一直没有完全建立。因为我使用的是非阻塞套接字,所以我只需要确保在尝试 TLS 握手之前套接字已完成连接。

希望这对遇到类似问题的人有所帮助。