Enrolling For Smartcard Certificates Across Domains

I originally posted this on my MSDN blog.)

In my current work, I have a specific scenario involving smart cards that works (roughly) as follows:

  1. Users have accounts in domain A.
  2. Administrators have accounts in domain B.
  3. Administrators need to run an application in domain B that will allow them to burn smart cards that allow users to access resources in domain A.

It turns out that it’s really hard to find decent documentation and C# sample code for doing this sort of thing.  After much web searching, experimentation, and picking the brains of people much smarter than me, I have some proof-of-concept code that I’d like to share.

The Disclaimer

First, the disclaimer.  This code is proof-of-concept only, not production-ready.  It represents only my current understanding of how things work, and is probably laughably wrong in some respects.  I’m emphatically not a smartcard, certificate, or security expert.  I’ve verified that it works on my machine, but that’s all I can promise.  Corrections welcome!

The Concept

Ok, now that’s out of the way, let’s talk about the concept.  The basic idea here goes like this:

  • The client builds a certificate request for a user in domain A.
  • The client gets a string representation of the request and sends it across the network to the server in the other domain.
  • The server component runs under the credentials of a system account that has the right to enroll on behalf of other users and has a valid enrollment agent certificate.
  • The server wraps the client’s certificate request inside another request, sets the requester name to the subject name of the client request, and signs it with its agent certificate.
  • The server submits the agent request, gets the resulting certificate string, and returns it to the client.
  • The client then saves the certificate to the smartcard.

The code uses the new-ish Certificate Enrollment API (certenroll) that’s available only on Vista+ and Windows Server 2008+.  It won’t run on XP or Server 2003.

The Code

So here it is.  I used the CopySourceAsHTML Visual Studio add-in because it works well in RSS, but the line wrapping is a bit obnoxious.  Oh well.  You’ll need to add references to two COM libraries in order to build this code:

  • CertCli 1.0 Type Library
  • CertEnroll 1.0 Type Library

using System;

using System.Collections.Generic;

using System.Text;

using System.Security.Cryptography.X509Certificates;

using System.Security.Cryptography;



using System.Text.RegularExpressions;

