使用 WCF 的相互 SSL 身份验证:握手阶段没有 CertificateRequest 和 CertificateVerify

Mutual SSL authentication with WCF: no CertificateRequest and CertificateVerify in handshake phase

我正在开发一个 WCF 服务,该服务将由不是我开发的客户端使用,也不是 .NET(可能 Java)。

在任何情况下,服务都应该支持双向 SSL 身份验证,其中服务和客户端都在传输层使用证书 X.509 证书进行身份验证。先前已在各方之间交换了证书。

我的问题是我似乎无法获得正确的 WCF 配置,因此客户端证书身份验证无法正常工作。我期望的是,作为 TLS 握手的一部分,服务器还包括一个 Certificate Request,如下所示:

在此之后,客户应回答“证书验证”等问题:

(最新的)服务配置是这个。我正在使用自定义绑定,身份验证模式设置为 MutualSslNegotiated

<bindings>
  <customBinding>
    <binding name="CarShareSecureHttpBindingCustom">
      <textMessageEncoding messageVersion="Soap11" />
      <security authenticationMode="MutualSslNegotiated"/>
      <httpsTransport requireClientCertificate="true" />
    </binding>
  </customBinding>
</bindings>

...

<serviceBehaviors>
  <behavior name="ServiceBehavior">
    <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
    <serviceDebug includeExceptionDetailInFaults="false" httpHelpPageEnabled="false" />
    <serviceCredentials>
      <serviceCertificate findValue="..." storeLocation="LocalMachine" x509FindType="FindByIssuerName" storeName="My" />
      <clientCertificate>
        <certificate findValue="..." storeName="My" storeLocation="LocalMachine" x509FindType="FindByIssuerName"/>
      </clientCertificate>
    </serviceCredentials>
  </behavior>
</serviceBehaviors>

对于我尝试过的所有服务配置,握手的服务器问候部分看起来像这样,没有 CertificateRequest。

我应该提到的其他事情:

如有任何想法,我们将不胜感激。

好的,经过多次尝试我弄明白了。发布这个以防其他人 运行 遇到同样的问题。

我应该继续指出,确实需要在 MSDN 上的某处提及此行为,该位置对于任何寻找 WCF 安全信息的人来说都是真正可见的,而不是深埋在某些工具的文档中。

我能够重现并修复此问题的平台:Windows 8.1 x64 和 Windows Server 2008 R2 Standard。

正如我所提到的,我的问题是我无法配置 WCF 安全性以使该服务需要客户端证书。我在寻找解决方案时注意到的一个常见困惑是,许多人认为客户端可以发送证书(如果有),而不会受到质疑。当然,情况并非如此 - 服务器需要先请求它,此外,指定允许哪些 CA through a CertificateRequest reply.

总而言之,我的情况是:

  • 服务是自托管的。
  • HTTPS 上的服务 运行s,在非标准端口(不是 443,而是 9000)上。

这意味着我必须使用 netsh.exe http add sslcert 为端口 9000 创建 SSL 证书绑定。好吧,绑定已经创建,但有一个问题。我只是在 运行ning netsh http show sslcert 之后才发现问题,只是为了检查我的绑定:

 IP:port                      : 0.0.0.0:9000
 Certificate Hash             : ...
 Application ID               : ...
 Certificate Store Name       : MY
 Verify Client Certificate Revocation : Enabled
 Verify Revocation Using Cached Client Certificate Only : Disabled
 Usage Check                  : Enabled
 Revocation Freshness Time    : 0
 URL Retrieval Timeout        : 0
 Ctl Identifier               : (null)
 Ctl Store Name               : (null)
 DS Mapper Usage              : Disabled
 -->Negotiate Client Certificate : Disabled

罪魁祸首是绑定的最后一个 属性,“协商客户端证书”,记录在案 here。显然,默认情况下,此 属性 是禁用的。您需要在创建绑定时明确启用它。

使用以下语句重新创建绑定解决了问题:

netsh.exe http add sslcert ipport=0.0.0.0:9000 certhash=... appid=... certstorename=MY verifyclientcertrevocation=Enable VerifyRevocationWithCachedClientCertOnly=Disable UsageCheck=Enable clientcertnegotiation=Enable

在检查绑定之前,我尝试在 IIS 中托管一个简单的 WCF 服务并从那里启用客户端证书身份验证。很好奇的看到虽然IIS没有发出CertificateRequest,但是还是失败了一个403.7。甚至 IIS 也没有使用适当的参数创建绑定。

无论如何,现在它可以正常工作了,这就是您可以修复它的方法。

不要忘记,为了允许证书协商,服务配置也发生了变化(绑定安全性):

<customBinding>
  <binding name="CustomHttpBindingCustom" receiveTimeout="01:00:00">
    <textMessageEncoding messageVersion="Soap11" />
    <security authenticationMode="SecureConversation" requireSecurityContextCancellation="true">
      <secureConversationBootstrap allowInsecureTransport="false" authenticationMode="MutualSslNegotiated" requireSecurityContextCancellation="true"></secureConversationBootstrap>
    </security>
    <httpsTransport requireClientCertificate="true" />
  </binding>
</customBinding>

当我的老板质疑为什么我们的 IIS 托管 WCF 服务实现了“2 向 SSL”(mutual certificate authentication) 时,我遇到了同样的问题,但没有观察到在 TLS 握手中发送 "Certificate Request" .经过一番排查,终于发现Negotiate Client Certificate的证书端口绑定配置被禁用了。

通过运行以下获取当前绑定信息。

netsh http show sslcert

从第一条记录(或相关的 SSL 端口)获取证书哈希和应用程序 GUID,然后 运行 使用 IIS 服务器上的管理员控制台执行以下 netsh command

netsh http add sslcert ipport=0.0.0.0:443 certhash=xxxx appid={xxxxx} clientcertnegotiation=enable

注意如果IIS地址和端口已经存在existing binding,会报如下错误

SSL Certificate add failed, Error: 183 Cannot create a file when that file already exists.

运行 删除命令删除现有绑定,然后再尝试重新添加它。

netsh http delete sslcert ipport=0.0.0.0:443

重新配置后,观察到的 Wireshark TLS 握手变为 expected。但是,在我看来,此设置最终并不重要,因为无论是在初始握手期间还是之后,在加密交换中都使用客户端证书进行身份验证,并且实现了 2 路 SSL。