具有全双工套接字通信的SSL重新协商

Kar*_*hik 2 c ssl openssl

我有一个非常简单的客户端服务器,其中一个阻塞套接字进行全双工通信.我已经为应用程序启用了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 }
Run Code Online (Sandbox Code Playgroud)

出于测试目的,我强制进行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
Run Code Online (Sandbox Code Playgroud)

事实证明,在客户端启动重新协商的同时(第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:
Run Code Online (Sandbox Code Playgroud)

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

Dav*_*rtz 5

你正试图"浏览"SSL黑盒子.这是一个巨大的错误.

     if (poll(pollin, timeout=0) || 0 < SSL_pending(ssl))
     {
             SSL_read();
Run Code Online (Sandbox Code Playgroud)

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

     while (!poll(pollout))
             ; // Wait until the pipe is ready for a send().
     SSL_write();
Run Code Online (Sandbox Code Playgroud)

您如何知道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可以返回WANT_READ,然后在读取方向上轮询.

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