12.3.1 安全连接.htm
来自「Windows2000后台服务程序开发手册」· HTM 代码 · 共 352 行 · 第 1/5 页
HTM
352 行
INCOMPLETE_CREDENTIALS的回传值得知这个请求。</FONT></P>
<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>实际状况中之客户端应用程序执行时,客户端可能不会搜寻凭证以及呼叫AcquireCredentialsHandle函数,直到它得知服务器要求彼此验证为止。我所列的范例函数皆假定使用客户端凭证的可能性,主要是简化范例之复杂逻辑的方法。</FONT></P>
<HR style="LINE-HEIGHT: 25px">
<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的细节使得情况更加复杂!然而,一旦您的验证程序完成后,通讯的一端或两端皆会持有另一端的凭证资讯。您可以经由呼叫QueryContextAttributes函数,使用完整的内容并传递SECPKG_ATTR_REMOTE_CERT_CONTEXT值及指标到PCCERT_CONTEXT结构中,以撷取凭证资讯。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>当您用完从SSL环境中撷取的凭证内容时,释放凭证内容是您的责任。可以使用CertFreeCertificateContext函数达成。</FONT></P>
<HR style="LINE-HEIGHT: 25px">
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>在您撷取凭证内容后,可以使用前面章节中讨论的CryptoAPI函数从远端原则的凭证中取出资讯。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>以下所列之程序代码范例显示撷取凭证资讯的方法。此程序代码假定一个完整的内容,并且以存放在szNameBuf缓冲器中的远端凭证名称作为结束。</FONT></P>
<DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT
style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">// 取得服务器的凭证 <BR>PCCERT_CONTEXT pCertContext = NULL; <BR>ss = QueryContextAttributes(&hContext, SECPKG_ATTR_REMOTE_CERT_CONTEXT, <BR> (PVOID)&pCertContext); <BR>if(ss != SEC_E_OK){ <BR> // 错误 <BR>} <BR>// 找到我们从凭证解码的区块大小 <BR>ULONG lSize = 0; <BR>CryptDecodeObject( <BR> X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, <BR> X509_NAME, <BR> pCertContext->pCertInfo->Subject.pbData, // Data to decode <BR> pCertContext->pCertInfo->Subject.cbData, // Size of data <BR> 0, NULL, &lSize); <BR>// 分配内存给区块 <BR>CERT_NAME_INFO* pcertNameInfo = (CERT_NAME_INFO*)alloca(lSize); <BR>// 实际从凭证中将主要区块解码 <BR>if(!CryptDecodeObject( <BR> X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, <BR> X509_NAME, <BR> pCertContext->pCertInfo->Subject.pbData, // Data to decode <BR> pCertContext->pCertInfo->Subject.cbData, // Size of data <BR> 0, pcertNameInfo, &lSize)){ <BR> // 错误 <BR>} <BR>// 查询凭证的常见名称属性 <BR>PCERT_RDN_ATTR pcertRDNAttr = <BR> CertFindRDNAttr(szOID_COMMON_NAME, pcertNameInfo); <BR>if(pcertRDNAttr == NULL){ <BR> // 错误 <BR>} <BR>// 将CERT_RDN_ATTR结构中的资讯转换成为字串以便在您的应用程序中使用 <BR>TCHAR szNameBuf[1024]; <BR>if(CertRDNValueToStr(CERT_RDN_PRINTABLE_STRING, <BR> &pcertRDNAttr->Value, szNameBuf, 1024) == 0){ <BR> // 错误 <BR>}</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e74d7
size=3><B style="LINE-HEIGHT: 25px">讯息的加密及解密<BR
style="LINE-HEIGHT: 25px"> </B></FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>如同您所猜想的,由于SSL的资料流导向方法,使得加密过程也有点复杂。然而,一旦您熟悉从一个接收函数组的末端接收附加资料的方法后,加密及解密即是可达成的任务。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>以下的两个函数说明了传送及接收加密讯息的方法。第一个函数为SendEncryptedMessage:</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 SendEncryptedMessage(CtxtHandle*phContext, <BR> PVOID pvData, ULONG lSize){ <BR> BOOL fSuccess = FALSE; <BR> __try <BR> { <BR> SecPkgContext_StreamSizes Sizes; <BR> // 取得资料流资料的属性 <BR> SECURITY_STATUS ss = <BR> QueryContextAttributes( <BR> phContext, <BR> SECPKG_ATTR_STREAM_SIZES, <BR> &Sizes); <BR> if (ss != SEC_E_OK){ <BR> __leave; <BR> } <BR> // 能够处理这么多的资料吗? <BR> if (lSize > Sizes.cbMaximumMessage){ <BR> __leave; <BR> } <BR> // 这个缓冲器将成为标头,加上讯息及档尾的部份 <BR> ULONG lIOBufferLength = Sizes.cbHeader + <BR> Sizes.cbMaximumMessage + <BR> Sizes.cbTrailer; <BR> PBYTE pbIOBuffer = (PBYTE)alloca(lIOBufferLength); <BR> if (pbIOBuffer == NULL){ <BR> __leave; <BR> } <BR> // 这个资料在标头之后复制到缓冲器中 <BR> CopyMemory(pbIOBuffer+Sizes.cbHeader, (PBYTE)pvData, lSize); <BR> SecBuffer Buffers[3]; <BR> // 设定缓冲器中的标头 <BR> Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; <BR> Buffers[0].pvBuffer = pbIOBuffer; <BR> Buffers[0].cbBuffer = Sizes.cbHeader; <BR> // 设定缓冲器中的资料 <BR> Buffers[1].BufferType = SECBUFFER_DATA; <BR> Buffers[1].pvBuffer = pbIOBuffer + Sizes.cbHeader; <BR> Buffers[1].cbBuffer = lSize; <BR> // 设定缓冲器中的档尾 <BR> Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; <BR> Buffers[2].pvBuffer = pbIOBuffer + Sizes.cbHeader + lSize; <BR> Buffers[2].cbBuffer = Sizes.cbTrailer; <BR> // 设定缓冲器描述项 <BR> SecBufferDesc secBufDescIn; <BR> secBufDescIn.ulVersion = SECBUFFER_VERSION; <BR> secBufDescIn.cBuffers = 3; <BR> secBufDescIn.pBuffers = Buffers; <BR> // 将资料加密 <BR> ss = EncryptMessage(phContext, 0, &secBufDescIn, 0); <BR> if (ss != SEC_E_OK){ <BR> __leave; <BR> } <BR> // 在一个区块(Chunk)中传送叁个缓冲器 <BR> ULONG lOut = Buffers[0].cbBuffer + Buffers[1].cbBuffer + <BR> Buffers[2].cbBuffer; <BR> SendData(pbIOBuffer, lOut); <BR> fSuccess = TRUE; <BR> }__finally{} <BR> return (fSuccess); <BR>} <BR></程序代码> <BR>以下为GetEncryptedMessage函数的内容: <BR><程序代码> <BR>PVOID GetEncryptedMessage( <BR> CtxtHandle* phContext, <BR> PULONG plSize, <BR> PBYTE* ppbExtraData, <BR> PULONG pcbExtraData, <BR> ULONG lSizeExtraDataBuf, <BR> BOOL* pfReneg){ <BR> PVOID pDecrypMsg = NULL; <BR> *pfReneg = FALSE; <BR> __try <BR> { <BR> // 宣告缓冲器描述项 <BR> SecBufferDesc SecBuffDesc = {0}; <BR> // 宣告缓冲器 <BR> SecBuffer Buffers[4] == {0}; <BR> // 附加到达的资料 <BR> PBYTE pbData = *ppbExtraData; <BR> ULONG cbData = *pcbExtraData; <BR> SECURITY_STATUS ss = SEC_E_INCOMPLETE_MESSAGE; <BR> do <BR> { <BR> // 设定指向附加资料的初始资料缓冲器 <BR> Buffers[0].BufferType = SECBUFFER_DATA; <BR> Buffers[0].pvBuffer = pbData; <BR> Buffers[0].cbBuffer = cbData; <BR> // 设定叁个空的输出缓冲器 <BR> Buffers[1].BufferType = SECBUFFER_EMPTY; <BR> Buffers[2].BufferType = SECBUFFER_EMPTY; <BR> Buffers[3].BufferType = SECBUFFER_EMPTY; <BR> // 设定描述项 <BR> SecBuffDesc.ulVersion = SECBUFFER_VERSION; <BR> SecBuffDesc.cBuffers = 4; <BR> SecBuffDesc.pBuffers = Buffers; <BR> // 假如真的有任何资料存在,试着将资料解密 <BR> if (cbData) <BR> ss = DecryptMessage(phContext, &SecBuffDesc, 0, NULL); <BR> if (ss == SEC_E_INCOMPLETE_MESSAGE || cbData == 0){ <BR> ULONG lReadSize; <BR> // 必须读进更多资料并且再试一次 <BR> lReadSize = lSizeExtraDataBuf - cbData; <BR> ReceiveData(pbData+cbData, &lReadSize); <BR> cbData += lReadSize; <BR> } <BR> }while (ss == SEC_E_INCOMPLETE_MESSAGE); <BR> // 解密成功了吗? <BR> if (ss == SEC_E_OK){ <BR> // 分配缓冲器给呼叫者并且复制解密后的讯息给它 <BR> *plSize = Buffers[1].cbBuffer; <BR> pDecrypMsg = (PVOID)LocalAlloc(0, *plSize); <BR> if (pDecrypMsg == NULL){ <BR> __leave; <BR> } <BR> CopyMemory(pDecrypMsg,Buffers[1].pvBuffer,*plSize); <BR> // 假如有附加资料的话,把它移到缓冲器的开始处,然后再设定附 <BR> // 加资料传回的大小值 <BR> int nIndex = 3; <BR> while(Buffers[nIndex].BufferType <BR> != SECBUFFER_EXTRA && (nIndex-- != 0)); <BR> if (nIndex != -1){ <BR> // 有更多资料要处理。把它移到附加资料缓冲器的前端, <BR> // 呼叫者可以处理解密后的讯息然后再返回完成其馀的动 <BR> //作 <BR> *pcbExtraData = Buffers[nIndex].cbBuffer; <BR> MoveMemory(pbData, pbData + (cbData - *pcbExtraData), <BR> *pcbExtraData); <BR> } <BR> } <BR> if (ss == SEC_I_RENEGOTIATE){ <BR> *pfReneg = TRUE; <BR> } <BR> }__finally{} <BR> // 传回解密后的讯息 <BR> return (pDecrypMsg); <BR>}</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>从这个程序代码中可以看到,这些函数除了必须注意附加资料的可能性以外,其逻辑与本章其他的范例函数类似。反之,传送函数不用担心附加资料的部份,其接收或解密函数必须取得具有潜在附加资料的缓冲器,并能够传回这个相同缓冲器里的附加资料。这个接收动作可以与它之前、之后,或两者的接收动作重叠。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>SSL合并了加密及讯息签章的概念,所以不必为SSL实作MakeSignature及VerifySignature函数的部份。Schannel安全性套件不支援这个函数。您可能会注意到GetEncryptedMessage范例函数中测试了SEC_I_RENEGOTIATE的传回值,假如这个值是由DecryptMessage函数传回时,它会填入一个为TRUE值的Boolean变数。此回传值指出服务器要求重新协商凭证,而且通常表示它想把匿名的客户端验证提升为具有凭证的完整客户端验证。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>当DecryptMessage传回SEC_I_RENEGOTIATE值时,表示没有解密过的讯息,而且任何输出缓冲器中不会有解密资料存在。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e74d7
size=3><B style="LINE-HEIGHT: 25px">重新协商的处理<BR
style="LINE-HEIGHT: 25px"> </B></FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>假如客户端对一个被保护的资源提出请求,此时您的服务器可能会想要把匿名的客户端验证提升为具有凭证的验证。如此一来,您的服务器就必须初始一个重新协商的程序。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>通常SSL的通讯中,客户端及服务器会经由传送及接收加密的资料缓冲器而互相通讯。然而,当服务器想要初始一个重新协商的程序而非加密及传送资讯时,服务器便会呼叫AcceptSecurityContext函数。在这种情况下,服务器没有传递输入缓冲器到函数中,而且函数会传回应该传送到客户端的Blob值。客户端会了解此Blob表示它应该开始一个重新协商的程序。以下的范例函数显示服务器实作这个程序代码的方法:</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 SSLServerInitReneg( <BR> CredHandle* phCredentials, <BR> CtxtHandle* phContext) <BR>{ <BR> BOOL fSuccess = FALSE; <BR> __try <BR> { <BR> // 宣告输出缓冲器 <BR> SecBuffer secBufferOut; <BR> SecBufferDesc secBufDescriptorOut; <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> ULONG lAttrOut = 0; <BR> SECURITY_STATUS ss = <BR> AcceptSecurityContext( <BR> phCredentials, <BR> phContext, <BR> NULL, <BR> ASC_REQ_ALLOCATE_MEMORY|ASC_REQ_STREAM|ASC_REQ_MUTUAL_AUTH, <BR> SECURITY_NATIVE_DREP, <BR> phContext, <BR> &secBufDescriptorOut, <BR> &lAttrOut, <BR> NULL); <BR> if (ss != SEC_E_OK) <BR> __leave; <BR> // 有资料要传送吗? <BR> if (secBufferOut.cbBuffer!=0){ <BR> // 传送它 <BR> ULONG lOut = secBufferOut.cbBuffer; <BR> SendData(secBufferOut.pvBuffer, lOut); <BR> // 然后释放输出缓冲器 <BR> FreeContextBuffer(secBufferOut.pvBuffer ); <BR> } <BR> fSuccess = TRUE; <BR> }__finally{} <BR> return (fSuccess); <BR>}</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>如同我们在DecryptMessage函数讨论中提到的,当DecryptMessage传回SEC_I_RENEGOTIATE值时,客户端首先会知道服务器要求重新协商的程序。这个时候,假如客户端同意重新协商,则它应该带着适当的凭证handle进入验证回圈的程序中。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>服务器也应该在要求重新协商后进入自己的重新协商验证程序。服务器的责任即是检验客户端是否已经提供适当的凭证。客户端很有可能只重新传送它的匿名凭证。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>以下列出的程序代码片段表示服务器在重新协商程序中所扮演的角色。</FONT></P>
<DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT
style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">// 服务器需要与客户端重新协商 <BR>if(!SSLServerInitReneg( <BR> &hCredentials, <BR> &hContext)){ <BR> // 错误情况 <BR>} <BR>// 设定验证交握的标记 <BR>dwSSPIFlags = ASC_REQ_SEQUENCE_DETECT | <BR> ASC_REQ_REPLAY_DETECT | <BR> ASC_REQ_CONFIDENTIALITY | <BR> /* 指出要求客户端凭证 */ <BR> ASC_REQ_MUTUAL_AUTH | <BR> ASC_RET_EXTENDED_ERROR; <BR>cbExtra = 0; <BR>// 再进入验证回圈 <BR>ss = SSLServerHandshakeAuth(&hCredentials, <BR> &dwSSPIFlags, &hContext, pIOBuff, &cbExtra, lIOBuffSize); <BR>if (ss == SEC_E_OK){ <BR> // 重新协商成功 <BR>}</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>同样的,以下列出的程序代码片段显示在重新协商程序中客户端所扮演的角色(假设客户端拥有一个重新协商的凭证):</FONT></P>
<DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT
style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">// 照惯例取得解密讯息 <BR>cbMsg = 0; <BR>cbExtra = 0; <BR>pMsg = <BR> GetEncryptedMessage( <BR> &hContext, <BR> &cbMsg, <BR> &pIOBuff, <BR> &cbExtra, <BR> lIOBuffSize, <BR> &fReneg); <BR>// 假如它执行失败,这是重新协商的请求吗? <BR>if ((pMsg == NULL) && fReneg){ <BR> // 如果是,那么带着与凭证相关的凭证handle重新进入验证回圈 <BR> ULONG lSSPIFlags = ISC_REQ_SEQUENCE_DETECT | <BR> ISC_REQ_REPLAY_DETECT | <BR> ISC_REQ_CONFIDENTIALITY | <BR> ISC_RET_EXTENDED_ERROR; <BR> ss = <BR> SSLClientHandshakeAuth( <BR> &hCertCredentials, <BR> &hCertCredentials, <BR> &lSSPIFlags, <BR> &hContext, <BR> TEXT("Jason’s Test Server Certificate on Davemm"), <BR> pIOBuff, &cbExtra, lIOBuffSize); <BR> if (ss == SEC_E_OK){ <BR> // 重新协商成功 <BR> } <BR>}</PRE></FONT></DIV>
<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>如果您的客户端软件必须与要求重新协商的服务器通讯时,无论它什么时候会呼叫DecryptMessage函数,能够让您的客户端优雅地处理重新协商要求是很重要的。</FONT></P>
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?