2

我有一个非常简单的客户端服务器,其中一个阻塞套接字进行全双工通信。我已经为应用程序启用了 SSL/TLS。该模型是典型的生产者-消费者模型。客户端生成数据,将其发送到服务器,服务器处理它们。唯一的问题是,服务器偶尔会将数据发送回客户端,客户端会相应地处理这些数据。下面是一个非常简单的应用程序伪代码:

  1 Client:
  2 -------
  3 while (true)
  4 {
  5         if (poll(pollin, timeout=0) || 0 < SSL_pending(ssl))
  6         {
  7                 SSL_read();
  8                 // Handle WANT_READ or WANT_WRITE appropriately.
  9                 // If no error, handle the received control message.
 10         }
 11         // produce data.
 12         while (!poll(pollout))
 13                 ; // Wait until the pipe is ready for a send().
 14         SSL_write();
 15         // Handle WANT_READ or WANT_WRITE appropriately.
 16         if (time to renegotiate)
 17                 SSL_renegotiate(ssl);
 18 }
 19
 20 Server:
 21 -------
 22 while (true)
 23 {
 24         if (poll(pollin, timeout=1s) || 0 < SSL_pending(ssl))
 25         {
 26                 SSL_read();
 27                 // Handle WANT_READ or WANT_WRITE appropriately.
 28                 // If no error, consume data.
 29         }
 30         if (control message needs to be sent)
 31         {
 32                 while (!poll(pollout))
 33                         ; // Wait until the pipe is ready for a send().
 34                 SSL_write();
 35                 // Handle WANT_READ or WANT_WRITE appropriately.
 36         }
 37 }

当出于测试目的,我强制 SSL 重新协商(第 16-17 行)时,就会出现问题。会话开始时很好很容易,但过了一会儿,我收到以下错误:

Client:
-------
error:140940F5:SSL routines:SSL3_READ_BYTES:unexpected record

Server:
-------
error:140943F2:SSL routines:SSL3_READ_BYTES:sslv3 alert unexpected message

事实证明,大约在客户端启动重新协商的同时(第 14 行),服务器最终将应用程序数据发送到客户端(第 34 行)。作为重新协商过程的一部分,客户端收到此应用程序数据并以“意外记录”错误进行轰炸。类似地,当服务器进行后续接收(第 26 行)时,它最终会在期待应用程序数据时接收到重新协商数据。

我究竟做错了什么?我应该如何使用全双工通道处理/测试 SSL 重新协商。请注意,不涉及线程。这是一个简单的单线程模型,读/写发生在套接字的任一端。

更新:为了验证我编写的应用程序没有任何问题,我什至可以使用 OpenSSL 的 s_client 和 s_server 实现非常轻松地重现这一点。我启动了一个 s_server,一旦 s_client 连接到服务器,我以编程方式将一堆应用程序数据从服务器发送到客户端,并将一堆“R”(重新协商请求)从客户端发送到服务器。最终,它们都以与上述完全相同的方式失败。

s_client:

RENEGOTIATING
4840:error:140940F5:SSL routines:SSL3_READ_BYTES:unexpected record:s3_pkt.c:1258:

s_server:

Read BLOCK
ERROR
4838:error:140943F2:SSL routines:SSL3_READ_BYTES:sslv3 alert unexpected message:s3_pkt.c:1108:SSL alert number 10
4838:error:140940E5:SSL routines:SSL3_READ_BYTES:ssl handshake failure:s3_pkt.c:1185:

更新2: 好的。正如 David 所建议的那样,我重新设计了测试应用程序以使用非阻塞套接字,并且始终首先执行 SSL_read 和 SSL_write,然后根据它们返回的内容进行选择,并且在重新协商期间我仍然遇到相同的错误(SSL_write 最终从另一方正在重新谈判中)。问题是,在任何时候,如果 SSL_read 返回 WANT_READ,我是否可以假设这是因为管道中没有任何内容并继续使用 SSL_write,因为我有东西要写?如果没有,那可能就是我最终出现错误的原因。要么,要么我做的重新谈判都错了。注意,如果 SSL_read 返回 WANT_WRITE,我总是选择并再次调用 SSL_read。

4

2 回答 2

5

