WebApi HttpClient不发送客户端证书

我正在尝试使用客户端证书使用ssl和客户端身份validation来保护我的RESTful WebApi服务。

去测试; 我已生成一个自签名证书并放置在本地计算机,受信任的根证书颁发机构文件夹中,并且我已生成“服务器”和“客户端”证书。 服务器的标准https无问题。

但是我在服务器中有一些代码来validation证书,当我使用提供我的客户端证书的测试客户端进行连接并且测试客户端返回403 Forbidden状态时,这永远不会被调用。

这实现了服务器在我的证书到达我的validation码之前失败了。 但是,如果我启动提琴手,它知道需要客户端证书,并要求我提供一个到我的文档\ Fiddler2。 我给了它我在我的测试客户端使用的相同客户端证书,我的服务器现在工作,并收到我期望的客户端证书! 这意味着WebApi客户端没有正确发送证书,我的客户端代码与我发现的其他示例几乎相同。

static async Task RunAsync() { try { var handler = new WebRequestHandler(); handler.ClientCertificateOptions = ClientCertificateOption.Manual; handler.ClientCertificates.Add(GetClientCert()); handler.ServerCertificateValidationCallback += Validate; handler.UseProxy = false; using (var client = new HttpClient(handler)) { client.BaseAddress = new Uri("https://hostname:10001/"); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); var response = await client.GetAsync("api/system/"); var str = await response.Content.ReadAsStringAsync(); Console.WriteLine(str); } } catch(Exception ex) { Console.Write(ex.Message); } } 

任何想法为什么它可以在提琴手而不是我的测试客户端?

编辑:这是GetClientCert()的代码

 private static X509Certificate GetClientCert() { X509Store store = null; try { store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "Integration Client Certificate", true); if (certs.Count == 1) { var cert = certs[0]; return cert; } } finally { if (store != null) store.Close(); } return null; } 

当然,测试代码不处理空证书,但我正在调试以确保找到正确的证书。

上面的代码是否在Fiddler运行的同一个用户帐户中运行? 如果没有,它可能有权访问证书文件,但不能访问正确的私钥。 同样,你的GetClientCert()函数返回什么? 具体来说,它是否设置了PrivateKey属性?

有两种类型的证书。 第一个是从服务器所有者发送给您的公共.cer文件。 这个文件只是一个很长的字符串。 第二个是密钥库证书,这是您创建的自签名证书,并将cer文件发送到您正在调用的服务器并安装它。 根据您拥有的安全性,您可能需要将其中一个或两个添加到客户端(在您的情况下为处理程序)。 我只看到一个服务器上使用的密钥库证书,其中安全性非常安全。 此代码从bin / deployed文件夹获取两个证书:

 #region certificate Add // KeyStore is our self signed cert // TrustStore is cer file sent to you. // Get the path where the cert files are stored (this should handle running in debug mode in Visual Studio and deployed code) -- Not tested with deployed code string executableLocation = Path.GetDirectoryName(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory); #region Add the TrustStore certificate // Get the cer file location string pfxLocation = executableLocation + "\\Certificates\\TheirCertificate.cer"; // Add the certificate X509Certificate2 theirCert = new X509Certificate2(); theirCert.Import(pfxLocation, "Password", X509KeyStorageFlags.DefaultKeySet); handler.ClientCertificates.Add(theirCert); #endregion #region Add the KeyStore // Get the location pfxLocation = executableLocation + "\\Certificates\\YourCert.pfx"; // Add the Certificate X509Certificate2 YourCert = new X509Certificate2(); YourCert.Import(pfxLocation, "PASSWORD", X509KeyStorageFlags.DefaultKeySet); handler.ClientCertificates.Add(YourCert); #endregion #endregion 

此外 – 您需要处理证书错误(注意:这很糟糕 – 它说所有证书问题都没问题)您应该更改此代码以处理名称不匹配等特定证书问题。 它在我的名单上。 有很多关于如何做到这一点的例子。

此代码位于方法的顶部

 // Ignore Certificate errors need to fix to only handle ServicePointManager.ServerCertificateValidationCallback = MyCertHandler; 

你class上某处的方法

 private bool MyCertHandler(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors error) { // Ignore errors return true; } 

在您使用store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

客户端证书不是从LocalMachine ,您应该使用StoreLocation.CurrentUser

检查MMC -> File -> Add or Remove Snap-ins -> Certificates -> My user account您将看到fiddler使用的证书。 如果您从My user account删除它并且只在Computer account导入它,您将看到Fiddler也无法接收它。

附注是在查找证书时,您还必须解决文化问题。

例:

 var certificateSerialNumber= "‎83 c6 62 0a 73 c7 b1 aa 41 06 a3 ce 62 83 ae 25".ToUpper().Replace(" ", string.Empty); //0 certs var certs = store.Certificates.Find(X509FindType.FindBySerialNumber, certificateSerialNumber, true); //null var cert = store.Certificates.Cast().FirstOrDefault(x => x.GetSerialNumberString() == certificateSerialNumber); //1 cert var cert1 = store.Certificates.Cast().FirstOrDefault(x => x.GetSerialNumberString().Equals(certificateSerialNumber, StringComparison.InvariantCultureIgnoreCase));