WebApi HttpClient不发送客户端证书

时间:2014-03-05 12:27:06

标签: c# .net ssl asp.net-web-api fiddler

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

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

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

这实现了服务器在我的证书到达验证码之前失败了。 但是,如果我启动提琴手它知道需要客户端证书并要求我提供一个到我的文档\ 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;
    }

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

4 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

有两种类型的证书。第一个是从服务器所有者发送给您的公共.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;

班级某处的方法

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

答案 2 :(得分:1)

 try this.

证书应与当前用户存储在一起。 或授予完整权限并从文件中读取文件,因为它是控制台应用程序。

// Load the client certificate from a file.
X509Certificate x509 = X509Certificate.CreateFromCertFile(@"c:\user.cer");

从用户存储中读取。

 private static X509Certificate2 GetClientCertificate()
    {
        X509Store userCaStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        try
        {
            userCaStore.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
            X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindBySubjectName, "localtestclientcert", true);
            X509Certificate2 clientCertificate = null;
            if (findResult.Count == 1)
            {
                clientCertificate = findResult[0];
            }
            else
            {
                throw new Exception("Unable to locate the correct client certificate.");
            }
            return clientCertificate;
        }
        catch
        {
            throw;
        }
        finally
        {
            userCaStore.Close();
        }
    }

答案 3 :(得分:-1)

在您使用的代码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<X509Certificate>().FirstOrDefault(x => x.GetSerialNumberString() == certificateSerialNumber);

//1 cert
var cert1 = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x =>
                x.GetSerialNumberString().Equals(certificateSerialNumber, StringComparison.InvariantCultureIgnoreCase));