Troubleshooting Web Applications Accepting Client Certificates
Configuring web applications which need to accept client certificates is one of those tasks which I am doing just rarely enough to forget about the issues I had to resolve to make everything work the previous time. Well, last week I had another opportunity to refresh my knowledge and as expected everything did not go smoothly.
I started out by configuring the server side:
- In Internet Information Services (IIS) Manager I added a binding for
https
to my website. The selected certificate subject name must match the hostname that will be used to access the server and its publisher must be trusted on the client machine. I already encountered my first issue here: after I configured the binding, the web site didn't start any more. It turned out the port was already in use by some other application: Skype. It didn't take me too long to solve this; thanks to the answer I found on Stack Overflow. - At the web application level I changed the SSL Settings in IIS Manager: I switched Client certificates option from Ignore to Accept.
Here's my IHttpModule
code handling AuthenticateRequest
on the server:
var context = HttpContext.Current;
var request = context.Request;
var certificate = request.ClientCertificate;
if (certificate.IsPresent)
{
var identity = new GenericIdentity(certificate.Subject);
var principal = new GenericPrincipal(identity, new string[0]);
context.User = principal;
}
else
{
var response = context.Response;
response.StatusCode = (int)HttpStatusCode.Unauthorized;
response.StatusDescription = "No valid certificate";
response.Flush();
response.Close();
}
Followed by the sample client code (comments contain some useful information):
var store = new X509Store(StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
// the selected certificate needs to be trusted on the server
var certificate = store.Certificates.Find(
X509FindType.FindBySubjectName, "Subject", true)[0];
var handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(certificate);
var httpClient = new HttpClient(handler);
// https is required and hostname must match server certificate subject name
var body = await httpClient.GetStringAsync(
"https://localhost/WebApplication/WebPage.cshtml");
Still, for some reason the IsPresent
property on HttpClientCertificate
in the above authentication code kept returning false. After going through all the configuration steps in detail once again, I determined that the reason for this behavior was that the client certificate was not trusted on the server. I haven't noticed that before because it showed as trusted when looking at it in my certificate store.
How could that happen? Well, I generated the client certificate myself and had to put it in the Trusted Root Certificate Authority store as well to establish trust. I did that; but I used the current user store for that instead of the local computer store, therefore the certificate was still not trusted by the server which was running with the application pool identity. Once I fixed that, everything started working as expected.