User Registration with CAC/Client Certificates in Asp.Net made easy - PART I

Implementing CAC registration in Asp.Net web applications is fairly vague and mystified. Quite frankly, there just isn't a lot of literature on this topic, and what is out there tends to be rather old and not very comprehensive.

That said, I'd like to share a way to implement a CAC registration in your Asp.Net web applications. Note, this article is only going to cover things from the .Net Application perspective. For more info on setting up CAC/Client Certs in general, view our other articles.

https://dcdevs.blogspot.com/2017/06/iis-express-client-certificates.html
https://dcdevs.blogspot.com/2017/06/aspnet-mvc-how-to-enabledisable_19.html


The first thing to do is to create a class that can take in a ClientCertificate, validate it, and parse out the information needed to create an account. Here is an example of a CertificateManager class that can be used in your app.


    public class CertificateManager
    {
        public Boolean HasCertificate { get; }
        public String FirstName { get; }
        public String LastName { get; }
        public String Email { get; }
        public String SerialNumber { get; }
        public String CertificateSerialNumber { get; }
        public String Organization { get; }
        public String Affiliation { get; }
        public DateTime ExpirationDate { get; }
        public Boolean HasValidCertificate => ExpirationDate > DateTime.Now;
        public Boolean HasValidEmail => Email.Contains("@") && Email.Contains(".mil");

        public CertificateManager(HttpClientCertificate cs)
        {
            HasCertificate = cs?.IsPresent;

            if (HasCertificate)
            {
                // NOTE: this parsing logic is specific to DoD CAC certs but should be fairly similar to other gov CACs
                var x509Cert2 = new X509Certificate2(cs.Certificate);
                Organization = (x509Cert2.Subject.Split(',')[3]).Split('=')[1];
                LastName = (x509Cert2.Subject.ParseCertSubject("CN").FirstOrDefault() ?? string.Empty).Split('=', '.')[0];
                FirstName = (x509Cert2.Subject.ParseCertSubject("CN").FirstOrDefault() ?? string.Empty).Split('=', '.')[1];
                var sanExtension = (X509Extension)x509Cert2.Extensions["Subject Alternative Name"];
                Email = (sanExtension.Format(true)).Split('=', '\r')[1];
                CertificateSerialNumber = x509Cert2.SerialNumber.HyphanateCert();
                SerialNumber = (x509Cert2.Subject.ParseCertSubject("CN").FirstOrDefault() ?? string.Empty).SubstringRight(10);
                Affiliation = (x509Cert2.Subject.ParseCertSubject("OU").FirstOrDefault() ?? string.Empty).Split('=', '.')[0];
                ExpirationDate = cs.ValidUntil;
            }
        }

        public List GetErrors()
        {
            var errorMessage = new List();
            if (!HasValidEmail)
            {
                errorMessage.Add("The selected certificate is not the DOD EMAIL CA-XX certificate. Please restart your browser and select the DOD EMAIL CA-XX certificate");
            }
            if (SerialNumber == null)
            {
                errorMessage.Add("Serial number field is empty. Please verify your CAC is properly inserted, or contact support");
            }
            if (Email == null)
            {
                errorMessage.Add("Email field is empty. Please verify your CAC is properly inserted, or contact support");
            }
            if (FirstName == null || LastName == null)
            {
                errorMessage.Add("Name field is empty. Please verify your CAC is properly inserted, or contact support");
            }
            if (!HasValidCertificate)
            {
                errorMessage.Add("The selected certificate is expired. Please select a different certificate, or contact support");
            }
            return errorMessage;
        }
    }


Ok, so let's walk through what this class does. It has a single constructor that takes an HttpClientCertificate. There are also numerous properties in this class,  2 are simple calculated properties, and all properties are readonly from outside the class.

The constructor body contains all the logic for parsing the client certificate, or a CAC card in our case. This example is using parsing logic specific to Department of Defense CACs. If you needed to support multiple kinds of certificates, it would be easy to create multiple parsing methods, and call the appropriate one based on the Organization value, for example.

We also have a GetErrors() method that validates the certificate based on criteria we have for creating an account i.e.., email, firstname, lastname.

HasValidEmail is just checking that .mil is found in email. A regex could be used for more accuracy, id needed.
SerialNumber will contain the unique CAC Id. This will be stored in our database and used to retrieve our users, instead of looking them up by email. The reason why we aren't relying on email as a unique identifier is because emails can change while the SerialNumber won't. For example, a user that changes from civilian work to active duty or vise versa will typically get a new CAC card with a different email, "@.civ.mil" for civilian work for example. The serial number will stay the same.
Email, FirstName, and LastName are also validated and appropriate error messages generated when validation fails.

In PART II, we will explore how to use this class to automatically create accounts for users through the app.

Stay Tuned!



Comments

Post a Comment

Popular posts from this blog

ASP.NET Identity Remember Me

IIS Express Client Certificates

ASP.NET MVC - How to enable/disable CaC/Client Certificate authentication per area or route.