【问题标题】:OpenSSL Handshake failure with two sessions and directly connected memory BIO's C#两个会话和直接连接内存 BIO 的 C# 的 OpenSSL 握手失败
【发布时间】:2016-01-27 17:14:48
【问题描述】:

我在 C# 中使用 OpenSSL 时遇到了一个奇怪的问题。

事实:我已经为C# 编写了一个包装器,它工作得很好,它可以连接到其他SSL 服务器,或者接受来自SSL 客户端的连接。我的包装器只使用内存 BIO。每当我将MEM BIO 连接到套接字握手成功的任何类型套接字的输入和输出时,数据传输工作,所以问题不在包装器中。

我用这个包装器制作了两个程序,一个客户端和一个服务器,它们建立了 ssl 会话,当我尝试将数据从服务器发送到客户端(或相反的方式)并且另一端回显它时由于某种原因,发送方的数据流量非常慢(即使在本地环回中最大 20kB/s,如果我尝试以更高的速度发送,例如在 SSL_read() 中抽取更多数据,那么使用 TCP 上的 TLS 会有巨大的延迟,使用 DTLS在 UDP 上,有时会有 75% 的数据包丢失),所以我决定使用包装器对 OpenSSL 进行基准测试,因为某些方法或错误的参数可能会减慢运行速度。

我的想法是在一个程序中初始化两个 SSL 上下文,一个客户端和一个服务器,将它们的 Memory BIO 相互连接(RX-TX TX-RX),然后开始握手。

我就是不能让他们成功握手!

在 Clienthello 之后使用 DTLS1,服务器开始以 4 个数据包(3x256 字节 + 145)响应 Serverhello 和 Cert 数据,但在第一个数据包之后客户端会发出警报:

After writing to CLIENT (packet: 2): 22016:error:14102044:SSL routines:dtls1_read_bytes:internal error:.\ssl\d1_pkt.c:1370:
After writing to CLIENT (packet: 3): 22016:error:14102044:SSL routines:dtls1_read_bytes:internal error:.\ssl\d1_pkt.c:1370:
After writing to CLIENT (packet: 4): 22016:error:14102044:SSL routines:dtls1_read_bytes:internal error:.\ssl\d1_pkt.c:1370:
After writing to CLIENT (packet: 5): 22016:error:14102044:SSL routines:dtls1_read_bytes:internal error:.\ssl\d1_pkt.c:1370:

两个会话之间的数据包流如下:

Client -> 178 -> Server
Server -> 256 -> Client
Server -> 256 -> Client
Server -> 256 -> Client
Server -> 145 -> Client
Client -> 15 -> Server

我已经用Wireshark 分析了这些数据包(在文件转储之后),一切似乎都很好(这个分析不是上面的数据包,而是同样的问题再现): http://reset.tor.hu/ssl/sima/programom_dtls1.txt

在这之后我得到更多的错误:

After writing to SERVER (packet: 5): 22016:error:141020E5:SSL routines:dtls1_read_bytes:ssl handshake failure:.\ssl\d1_pkt.c:819:
Before writing to SERVER (packet: 6): 22016:error:140FD10F:SSL routines:DTLS1_GET_MESSAGE_FRAGMENT:bad length:.\ssl\d1_both.c:892:
After writing to SERVER (packet: 6): 22016:error:14102417:SSL routines:dtls1_read_bytes:sslv3 alert illegal parameter:.\ssl\d1_pkt.c:1200:SSL alert number 47
22016:error:141020E5:SSL routines:dtls1_read_bytes:ssl handshake failure:.\ssl\d1_pkt.c:819:

我唯一可用的想法是调试这个问题,以便在从 BIO 读取数据包之后以及在另一个会话中将它们写入 BIO 之前将数据包转储到文件中,但我发现没有任何区别字节比较后的内容,包序也OK。

文件转储在这里:http://reset.tor.hu/ssl/y/direct_bio_error/

我只是无法想象可能是什么问题,因为他们从来没有完成握手而没有失败,所以我决定在这个程序中添加两个 UDP 套接字,并将这些套接字连接到 BIO 并相互连接,并且毫不奇怪,握手是第一次成功。

文件转储在这里:http://reset.tor.hu/ssl/y/socket_ok/

