客户端证书未被添加到请求中(证书验证)

16

我正在尝试向外部生产服务器发起一个简单的GET请求,并使用客户端证书进行身份验证。他们已将我们的证书添加到他们的服务器上,我已经成功地通过Postman(Chrome应用程序和Windows本机应用程序)以及标准浏览器进行了请求: Postman showing status OK

Postman的Chrome应用程序版本使用Chrome内置的证书查找器。本机Postman应用程序需要一个.crt和一个.key文件,这些文件我已经从我的.p12文件中提取出来

换句话说,证书已经成功地在存储中找到,并且在使用文件时也可以正常工作(在Windows本机应用程序中),这表明在.NET中也应该是可能的。


在C#中获取证书

在我的简单C# (.NET Framework 4.5.1) 控制台应用程序中,我能够从存储区(或文件)中获取证书,并成功地使用它来加密和解密文件(这意味着我可以完全访问它从我的应用程序中):

private static X509Certificate2 GetCertificate(string thumbprint)
{
    X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
    X509Certificate2Collection coll =
        store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint,
            validOnly: true);
    X509Certificate2 certificate = coll.Count == 0 ? null : coll[0];
    return certificate;
}

应用程序代码

我使用HttpClientHttpWebRequest向服务器发出请求:

//A global setting to enable TLS1.2 which is disabled in .NET 4.5.1 and 4.5.2 by default,
//and disable SSL3 which has been deprecated for a while.
//The server I'm connecting to uses TLS1.2
ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11
    | SecurityProtocolType.Tls12;

X509Certificate cert = GetCertificate(thumbprint);
string url = "https://sapxi.example.com/XISOAPAdapter/MessageServlet";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.ClientCertificates.Add(cert);
request.Method = WebRequestMethods.Http.Get;

WebResponse basicResponse = request.GetResponse(); //This is where the exception is thrown
string responseString = new StreamReader(basicResponse.GetResponseStream()).ReadToEnd();

异常

HttpClientHttpWebRequest都会抛出以下相同的异常:

(WebException) 基础连接已关闭:在发送过程中发生了意外错误。

(IOException) 无法从传输连接读取数据:远程主机强制关闭了现有的连接。

(SocketException) 远程主机强制关闭了现有连接。


在Visual Studio中跟踪请求

启用跟踪后,我得到了一个输出,其中找到了证书和私钥(我已经过滤掉了冗长的消息):

System.Net Error: 0 : [29136] Can't retrieve proxy settings for Uri 'https://sapxi.example.com/XISOAPAdapter/MessageServlet'. Error code: 12180.
System.Net Information: 0 : [29136] Associating HttpWebRequest#21454193 with ServicePoint#60068066
System.Net Information: 0 : [29136] Associating Connection#3741682 with HttpWebRequest#21454193
System.Net.Sockets Information: 0 : [29136] Socket#33675143 - Created connection from 192.168.168.177:56114 to 131.165.*.*:443.
System.Net Information: 0 : [29136] Connection#3741682 - Created connection from 192.168.168.177:56114 to 131.165.*.*:443.
System.Net Information: 0 : [29136] TlsStream#43332040::.ctor(host=sapxi.example.com, #certs=1)
System.Net Information: 0 : [29136] Associating HttpWebRequest#21454193 with ConnectStream#54444047
System.Net Information: 0 : [29136] HttpWebRequest#21454193 - Request: GET /XISOAPAdapter/MessageServlet HTTP/1.1

