我正在尝试向外部生产服务器发起一个简单的GET
请求,并使用客户端证书进行身份验证。他们已将我们的证书添加到他们的服务器上,我已经成功地通过Postman(Chrome应用程序和Windows本机应用程序)以及标准浏览器进行了请求:
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;
}
应用程序代码
我使用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) 远程主机强制关闭了现有连接。
在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(和浏览器)发送。
对我来说,似乎我的应用程序完全忽略了客户端证书。
当我不提供客户端证书时(//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文件获取证书,在代码中组合
- 使用
HttpClient
的SendAsync()
与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应该没有什么问题。
但是,如果我能成功连接到自己的页面/服务并在那里看到客户端证书,那么我认为无论如何都已经超过了目标线,所以我认为这是正确的方法。
对于问题的长度,我很抱歉,但这样我提供了大量的背景研究和细节,这应该有助于回答者和未来诊断非常相似问题的人。我也尝试在我的问题中包含一些常见问题。
当我解决了这个问题时,我当然会自己回答这个问题,如果这个问题没有得到任何答案。
提前感谢您。