我尝试使用客户端证书向外部生产服务器执行简单的GET
请求。
他们已将我们的证书添加到他们的服务器,我已成功通过Postman(Chrome应用程序和Windows本机应用程序)和标准浏览器发出请求:
Postman的Chrome应用版本使用Chrome内置的证书查找程序。本机邮递员应用程序需要.crt和.key文件,我extracted from my .p12 file。
换句话说,证书在商店中成功找到,并且在从文件中使用时也可以使用(在Windows本机应用程序中,表明它应该可以在.NET中使用)。
在我的简单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;
}
我使用HttpClient
或HttpWebRequest
:
//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();
HttpClient
或HttpWebRequest
都会抛出相同的例外:
(WebException)基础连接已关闭:发送时发生意外错误。
(IOException)无法从传输连接读取数据:远程主机强行关闭现有连接。
(SocketException)远程主机强制关闭现有连接
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中,我比较了Postman请求和我的C#代码,我看到的唯一区别是Client Verify
部分(包括整个证书)不是从C#发送的,而是通过邮递员(和浏览器)。
对我而言,我的应用程序似乎完全忽略了客户端证书。
当我没有提供客户端证书(//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#中的库没有正确处理。
HttpWebRequest
' HttpClient
SendAsync()
WebRequestHandler
)ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
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>
对不起问题的长度,但这样我提供了大量的背景研究和细节,这些研究和细节应该有助于回答未来的人诊断非常类似的问题。我也试图在我的问题中包含一些常见问题。
当我弄明白时,如果没有得到任何答案,我当然会自己回答这个问题。
提前致谢。
答案 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更新非常相似:
我意识到这不是一个好的答案(当谈到“为什么”的细节时),但至少它提供了一个暗示,如果遇到类似的问题,可能会尝试什么。
如果有人理解这个问题,甚至可能知道我如何支持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将成功将证书发送到服务器。希望对您有所帮助。