System.Net Information: 0 : [29136] ConnectStream#54444047 - Sending headers
{
Host: sapxi.example.com
Connection: Keep-Alive
}.
System.Net Information: 0 : [29136] SecureChannel#20234383::.ctor(hostname=sapxi.example.com, #clientCertificates=1, encryptionPolicy=RequireEncryption)
System.Net Information: 0 : [29136] Enumerating security packages:
System.Net Information: 0 : [29136]     Negotiate
System.Net Information: 0 : [29136]     NegoExtender
System.Net Information: 0 : [29136]     Kerberos
System.Net Information: 0 : [29136]     NTLM
System.Net Information: 0 : [29136]     TSSSP
System.Net Information: 0 : [29136]     pku2u
System.Net Information: 0 : [29136]     WDigest
System.Net Information: 0 : [29136]     Schannel
System.Net Information: 0 : [29136]     Microsoft Unified Security Protocol Provider
System.Net Information: 0 : [29136]     Default TLS SSP
System.Net Information: 0 : [29136]     CREDSSP
System.Net Information: 0 : [29136] SecureChannel#20234383 - Attempting to restart the session using the user-provided certificate:
*my certificate is here* (Issuer = CN=TRUST2408 OCES CA II, O=TRUST2408, C=DK)
System.Net Information: 0 : [29136] SecureChannel#20234383 - Left with 1 client certificates to choose from.
System.Net Information: 0 : [29136] SecureChannel#20234383 - Trying to find a matching certificate in the certificate store.
System.Net Information: 0 : [29136] SecureChannel#20234383 - Locating the private key for the certificate:
*my certificate is here*
System.Net Information: 0 : [29136] SecureChannel#20234383 - Certificate is of type X509Certificate2 and contains the private key.
System.Net Information: 0 : [29136] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent  = Outbound, scc     = System.Net.SecureCredential)
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = (null), targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffer length=0, Out-Buffer length=171, returned code=ContinueNeeded).
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 278cca8:6d23888, targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=ContinueNeeded).
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 278cca8:6d23888, targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=ContinueNeeded).
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 278cca8:6d23888, targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=ContinueNeeded).
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 278cca8:6d23888, targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=CredentialsNeeded).
System.Net Information: 0 : [29136] SecureChannel#20234383 - We have user-provided certificates. The server has specified 8 issuer(s). Looking for certificates that match any of the issuers.
System.Net Information: 0 : [29136] SecureChannel#20234383 - Selected certificate:
*my certificate is here*
System.Net Information: 0 : [29136] SecureChannel#20234383 - Left with 1 client certificates to choose from.
System.Net Information: 0 : [29136] SecureChannel#20234383 - Trying to find a matching certificate in the certificate store.
System.Net Information: 0 : [29136] SecureChannel#20234383 - Locating the private key for the certificate:
*my certificate is here*
System.Net Information: 0 : [29136] SecureChannel#20234383 - Certificate is of type X509Certificate2 and contains the private key.
System.Net Information: 0 : [29136] AcquireCredentialsHandle(package = Microsoft Unified Security Protocol Provider, intent  = Outbound, scc     = System.Net.SecureCredential)
System.Net Information: 0 : [29136] InitializeSecurityContext(credential = System.Net.SafeFreeCredential_SECURITY, context = 278cca8:6d23888, targetName = sapxi.example.com, inFlags = ReplayDetect, SequenceDetect, Confidentiality, AllocateMemory, InitManualCredValidation)
System.Net Information: 0 : [29136] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=349, returned code=ContinueNeeded).
System.Net.Sockets Error: 0 : [29136] Socket#33675143::UpdateStatusAfterSocketError() - ConnectionReset
System.Net.Sockets Error: 0 : [29136] Exception in Socket#33675143::Receive - An existing connection was forcibly closed by the remote host.
System.Net Error: 0 : [29136] Exception in HttpWebRequest#21454193:: - The underlying connection was closed: An unexpected error occurred on a send..

上面的部分再次重复,然后最终抛出异常链。我已经用示例替换了服务器的真实URL和IP。
从以下行开始
我们有用户提供的证书。服务器指定了8个发行人。查找与任何发行人匹配的证书。

证书类型为X509Certificate2,并包含私钥。
让我觉得证书在HttpWebRequests的内部工作中被正确地找到了。
从这个输出中我无法判断出问题出在哪里。

使用Wireshark进行调试

在Wireshark中,我比较了Postman请求和我的C#代码,唯一的区别是C#没有发送客户端验证部分(包括整个证书),但是它通过Postman(和浏览器)发送。

通过Postman和浏览器,它看起来像这样: Wireshark Postman successful request has "Certificate Verify"

而通过C#,它看起来像这样: Wireshark C# unsuccessful request missing "Certificate Verify"

对我来说,似乎我的应用程序完全忽略了客户端证书。

