2

在我们的 Windows C++ 应用程序中,我使用 InitializeSecurityContext() 客户端打开到运行 stunnel SSL 代理的服务器的 schannel 连接。我的代码现在可以工作,但只有我想消除的黑客。

我从这个示例代码开始:http://msdn.microsoft.com/en-us/library/aa380536%28v=VS.85%29.aspx

在示例代码中,查看 SendMsg 和 ReceiveMsg。发送或接收的任何消息的前 4 个字节表示消息长度。这对于示例来说很好,其中示例的服务器部分符合相同的约定。

stunnel 似乎没有使用这个约定。当客户端在握手期间接收数据时,它如何知道何时停止接收并再次调用 InitializeSecurityContext()?

根据我从文档中收集到的信息,这就是我构建代码的方式:

1. call InitializeSecurityContext which returns an output buffer
2. Send output buffer to server
3. Receive response from server
4. call InitializeSecurityContext(server_response) which returns an output buffer
5. if SEC_E_INCOMPLETE_MESSAGE, go back to step 3, 
   if SEC_I_CONTINUE_NEEDED go back to step 2

如果在第 3 步中没有从服务器读取足够的数据,我希望第 4 步中的 InitializeSecurityContext 返回 SEC_E_INCOMPLETE_MESSAGE。相反,我得到 SEC_I_CONTINUE_NEEDED 但一个空的输出缓冲区。我已经尝试了几种处理这种情况的方法(例如返回第 3 步),但似乎没有一种方法有效,更重要的是,我没有看到这种行为被记录在案。

在第 3 步中,如果我添加一个在超时到期之前接收数据的循环,那么在我的测试环境中一切正常。但必须有更可靠的方法。

知道在第 3 步中接收多少数据的正确方法是什么?

4

5 回答 5

4

SChannel 不同于 Negotiate 安全包。您需要接收至少 5 个字节,即 SSL/TLS 记录头大小:

struct {
    ContentType type;
    ProtocolVersion version;
    uint16 length;
    opaque fragment[TLSPlaintext.length];
} TLSPlaintext;

ContentType 是 1 字节,ProtocolVersion 是 2 字节,你有 2 字节的记录长度。一旦你读取了这 5 个字节,SChannel 将返回 SEC_E_INCOMPLETE_MESSAGE 并准确地告诉你还需要多少字节:

SEC_E_INCOMPLETE_MESSAGE
Data for the whole message was not read from the wire.

When this value is returned, the pInput buffer contains a SecBuffer structure with a BufferType member of SECBUFFER_MISSING. The cbBuffer member of SecBuffer contains a value that indicates the number of additional bytes that the function must read from the client before this function succeeds. 

一旦你得到这个输出,你就知道要从网络中读取多少。

于 2011-07-26T15:34:26.310 回答
1

我发现了问题。

我找到了这个样本:

http://www.codeproject.com/KB/IP/sslsocket.aspx

我错过了对 SECBUFFER_EXTRA 的处理(第 987 行 SslSocket.cpp)

于 2011-07-27T12:47:28.817 回答
1

当没有足够的数据被读取时,SChannel SSPSEC_E_INCOMPLETE_MESSAGE从两者返回。InitializeSecurityContextDecryptMessage

SECBUFFER_MISSING消息类型从所需字节数量的值DecryptMessage返回。cbBuffer

但在实践中,我没有使用“缺失数据”值。文档表明该值不能保证正确,并且只是开发人员可以用来减少调用的提示。

InitalizeSecurityContext MSDN 文档

虽然这个数字并不总是准确的,但使用它可以通过避免多次调用此函数来帮助提高性能。

SEC_E_INCOMPLETE_MESSAGE因此,每当返回时,我都会无条件地将更多数据读入同一个缓冲区。从套接字一次读取多个字节。

需要一些额外的输入缓冲区管理来附加更多读取数据并保持长度正确。DecryptMessage失败时会修改输入缓冲区的cbBuffer属性,这让我感到惊讶。

