重新协商的处理
假如客户端对一个被保护的资源提出请求,此时您的服务器可能会想要把匿名的客户端验证提升为具有凭证的验证。如此一来,您的服务器就必须初始一个重新协商的程序。
通常SSL的通讯中,客户端及服务器会经由传送及接收加密的资料缓冲器而互相通讯。然而,当服务器想要初始一个重新协商的程序而非加密及传送资讯时,服务器便会呼叫AcceptSecurityContext函数。在这种情况下,服务器没有传递输入缓冲器到函数中,而且函数会传回应该传送到客户端的Blob值。客户端会了解此Blob表示它应该开始一个重新协商的程序。以下的范例函数显示服务器实作这个程序代码的方法:
BOOL SSLServerInitReneg(
CredHandle* phCredentials,
CtxtHandle* phContext)
{
BOOL fSuccess = FALSE;
__try
{
// 宣告输出缓冲器
SecBuffer secBufferOut;
SecBufferDesc secBufDescriptorOut;
// 设定输出缓冲器(经由SSPI分配)
secBufferOut.BufferType = SECBUFFER_TOKEN;
secBufferOut.cbBuffer = 0;
secBufferOut.pvBuffer = NULL;
// 设定输出缓冲器描述项
secBufDescriptorOut.cBuffers = 1;
secBufDescriptorOut.pBuffers = &secBufferOut;
secBufDescriptorOut.ulVersion = SECBUFFER_VERSION;
ULONG lAttrOut = 0;
SECURITY_STATUS ss =
AcceptSecurityContext(
phCredentials,
phContext,
NULL,
ASC_REQ_ALLOCATE_MEMORY|ASC_REQ_STREAM|ASC_REQ_MUTUAL_AUTH,
SECURITY_NATIVE_DREP,
phContext,
&secBufDescriptorOut,
&lAttrOut,
NULL);
if (ss != SEC_E_OK)
__leave;
// 有资料要传送吗?
if (secBufferOut.cbBuffer!=0){
// 传送它
ULONG lOut = secBufferOut.cbBuffer;
SendData(secBufferOut.pvBuffer, lOut);
// 然后释放输出缓冲器
FreeContextBuffer(secBufferOut.pvBuffer );
}
fSuccess = TRUE;
}__finally{}
return (fSuccess);
}
如同我们在DecryptMessage函数讨论中提到的,当DecryptMessage传回SEC_I_RENEGOTIATE值时,客户端首先会知道服务器要求重新协商的程序。这个时候,假如客户端同意重新协商,则它应该带着适当的凭证handle进入验证回圈的程序中。
服务器也应该在要求重新协商后进入自己的重新协商验证程序。服务器的责任即是检验客户端是否已经提供适当的凭证。客户端很有可能只重新传送它的匿名凭证。
以下列出的程序代码片段表示服务器在重新协商程序中所扮演的角色。
// 服务器需要与客户端重新协商
if(!SSLServerInitReneg(
&hCredentials,
&hContext)){
// 错误情况
}
// 设定验证交握的标记
dwSSPIFlags = ASC_REQ_SEQUENCE_DETECT |
ASC_REQ_REPLAY_DETECT |
ASC_REQ_CONFIDENTIALITY |
/* 指出要求客户端凭证 */
ASC_REQ_MUTUAL_AUTH |
ASC_RET_EXTENDED_ERROR;
cbExtra = 0;
// 再进入验证回圈
ss = SSLServerHandshakeAuth(&hCredentials,
&dwSSPIFlags, &hContext, pIOBuff, &cbExtra, lIOBuffSize);
if (ss == SEC_E_OK){
// 重新协商成功
}
同样的,以下列出的程序代码片段显示在重新协商程序中客户端所扮演的角色(假设客户端拥有一个重新协商的凭证):
// 照惯例取得解密讯息
cbMsg = 0;
cbExtra = 0;
pMsg =
GetEncryptedMessage(
&hContext,
&cbMsg,
&pIOBuff,
&cbExtra,
lIOBuffSize,
&fReneg);
// 假如它执行失败,这是重新协商的请求吗?
if ((pMsg == NULL) && fReneg){
// 如果是,那么带着与凭证相关的凭证handle重新进入验证回圈
ULONG lSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY |
ISC_RET_EXTENDED_ERROR;
ss =
SSLClientHandshakeAuth(
&hCertCredentials,
&hCertCredentials,
&lSSPIFlags,
&hContext,
TEXT("Jason’s Test Server Certificate on Davemm"),
pIOBuff, &cbExtra, lIOBuffSize);
if (ss == SEC_E_OK){
// 重新协商成功
}
}
说明
如果您的客户端软件必须与要求重新协商的服务器通讯时,无论它什么时候会呼叫DecryptMessage函数,能够让您的客户端优雅地处理重新协商要求是很重要的。
SSL及模拟(Impersonation)
假如服务器已经透过客户端之凭证而验证客户端(相对于匿名的客户端验证)时,服务器有可能在那个时候模拟客户端。事实上,模拟的细节与其他使用SSPI安全性通讯协定的模拟方式不同。您只需传递完整的环境handle到ImpersonateSecurityContext函数中,或是直接使用QuerySecurityContextToken函数撷取权杖即可(这两种技巧在本章稍早时曾讨论过)。
然而,剩下一个重要的问题:Windows 2000为何只使用凭证即可为使用者建立一个权杖?这个问题的答案是透过凭证对应到Active Directory,或是经由存放在凭证本身的特定Microsoft资讯。有叁种方法,可以把凭证对应到Windows 2000网域的使用者信任帐户权杖。
在讨论可以用来把凭证对应到网域里的使用者方法前,我想要花些时间说明这个对应的重要性。
假如在Windows 2000上执行的服务器连接到具有已知凭证的客户端时,不管客户端使用的操作系统为何,您的服务器可能会建立一个权杖并把这个客户端登录您的服务器机器上。这个方法可以让您模拟连结到客户端的情形,可以在UNIX、Windows,或任何其他具有SSL通讯能力的机器上执行客户端软件。
这里有叁种不同的方法,可让您将凭证对应到Windows 2000使用者帐户中:
- 一对一对应 任何经由服务器所信任之发行者签章过的凭证皆可以与使用者帐户关联。当SSL连结使用此凭证初始一个模拟时,系统会在Active Directory中查询凭证上的常见名称及发行者名称,以找到应该建立权杖的使用者帐户。
- 多对一对应 任何服务器所信任的CA可以与使用者帐户关联。当这个CA所签署的任何凭证在SSL连结中被用来初始一个模拟时,系统会在Active Directory中查询发行者的名称,以找出应该建立权杖的使用者帐户。
- 使用者主要名称(User principal name,UPN)对应 和其他两个方法不同的是,UPN对应使用凭证内的资讯以找出应该建立权杖的使用者帐户。UPN是应该被模拟之帐户的使用者主要名称,并且会以特殊栏位的方式存放在凭证上。UPN看起来大约像这样:「jclark@subdomain.microsoft.com」。
尽管SSL连结的模拟方式是简单的,这些凭证对应到的管理工作是您的责任。最简单的方法及要求最少的管理即是UPN对应的方式。使用UPN对应,您只需执行Microsoft凭证服务的CA,以作为企业的CA即可。然后您可以从这个CA中要求一个「使用者」凭证。这个使用者凭证将包括UPN属性,它包含要求凭证的使用者帐户名称。假如这个凭证被使用在客户端到SSL服务器的连结中,它就可以被模拟。
当然,您可能会发现您想要模拟不具有特定Microsoft之延伸部分所产生的凭证,这样做会需要您在Active Directory中作明确的对应。一对一对应及多对一对应都使用类似的技巧,把凭证对应到Active Directory中的使用者。
刚开始时,您必须将凭证汇出到一个 .CER文件中。这个文件类型是标准凭证汇出的格式,它包括凭证的签章副本及公开金钥部份,而不包括与凭证相配的私密金钥。MMC中的凭证嵌入式管理单元可让您在凭证上按下滑鼠右键,然后选择所有工作和汇出选项,以汇出一个凭证。这会启动凭证汇出精灵。一旦您有了 .CER文件,就应该采用这些步骤去建立对应到Active Directory中的使用者帐户:
- 开启MMC中的Active Directory使用者及电脑嵌入式管理单元。
- 从检视功能表中选择进阶特色选项。
- 在左窗格中选择使用者资料夹。
- 在您想要将与右边窗格中的凭证相对应的使用者上点选滑鼠右键,然后从内容功能表中选择名称对应选项。
- 在安全性识别对应之对话方块中的X509凭证标签页中,点选新增按钮。
- 选择您想对应之凭证的 .CER文件,然后点选开启选项。此时会出现新增凭证对话方块。
- 假如您核取了使用主体作为其他的安全性识别核取方块时,表示您将使用一对一的对应方式。
- 假如您取消核取使用主体作为其他的安全性识别核取方块,而只留下已核取的使用发行者作为其他的安全性识别核取方块时,表示您将使用多对一的对应方式。
在这些步骤被适当地执行后,您的服务器软件可以开始模拟客户端。
说明
使用凭证对应时,有两个常见的情形必须注意。首先,假如服务器并不信任凭证的发行者,与该凭证关联的客户端即不能被模拟。第二,如果凭证是执行Microsoft凭证服务之企业所建立的使用者凭证,在Active Directory中搜寻明确的对应前,会先使用UPN对应方式。假如您已明确地对应这种凭证时,会导致无法预料的结果。
SSL之疑难排解
在Windows 2000上实作SSL的Schannel安全性套件支援了错误及追踪报告的机制,并会把事件加入事件日志中。它会预设将错误情形记录到系统日志中。然而,它也可以被设定为将警告等级和追踪等级的事件记录到日志中。
为了赋予这些特色,您必须修改登录中的某个值。这个机码位于HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SecurityProviders\Schannel中,而这个值的名称为EventLogging。
EventLogging值是叁个位元值的组合,位元值1表示错误,位元值2表示警告报告,而位元值4则表示追踪事件报告。您可以组合这些值以达到想要的任何报告组合。例如,EventLogging值为3会指示错误及警告报告,反之,EventLogging值为7会打开所有报告等级。这个特色对于了解安全性提供者在执行代表使用SSL客户端及服务器软件的工作时很有帮助。
使用SSPI实作SSL时,必定会牵涉一些比较难的程序代码,但是了解必须做什么再加上一个好的范例会很有帮助。在此强烈地建议您在试图实作自己的SSL服务器前,花些时间更深入地了解本书的范例和《Platform SDK》文件中的范例。
SSLChat范例应用程序
SSLChat范例应用程序(「12 SSLChat.exe」)说明如何使用目前为止我们已讨论过的所有SSL相关技术,包括客户端及服务器凭证的验证、讯息签章及加密的部份。范例应用程序的原始码及资源档存放在随书光碟上的12-SSLChat目录中。图12-7为SSLChat范例应用程序执行时的使用者介面。
SSLChat范例应用程序与SSPIChat范例应用程序非常类似。然而,在SSLChat范例应用程序中,客户端及服务器都可以选择一个凭证去验证远端原则。
客户端及服务器机器必须能够识别彼此之凭证的根CA,这是很重要的。完整讨论从使用者观点出发的凭证管理部份超出了本书的范围,您可以在Windows 2000 Help中查询这个主题,以取得一些资讯。
| 图12-7 SSLChat范例应用程序的使用者介面 |
安全通讯的应用
您应该尽可能提供所取得的资讯,以决定如何保护服务之通讯安全。以下是一些您应该考虑的问题:
- 在Windows网域帐户中验证我的客户端是重要的吗?
- 我的客户端存在我的网域中、我的网域外(可能经由Internet)或两者皆是?
- 我将与非Windows客户端互相操作吗?
假如您的验证需求将会简化Windows的权杖(对于模拟及存取控制是极好的),则您可能会优先考虑使用NTLM或Kerberos。然而,您也可以使用SSL并让系统维护凭证到网域帐户的对应部份。这需要额外的维护工作,如先前所述。
NTLM及Kerberos会向网域验证权杖。所以若您对第一个问题的答案为「是」,则您需要额外的动机来使用SSL及凭证。
如果您的客户端将会存在您的网域中,则您应选择开放的方式。可以使用命名管道,它会自动使用Kerberos或NTLM验证的方式,并且会大大地减轻这个程序的负载。当然,管道也有可调适的议题,假如您须要与无数的客户端连结,此时您应该使用TCP/IP通讯端(Socket)。
若客户端确实位于您的网域中,会使程序代码变得更容易。您可以将您的服务器设定为使用协商安全性套件,您的客户端则可以使用对他们最适当的套件。Windows 2000将会使用Kerberos,而其他则可能使用NTLM。使用这些选择,可以让您设计一个乾净且相对容易实作的安全环境。
假如您的客户端位于您的网域外会发生什么事?若与取得Windows权杖无关,在您网域外的客户端可能是使用SSL及凭证。如果在服务器端不需要使用者的权杖时,表示您有一些与模拟相关的议题需要处理。
假如客户端位于您的网域外,您可以假定客户端能够与您的服务器通讯。这表示您可以考虑使用NTLM通讯协定。客户端将会传递它的使用者名称、密码及服务器的网域到AcquireCredentialsHandle函数中。这个资讯会被传递到服务器。由于客户端只与服务器通讯,所以客户端没有必要存取DC的内容,此时客户端可以位于网域外。同时,服务器位于您的网域内,会存取到DC的内容,并且可以取得客户端的权杖。这是非常好的。
在使用Kerberos通讯协定时,处理位于网域外的客户端就没有这么简单了。许多验证程序会牵涉到与KDC通讯的客户端。就效能来说这是很好的,但是与您网域外的客户端通讯可能会变得复杂。所以一旦决定再次验证时,可以考虑使用NTLM通讯协定,因为位于网域外的使用方式会比较简单。
SSL可被用来取得您服务器端的权杖,它要求客户端只与服务器通讯,但是它存在着凭证管理的包袱。您会在哪里取得您的凭证?您会付给他们或是由自己发行?假如由您自行发行,则取得客户端的信任会很困难吗?假如不会,您和您的客户端信任相同的根CA吗?最后,若权杖是您的目标,您必须管理对应到Active Directory的凭证,或使用执行Microsoft凭证服务之企业CA所发行的凭证(在其他方面可能不符合您的需要)。有关与其他操作系统沟通的议题呢?SSL在这方面很杰出。SSL及凭证之间调适得很好,并且可在其他操作系统上实作。我们的实作就沟通得很好!但是SSL也带来了一些您所知的限制。
您还有一些其他的选择。Kerberos SSP就可以与UNIX之一般安全性服务(Generic Security Service,GSS)程序库沟通。在使用EncryptMessage及DecryptMessage函数时,您必须处理一些额外的细节部份。无论如何,这个沟通能力使得您能够让UNIX客户端使用Kerberos通讯协定,并与您的Windows服务器通讯。GSS及其沟通的部份在《Platform SDK》文件中有详细的讨论。《Platform SDK》中也包括完整的GSS范例。
加密不等于具有安全性
一旦您已经掌握SSPI及其他Windows所提供的特色后,可能就会认为您的服务器已经是安全的了,然而这并不一定真是如此。
Kerberos及NTLM使安全性变得更容易,因为他们使用以使用者密码为基础的对称性加密方式。一旦您的凭证通过验证后,即可以适度地确信您正在与知道密码的实体通讯,尤其当您在使用Kerberos时(然而,密码并不具有「强大的」安全性,因为它们比私密金钥更容易被「知道」或取得。所以密码的安全性是首要关切的部分)。
SSL和NTLM及Kerberos一样不简单。凭证并不是原来就被已对应到网域中的使用者帐户,他们也不直接对应到与密码一样简单的已知机密上。所以这是您必须不断考虑的问题:我正在与这个凭证私密金钥的持有者做安全地通讯,如何才能得知谁是持有者?
公开金钥加密及解密给予我们强大的工具,用来确保我们正与某个实体通讯,并且保证只有一个实体。然而是否应该信任第一个实体的问题仍然未定。这是一个隐含的困难点:所有凭证中的资讯皆是公开的。所以您必须检查什么资讯以确保考虑中的凭证属于您想要连结的实体?
- 您信赖凭证中的常见名称吗?某人可以使用另一个CA建立一个与常见名称相同的凭证吗?
- 您信赖和URL相符的常见名称吗?某人有可能会建立另一个与相同URL相称的凭证吗?
所有这些问题的答案皆为「是」,但是了解这些资讯并不会使您的验证程序更安全。所以对您来说,了解与发行凭证之CA相关的附加资讯才是必要的:
- CA保证什么资讯是唯一的?
- 在获得凭证之前,CA做了多少调查的动作?
另一个要考虑的事情是您应该如何传递这些问题给使用者。您如何以不令人感到困惑并且不会导致使用者放弃的方式而使其信任每件事及每个人?
这些问题所造成的挑战是严重的,尽管没有出现单一的解决办法为每个方案解决这些问题,但是它可以被克服。适当的加以考虑后,您便可以解决这些问题,并使其符合您的特殊需求。总之,公开金钥基础建设是很强大的,假如您选择使用它,表示您可以使用安全的方法实行它。您只须回答我刚刚为您的软件所提出的所有问题。指出这些问题的答案是安全性程序设计的挑战,而其原因有助于安全性软件的编写。
