【发布时间】:2016-02-23 16:06:57
【问题描述】:
我正在尝试使用客户端证书通过 Web API 对设备进行身份验证和授权,并开发了一个简单的概念证明来解决潜在解决方案的问题。我遇到了 Web 应用程序没有收到客户端证书的问题。许多人报告了这个问题,including in this Q&A,但他们都没有答案。我希望提供更多细节来重振这个问题,并希望得到我的问题的答案。我对其他解决方案持开放态度。主要要求是用 C# 编写的独立进程可以调用 Web API 并使用客户端证书进行身份验证。
这个 POC 中的 Web API 非常简单,只返回一个值。它使用一个属性来验证是否使用了 HTTPS 以及是否存在客户端证书。
public class SecureController : ApiController
{
[RequireHttps]
public string Get(int id)
{
return "value";
}
}
这里是 RequireHttpsAttribute 的代码:
public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "HTTPS Required"
};
}
else
{
var cert = actionContext.Request.GetClientCertificate();
if (cert == null)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "Client Certificate Required"
};
}
base.OnAuthorization(actionContext);
}
}
}
在这个 POC 中,我只是检查客户端证书的可用性。一旦这工作正常,我可以在证书中添加信息检查,以根据证书列表进行验证。
以下是 IIS 中针对此 Web 应用程序的 SSL 设置。
这是发送带有客户端证书的请求的客户端的代码。这是一个控制台应用程序。
private static async Task SendRequestUsingHttpClient()
{
WebRequestHandler handler = new WebRequestHandler();
X509Certificate certificate = GetCert("ClientCertificate.cer");
handler.ClientCertificates.Add(certificate);
handler.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
using (var client = new HttpClient(handler))
{
client.BaseAddress = new Uri("https://localhost:44398/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync("api/Secure/1");
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine("Received response: {0}",content);
}
else
{
Console.WriteLine("Error, received status code {0}: {1}", response.StatusCode, response.ReasonPhrase);
}
}
}
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
Console.WriteLine("Validating certificate {0}", certificate.Issuer);
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
// Do not allow this client to communicate with unauthenticated servers.
return false;
}
当我运行这个测试应用程序时,我得到一个 403 Forbidden 状态代码,其中包含“需要客户端证书”的原因短语,表明它正在进入我的 RequireHttpsAttribute 并且没有找到任何客户端证书。通过调试器运行它,我已经验证证书正在加载并添加到 WebRequestHandler。证书将导出到正在加载的 CER 文件中。带有私钥的完整证书位于 Web 应用程序服务器的本地计算机的个人和受信任的根存储中。对于此测试,客户端和 Web 应用程序在同一台机器上运行。
我可以使用 Fiddler 调用这个 Web API 方法,附加相同的客户端证书,它工作正常。使用 Fiddler 时,它通过了 RequireHttpsAttribute 中的测试并返回成功状态码 200 并返回预期值。
有没有人遇到过同样的问题,HttpClient 没有在请求中发送客户端证书并找到了解决方案?
更新 1:
我还尝试从包含私钥的证书存储中获取证书。这是我检索它的方法:
private static X509Certificate2 GetCert2(string hostname)
{
X509Store myX509Store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
myX509Store.Open(OpenFlags.ReadWrite);
X509Certificate2 myCertificate = myX509Store.Certificates.OfType<X509Certificate2>().FirstOrDefault(cert => cert.GetNameInfo(X509NameType.SimpleName, false) == hostname);
return myCertificate;
}
我确认此证书已正确检索,并且已添加到客户端证书集合中。但是在服务器代码没有检索任何客户端证书的情况下,我得到了相同的结果。
为了完整起见,这里是用于从文件中检索证书的代码:
private static X509Certificate GetCert(string filename)
{
X509Certificate Cert = X509Certificate.CreateFromCertFile(filename);
return Cert;
}
您会注意到,当您从文件中获取证书时,它会返回 X509Certificate 类型的对象,而当您从证书存储中检索它时,它会返回 X509Certificate2 类型的对象。 X509CertificateCollection.Add 方法需要 X509Certificate 类型。
更新 2: 我仍在努力解决这个问题,并尝试了许多不同的选择,但无济于事。
- 我将 Web 应用程序更改为在主机名而不是本地主机上运行。
- 我将 Web 应用程序设置为需要 SSL
- 我确认证书已设置为客户端身份验证,并且它位于受信任的根目录中
- 除了在 Fiddler 中测试客户端证书外,我还在 Chrome 中对其进行了验证。
在尝试这些选项的某个时刻,它开始起作用。然后我开始撤销更改,看看是什么导致它工作。它继续工作。然后我尝试从受信任的根中删除证书以验证这是必需的并且它停止工作,现在即使我将证书放回受信任的根中,我也无法使其恢复工作。现在 Chrome 甚至不会提示我使用它使用的证书,它在 Chrome 中失败,但在 Fiddler 中仍然有效。一定是我缺少一些神奇的配置。
我还尝试在绑定中启用“协商客户端证书”,但 Chrome 仍然不会提示我输入客户端证书。这是使用“netsh http show sslcert”的设置
IP:port : 0.0.0.0:44398
Certificate Hash : 429e090db21e14344aa5d75d25074712f120f65f
Application ID : {4dc3e181-e14b-4a21-b022-59fc669b0914}
Certificate Store Name : MY
Verify Client Certificate Revocation : Disabled
Verify Revocation Using Cached Client Certificate Only : Disabled
Usage Check : Enabled
Revocation Freshness Time : 0
URL Retrieval Timeout : 0
Ctl Identifier : (null)
Ctl Store Name : (null)
DS Mapper Usage : Disabled
Negotiate Client Certificate : Enabled
这是我正在使用的客户端证书:
我对问题所在感到困惑。我正在为任何可以帮助我解决这个问题的人添加赏金。
【问题讨论】:
标签: c# security asp.net-web-api ssl-certificate client-certificates