在我们的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中接收多少数据的正确方法是什么?
答案 0 :(得分: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.
获得此输出后,您确切地知道从网络中读取多少内容。
答案 1 :(得分:1)
我发现了问题。
我找到了这个样本:
http://www.codeproject.com/KB/IP/sslsocket.aspx
我错过了对SECBUFFER_EXTRA的处理(第987行SslSocket.cpp)
答案 2 :(得分:1)
当读取的数据不足时,SChannel SSP会从SEC_E_INCOMPLETE_MESSAGE
和InitializeSecurityContext
返回DecryptMessage
。
从SECBUFFER_MISSING
返回DecryptMessage
消息类型,其中cbBuffer
值为所需字节数。
但实际上,我没有使用“缺失数据”值。文档表明该值不能保证是正确的,只是开发人员可以用来减少调用的提示。
InitalizeSecurityContext MSDN doc:
虽然这个数字并不总是准确的,但使用它可以避免多次调用此功能,从而有助于提高性能。
每当返回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个用于应用程序数据),然后是第三个应用程序数据有效负载。
答案 3 :(得分:1)
您第一次必须读取任意数量,当您收到 SEC_E_INCOMPLETE_MESSAGE
时,您必须在 pInput SecBufferDesc
中查找 SECBUFFER_MISSING
并读取其 cbBuffer
以找到找出您丢失了多少字节。
今天这个问题困扰着我,因为我试图自己修改我的握手方式, 遇到了其他评论者遇到的同样问题,即找不到 SECBUFFER_MISSING
。我不想想自己解释 tls 数据包,而且我不想想无条件地读取一些未指定数量的字节。我找到了解决方案,所以我也将解决他们的意见。
这里的混乱是因为 API 令人困惑。通常,要读取 InitializeSecurityContext
的输出,请查看 pOutput
参数的内容(如 the signature 中所定义)。 SecBufferDesc
包含要传递给 SECBUFFER_TOKEN
的 AcceptSecurityContext
等。
但是,在 InitializeSecurityContext
返回 SEC_E_INCOMPLETE_MESSAGE
的情况下,SECBUFFER_MISSING
在 pInput SecBufferDesc
中返回,代替传入的 SECBUFFER_ALERT SecBuffer
.
文档确实说明了这一点,但没有将这种情况与 SEC_I_CONTINUE_NEEDED
和 SEC_E_OK
情况形成鲜明对比。
这个答案也适用于AcceptSecurityContext
。
答案 4 :(得分: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
不为空,则将其内容发送到服务器