【发布时间】:2018-08-15 11:55:00
【问题描述】:
我正在尝试使用客户端证书向外部生产服务器发出简单的GET 请求。
他们已将我们的证书添加到他们的服务器,我已通过 Postman(Chrome 应用程序和 Windows 本机应用程序)和标准浏览器成功发出请求:
Postman 的 Chrome 应用版本使用 Chrome 的内置证书查找器。原生 Postman 应用需要一个 .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;
}
应用代码
我使用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 中跟踪请求
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(和浏览器)发送的)。
在我看来,我的应用程序完全忽略了客户端证书。
当我不提供客户端证书 (//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 提示我安装的自签名证书。我尚未使用有效证书在生产服务器上设置项目,并查看其行为是否相同。
我不确定这到底是什么意思,但我想我可以确认我没有忘记一些基本的东西,这要么是边缘情况,要么是 C# 中 HttpWebRequest 库没有的协议'处理不当。
我还尝试了什么
- Adding the entire certificate chain/collection to the request(据我了解,这是不必要的,因为服务器有颁发者/CA 来验证我的证书)
- Getting the certificate from a .key and .crt file, combining it in the code
- 使用
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# 中的 TLS 1.2 使用我的客户端证书向 SAP XI 服务器发出 GET 请求?
另外,我不确定是否可以透露生产服务器的 URL 或 IP。当然,无论哪种方式,你们都无法自己连接到它,因为他们不允许您将证书添加到他们的服务器。因此,恐怕这将无法完全重现。我想透露服务器属于 KMD 并没有什么坏处。
但如果我可以成功连接到我自己的页面/服务并在那里看到客户端证书,那么我认为无论哪种方式我都会超过目标,所以我认为这就是要走的路。
对不起,问题的长度,但通过这种方式,我提供了很多背景研究和细节,应该有助于回答者和未来的人诊断非常相似的问题。我也尝试在我的问题中包含一些常见问题。
如果没有得到任何答案,我当然会在我弄明白这个问题后自己回答。
提前致谢。
【问题讨论】:
-
我也有同样的问题,不幸的是,将安全设置为和不安全的 Tls1.0 版本现在无法解决问题。是否有不同工作的更新答案?
标签: c# ssl client-certificates x509certificate2