12.2.1 安全连接.htm
来自「Windows2000后台服务程序开发手册」· HTM 代码 · 共 514 行 · 第 1/5 页
HTM
514 行
<TBODY style="LINE-HEIGHT: 25px">
<TR>
<TD align=middle><FONT style="LINE-HEIGHT: 25px" face=arial
color=#000000 size=2><FONT style="LINE-HEIGHT: 25px"
face=arial color=#3e80d7 size=2><B
style="LINE-HEIGHT: 25px"> 图12-5 </B></FONT>SSPI输入及输出缓冲器</FONT></TD></TR></TBODY></TABLE></CENTER>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e72d7
size=4><B style="LINE-HEIGHT: 25px">InitializeSecurityContext及缓冲器<BR
style="LINE-HEIGHT: 25px"> </B></FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>在您学习相关的细节前,试着去了解SSPI处理缓冲器的方式可能会令人困惑,然后事情会变得相当清楚。如同之前所提的,InitializeSecurityContext使用缓冲器输入及输出所传递到服务器而准备的安全性Blobs。在第一次呼叫InitializeSecurityContext的时候,可以传递NULL给pInput参数。然而,当您从服务器接收资料时,应建构缓冲器并把它们传递给函数。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>对于输入及输出的部份,每个对InitializeSecurityContext的呼叫都会取得一个SECBUFFER_TOKEN类型的SecBuffer阵列。这个缓冲器类型向系统指出这个缓冲器建立了一个环境的输入Blob或输出Blob。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>以下所列的程序片段显示您如何建构与InitializeSecurityContext一起使用的缓冲器:</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>SecBuffer secBufferOut[1]; <BR>secBufferOut[0].BufferType = SECBUFFER_TOKEN; <BR>secBufferOut[0].cbBuffer = cbBlockToSend; <BR>secBufferOut[0].pvBuffer = pbBlockToSend; <BR>// 建立「输出」缓冲器的描述项 <BR>SecBufferDesc secBufDescriptorOut; <BR>secBufDescriptorOut.cBuffers = 1; <BR>secBufDescriptorOut.pBuffers = secBufferOut; <BR>secBufDescriptorOut.ulVersion = SECBUFFER_VERSION; <BR>// 建立「输入」缓冲器 <BR>SecBuffer secBufferIn[1]; <BR>secBufferIn[0].BufferType = SECBUFFER_TOKEN; <BR>secBufferIn[0].cbBuffer = cbBlockReceived; <BR>secBufferIn[0].pvBuffer = pbBlockReceived; <BR>// 建立「输入」缓冲器的描述项 <BR>SecBufferDesc secBufDescriptorIn; <BR>secBufDescriptorIn.cBuffers = 1; <BR>secBufDescriptorIn.pBuffers = secBufferIn; <BR>secBufDescriptorIn.ulVersion = SECBUFFER_VERSION;</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>pbBlockReceived及pbBlockToSend缓冲器在呼叫InitializeSecurityContext之前即被分配。您可以选择要函数分配一个区块给您的输出缓冲器。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>在呼叫InitializeSecurityContext后,输出缓冲器的cbBuffer成员会包含被传送到服务器的Blob大小。假如不为0的大小,则您应该从pvBuffer成员所指向的缓冲器中传递与这个值指示之一样多的位元组给服务器。这是验证的「交握(Handshake)」。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>SSPI定义了一些不同的缓冲器类型。当它们与我们的讨论有关时,我将会加以说明。有关现在所定义的缓冲器类型清单,请参阅《Platform
SDK》文件。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e74d7
size=3><B
style="LINE-HEIGHT: 25px">InitializeSecurityContext-把它们全部放在一起<BR
style="LINE-HEIGHT: 25px"> </B></FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>当您在处理SSPI时,必须记住的细节数量之多毫无疑问会使人感到怯步,不过,先看它如何在简单的函数中产生作用将会使处理程序变得较清楚。</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">BOOL ClientHandshakeAuth(CredHandle* phCredentials, <BR> PULONG plAttributes, CtxtHandle* phContext, PTSTR pszServer){ <BR> BOOL fSuccess = FALSE; <BR> __try{ <BR> SECURITY_STATUS ss; <BR> // 宣告输入及输出的缓冲器 <BR> SecBuffer secBufferOut[1]; <BR> SecBufferDesc secBufDescriptorOut; <BR> SecBuffer secBufferIn[1]; <BR> SecBufferDesc secBufDescriptorIn; <BR> // 设定一些「回圈状态」资讯 <BR> BOOL fFirstPass = TRUE; <BR> ss = SEC_I_CONTINUE_NEEDED; <BR> while (ss == SEC_I_CONTINUE_NEEDED ){ <BR> // 在Blob指标里 <BR> PBYTE pbData = NULL; <BR> if(fFirstPass){ // 第一次传递,没有里面的缓冲器 <BR> secBufDescriptorIn.cBuffers = 0; <BR> secBufDescriptorIn.pBuffers = NULL; <BR> secBufDescriptorIn.ulVersion = SECBUFFER_VERSION; <BR> }else{ // 后续的传递 <BR> // 取得Blob的大小 <BR> ULONG lSize; <BR> ULONG lTempSize = sizeof(lSize); <BR> ReceiveData(&lSize, &lTempSize); <BR> // 取得Blob <BR> pbData = (PBYTE)alloca(lSize); <BR> ReceiveData(pbData, &lSize); <BR> // 把「输入缓冲器」指到Blob <BR> secBufferIn[0].BufferType = SECBUFFER_TOKEN; <BR> secBufferIn[0].cbBuffer = lSize; <BR> secBufferIn[0].pvBuffer = pbData; <BR> // 把「输入」BufDesc指到里面的缓冲器 <BR> secBufDescriptorIn.cBuffers = 1; <BR> secBufDescriptorIn.pBuffers = secBufferIn; <BR> secBufDescriptorIn.ulVersion = SECBUFFER_VERSION; <BR> } <BR> // 设定输出缓冲器(SSPI将分配缓冲器给我们) <BR> secBufferOut[0].BufferType = SECBUFFER_TOKEN; <BR> secBufferOut[0].cbBuffer = 0; <BR> secBufferOut[0].pvBuffer = NULL; <BR> // 把「输出」Bufdesc指到外面的缓冲器 <BR> secBufDescriptorOut.cBuffers = 1; <BR> secBufDescriptorOut.pBuffers = secBufferOut; <BR> secBufDescriptorOut.ulVersion = SECBUFFER_VERSION; <BR> ss= <BR> InitializeSecurityContext( <BR> phCredentials, <BR> fFirstPass?NULL:phContext, <BR> pszServer, <BR> *plAttributes | ISC_REQ_ALLOCATE_MEMORY, <BR> 0, SECURITY_NETWORK_DREP, <BR> &secBufDescriptorIn, 0, <BR> phContext, <BR> &secBufDescriptorOut, <BR> plAttributes, NULL); <BR> // 通过回圈表示不再是第一次传递 <BR> fFirstPass = FALSE; <BR> // 是Blob输出吗?假如是则传递它。 <BR> if (secBufferOut[0].cbBuffer!=0){ <BR> // 服务器通讯!!! <BR> // 传递Blob的大小 <BR> SendData(&secBufferOut[0].cbBuffer, sizeof(ULONG)); <BR> // 传送Blob本身 <BR> SendData(secBufferOut[0].pvBuffer, <BR> secBufferOut[0].cbBuffer); <BR> // 释放缓冲器 <BR> FreeContextBuffer(secBufferOut[0].pvBuffer); <BR> } <BR> }// 重覆执行if ss == SEC_I_CONTINUE_NEEDED; <BR> // 最后的结果 <BR> if (ss != SEC_E_OK){ <BR> __leave; <BR> } <BR> fSuccess = TRUE; <BR> }__finally{ <BR> // 假如我们执行失败了,清除环境handle <BR> if (!fSuccess){ <BR> ZeroMemory(phContext, sizeof(*phContext)); <BR> } <BR> } <BR> return (fSuccess); <BR>}</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>ClientHandshakeAuth函数取得从AcquireCredentialsHandle传回的凭证handle、一些标记及服务器名称,以及假如建立环境成功所传回的一个完整环境。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>请注意,ClientHandshakeAuth函数在程序代码中的两个地方与服务器通讯。我用来通讯的函数名称为SendData及ReceiveData的虚构函数。它们取得一个缓冲器及其大小,并且为您的软件所选择的任何通讯机制的替代符号(Placeholders)。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>也请注意到当传输一个Blob时,会在传送Blob前先传送Blob的大小,而当收到一个Blob时,会在接收它之前先读取Blob的大小。</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>因为SSPI是通讯传输独立的,建立一些用来传递Blobs到另一端的原则通讯协定类型对您来说是必要的。</FONT></P>
<HR style="LINE-HEIGHT: 25px">
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>特别注意这个函数中的缓冲器管理,以及当跨越线路传递需要再次循环的Blob及函数时,InitializeSecurityContext会如何传递到我们的软件。这是客户端与SSPI验证交握的职责核心。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>以下的程序代码片段可以用来取得凭证handle及呼叫ClientHandshakeAuth,表示开始使用Kerberos通讯协定验证:</FONT></P>
<DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT
style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">CredHandle hCredentials; <BR>TimeStamp tsExpires; <BR>SECURITY_STATUS ss = AcquireCredentialsHandle(NULL, <BR> MICROSOFT_KERBEROS_NAME, SECPKG_CRED_BOTH, NULL, NULL, <BR> NULL, NULL, &hCredentials, &tsExpires ); <BR>if(ss != SEC_E_OK){ <BR> // 错误 <BR>} <BR>ULONG lAttributes = <BR> ISC_REQ_STREAM|ISC_REQ_CONFIDENTIALITY|ISC_REQ_MUTUAL_AUTH; <BR>CtxtHandle hContext = {0}; <BR>if(!ClientHandshakeAuth(&hCredentials, &lAttributes, <BR> &hContext, TEXT("jclark-piii600"))){ <BR> // 错误 <BR>} <BR> // 假如成功,我们已经在这里验证 <BR>DeleteSecurityContext(&hContext); <BR>FreeCredentialsHandle(&hCredentials);</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>在这个程序代码里,AcquireCredentialsHandle传回了凭证handle,表示呼叫函数的身分识别。然后我们呼叫了我们的范例函数ClientHandshakeAuth,指出我们要使用彼此验证及加密功能,以及我们将使用资料流技术通讯。我们现在要谈论SSPI中有关服务器端的验证部份。在您离开这个主题前,您会发现花些时间复习 </FONT></A><FONT
style="LINE-HEIGHT: 25px" face=arial color=#000000 size=2><A
style="LINE-HEIGHT: 25px"
href="http://www.acejoy.com/doc/serverside/12.htm#123_1"
target=_new>图12-3</A> 及 <A style="LINE-HEIGHT: 25px"
href="http://www.acejoy.com/doc/serverside/12.htm#124_1"
target=_new>12-4</A> 中,说明客户端及服务器端的SSPI会谈内容是有帮助的。请在我们刚刚检查的程序代码片段环境中思考这些图。</FONT>
</P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e72d7
size=4><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>使用SSPI了解客户端在验证交握中的角色,对于了解服务器的角色是有帮助的。事实上,一旦了解其中一端后,另一端就变得非常容易。管理Blobs的服务器函数是AcceptSecurityContext:</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 AcceptSecurityContext( <BR> PcredHandle phCredential, <BR> PCtxtHandle phContext, <BR> PSecBufferDesc pInput, <BR> ULONG lContextReq, <BR> ULONG lTargetDataRep, <BR> PCtxtHandle phNewContext, <BR> PSecBufferDesc pOutput, <BR> PULONG pfContextAttr, <BR> PTimeStamp ptsExpiration);</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>请注意,AcceptSecurityContext拥有与InitializeSecurityContext相同的参数,它没有两个预留的参数及指出服务器名称的参数。当然,它不需要服务器名称,因为它呼叫AcceptSecurityContext的服务器。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>就像它的客户端角色一样,AcceptSecurityContext的角色是接收及产生Blobs。AcceptSecurityContext及InitializeSecurityContext都必须被允许重覆执行,直到它们传回SEC_E_OK为止。在使用AcceptSecurityContext时,有两个值得注意的地方:</FONT></P><FONT
style="LINE-HEIGHT: 25px" face=arial color=#000000 size=2>
<UL style="LINE-HEIGHT: 25px">
<LI
style="LINE-HEIGHT: 25px">在您第一次呼叫AcceptSecurityContext时,您已经从您的客户端收到了第一个Blob,所以总是有一个与这个函数一起使用的输入缓冲器(这和InitializeSecurityContext不同,它的初始传递没有输入缓冲器)。<BR
style="LINE-HEIGHT: 25px">
<LI
style="LINE-HEIGHT: 25px">除了AcceptSecurityContext的lContextReq参数值有不同的前置字元外,AcceptSecurityContext使用了与InitializeSecurityContext相同的环境需求(列于表12-7)。取代以「ISC_REQ_」开始的「InitializeSecurityContext需求」,AcceptSecurityContext的需求以「ASC_REQ_」开始。例如,ISC_REQ_CONFIDENTIALITY
等于 ASC_REQ_CONFIDENTIALITY。<BR style="LINE-HEIGHT: 25px">
</LI></UL></FONT>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>除了这两个差别之外,在您使用AcceptSecurityContext时,大部分皆与使用InitializeSecurityContext的方法相同。然而,AcceptSecurityContext产生的环境更有能力,因为服务器可以用它来模拟或用其他方式取得权杖的handle,我们稍后将讨论它。让我们看一个范例函数,它在服务器程序代码中显示AcceptSecurityContext的用途:</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 ServerHandshakeAuth(CredHandle* phCredentials, <BR> PULONG plAttributes, CtxtHandle *phContext){ <BR> BOOL fSuccess = FALSE; <BR> __try{ <BR> SECURITY_STATUS ss; <BR> // 宣告输入及输出缓冲器 <BR> SecBuffer secBufferIn[1]; <BR> SecBufferDesc secBufDescriptorIn; <BR> SecBuffer secBufferOut[1]; <BR> SecBufferDesc secBufDescriptorOut; <BR> // 设定一些「回圈状态」资讯 <BR> BOOL fFirstPass = TRUE; <BR> ss = SEC_I_CONTINUE_NEEDED; <BR> while (ss == SEC_I_CONTINUE_NEEDED){ <BR> // 客户端通讯!!! <BR> // 取得Blob的大小。 <BR> ULONG lSize; <BR> ULONG lTempSize = sizeof(lSize); <BR> ReceiveData(&lSize, &lTempSize); <BR> // 取得Blob <BR> PBYTE pbTokenBuf = (PBYTE)alloca(lSize); <BR> ReceiveData(pbTokenBuf, &lSize); <BR> // 把「输入缓冲器」指到Blob <BR> secBufferIn[0].BufferType = SECBUFFER_TOKEN; <BR> secBufferIn[0].cbBuffer = lSize; <BR> secBufferIn[0].pvBuffer = pbTokenBuf; <BR> // 把「输入」BufDesc指到里面的缓冲器 <BR> secBufDescriptorIn.ulVersion = SECBUFFER_VERSION; <BR> secBufDescriptorIn.cBuffers = 1; <BR> secBufDescriptorIn.pBuffers = secBufferIn; <BR> // 设定输出缓冲器 <BR> //(SSPI将分配缓冲器给我们) <BR> secBufferOut[0].BufferType = SECBUFFER_TOKEN; <BR> secBufferOut[0].cbBuffer = 0; <BR> secBufferOut[0].pvBuffer = NULL; <BR> // 把「输出」Bufdesc指到外面的缓冲器 <BR> secBufDescriptorOut.ulVersion = SECBUFFER_VERSION; <BR> secBufDescriptorOut.cBuffers = 1; <BR> secBufDescriptorOut.pBuffers = secBufferOut; <BR> // 这是我们的Blob管理函数 <BR> ss = <BR> AcceptSecurityContext( <BR> phCredentials, <BR> fFirstPass?NULL:phContext, <BR> &secBufDescriptorIn, <BR> *plAttributes | ASC_REQ_ALLOCATE_MEMORY, <BR> SECURITY_NETWORK_DREP, <BR> phContext, <BR> &secBufDescriptorOut, <BR> plAttributes, NULL); <BR> // 通过回圈表示不再是第一次传递 <BR> fFirstPass = FALSE; <BR> // 输出Blob吗?假如是,传递它。 <BR> if (secBufferOut[0].cbBuffer != 0){ <BR> // 客户端通讯!!! <BR> // 传递Blob的大小。 <BR> SendData(&secBufferOut[0].cbBuffer, sizeof(ULONG)); <BR> // 传送Blob本身 <BR> SendData(secBufferOut[0].pvBuffer, <BR> secBufferOut[0].cbBuffer); <BR> // 释放缓冲器 <BR> FreeContextBuffer(secBufferOut[0].pvBuffer); <BR> } <BR> }// 重覆执行if ss == SEC_I_CONTINUE_NEEDED; <BR> // 最后的结果 <BR> if(ss != SEC_E_OK){ <BR> __leave; <BR> } <BR> fSuccess = TRUE; <BR> }__finally{ <BR> // 假如我们执行失败了,清除环境handle <BR> if (!fSuccess){ <BR> ZeroMemory(phContext, sizeof(*phContext)); <BR> } <BR> } <BR> return (fSuccess); <BR>}</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>请注意,ServerHandshakeAuth使用了名称为SendData及ReceiveData的虚拟函数,它们意图代表任何的通讯机制。同样地,ServerHandshakeAuth会传送及接收Blobs的大小以使另一端知道要接收多少资料。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>再者,特别注意缓冲器的管理程序代码。请注意,收到的Blobs会经由里面的缓冲器被传递到函数里。同时会检查外面缓冲器是否存在;假设它存在,则跨越线路传送它。同样地,AcceptSecurityContext的用途可让函数分配内存缓冲器给输出Blobs,最后使用和InitializeSecurityContext相同的方法,用FreeContextBuffer释放这些缓冲器。</FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>以下的程序代码片段可被用来设定呼叫ServerHandshakeAuth范例函数里使用的凭证:</FONT></P>
<DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT
style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">CredHandle hCredentials; <BR>TimeStamp tsExpires; <BR>SECURITY_STATUS ss = AcquireCredentialsHandle(NULL, <BR> MICROSOFT_KERBEROS_NAME, SECPKG_CRED_BOTH, NULL, NULL, <BR> NULL, NULL, &hCredentials, &tsExpires ); <BR>if(ss != SEC_E_OK){ <BR> // 错误 <BR>} <BR>ULONG lAttributes = ASC_REQ_STREAM; <BR>CtxtHandle hContext = {0}; <BR>if(!ServerHandshakeAuth(&hCredentials, <BR> &lAttributes, &hContext)){ <BR> // 错误 <BR>} <BR>ss = ImpersonateSecurityContext(&hContext); <BR>if(ss != SEC_E_OK){ <BR> __leave; <BR>} <BR>DeleteSecurityContext(&hContext); <BR>FreeCredentialsHandle(&hCredentials);</PRE></FONT></DIV>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e72d7
size=4><B
style="LINE-HEIGHT: 25px">模拟(Impersonation)及权杖(Token)的取得<BR
style="LINE-HEIGHT: 25px"> </B></FONT></P>
<P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000
size=2>从服务器的观点来说,模拟是验证的重要部分。幸运的是,当您拥有完整的环境handle时,模拟是容易的。使用以下列出示的ImpersonateSecurityContext及RevertSecurityContext函数可以达成:</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 ImpersonateSecurityContext( <BR> PCtxtHandle phContext); <BR>SECURITY_STATUS RevertSecurityContext( <BR> PCtxtHandle phContext);</PRE></FONT></DIV>
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?