当我不提供客户端证书时(//request.ClientCertificates.Add(cert)),我在Wireshark中得到了完全相同的输出,这似乎证实了这种怀疑。在Visual Studio的跟踪输出中,我只得到了Left with 0 client certificates to choose from.,没有在存储中搜索证书或类似的操作。
值得注意的是,Wireshark清楚地表明Postman成功使用了TLS1.2,而我的应用程序代码也使用了TLS1.2。

创建需要证书的本地网页

我已经成功实现了这个功能,设置IIS Express需要证书,然后调用它。 在网页上,我可以在Request.ClientCertificates属性中看到证书。 在Wireshark中,它没有发送证书验证,因此仍然有所不同。

但是这个页面在我的本地机器上运行,使用IIS Express提示我安装的自签名证书。我还没有在生产服务器上设置项目并使用有效证书来查看它是否表现相同。

我不确定这具体意味着什么,但我认为我可以确认我没有忘记任何基本内容,而且这可能是一个边缘情况,或者是C#中HttpWebRequest库无法正确处理的某些协议。


我尝试过的其他方法

  • 将整个证书链/集合添加到请求中(据我理解,这是不必要的,因为服务器有颁发机构/CA来验证我的证书)
  • 从.key和.crt文件获取证书,在代码中组合
  • 使用HttpClientSendAsync()WebRequestHandler
  • 针对.NET Framework 4.6.1
  • 针对.NET Framework 4.7.1
  • 仅启用TLS 1.2(ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
  • 将服务器的证书添加到request.ClientCertificates.Add(serverCert)
  • 使用WinHttpCertCfg.exe授予当前用户访问证书的权限
  • 以管理员身份运行Visual Studio
  • 打开或关闭各种设置:

.

//Automatically verifying all server certificates (don't use this in production)
ServicePointManager.ServerCertificateValidationCallback =
    (sender, certificate, chain, sslPolicyErrors) =>
{
    return true; //This is not reached
};
ServicePointManager.Expect100Continue = true;
request.AllowAutoRedirect = true;
request.PreAuthenticate = true;
request.KeepAlive = false;
request.UserAgent = null;
request.CachePolicy = new HttpRequestCachePolicy(
    HttpCacheAgeControl.MaxAge, TimeSpan.FromSeconds(0));
//And several more that I didn't expect any effect from

如果有帮助的话,他们的服务器正在运行 SAP XI,这是拒绝我访问的应用程序。我不知道这个设置是否与其他设置非常不同,但由于Postman能够成功发送请求,我不认为它会很不同。最坏的情况只是一个高于平均水平的安全协议,仍然遵循标准。
我的主要想法是设置一个简单的ASP页面/ API(需要客户端证书),并将其放在我们的生产服务器上。另一个想法是找到替代 HttpClient 的方法。甚至更糟的是,创建自己的方式,并尝试复制我在Postman中看到的事务流程。但基本上我已经没有了更多的想法。任何帮助都将不胜感激。

问题

如果我必须提出一个具体的问题,我认为是:

如何使用C#向SAP XI服务器进行GET请求,使用我的客户端证书,并使用TLS 1.2?


另外,我不确定是否可以透露生产服务器的URL或IP。无论如何,你们中没有人能够连接到它,因为他们不会允许你将证书添加到他们的服务器。所以恐怕这不是完全可重现的。我想透露服务器属于KMD应该没有什么问题。
但是,如果我能成功连接到自己的页面/服务并在那里看到客户端证书,那么我认为无论如何都已经超过了目标线,所以我认为这是正确的方法。
对于问题的长度,我很抱歉,但这样我提供了大量的背景研究和细节,这应该有助于回答者和未来诊断非常相似问题的人。我也尝试在我的问题中包含一些常见问题。
当我解决了这个问题时,我当然会自己回答这个问题,如果这个问题没有得到任何答案。
提前感谢您。

1
我也遇到了同样的问题,不幸的是,将安全性设置为不安全的Tls1.0版本现在行不通了。是否有更新的答案或不同的解决方法? - Remy Burney
4个回答

5

在研究如何将套接字数据捕获到Wireshark时,我偶然发现一篇文章说"证书验证"在较新版本的Windows(如Windows 10)中未发送到TLS 1.2

因此,我将协议更改为TLS 1.0,请求成功通过:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;

使用TLS 1.1时,我遇到了一个异常,与那篇文章中的描述不同:
(WebException) The request was aborted: Could not create SSL/TLS secure channel.
目前我没有时间去调查这为什么会起作用,因为我已经落后于调试此问题的进度,但对我来说,这听起来像一个错误,就像另一个用户在另一个问题中声称的一样
我找到了一篇微软文章,其中提到:
This issue only occurs with servers that downgrade the TLS session in an ungraceful way (such as by sending a TCP reset when receiving a TLS protocol version that the server does not support)
但是由于我开始使用TLS 1.2,并且服务器明显接受TLS 1.2(通过Postman和Chrome),因此必须是TLS 1.2协议的一小部分没有以相同的方式实现。我仍然不明白Postman本机Windows应用程序如何使用TLS 1.2。

值得注意的是,Internet Explorer首先尝试TLS 1.2,然后在2次重置后(像我的客户端一样),它只降级到TLS 1.0并通过。对我来说,这听起来与文章中谈到的Internet Explorer更新非常相似:

Wireshark showing IE resetting TLS 1.2 connection with Certificate Verify, and then downgrading to TLS 1.0


我知道这不是一个很好的答案(关于“为什么”的细节),但至少它给出了一个提示,如果遇到类似问题,可以尝试什么。
如果有人理解这个问题,甚至知道我如何支持TLS 1.2,那我会非常感激。

3
你找到解决方法了吗?我们也遇到了同样的问题。已经尝试了你尝试过的几乎所有方法 :) - Polat Aydın

4

最近我在使用.NET 4.7.2时遇到了同样的问题。在我的情况下,cert.HasPrivateKey返回true,但cert.PrivateKey返回null。解决方法是将带有私钥的证书导出为pfx格式,然后重新加载到内存中:

// Load the cert with private key
X509Certificate2 cert = ... 

// When the certificate is created, the private key is not associated with the object. In order
// for this to work correctly, we need to export it to a PFX and then re-import the cert.
byte[] certBytes = cert.Export(X509ContentType.Pfx);
cert = new X509Certificate2(certBytes);

在此之后,HttpClient将能够成功地将证书发送到服务器。希望这能帮助到您。


1

这种情况发生的可能原因之一是,.NET客户端代码在发送证书链之前尝试检索完整的证书链。由于无法提供有效的证书,它终止了流。

根本原因是低级别的SslStream类尝试从证书存储中检索证书链。不幸的是,目前(2022年8月)没有提供显式证书链的方法

解决方法是编写你的代码以加载整个链,然后使用根和中间证书填充证书存储:

X509Certificate2 LoadCertificateWithChain(string path, string password, string certThumbprint)
{
    var certCollection = new X509Certificate2Collection();
    certCollection.Import(path, password);

    var clientCert = certCollection.SingleOrDefault(c => c.Thumbprint == certThumbprint);
    if (clientCert == null)
    {
        throw new ArgumentNullException(nameof(certThumbprint),
            $"Couldn't find certificate with thumbprint {certThumbprint}");
    }

    if (certCollection.Count == 1)
    {
        return clientCert;
    }

    using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadWrite);
    
    foreach (var cert in certCollection)
    {
        if (cert.Thumbprint != certThumbprint && !cert.HasPrivateKey)
        {
            store.Add(cert);
        }
    }

    return clientCert;
}

var cert = LoadCertificateWithChain(
    @"path/to/your/chain.pfx", 
    "redacted",
    "thumbprint-of-cert");

每次调用时,它都会尝试将证书填充到证书存储中。如果证书已经存在,则除了返回实际的客户端证书之外,它不会执行任何其他操作。

您可以通过省略指纹检查并查找第一个具有 HasPrivateKey 的证书来简化此过程。

严格来说,StoreName.CertificateAuthority 更适合链的位置。但是,在 Azure Web 应用程序或 Azure 函数中运行的代码将无法访问该存储,而 StoreName.My 是可写的。


-1

我已经解决了,伙计。 TLS1.2 没有问题,你只需要在 HttpWebRequest 类中设置 request.UserAgent = "从浏览器的请求头中获取"; 成员即可。

如果这能帮助你解决问题,请告诉我。


换句话说,您是在说我的客户端只需要假装成现代浏览器?即 request.UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"?我会在周一测试一下,但我想知道为什么这会是一个解决方案,以防它起作用。 - Aske B.
2
很遗憾,你的解决方案对我不起作用。我刚刚使用与我的浏览器相同的“UserAgent”标头以及“TLS1.2”进行了测试(可以正常工作),但是仍然收到了相同的错误。 - Aske B.

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