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

时间:2018-03-07 11:34:57

标签: c# ssl client-certificates x509certificate2

我尝试使用客户端证书向外部生产服务器执行简单的GET请求。 他们已将我们的证书添加到他们的服务器,我已成功通过Postman(Chrome应用程序和Windows本机应用程序)和标准浏览器发出请求: Postman showing status OK

Postman的Chrome应用版本使用Chrome内置的证书查找程序。本机邮递员应用程序需要.crt和.key文件,我extracted from my .p12 file

换句话说,证书在商店中成功找到,并且在从文件中使用时也可以使用(在Windows本机应用程序中,表明它应该可以在.NET中使用)。

使用C#

获取证书

在我的简单C#(.NET Framework 4.5.1)控制台应用程序中,我能够从商店(或从文件)获取证书,并成功将其用于encrypt and decrypt a file(我认为这意味着我从我的应用程序中完全访问它):

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中跟踪请求

Enabling tracing,我得到一个输出,其中找到了证书和私钥(我已经过滤掉了详细消息):

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#代码,我看到的唯一区别是Client Verify部分(包括整个证书)不是从C#发送的,而是通过邮递员(和浏览器)。

通过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。

创建需要证书的本地网页

我让它工作,setting up the IIS Express to require certificates然后调用它。 在页面上,我可以在Request.ClientCertificates属性中看到证书。 在wireshark中,它不会发送证书验证,所以某些内容仍然不同。

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

我不确定这究竟是什么意思,但我想我可以确认我没有忘记一些基本的东西,这是一个边缘案例,或者是{{1 C#中的库没有正确处理。

我还尝试了什么

request.ClientCertificates.Add(serverCert)

如果有帮助,他们的服务器正在运行SAP XI,这是拒绝我访问的应用程序。我不知道这个设置是否与其他设置截然不同,但由于Postman能够成功完成请求,我不会怀疑它是非常不同的。在最坏的情况下,它只是一个高于平均水平的安全协议,仍然遵循标准。

我的主要想法是设置简单的ASP页面/ API(需要客户端证书)并将其放在我们的生产服务器上。 另一个想法是找到//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 的替代方案。 或者更糟糕的是,创建我自己的,并尝试复制我看到Postman所做的事务流程。 但基本上我的想法已经不多了。任何帮助表示赞赏。

问题

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

如何使用C#1.2在C#中使用我的客户端证书向SAP XI服务器发出GET请求?

另外,我不确定是否可以显示生产服务器的URL或IP。当然,没有人能够以任何方式自己连接,因为他们不允许您将证书添加到他们的服务器。所以这不会完全可以重现,我害怕。我想在揭示服务器属于KMD方面没有坏处。

但是,如果我能成功连接到我自己的页面/服务并在那里看到客户证书,那么我想我会以任何一种方式超越目标,所以我认为这是最佳选择。< / p>

对不起问题的长度,但这样我提供了大量的背景研究和细节,这些研究和细节应该有助于回答未来的人诊断非常类似的问题。我也试图在我的问题中包含一些常见问题。

当我弄明白时,如果没有得到任何答案,我当然会自己回答这个问题。

提前致谢。

3 个答案:

答案 0 :(得分:3)

在研究如何从我的本地托管页面捕获套接字数据到Wireshark时,我偶然发现了an article saying that "Certificate Verify" isn't sent over TLS 1.2 in "newer versions of Windows"(如Windows 10)。

所以我将协议更改为TLS 1.0,请求通过:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;

使用TLS 1.1,我得到一个例外,不像那篇文章中的那个人说的那样:

  

(WebException)请求已中止:无法创建SSL / TLS安全通道。

为什么这个工作不是我现在有时间调查的事情,因为我已经落后于调试此问题的时间表,但它听起来像一个bug,就像another user claimed in another question一样。

我发现a Microsoft article沿着这些方向说:

  

此问题仅发生在以非正常方式降级TLS会话的服务器上(例如,在接收服务器不支持的TLS协议版本时发送TCP重置)

但是,由于我从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,那么我会非常感激。

答案 1 :(得分:0)

我已经解决了它的伙伴。 TLS1.2没有任何问题,您只需要在request.UserAgent = "Take it from your broewser's request header";课程中设置HttpWebRequest成员。

如果这有助于您解决问题,请与我们联系。

答案 2 :(得分:0)

最近我在使用.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将成功将证书发送到服务器。希望对您有所帮助。

相关问题