【问题标题】:Why is my client certificate not being included in the TLS handshake?为什么我的客户端证书未包含在 TLS 握手中?
【发布时间】:2012-10-29 16:08:08
【问题描述】:

几年前我编写了一个 Windows 服务,它处理来自多个来源的数据,其中一个是第三方 SOAP Web 服务。它使用第三方提供的客户端证书通过此 Web 服务进行身份验证。这在当时运行良好,并且在它部署到的原始机器上仍然运行良好,但现在他们正在迁移到连接尝试引发异常并显示消息的新机器:

HTTP 请求被客户端身份验证方案禁止 '匿名'

现在我的开发机器上也发生了同样的事情。这是配置与 Web 服务的连接的代码:

Friend Shared Function GetProperlyConfiguredConnection() As SEMOService.MIWebServiceClient
    ' Ensure that the settings are valid
    ValidateSettings()

    Dim destAddress As New System.ServiceModel.EndpointAddress(Url)
    ' The service uses SOAP 1.1 which is what BasicHttpBinding uses. WSHttpBinding uses SOAP 1.2.
    'Dim destBinding As New System.ServiceModel.WSHttpBinding
    Dim destBinding As New System.ServiceModel.BasicHttpBinding
    With destBinding
        .CloseTimeout = New TimeSpan(0, 1, 0)
        .OpenTimeout = New TimeSpan(0, 1, 0)
        .ReceiveTimeout = New TimeSpan(0, 10, 0)
        .SendTimeout = New TimeSpan(0, 1, 0)
        .BypassProxyOnLocal = False
        .HostNameComparisonMode = ServiceModel.HostNameComparisonMode.StrongWildcard
        .MaxBufferPoolSize = 524288
        .MaxReceivedMessageSize = 2147483647
        .MessageEncoding = ServiceModel.WSMessageEncoding.Text
        .TextEncoding = System.Text.Encoding.UTF8
        .UseDefaultWebProxy = True
        .AllowCookies = False

        With .ReaderQuotas
            .MaxDepth = 32
            .MaxStringContentLength = 2147483647
            .MaxArrayLength = 50000000
            .MaxBytesPerRead = 4096
            .MaxNameTableCharCount = 16384
        End With

        .Security.Mode = ServiceModel.BasicHttpSecurityMode.Transport
        .Security.Transport.ClientCredentialType = ServiceModel.HttpClientCredentialType.Certificate
    End With

    Dim wcfService As New SEMOService.MIWebServiceClient(destBinding, destAddress)

    ' Load the certificate from the specified file.
    Dim keyData As Byte()
    Dim keyFileInfo As New IO.FileInfo(SEMOServiceSettings.KeyFilePath)
    Dim keyFileReader As New IO.BinaryReader(New IO.FileStream(SEMOServiceSettings.KeyFilePath, IO.FileMode.Open, IO.FileAccess.Read))
    keyData = keyFileReader.ReadBytes(keyFileInfo.Length)
    keyFileReader.Close()

    Dim cert As New X509Certificates.X509Certificate2
    cert = New X509Certificates.X509Certificate2(keyData, SEMOServiceSettings.KeyFilePassword)

    wcfService.ClientCredentials.ClientCertificate.Certificate = cert

    Return wcfService
End Function

它实际上不是 WCF Web 服务,但 Visual Studio 在从 WSDL 自动生成客户端代码时似乎并不介意。客户端证书是从文件而不是从证书存储中加载的。所有机器上都在使用相同的证书文件,并且在单步执行代码时似乎可以正常加载。

我比较了使用 WireShark 的机器发出的请求与我的开发机器发出的请求,并且客户端证书似乎没有包含在握手中:

No client certificates

这是来自有效机器上的请求的相应数据包:

Full certificate chain

有很多关于 WCF、SOAP 和密码学的内容我不了解,因此对于导致这种行为的环境可能有什么不同以及需要更改哪些内容来纠正,我有点茫然它。

This question 似乎是相关的,但我似乎无法通过代码访问任何 RequireClientCertificate 属性,并且我没有使用 app.config 来配置绑定。

可以向 ServicePointManager 类添加回调,以执行服务器证书的自定义验证。在将客户端证书发送到服务器之前,基客户端类或绑定是否对客户端证书进行了一些验证?如果是这样,我是否可以像处理服务器证书验证一样干预该过程,以便查看发生了什么?

【问题讨论】:

  • 只是为了确保您上面的代码确实可以在旧机器上运行,但不能在新机器上运行?
  • 这是正确的 iMortalitySX。从那以后,我已经在另外几台机器上尝试过它,但也无法让它在这些机器上运行。
  • 你能拼凑一个虚拟应用程序并添加一个“Web Reference”而不是“Service Reference”吗?这应该可以创建一个快速应用来测试您是否可以与该服务进行通信。
  • 好主意,我稍后会尝试并返回结果。感谢您的所有帮助!
  • 出于某种原因,Visual Studio 不会从 Web 服务的 WSDL 自动生成 Web 引用的客户端代码。我决定继续在测试应用程序中重新创建 WCF 服务引用,认为无论如何进行试验会更容易。我重新创建了引用,并在 app.config 中重新创建了绑定,而不是在代码中,并且......它连接了!我不知道该笑还是该哭……

标签: .net vb.net wcf soap wcf-security


【解决方案1】:

我找到了解决方案,但我不确定我是否完全理解它。问题是客户端证书(或者可能只是链中的一些其他证书)需要存在于发出请求的用户的个人证书存储中。我以运行 Windows 服务的用户身份登录,将证书导入其存储区,现在一切正常。

这表明客户端证书以某种方式得到验证,即使它们是从文件加载而不是在存储中引用的。这实际上在 ProcMon 的输出中很明显,如果我更加注意,我会意识到它正在搜索证书存储并得出 NOT FOUND 结果。

如果 Microsoft 的 WCF 客户端代码在证书有问题时抛出异常,而不是试图在没有证书的情况下继续进行,那将是很好的。嗯……

【讨论】:

    【解决方案2】:

    似乎这可能是他们新服务器的部署问题。我之所以这么说,是因为“它以前有效”。我会让他们检查配置,特别是应用程序池和应该运行它的应用程序/用户帐户。他们很有可能将服务安装在无权访问某些内容的系统或本地服务下。在许多情况下,我看到他们复制所有内容的部署,包括服务/用户帐户,但从未创建帐户!这会给你这个错误,如果服务没有权限访问机器证书或运行链,它永远不会请求客户端发送证书!

    查看"The HTTP request was forbidden with client authentication scheme 'Anonymous'?

    编辑:回答您的其他问题,我以前没有...客户端在发送之前不会对客户端证书进行任何类型的检查,除了它使用证书在消息凭据中签署消息模式。您的客户端确实验证了服务,如果您使用的是服务凭据,特别是,如果您在服务中使用证书(甚至通过 IIS 的 SSL),您的 WCF 客户端将确保证书是受信任的并且在允许任何通信之前存储在存储中.

    我从未见过任何创建自定义客户端服务验证器的方法,但最好的办法是打开Diagnostic Tracing and Message Logging 客户端,然后从那里进行分析。

    EDIT2:好的,所以我也没有仔细阅读您的问题。我会假设您的原始服务作为服务帐户运行,可能是网络服务(因为这是最有意义的,对吧?)。好吧,事实证明,在较新版本的 Windows 中,一些内置服务帐户已受到阻碍,以帮助防止安全问题,例如机器人等,它们会做类似于您正在尝试做的事情。最重要的是,根据您的设置,您正在使用代理,我敢猜测可能需要 Windows 身份验证才能工作。我建议遵循“正确”的做事方式,并为该服务创建一个特定的服务用户。如果您将服务设置为“本地系统”运行,我愿意打赌它可能会工作(虽然不确定代理)。

    【讨论】:

    • 是我自己在新服务器上设置了客户端。它是一个 Windows 服务,它以正确的帐户运行,该帐户是管理员组的成员,并且对客户端证书文件具有完全权限。我不知道它还需要什么权限?它连接的服务不在我/他们的控制之下。新服务器(不工作)正在连接到与旧服务器(工作)相同的 Web 服务。
    • 我确实为服务证书设置了一个客户验证器,但它似乎没有任何问题。我将尝试按照您的建议设置跟踪,看看是否有什么有趣的结果。
    • 我在工作机器和非工作机器上都设置了日志记录。似乎没有记录任何关于正在使用客户端证书的事实,并且在我看来,在发送第一个 SOAP 请求之前,这些跟踪基本上是相同的。此时,工作机器收到 200 OK 响应并继续接收预期数据,而非工作机器收到 403 Forbidden,导致问题中描述的异常消息。
    • 感谢您的帮助 iMortalitySX。它与用户帐户有关,但与权限不完全相关。
    【解决方案3】:

    我在使用第三方 Web 服务时遇到了同样的问题,该服务需要在 Windows 7 服务器上的传输级别进行证书身份验证。我们必须在套接字级别设置跟踪以找到根本原因,即在自定义应用程序 ID 下运行的服务使用者无权读取客户端证书上的私钥。我们看到证书已被发现,但由于无法读取私钥,AcquireCredentailsHandle 将失败并出现错误 0X8009030D。

    套接字跟踪:

    System.Net Information: 0 : [5708] SecureChannel#15271547 - Certificate is of type     X509Certificate2 and contains the private key.
    ProcessId=6572
    ThreadId=1
    DateTime=2013-12-18T03:01:54.0189534Z
    Timestamp=38085697406996
    System.Net Information: 0 : [5708] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent  = Outbound, scc     = System.Net.SecureCredential)
    ProcessId=6572
    ThreadId=1
    DateTime=2013-12-18T03:01:54.0194535Z
    Timestamp=38085697408545
    System.Net Error: 0 : [5708] AcquireCredentialsHandle() failed with error 0X8009030D.
    ProcessId=6572
    ThreadId=1
    DateTime=2013-12-18T03:01:54.0724641Z
    Timestamp=38085698170444
    

    一旦我们授予我们的应用程序 ID 对私钥的访问权限,它就能够完成 tls 握手并将我们的证书发送到服务。在 Windows 7 服务器中授予对私钥的访问权限要比在 xp 机器上更容易。只需在 mmc 中打开证书存储,找到您的客户端证书,然后右键单击 alltask-->manage private keys

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-28
      • 1970-01-01
      • 1970-01-01
      • 2021-05-07
      • 2012-03-07
      • 2016-06-15
      • 2018-04-20
      • 1970-01-01
      相关资源
      最近更新 更多