调用后打印出缓冲区并返回结果InitializeSecurityContext如下:

read socket:bytes(5).
InitializeSecurityContext:result(80090318). // SEC_E_INCOMPLETE_MESSAGE
inBuffers[0]:type(2),bytes(5).
inBuffers[1]:type(0),bytes(0).   // no indication of missing data
outBuffer[0]:type(2),bytes(0).

read socket:bytes(74).
InitializeSecurityContext:result(00090312). // SEC_I_CONTINUE_NEEDED
inBuffers[0]:type(2),bytes(79).  // notice 74 + 5 from before
inBuffers[1]:type(0),bytes(0).
outBuffer[0]:type(2),bytes(0).

对于DecryptMessage函数,输入始终为dataBuf[0],其余为零。

read socket:bytes(5).
DecryptMessage:len 5, bytes(17030201). // SEC_E_INCOMPLETE_MESSAGE
DecryptMessage:dataBuf[0].BufferType 4, 8  // notice input buffer modified
DecryptMessage:dataBuf[1].BufferType 4, 8
DecryptMessage:dataBuf[2].BufferType 0, 0
DecryptMessage:dataBuf[3].BufferType 0, 0

read socket:bytes(8).
DecryptMessage:len 13, bytes(17030201). // SEC_E_INCOMPLETE_MESSAGE
DecryptMessage:dataBuf[0].BufferType 4, 256
DecryptMessage:dataBuf[1].BufferType 4, 256
DecryptMessage:dataBuf[2].BufferType 0, 0
DecryptMessage:dataBuf[3].BufferType 0, 0

read socket:bytes(256).
DecryptMessage:len 269, bytes(17030201). // SEC_E_OK

我们可以看到我的 TLS 服务器对等方在一个数据包中发送 TLS 标头(5 个字节),然后是 TLS 消息(8 个用于应用程序数据),然后是第三个数据包中的应用程序数据有效负载。

于 2016-05-10T14:49:31.847 回答
1

您必须在第一次读取一些任意数量,当您收到 时SEC_E_INCOMPLETE_MESSAGE,您必须查看pInput SecBufferDescfor aSECBUFFER_MISSING并读取它cbBuffer以找出您丢失了多少字节。

这个问题在今天引起了我的注意,因为我试图自己修改我的握手,并且遇到其他评论者遇到的同样问题,即没有找到SECBUFFER_MISSING. 我不想自己解释 tls 数据包,也不想无条件地读取一些未指定的字节数。我找到了解决方案,所以我也将解决他们的意见。

这里的混乱是因为 API 令人困惑。通常,要读取 的输出InitializeSecurityContext,您需要查看参数的内容(在签名pOutput中定义)。它包含要传递给的etc 。 SecBufferDescSECBUFFER_TOKENAcceptSecurityContext

InitializeSecurityContext但是,在返回的情况下,将在,中返回SEC_E_INCOMPLETE_MESSAGE,而不是传入的 。SECBUFFER_MISSINGpInput SecBufferDescSECBUFFER_ALERT SecBuffer

文档确实这么说,但并没有明确将这种情况与SEC_I_CONTINUE_NEEDEDandSEC_E_OK情况进行对比。

这个答案适用于AcceptSecurityContext.

于 2021-07-15T22:59:12.877 回答
0

从 MSDN 中,我假设SEC_E_INCOMPLETE_MESSAGE目前没有从服务器接收到足够的数据时返回。相反,SEC_I_CONTINUE_NEEDED返回InBuffers[1]指示未读数据量(请注意,某些数据已处理并且必须跳过)并且OutBuffers不包含任何内容。

所以算法是:

  • 如果SEC_I_CONTINUE_NEEDED返回,请检查类型InBuffers[1]
  • 如果是SECBUFFER_EXTRA,则处理它(将InBuffers[1].cbBuffer字节移动到输入缓冲区的开头)并跳转到下recv & InitializeSecurityContext一次迭代
  • 如果OutBuffers不为空,则将其内容发送到服务器
于 2019-09-30T13:45:56.040 回答