namespace CertTest


    public enum RequestDisposition



        CR_DISP_ERROR = 0x1,

        CR_DISP_DENIED = 0x2,

        CR_DISP_ISSUED = 0x3,



        CR_DISP_REVOKED = 0x6,


        CCP_DISP_CONFIG = 0x8,

        CCP_DISP_DB_FAILED = 0x9


    public enum Encoding


        CR_IN_BASE64HEADER = 0x0,

        CR_IN_BASE64 = 0x1,

        CR_IN_BINARY = 0x2,

        CR_IN_ENCODEANY = 0xff,

        CR_OUT_BASE64HEADER = 0x0,

        CR_OUT_BASE64 = 0x1,

        CR_OUT_BINARY = 0x2


    public enum Format


        CR_IN_FORMATANY = 0x0,

        CR_IN_PKCS10 = 0x100,

        CR_IN_KEYGEN = 0x200,

        CR_IN_PKCS7 = 0x300,

        CR_IN_CMC = 0x400


    public enum CertificateConfiguration


        CC_DEFAULTCONFIG = 0x0,

        CC_UIPICKCONFIG = 0x1,

        CC_FIRSTCONFIG = 0x2,

        CC_LOCALCONFIG = 0x3,




    class Program


        static void Main(string[] args)


            // Do this on the client side

            SmartCardCertificateRequest request = new SmartCardCertificateRequest(“user”);

            string base64EncodedRequestData = request.Base64EncodedRequestData;

            // Do this on the server side

            EnrollmentAgent enrollmentAgent = new EnrollmentAgent();

            string base64EncodedCertificate = enrollmentAgent.GetCertificate(base64EncodedRequestData);

            // Do this on the client side




    public class SmartCardCertificateRequest


        IX500DistinguishedName _subjectName;

        IX509PrivateKey _privateKey;

        IX509CertificateRequestPkcs10 _certificateRequest;

        public SmartCardCertificateRequest(string userName)






        public string Base64EncodedRequestData




                return _certificateRequest.get_RawData(EncodingType.XCN_CRYPT_STRING_BASE64);



        public void SaveCertificate(string base64EncodedCertificate)


            _privateKey.set_Certificate(EncodingType.XCN_CRYPT_STRING_BASE64, base64EncodedCertificate);


        private void BuildSubjectNameFromCommonName(string commonName)


            _subjectName = new CX500DistinguishedName();

            _subjectName.Encode(“CN=” + commonName, X500NameFlags.XCN_CERT_NAME_STR_NONE);


        private void BuildPrivateKey()


            _privateKey = new CX509PrivateKey();

            _privateKey.Pin = “0000”;

            _privateKey.ProviderName = “Microsoft Base Smart Card Crypto Provider”;

            _privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE;

            _privateKey.Length = 1024;

            _privateKey.Silent = true;


        private void BuildCertificateRequest()


            _certificateRequest = new CX509CertificateRequestPkcs10();

            _certificateRequest.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, (CX509PrivateKey)_privateKey, null);

            _certificateRequest.Subject = (CX500DistinguishedName)_subjectName;




    public class EnrollmentAgent


        private readonly string _certificateTemplateName = “MyTemplate”;

        private readonly Regex _commonNameRegularExpression = new Regex(“CN=(.+?)(?:[,/]|$)”, RegexOptions.Compiled);

        public string GetCertificate(string base64EncodedRequestData)


            IX509CertificateRequestPkcs10 userRequest = new CX509CertificateRequestPkcs10();

            userRequest.InitializeDecode(base64EncodedRequestData, EncodingType.XCN_CRYPT_STRING_BASE64);

            IX509CertificateRequestCmc agentRequest = BuildAgentRequest(userRequest);

            string certificate = Enroll(agentRequest);

            return certificate;


        private IX509CertificateRequestCmc BuildAgentRequest(IX509CertificateRequestPkcs10 userRequest)


            IX509CertificateRequestCmc agentRequest = new CX509CertificateRequestCmc();

            agentRequest.InitializeFromInnerRequestTemplateName(userRequest, _certificateTemplateName);

            agentRequest.RequesterName = GetCommonNameFromDistinguishedName(userRequest.Subject);



            return agentRequest;


        private string GetCommonNameFromDistinguishedName(IX500DistinguishedName distinguishedName)


            MatchCollection matches = _commonNameRegularExpression.Matches(distinguishedName.Name);

            if (matches.Count > 0)


                return matches[0].Groups[1].Value;




                throw new Exception(“There is no common name defined in the distinguished name ‘” + distinguishedName.Name + “‘”);



        private ISignerCertificate GetSignerCertificate()


            ISignerCertificate signerCertificate = new CSignerCertificate();

            signerCertificate.Silent = true;

            signerCertificate.Initialize(false, X509PrivateKeyVerify.VerifyNone, EncodingType.XCN_CRYPT_STRING_BASE64, GetBase64EncodedEnrollmentAgentCertificate());

            return signerCertificate;


        private string GetBase64EncodedEnrollmentAgentCertificate()


            X509Store store = new X509Store(StoreLocation.CurrentUser);


            X509Certificate2Collection enrollmentCertificates = store.Certificates.Find(X509FindType.FindByTemplateName, “EnrollmentAgent”, true);

            if (enrollmentCertificates.Count > 0)


                X509Certificate2 enrollmentCertificate = enrollmentCertificates[0];

                byte[] rawBytes = enrollmentCertificate.GetRawCertData();

                return Convert.ToBase64String(rawBytes);




                throw new Exception(“The service account does not have an enrollment agent certificate available.”);



        private string Enroll(IX509CertificateRequestCmc agentRequest)


            ICertRequest2 requestService = new CCertRequestClass();

            string base64EncodedRequest = agentRequest.get_RawData(EncodingType.XCN_CRYPT_STRING_BASE64);

            RequestDisposition disposition = (RequestDisposition)requestService.Submit((int)Encoding.CR_IN_BASE64 | (int)Format.CR_IN_FORMATANY, base64EncodedRequest, null, GetCAConfiguration());

            if (disposition == RequestDisposition.CR_DISP_ISSUED)


                string base64EncodedCertificate = requestService.GetCertificate((int)Encoding.CR_OUT_BASE64);

                return base64EncodedCertificate;




                string message = string.Format(“Failed to get a certificate for the request.  {0}”, requestService.GetDispositionMessage());

                throw new Exception(message);



        private string GetCAConfiguration()


            CCertConfigClass certificateConfiguration = new CCertConfigClass();

            return certificateConfiguration.GetConfig((int)CertificateConfiguration.CC_DEFAULTCONFIG);





Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.