HttpClient拒绝发送自签名客户端证书

3

我尝试使用以下代码通过HttpClient发送自签名的客户端证书

var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual; 
handler.ClientCertificates.Add(GetClientCertificate()); //Loads cert from a .pfx file
var client = new HttpClient(handler);
PerformRequest(client);

我看过许多关于此问题的 Stack Overflow 帖子,但没有一个解决了我的问题。

需要注意的事项:

  • 通过 Wireshark 验证,服务器正在请求客户端证书,但 HttpClient 没有发送证书。

When sent via HttpClient

  • 当我使用 Postman 发送请求时,使用相同的证书(相同的 .pfx 文件)。

When sent via Postman

  • 我尝试通过 handler.SslProtocols 强制指定 TLS 版本为 1.0、1.1、1.2 —— 都无效。
  • GetClientCertificate() 返回的 X509Certificate2 具有私钥。
  • 此代码运行在 .NET Framework 4.7.2 上 —— 我不能更改。

我已经进行了3天的故障排除,甚至阅读了参考源代码,试图弄清楚为什么我的证书未包含在内,但我找不到原因。
我怀疑在 HttpClient 实现的深处,我的证书因为是自签名而被拒绝。

如何强制它发送我的证书?(服务器维护白名单,所以我不在乎它认为我的证书是否无效)。 或者,至少如何从中获得任何调试信息? 有没有办法获取证书被拒绝的原因?


更新:

启用跟踪后,我发现 .NET 正确地选择了我的证书:

System.Net Information: 0 : [23960] SecureChannel#64538993 - Selected certificate: <omitted>
System.Net Information: 0 : [23960] SecureChannel#64538993 - Left with 1 client certificates to choose from.
System.Net Information: 0 : [23960] SecureChannel#64538993 - Trying to find a matching certificate in the certificate store.
System.Net Information: 0 : [23960] SecureChannel#64538993 - Locating the private key for the certificate: <omitted>
System.Net Information: 0 : [23960] SecureChannel#64538993 - Certificate is of type X509Certificate2 and contains the private key.
System.Net Information: 0 : [23960] SecureChannel#64538993::.AcquireClientCredentials, new SecureCredential() (flags=(ValidateManual, NoDefaultCred, SendAuxRecord), m_ProtocolFlags=(Tls12Client), m_EncryptionPolicy=RequireEncryption)
System.Net Information: 0 : [23960] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent  = Outbound, scc     = System.Net.SecureCredential)
System.Net Information: 0 : [23960] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 21c5cd98:21cd2108, targetName = <omitted>, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [23960] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=100, returned code=ContinueNeeded).
... etc

然而,它仍未被发送。若有其他查找建议,请不吝赐教。


1
通过在“System.Net”名称上启用.NET跟踪,您可以获取更多的诊断信息,其中包括“SSL调试信息(无效证书、缺少发行者列表和客户端证书错误)”。请参见链接 - John Wu
您已经建立了一条连接,并在连接完成后将证书发送到连接的主体中。该证书并未用作 TLS 认证的一部分。TLS 优先进行。当 TLS 失败时,则不存在请求。当您获得一个请求时,说明 TLS 已成功。 - jdweng
@jdweng 服务器(IIS)已配置为接受客户端证书,但未进行验证。这留给了应用程序服务器。因此,是的,TLS在两种情况下都成功了,但是服务器只能通过C#在TLS协议的“客户端身份验证TLS握手”部分期间不发送证书来验证Postman请求。请参见维基百科该部分的第七个项目符号:“客户端响应Certificate消息,其中包含客户端的证书。” -- C#没有执行此操作。 - MrZander
如何将证书添加到受信任列表取决于操作系统。使用 Powershell,您可以使用 Import-Certificate -FilePath $certFilePath -CertStoreLocation 'Cert:\LocalMachine\Root' - Panagiotis Kanavos
1
@PanagiotisKanavos:为什么Postman能够工作? - jdweng
显示剩余10条评论
1个回答

1

这个答案帮助我找到了解决方案。

X509Certificate2.PrivateKey 抛出了一个 NotSupportedException,因为我使用的是 ECD 作为签名算法。我切换到 RSA 现在它可以正确地发送证书了。

奇怪的是,跟踪显示 ECD 证书没有问题,并且能够成功识别私钥。我不知道为什么会这样 -- 对我来说这听起来像是一个 bug。尽管如此,RSA 将适用于我的用例,我已经厌倦了调试。


这不是一个 bug,它在 属性文档 中有记录:一个 AsymmetricAlgorithm 对象,它是 RSA 或 DSA 加密服务提供程序。 不过 .NET 确实有 ECDsa 类。 - Panagiotis Kanavos
您可以使用ECDsaCertificateExtensions.CopyWithPrivateKey(X509Certificate2, ECDsa)方法来结合证书和私钥。该方法在4.7.2中被添加。 - Panagiotis Kanavos
我明白PrivateKey表明它不支持ECDsa,这是引导我更改签名算法的线索。但我看不出为什么使用ECDsa的X509Cert不应该被静默地发送到我的请求中。同一证书在Postman中能够工作的事实使我相信这确实是一个错误。 - MrZander
已经记录下来,该属性不接受 ECDsa。这就是为什么它会抛出 NotSupportedException 而不是其他随机错误 - 明确拒绝 ECDsa,即使在 .NET Core 中也是如此(https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs#L253)。您尝试使用 CopyWithPrivateKey 吗? - Panagiotis Kanavos
这一切都很好,但没有记录的是,一个ECDsa签名的证书将在HttpClient中静默失败。我不会再继续处理它了,我只会使用RSA。 - MrZander

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接