您正在尝试“查看” SSL 黑匣子。这是一个巨大的错误。

     if (poll(pollin, timeout=0) || 0 < SSL_pending(ssl))
     {
             SSL_read();

您假设为了SSL_read取得进展,它需要从套接字读取数据。这是一个可能是错误的假设。例如,如果重新协商正在进行中,SSL 引擎可能需要接下来发送数据,而不是读取数据。

     while (!poll(pollout))
             ; // Wait until the pipe is ready for a send().
     SSL_write();

您如何知道 SSL 引擎想要将数据写入管道?有没有给你WANT_WRITE暗示?如果没有,也许它需要读取重新协商数据才能发送。

要在非阻塞模式下使用 SSL,只需尝试您想要执行的操作。如果要读取解密数据,请调用SSL_read. 如果要发送加密数据,请调用SSL_write. 仅poll当 SSL 引擎告诉您使用WANT_READWANT_WRITE指示时才调用。

更新:: 您在阻塞和非阻塞方法之间有一个“各一半”的混合体。这不可能奏效。问题很简单:在您调用 之前SSL_read,您不知道它是否需要从套接字读取。如果你poll先调用,即使SSL_read不需要从套接字读取,你也会阻塞。如果您SSL_read先调用,如果确实需要从套接字读取,它将阻塞。SSL_pending不会帮你的。如果SSL_read需要写入套接字以进行前进,SSL_pending将返回零,但调用poll将永远阻塞。

你有两个明智的选择:

  1. 阻塞。让套接字设置阻塞。只需SSL_read在您想阅读和SSL_write想写的时候打电话。他们阻止。阻塞套接字可以阻塞,这就是它们的工作方式。

  2. 非阻塞。将套接字设置为非阻塞。只需SSL_read在您想阅读和SSL_write想写的时候打电话。他们不会阻止。如果您得到WANT_READ指示,请在读取方向进行轮询。如果您得到WANT_WRITE指示,请在写入方向进行轮询。请注意,SSL_read返回是完全正常的WANT_WRITE,然后您在写入方向进行轮询。同样,SSL_write可以 return WANT_READ,然后你在读取方向进行轮询。

如果 SSL_read 的实现基本上是“读取一些数据然后解密它”并且 SSL_write 是“加密一些数据并发送它”,那么您的代码将(大部分)工作。问题是,这些函数实际上运行一个复杂的状态机,它根据需要读取和写入套接字,并最终导致为您提供解密数据或加密数据并发送它的效果。

于 2013-09-10T20:53:32.080 回答
4

在花时间用 OpenSSL 调试我的应用程序之后,我找到了我最初发布的问题的答案。我在这里分享它以防它帮助像我这样的其他人。

我最初发布的问题与 OpenSSL 的一个明显错误有关,表明它在握手过程中接收应用程序数据。我不明白的是,OpenSSL 在握手过程中接收应用程序数据时会感到困惑。在接收/发送应用程序数据时接收握手数据是可以的,但反之则不行(至少使用 OpenSSL)。那是我没有意识到的事情。这也是大多数启用 SSL 的应用程序运行良好的原因,因为它们中的大多数本质上是半双工的(例如 HTTPS),这隐含地保证在握手时没有应用程序数据异步到达。

这意味着,如果您正在设计一个自定义的客户端-服务器全双工协议(我就是这种情况)并且想要在其上添加 SSL,那么应用程序有责任在两端都没有发送任何数据时启动重新协商. 这在Mozilla 的 NSS API中有明确的记录。更不用说 OpenSSL 的 bug 存储库中关于此问题的公开票证。当我更改我的应用程序以启动握手时,客户端/服务器之间没有什么可以说的,我不再面临上述错误。

此外,我同意 David 关于阻塞套接字的评论,并且我也在 OpenSSL 邮件列表中阅读了他的许多论点。但是,可悲的是,大多数遗留应用程序都是围绕轮询和阻塞套接字构建的,它们“Just Work Fine (TM)”。处理 SSL 重新协商时会出现此问题。我仍然相信至少我的应用程序可以在存在阻塞套接字的情况下处理 SSL 重新协商,因为它是一个非常受限和自定义的协议,我们(作为应用程序开发人员)可以决定在协议静止时进行重新协商。如果这不起作用,我将采用非阻塞套接字路由。

于 2013-09-14T16:07:00.577 回答