可见,文件内容或数据包序列没有任何区别。

那他们为什么不使用 BIO 的直接连接?!

您可以在此视频中看到程序产生错误和正常工作:https://www.youtube.com/watch?v=4crbSz6JMMY

我唯一的想法是 C# 出于某种原因在当时混合了方法调用,但我没有使用任何线程!非托管代码调用回调来读取 BIO,我将 BIO 读取到我的缓冲区,我调用另一个方法将缓冲区写入 BIO 并在非托管代码中调用 SSL_read(),以使用 BIO 中的数据。而这恰恰相反。如果我没有使用缓冲区,而是使用套接字,它正在工作。套接字可以做什么使它工作?

我什至尝试将 Thread.Sleep(100) 放在 BIO 写作之间,但没有帮助,实际上超过 140ms OpenSSL 崩溃了(但使用套接字它甚至可以在 1000 毫秒内工作,更奇怪)。

直接 BIO 和慢速交通问题可能是什么问题和解决方案?

感谢您提前回答!

【问题讨论】:

    标签: c# sockets ssl openssl handshake


    【解决方案1】:

    好的,实际上我似乎刚刚解决了 Direct-Memory-BIO-Handshake 问题。

    建议在BIO_write()之后调用SSL_read(),否则OpenSSL不会检查READ BIO中的数据也不会处理,所以几乎每次写BIO后直接使用。

    如果将OpenSSL 上下文配置为客户端,那么不应BIO_write() 之后直接使用SSL_read()直到握手未完成

    如果OpenSSL 与套接字一起使用(即使是内存生物),如果我们在BIO_write() 之后直接使用SSL_read(),它就可以工作,但两个会话之间没有直接的 BIO 连接。至少与 C# 中的非托管代码一样!

    可以正常工作的BIO写法如下:

        public bool BIO_Write(byte[] data)
        {
            if (Inited & data.Length > 0)
            {
                Wrapper.BIO_write(BIOR, data, data.Length);
                if (isServer(SSL_method))
                {
                    // For SERVER MODE
                    if (Wrapper.SSL_is_init_finished(SSL))
                    {
                        readFromSSL();
                    }
                    else
                    {
                        Wrapper.SSL_do_handshake(SSL);
                    }               
                }
                else
                {
                    // For CLIENT MODE
                    if (Wrapper.SSL_is_init_finished(SSL))
                    {
                        readFromSSL();
                    }
                    else
                    {
                        // It is extremely important to call the SSL_do_handshake()
                        // only if SSL_want() returns 3, otherwise OpenSSL will
                        // throw a FATAL ERROR and crash with direct BIO connection
                        // return value 3 is SSL_ERROR_WANT_WRITE
                        if (Wrapper.SSL_want(SSL) == 3)
                        {
                            Wrapper.SSL_do_handshake(SSL);
                        }
                    }
                }
                return true;
            }
            else
            {
                return false;
            }
        }
    

    我希望这会对某人有所帮助!

    【讨论】:

    • 使用 SSL_read 的想法帮助我修复了我的 Web 服务器 httpstorm 的一个有趣情况:如果服务器在握手期间接收到应用程序数据,例如HTTP 请求可能在我们准备发送服务器完成消息时到达,那么我们必须记住在等待更多数据之前在我们的接收函数中调用 SSL_read。
    • @GeorgeValkov - 谢谢你的提示,但正如我上面描述的调用 SSL_read() 并没有帮助,实际上在完成握手之前调用它会导致错误。解决方案是调用 SSL_do_handshake() 直到 SSL_want 不想再写了。问题出在握手过程中,而不是在通信中。奇怪的是,当我在两个直接连接的 BIO 之间注入两个套接字时,我仍然不知道为什么它会起作用。
    • 不确定这是否有帮助,但这里是我为我的 Web 服务器 httpstorm.com 设计的实现的链接。 pastebin.com/4UrStN13
    • @GeorgeValkov - 谢谢,但是这个问题在 6 年前就已经解决了,我们现在正在讨论解决方案。
    猜你喜欢
    • 2015-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-20
    • 2017-11-08
    • 1970-01-01
    • 2014-05-10
    相关资源
    最近更新 更多