12.3.1 安全连接.htm
来自「Windows2000后台服务程序开发手册」· HTM 代码 · 共 352 行 · 第 1/5 页
HTM
352 行
<HR style="LINE-HEIGHT: 25px">
<P><FONT style="LINE-HEIGHT: 25px" face=Arial color=#3e77d7 size=3
Black><B style="LINE-HEIGHT: 25px">说明</B></FONT> </P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>尽管SSL预期传送及接收逐字的资料,但是没有任何事情可以阻止您对「中继通讯协定(meta
protocol)」的实作,以指出Blob大小。这样做可以大大地简化您的SSL程序代码,虽然技术上您不需采用HTTPS规格。假如您要跨越线路传递附加的资料,您的客户端或服务器大概不能与实作HTTPS的客户端或服务器通讯,通常是Web浏览器及Web服务器。</FONT></P>
<HR style="LINE-HEIGHT: 25px">
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>您可能会纳闷如何从收到通讯的大小中得知要读取多少资料,答案是无法得知。SSL通讯使用的是一个资料流。以下的情形说明了SSL在资讯流程上的影响。它显示如何读取资讯到完成讯息为止的情形。</FONT></P><FONT
style="LINE-HEIGHT: 25px" face=arial color=#000000 size=2>
<OL style="LINE-HEIGHT: 25px">
<LI style="LINE-HEIGHT: 25px">一端(如客户端)跨越线路传送未修改的Blob。
<LI
style="LINE-HEIGHT: 25px">接收端读取可用的资讯并将它传递到它的「blob-handling」函数中(在我们的范例中,服务器会传递资料到AcceptSecurityContext函数)。
<LI
style="LINE-HEIGHT: 25px">假如接收端没有从线路读取足够的资讯以完成交易流程,blob-handling函数会记录SEC_E_INCOMPLETE_MESSAGE。
<LI
style="LINE-HEIGHT: 25px">假如未完成的讯息值被传回,您必须储存已经拥有的资料,并回到线路上尽可能地接收附加资料,直到取得除了SEC_E_INCOMPLETE_MESSAGE以外的传回值为止。
</LI></OL></FONT>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>您可能已了解除了接收过少资料的可能性外,SSL的资料流性质产生了复杂化的情形。您的软件可能跨越线路读取了<FONT
style="LINE-HEIGHT: 25px" face=arial color=#3e80d7 size=2><B
style="LINE-HEIGHT: 25px"> 过多的 </B></FONT>资讯。在这个情形下,<FONT
style="LINE-HEIGHT: 25px" face=arial color=#3e80d7 size=2><B
style="LINE-HEIGHT: 25px"> AcceptSecurityContext </B></FONT>或<FONT
style="LINE-HEIGHT: 25px" face=arial color=#3e80d7 size=2><B
style="LINE-HEIGHT: 25px"> InitializeSecurityContext </B></FONT>函数会记录您已为将来使用所传递之必须储存的附加资料(大概在跨越线路读取更多资料之后)。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>如您所见,还有更多的方案等待使用SSL。以下是两种必须随时察觉的情况:</FONT></P><FONT
style="LINE-HEIGHT: 25px" face=arial color=#000000 size=2>
<OL style="LINE-HEIGHT: 25px">
<LI style="LINE-HEIGHT: 25px">您的SSPI函数处理之资讯太少,所以必须接收更多资料。
<LI
style="LINE-HEIGHT: 25px">SSPI函数可能有太多资讯,所以必须为将来呼叫SSPI函数而储存资讯<BR
style="LINE-HEIGHT: 25px">A. 您必须从线路上取得更多资讯。<BR
style="LINE-HEIGHT: 25px">B. 您不必从线路上取得更多资讯。 </LI></OL></FONT>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>请注意,第二个方案中有两个子方案。方案2A是预期中的,方案2B则可能令人感到惊讶。在两种情况下,AcceptSecurityContext或InitializeSecurityContext函数会记录已经传入的附加资料,而其「持续性」则是必需的。然而在这些情况中,blob-handling函数并不会记录任何跨越线路所传回的Blob资料。所以您必须修改刚刚传入的缓冲器,然后再将它传回blob-handling函数中。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>依照我的看法,这个拥有太多资料的方案最好不要从SSPI显示给应用程序,因为AcceptSecurityContext及InitializeSecurityContext函数皆拥有处理资料所需的全部资讯。这个函数只有在没有足够资料可用来处理完整的讯息时才需要停止执行。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>处理附加资料的问题没有您想像的那么容易解决。请考虑这个情形:您在线路上收到一个Blob,它的内部分成叁个「区段(Section)」,最后一区段是不完整的,所以您必须从线路上取得更多资料。假设您把这个Blob传递到AcceptSecurityContext函数中,它会处理Blob内的前两个区段。当它完成时,发现最后一个区段是不完整的,而且需要更多资料。然而,它并不需要让您的应用程序回到线路上取得更多资料并传送<FONT
style="LINE-HEIGHT: 25px" face=arial color=#3e80d7 size=2><B
style="LINE-HEIGHT: 25px"> 整个Blob </B></FONT>回blob-handling函数,因为它已经处理Blob内的前两区段。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>为了避免这样的情况产生,由系统的设计者决定Blob内的任一节或子节是否应被处理,不管是否有足够的附加资料可以传递给更多的程序,
AcceptSecurityContext及InitializeSecurityContext函数皆会返回。这样一来您便可以察觉更多的附加资料、调整您的缓冲器并重新呼叫函数。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>因为附加资料的环境很复杂,而且可能会有读取太少对SSPI有用之资料的情形,所以最好对所有与客户端的通讯分配及使用单一的缓冲器。这个缓冲器应该大到足够持有用SSL协定跨越线路所传输的最大单一讯息。要找出这个大小,您应该在开始通讯之前呼叫QuerySecurityPackageInfo函数:</FONT></P>
<DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT
style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">SECURITY_STATUS QuerySecurityPackageInfo( <BR> SEC_CHAR *pszPackageName, <BR> PSecPkgInfo *ppPackageInfo);</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>此函数传回包含SecPkgInfo结构的缓冲器,当您使用完时,必须使用FreeContextBuffer函数来释放它:</FONT></P>
<DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT
style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">typedef struct _SecPkgInfo { <BR> ULONG fCapabilities; // 套件的功能标记 <BR> USHORT wVersion; // 驱动程序版本 <BR> USHORT wRPCID; // 由系统使用 <BR> ULONG cbMaxToken; // 封包的最大讯息大小 <BR> SEC_CHAR *Name; // 文字名称 <BR> SEC_CHAR *Comment; // 注释 <BR>} SecPkgInfo;</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>如您所见,QuerySecurityPackageInfo函数传回令人关注的资讯到SecPkgInfo结构中。然而,通常您只需要使用cbMaxToken值指出使用SSL协商验证时应该使用的最小缓冲器。在我们先前对SSPI的讨论中,我实作了用来在客户端及服务器之间执行验证交握的函数。以下的程序代码显示了执行类似SSL工作的程序代码。您将会注意到,这些函数取得了一个指向缓冲器的指标及该缓冲器的大小。它们也取得一个指出有多少资料已经存在缓冲器里的参数。这些参数提供给SSL的附加资料方案。以下是SSLServerHandshakeAuth函数原型:</FONT></P>
<DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT
style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">BOOL SSLServerHandshakeAuth( <BR> CredHandle* phCredentials, <BR> PULONG plAttributes, <BR> PCtxtHandle phContext, <BR> PBYTE pbExtraData, <BR> PULONG pcbExtraData, <BR> ULONG lSizeExtraDataBuf){ <BR> BOOL fSuccess = FALSE; <BR> __try <BR> { <BR> // 就区域变数设定一个缓冲器 <BR> ULONG lEndBufIndex = *pcbExtraData; <BR> ULONG lBufMaxSize = lSizeExtraDataBuf; <BR> PBYTE pbTokenBuf = pbExtraData; <BR> // 宣告输出及输入缓冲器 <BR> SecBuffer secBufferIn[3]={0}; <BR> SecBufferDesc secBufDescriptorIn; <BR> SecBuffer secBufferOut; <BR> SecBufferDesc secBufDescriptorOut; <BR> // 设定回圈状态资讯 <BR> BOOL fFirstPass = TRUE; <BR> SECURITY_STATUS ss = SEC_I_CONTINUE_NEEDED; <BR> while (ss == SEC_I_CONTINUE_NEEDED || <BR> ss == SEC_E_INCOMPLETE_MESSAGE){ <BR> // 每次操作可以读取多少资料 <BR> ULONG lReadBuffSize; <BR> // 重新设定,如果我们不执行一个「不完整的」回圈 <BR> if (ss != SEC_E_INCOMPLETE_MESSAGE){ <BR> // 为另一个「Blob交换」重新设定状态 <BR> lEndBufIndex = 0; <BR> lReadBuffSize = lBufMaxSize; <BR> } <BR> // 从客户端接收Blob资料 <BR> ReceiveData(pbTokenBuf+lEndBufIndex, &lReadBuffSize); <BR> // 这是我们迄今已经读取的部分 <BR> lEndBufIndex += lReadBuffSize; <BR> // 设定我们的输入缓冲器 <BR> secBufferIn[0].BufferType = SECBUFFER_TOKEN; <BR> secBufferIn[0].cbBuffer = lEndBufIndex; <BR> secBufferIn[0].pvBuffer = pbTokenBuf; <BR> // 变成一个SECBUFFER_EXTRA缓冲器的方法, <BR> // 用来让我们知道后来是否含有附加资料 <BR> secBufferIn[1].BufferType = SECBUFFER_EMPTY; <BR> secBufferIn[1].cbBuffer = 0; <BR> secBufferIn[1].pvBuffer = NULL; <BR> // 设定输入缓冲器描述项 <BR> secBufDescriptorIn.ulVersion = SECBUFFER_VERSION; <BR> secBufDescriptorIn.cBuffers = 2; <BR> secBufDescriptorIn.pBuffers = secBufferIn; <BR> // 设定输出缓冲器(由SSPI分配) <BR> secBufferOut.BufferType = SECBUFFER_TOKEN; <BR> secBufferOut.cbBuffer = 0; <BR> secBufferOut.pvBuffer = NULL; <BR> // 设定输出缓冲器描述项 <BR> secBufDescriptorOut.ulVersion = SECBUFFER_VERSION; <BR> secBufDescriptorOut.cBuffers = 1; <BR> secBufDescriptorOut.pBuffers = &secBufferOut; <BR> // 此内部回圈处理那些不要传送Blob资料的「持续情况」。 <BR> // 这种情况下,仍然有很多「区段」存在最后必须处理的Blob <BR> // 项目中。 <BR> BOOL fMoreSections; <BR> // 这个回圈处理所有缓冲器中完整的资料「区段」 <BR> do { <BR> fMoreSections = FALSE; <BR> //Blob程序 <BR> ss = <BR> AcceptSecurityContext( <BR> phCredentials, <BR> fFirstPass ? NULL : phContext, <BR> &secBufDescriptorIn, <BR> *plAttributes| <BR> ISC_REQ_ALLOCATE_MEMORY|ISC_REQ_STREAM, <BR> SECURITY_NATIVE_DREP, <BR> phContext, <BR> &secBufDescriptorOut, <BR> plAttributes, <BR> NULL); <BR> // 有更多「区段」要处理吗? <BR> if ((ss == SEC_I_CONTINUE_NEEDED) && <BR> (secBufferOut.cbBuffer == 0)){ <BR> fMoreSections = TRUE; //Set state to loop <BR> // 有多少资料被留下 <BR> ULONG lExtraData = secBufferIn[1].cbBuffer; <BR> // 让我们移动资料回到缓冲器的开始处 <BR> MoveMemory(pbTokenBuf, <BR> pbTokenBuf+(lEndBufIndex - lExtraData), lExtraData); <BR> // 我们的缓冲器中有多少资料 <BR> lEndBufIndex = lExtraData; <BR> // 重新设定输入缓冲器 <BR> secBufferIn[0].BufferType = SECBUFFER_TOKEN; <BR> secBufferIn[0].cbBuffer = lEndBufIndex; <BR> secBufferIn[0].pvBuffer = pbTokenBuf; <BR> secBufferIn[1].BufferType = SECBUFFER_EMPTY; <BR> secBufferIn[1].cbBuffer = 0; <BR> secBufferIn[1].pvBuffer = NULL; <BR> } <BR> }while(fMoreSections); <BR> // 下一次从线路上可以读取多少资料进来而不会超出缓冲器范围 <BR> lReadBuffSize = lBufMaxSize - lEndBufIndex; <BR> if (ss != SEC_E_INCOMPLETE_MESSAGE){ <BR> // 不再是第一次传递 <BR> fFirstPass = FALSE; <BR> } <BR> // 有资料要传送吗? <BR> if (secBufferOut.cbBuffer != 0){ <BR> // 传送它 <BR> ULONG lOut = secBufferOut.cbBuffer; <BR> SendData(secBufferOut.pvBuffer, lOut); <BR> // 然后释放缓冲器 <BR> FreeContextBuffer(secBufferOut.pvBuffer); <BR> } <BR> } <BR> if (ss == SEC_E_OK){ <BR> // 假如有附加资料,这是被加密的应用层资料。我们将放入缓冲器, <BR> // 而应用层之后可以使用DecryptMessage解密 <BR> int nIndex = 1; <BR> while(secBufferIn[nIndex].BufferType <BR> != SECBUFFER_EXTRA && (nIndex-- != 0)); <BR> if ((nIndex != -1) && (secBufferIn[nIndex].cbBuffer != 0)){ <BR> *pcbExtraData = secBufferIn[nIndex].cbBuffer; <BR> PBYTE pbTempBuf = pbTokenBuf; <BR> pbTempBuf += (lEndBufIndex - *pcbExtraData); <BR> MoveMemory(pbExtraData, pbTempBuf, *pcbExtraData); <BR> } <BR> fSuccess = TRUE; <BR> } <BR> }__finally{} <BR> return (fSuccess); <BR>}</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>以下是SSLClientHandshakeAuth函数的定义:</FONT></P>
<DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT
style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">BOOL SSLClientHandshakeAuth( <BR> CredHandle* phCredentials, <BR> CredHandle* phCertCredentials, <BR> PULONG plAttributes, <BR> CtxtHandle* phContext, <BR> PTSTR pszServer, <BR> PBYTE pbExtraData, <BR> PULONG pcbExtraData, <BR> ULONG lSizeExtraDataBuf) <BR>{ <BR> BOOL fSuccess = FALSE; <BR> __try <BR> { <BR> // 设定我们自己的凭证handle副本 <BR> CredHandle credsUse; <BR> CopyMemory(&credsUse, phCredentials, sizeof(credsUse)); <BR> // 就区域变数的可读性设定缓冲器 <BR> ULONG lEndBufIndex = *pcbExtraData; <BR> ULONG lBufMaxSize = lSizeExtraDataBuf; <BR> PBYTE pbData = pbExtraData; <BR> // 宣告输入及输出缓冲器 <BR> SecBuffer secBufferOut; <BR> SecBufferDesc secBufDescriptorOut; <BR> SecBuffer secBufferIn[2]; <BR> SecBufferDesc secBufDescriptorIn; <BR> // 设定回圈状态资讯 <BR> BOOL fFirstPass = TRUE; <BR> SECURITY_STATUS ss = SEC_I_CONTINUE_NEEDED; <BR> while ((ss == SEC_I_CONTINUE_NEEDED) || <BR> (ss == SEC_E_INCOMPLETE_MESSAGE)){ <BR> // 每次操作可以读取多少资料 <BR> ULONG lReadBuffSize; <BR> // 重新设定,假如我们不执行「不完整的」回圈 <BR> if (ss !=SEC_E_INCOMPLETE_MESSAGE){ <BR> // 为另一个Blob交换重新设定状态 <BR> lEndBufIndex = 0; <BR> lReadBuffSize = lBufMaxSize; <BR> } <BR> // 有些东西我们只有在第一次传递后执行 <BR> if (!fFirstPass){ <BR> // 尽可能接收资料 <BR> ReceiveData(pbData+lEndBufIndex, &lReadBuffSize); <BR> // 这是我们目前为止拥有的资料 <BR> lEndBufIndex += lReadBuffSize; <BR> // 用我们当前的资料设定里面的缓冲器 <BR> secBufferIn[0].BufferType = SECBUFFER_TOKEN; <BR> secBufferIn[0].cbBuffer = lEndBufIndex; <BR> secBufferIn[0].pvBuffer = pbData; <BR> // 变成一个SECBUFFER_EXTRA缓冲器,用来让 <BR> // 我们知道其后是否有附加资料 <BR> secBufferIn[1].BufferType = SECBUFFER_EMPTY; <BR> secBufferIn[1].cbBuffer = 0; <BR> secBufferIn[1].pvBuffer = NULL; <BR> // 设定输入缓冲器描述项 <BR> secBufDescriptorIn.cBuffers = 2; <BR> secBufDescriptorIn.pBuffers = secBufferIn; <BR> secBufDescriptorIn.ulVersion = SECBUFFER_VERSION; <BR> } <BR> // 设定输出缓冲器(由SSPI分配) <BR> secBufferOut.BufferType = SECBUFFER_TOKEN; <BR> secBufferOut.cbBuffer = 0; <BR> secBufferOut.pvBuffer = NULL; <BR> // 设定输出缓冲器描述项 <BR> secBufDescriptorOut.cBuffers = 1; <BR> secBufDescriptorOut.pBuffers = &secBufferOut; <BR> secBufDescriptorOut.ulVersion = SECBUFFER_VERSION; <BR> // 这个内部回圈处理那些没有要传送Blob资料的「持续情况」。 <BR> // 这种情况下,仍然有更多「区段」在我们最后必须处理的Blob <BR> // 项目。 <BR> BOOL fNoOutBuffer; <BR> do { <BR> fNoOutBuffer = FALSE; <BR> // Blob处理程序 <BR> ss = <BR> InitializeSecurityContext( <BR> &credsUse, <BR> fFirstPass ? NULL : phContext, <BR> fFirstPass ? pszServer : NULL, <BR> *plAttributes| <BR> ISC_REQ_ALLOCATE_MEMORY|ISC_REQ_STREAM, <BR> 0, <BR> SECURITY_NATIVE_DREP, <BR> fFirstPass ? NULL : &secBufDescriptorIn, <BR> 0, <BR> phContext, <BR> &secBufDescriptorOut, <BR> plAttributes, <BR> NULL); <BR> // 有更多「区段」要处理吗? <BR> if ((ss == SEC_I_CONTINUE_NEEDED) && <BR> (secBufferOut.cbBuffer == 0)){ <BR> fNoOutBuffer = TRUE; // 设定回圈状态 <BR> // 有多少资料被留下 <BR> ULONG lExtraData = secBufferIn[1].cbBuffer; <BR> // 我们要把资料移动回缓冲器的开始处 <BR> MoveMemory(pbData, <BR> pbData+(lEndBufIndex - lExtraData), lExtraData); <BR> // 现在我们有新的lEndBufIndex <BR> lEndBufIndex = lExtraData; <BR> // 让我们重新设定输入缓冲器 <BR> secBufferIn[0].BufferType = SECBUFFER_TOKEN; <BR> secBufferIn[0].cbBuffer = lEndBufIndex; <BR> secBufferIn[0].pvBuffer = pbData; <BR> secBufferIn[1].BufferType = SECBUFFER_EMPTY; <BR> secBufferIn[1].cbBuffer = 0; <BR> secBufferIn[1].pvBuffer = NULL; <BR> } <BR> if (ss == SEC_I_INCOMPLETE_CREDENTIALS){ <BR> // 服务器要求凭证 <BR> // 用凭证复制凭证 <BR> // 通常,我们会在这里呼叫AcquireCredentialsHandle函数 <BR> // 以获得新的凭证。 <BR> // 然而,我们已经在这个范例函数中传入 <BR> // 凭证。 <BR> CopyMemory(&credsUse, phCertCredentials, sizeof(credsUse)); <BR> // 此操作不需输入 <BR> secBufDescriptorIn.cBuffers = 0; <BR> // 继续传输 <BR> fNoOutBuffer = TRUE; // 设定回圈状态 <BR> } <BR> }while(fNoOutBuffer); <BR> // 下一次从线路上可以读取多少资料进来而不会超出缓冲器范围 <BR> lReadBuffSize = lBufMaxSize - lEndBufIndex; <BR> // 有资料要传送吗? <BR> if (secBufferOut.cbBuffer!=0){ <BR> // 传送它 <BR> ULONG lOut = secBufferOut.cbBuffer; <BR> SendData(secBufferOut.pvBuffer, lOut); <BR> // 然后释放输出缓冲器 <BR> FreeContextBuffer(secBufferOut.pvBuffer ); <BR> } <BR> if (ss != SEC_E_INCOMPLETE_MESSAGE){ <BR> fFirstPass = FALSE; <BR> } <BR> } <BR> if(ss == SEC_E_OK){ <BR> int nIndex = 1; <BR> while(secBufferIn[nIndex].BufferType <BR> != SECBUFFER_EXTRA && (nIndex-- != 0)); <BR> if((nIndex !=-1)&&(secBufferIn [nIndex ].cbBuffer !=0)){ <BR> *pcbExtraData = secBufferIn[nIndex].cbBuffer; <BR> PBYTE pbTempBuf = pbData; <BR> pbTempBuf += (lEndBufIndex - *pcbExtraData); <BR> MoveMemory(pbExtraData, pbTempBuf, *pcbExtraData); <BR> } <BR> fSuccess = TRUE; <BR> } <BR> }__finally{} <BR> return (fSuccess); <BR>}</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>这些函数有几个方法与其Kerberos及NTLM副本不同。假如产生这些情形的话,这些函数会检查SEC_E_INCOMPLETE_MESSAGE的传回值,并且持续建立讯息缓冲器。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>这些函数也与其副本不同,客户端函数会取得两个凭证handles。一个是匿名的凭证handle,另一个则是用客户端凭证建立的凭证handle(假如您没有客户端凭证,可以传递匿名handle的位址给他们)。这是因为客户端先提出它的匿名凭证,只有在服务器要求彼此验证时才会交换凭证。客户端会经由SEC_I_
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?