I'm trying to build an AuthorizeAttribute that requires that an HTTPRequest contain a specified client certificate.
I found what I thought was a good series of blog posts on the issues, by Andras Nemes, here:
Using Client Certificates in .NET Part 1 - Introduction
Using Client Certificates in .NET Part 2 Creating Self-Signed Client Certificates
Using Client Certificates in .NET Part 3 -Installing the Client Certficate
Using Client Certificates in .NET Part 4 - Working with Client Certificates in Code
Using Client Certificates in .NET Part 5 - Working with Client Certificates in a Web Project
Using Client Certificates in .NET Part 6 - Setting up Client Certificates for Local Test Usage
I have a test Web API project running, in VS2015, debugging against a site running on my Local IIS, instead of IIS Express, with https configured and SSL Settings set to allow client certificates.
I'm pretty sure it's setup right, because it works fine for one of the certificates I've created.
My attribute is simple:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class KtRequireClientCertAttribute : AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
byte[] cert = HttpContext.Current.Request.ClientCertificate.Certificate;
// if the ClientCertificate is empty, pass null
X509Certificate2 suppliedCert = cert.Any() ? new X509Certificate2(cert) : null;
if (suppliedCert != null && isExpectedCert(suppliedCert)
return;
base.OnAuthorization(actionContext);
}
}
My problem - this works fine for one of the certificates I created following Andras's instructions, last week.
ClientCertificate.Certificate is a byte[] containing 808 elements, the X509Certificate2 constructs properly, and my validation logic works as expected.
But with every certificate I try creating today, HttpContext.Current.Request.ClientCertificate.Certificate is empty.
I'm making the certs thus:
MAKECERT.EXE -ic DevRootCertificate.cer -iv DevRootCertificate.pvk -pe -sv testclientcert.pvk -a sha1 -n "CN=testclientcert" -len 2048 -b 01/01/2015 -e 01/01/2030 -sky exchange testclientcert.cer -eku 1.3.6.1.5.5.7.3.2
My test client app is simple:
using (var requestHandler = new WebRequestHandler())
{
var certificate = new X509Certificate2(certificateFile);
requestHandler.ClientCertificates.Add(certificate);
var url = new Uri(baseUrl);
using (var client = new HttpClient(requestHandler){BaseAddress = url})
{
var response = client.GetAsync(endPoint).Result;
response.EnsureSuccessStatusCode();
var content = response.Content.ReadAsStringAsync().Result;
Console.Out.WriteLine(JToken.Parse(content).ToString(Formatting.Indented));
}
}
The question is why does the one certificate get passed to the attribute and the other not?
Additional info:
I'm running the same client against the same server, running on IIS on my local machine. The website is configured to accept, but not to require, client certificates.
The only difference between a test run that works and one that doesn't is which .cer file I load the cert from.
I'm not sure whether the cert that works has or has not a private key, but the file that works is a .cer, not a .pfx.
The file that hasn't worked was a .cer, as well. And I knew it wasn't signed. So I created a signed cert and tried to constuct X509Certificate2() with the .pfx file. I got a 403 error without even hitting the server.
Web server and client negotiate what certificates the client will send. The server sends a list of signing authorities it will accept, and the client sends whichever certificates are acceptable.
If you're playing around with self-signed certificates, the first thing you do is to create a root certificate and import its public key into your current user certificate store as a "Trusted Root Certification Authority".
Then you need to do the same into the machine store of the machine that's running the Web Server. Both client and server needs to consider the root certificate as a trusted authority, or they'll never agree on any certs signed by it.
The one that worked was a .cer file, so why did it work when the other .cer files didn't?
Private keys, of course.
But, you say, .cer files don't have private keys. True, they don't. But the .cer file was working for me was exported from the user store.
That is, I'd created a cert as a .cer and a .pvk file, then combined both together into a .pfx file.
I imported the .pfx file into my user store, then exported it into a .cer file.
When my client loaded the exported.cer file, it knew that the private key was valid because it was in the user store, so it didn't need a password supplied when it created the X509Certificate2 object.
When I tried to do the same with a cert that wasn't in the user store, using the .cer file didn't work. Using the .pfx file did - if I supplied a password.
var certificate = new X509Certificate2("testcert.pfx", "Password1");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With