12.3.1 安全连接.htm

来自「Windows2000后台服务程序开发手册」· HTM 代码 · 共 352 行 · 第 1/5 页

HTM
352
字号
            <P class=content></P>
            <DIV style="WIDTH: 650px"><A style="LINE-HEIGHT: 25px" name=212005>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e70d7 
            size=5><B style="LINE-HEIGHT: 25px">SSL与SSPI<BR 
            style="LINE-HEIGHT: 25px">  </B></FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>我们已经利用Windows信任帐户讨论过使用NTLM及Kerberos之SSPI及验证的部份。您也可以经由数位凭证使用SSPI去做验证的动作。经由使用SSPI及SSL的Schannel安全性提供者可以达成此工作。若您打算使用SSL,则您的程序代码中必定要包含Security.h及SChannel.h标头档,并且应该把您的专案与Secur32.lib程序库文件做连结。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>和目前为止曾讨论的通讯协定不同,SSL比较不集中于从客户端至服务器的验证部份,通常从服务器到客户端验证的部份开始。从客户端至服务器验证的方式也是可能的,在这种情况下,可以选择使用模拟(Impersonation)的方式。首先让我们看一些常见的方案。客户端想要与某些站台连接时,最大的可能是在Internet上。在传送站台的关键性资讯前,它想要证明自己正在与所要求的实体通讯。此时服务器会传送客户端的凭证。此凭证已被签章,其中并包含了公开/私密金钥之公开金钥。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>客户端可以辨认该凭证是由谁发行的,并帮助客户端决定是否应信任这个凭证。假如它信任签署凭证的CA,则客户端可以相信凭证内的资讯是有效的。凭证经过验证后,客户端即可以读取凭证资讯,例如服务器的URL等,并把这些资讯与正在与它通讯的服务器做比较。假如对照的结果符合,则客户端可以确信它正在与正确的服务器联系。</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>有许多的凭证模组议题尚未被业界开发。客户端软件如何能够真正信任凭证内的资讯?有较好的方法可以把信任的重担从客户端软件转移到使用者身上吗?如何转达资讯?什么样的根授权单位应被信任?使用者可以选择这些授权单位吗?所有的授权单位应该被凭证的任何类型信任吗?凭证授权单位也应该是特定群组的业界授权单位,例如doctors’ 
            offices或预约的传递者吗?这些问题的回答将显露出成熟的技术。从现在开始,我们将集中在技术的讨论上。</FONT></P>
            <HR style="LINE-HEIGHT: 25px">

            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>根据您设计的安全通讯环境,可以决定您自己的凭证授权单位。Microsoft凭证服务可让您发行自己的凭证,而且只要您的客户端信任您的CA,就可以建立符合确切标准的凭证。假如您是自己的凭证授权单位,您甚至可以编写包含信任您的CA之内部根凭证的客户端软件。当客户端连接到您的服务器时,可以使用这个内含的信任来启始一个客户端的工作阶段,然后再为客户端产生凭证(在那里,客户端会产生私密金钥并传递公开金钥给您的服务器)。您的服务器会使用公开金钥从您自己的CA产生凭证,然后储藏这个资讯。此后每当客户端连接到您的服务器时,便可以使用彼此的公开金钥,以凭证的形式,快速地对彼此验证、协商工作阶段金钥,并开始交易。</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>在标准情形下所要求的安全通讯是使浏览器或其他客户端跨越线路传送信用卡或其他敏感的资讯到服务器端。您可以使用SSPI为这种交易实作SSL机制。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e72d7 
            size=4><B style="LINE-HEIGHT: 25px">SSL程序设计<BR 
            style="LINE-HEIGHT: 25px">  </B></FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>您已经知道了许多应用于SSL通讯的相关SSPI。本节假定您已经具备使用Kerberos的SSPI及NTLM的知识;我们要开始建立目前为止本章所提及之大量资讯。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>使用NTLM及Kerberos通讯协定时,您会在通讯的客户端及服务器端呼叫AcquireCredentialsHandle函数。然而,SSL必须经由传入特定的SSP验证结构,即SCHANNEL_CRED,至少为通讯的一端建立凭证。SCHANNEL_CRED结构与SEC_WINNT_AUTH_ 
            IDENTITY结构(本章稍早的时候讨论过)相似,除了SCHANNEL_CRED包括凭证资讯外,它提供了使用者名称、密码及网域名称等资讯。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>SCHANNEL_CRED结构的定义如下:</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 _SCHANNEL_CRED { <BR> DWORD dwVersion; <BR> DWORD cCreds; <BR> PCCERT_CONTEXT *paCred; <BR> HCERTSTORE hRootStore; <BR> DWORD cMappers; <BR> struct _HMAPPER **aphMappers; <BR> DWORD cSupportedAlgs; <BR> ALG_ID * palgSupportedAlgs; <BR> DWORD grbitEnabledProtocols; <BR> DWORD dwMinimumCipherStrength; <BR> DWORD dwMaximumCipherStrength; <BR> DWORD dwSessionLifespan; <BR> DWORD dwFlags; <BR> DWORD reserved; <BR>} SCHANNEL_CRED;</PRE></FONT></DIV>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>服务器通常在将它传递到AcquireCredentialsHandle之前即使用凭证来初始SCHANNEL_CRED。以下的程序代码片段显示使用它的方法:</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>HCERTSTORE hMyCertStore = <BR> CertOpenStore( <BR>&nbsp;&nbsp;CERT_STORE_PROV_SYSTEM_A, <BR>&nbsp;&nbsp;X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, <BR>&nbsp;&nbsp;0, <BR>&nbsp;&nbsp;CERT_SYSTEM_STORE_LOCAL_MACHINE, <BR>&nbsp;&nbsp;"MY"); <BR>if(hMyCertStore==NULL){ <BR> // 错误 <BR>} <BR>// 填入凭证名称的属性结构 <BR>PSTR pszCommonName = "Jason’s Test Certificate"; <BR>CERT_RDN_ATTR certRDNAttr[1]; <BR>certRDNAttr[0].pszObjId = szOID_COMMON_NAME; <BR>certRDNAttr[0].dwValueType = CERT_RDN_PRINTABLE_STRING; <BR>certRDNAttr[0].Value.pbData = (PBYTE)pszCommonName; <BR>certRDNAttr[0].Value.cbData = lstrlen(pszCommonName); <BR>CERT_RDN certRDN = {1, certRDNAttr}; <BR>// 搜寻凭证内容 <BR>PCCERT_CONTEXT pCertContext = <BR> CertFindCertificateInStore( <BR>&nbsp;&nbsp;hMyCertStore, <BR>&nbsp;&nbsp;X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, <BR>&nbsp;&nbsp;0, <BR>&nbsp;&nbsp;CERT_FIND_SUBJECT_ATTR, <BR>&nbsp;&nbsp;&amp;certRDN, <BR>&nbsp;&nbsp;NULL); <BR>if (pCertContext == NULL){ <BR> // 错误 <BR>} <BR>// 填入凭证资讯到SCHANNEL_CRED变数 <BR>SCHANNEL_CRED sslCredentials = {0}; <BR>sslCredentials.dwVersion = SCHANNEL_CRED_VERSION; <BR>sslCredentials.cCreds = 1; <BR>sslCredentials.paCred = &amp;pCertContext; <BR>sslCredentials.grbitEnabledProtocols = SP_PROT_SSL3; <BR>// 取得凭证handle <BR>CredHandle hCredentials; <BR>TimeStamp tsExpires; <BR>SECURITY_STATUS ss = <BR> AcquireCredentialsHandle( <BR>&nbsp;&nbsp;NULL, <BR>&nbsp;&nbsp;UNISP_NAME, <BR>&nbsp;&nbsp;SECPKG_CRED_INBOUND, <BR>&nbsp;&nbsp;NULL, <BR>&nbsp;&nbsp;&amp;sslCredentials, <BR>&nbsp;&nbsp;NULL, NULL, <BR>&nbsp;&nbsp;&amp;hCredentials, <BR>&nbsp;&nbsp;&amp;tsExpires ); <BR>if(ss != SEC_E_OK){ <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>请注意,这个程序代码使用了下一节即将讨论的凭证函数,即撷取凭证名称为「Jason’s Test 
            Certificate」的PCCERT_CONTEXT结构。</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>当您使用凭证去取得经由呼叫AcquireCredentialsHandle函数所得到的凭证时,该凭证必须与隐含的目的相符。例如,对服务器来说,必须被签章的凭证可让服务器验证。否则Acquire 
            CredentialsHandle函数会传回错误,并指出未知的凭证。</FONT></P>
            <HR style="LINE-HEIGHT: 25px">

            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>客户端会使用前面的方法,以凭证开始通讯。客户端使用NULL凭证也是常见的,它指出以匿名的方式向服务器提出验证。以下的程序代码片段显示其方式:</FONT></P>
            <DIV style="LINE-HEIGHT: 25px; BACKGROUND-COLOR: #d7d7d7"><FONT 
            style="LINE-HEIGHT: 25px" face=Arial size=3><PRE style="LINE-HEIGHT: 25px">SCHANNEL_CRED sslCredentials = {0}; <BR>sslCredentials.dwVersion = SCHANNEL_CRED_VERSION; <BR>sslCredentials.grbitEnabledProtocols = SP_PROT_SSL3; <BR>sslCredentials.dwFlags = <BR> SCH_CRED_NO_DEFAULT_CREDS|SCH_CRED_MANUAL_CRED_VALIDATION; <BR>CredHandle hCredentials; <BR>TimeStamp tsExpires; <BR>SECURITY_STATUS ss = <BR> AcquireCredentialsHandle( <BR>&nbsp;&nbsp;NULL, <BR>&nbsp;&nbsp;UNISP_NAME, <BR>&nbsp;&nbsp;SECPKG_CRED_OUTBOUND, <BR>&nbsp;&nbsp;NULL, <BR>&nbsp;&nbsp;&amp;sslCredentials, <BR>&nbsp;&nbsp;NULL, <BR>&nbsp;&nbsp;NULL, <BR>&nbsp;&nbsp;&amp;hCredentials, <BR>&nbsp;&nbsp;&amp;tsExpires); <BR>if(ss != SEC_E_OK){ <BR> // 错误 <BR>}</PRE></FONT></DIV>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>至目前为止,使用SSL与我们习惯使用的验证过程并没有很大的不同。然而,从此以后事情会开始有点改变,即使客户端仍然呼叫InitializeSecurityContext函数,而服务器仍旧呼叫AcceptSecurityContext函数也是一样。</FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#3e74d7 
            size=3><B style="LINE-HEIGHT: 25px">SSL是一个资料流(Stream)<BR 
            style="LINE-HEIGHT: 25px">  </B></FONT></P>
            <P><FONT style="LINE-HEIGHT: 25px" face=arial color=#000000 
            size=2>和我们讨论过的其他通讯协定不同,在通讯前,您应该包装即将被传送的Blobs,并从InitializeSecurityContext以及AcceptSecurityContext函数中传递,SSL预期您的软件并没有包装Blobs!当您从InitializeSecurityContext中接收Blob时,只需传送逐字的资料取代传送资讯大小及资料即可。</FONT></P>

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?