Programmatically communicating with a Certificate Authority

Tony picture Tony · Oct 19, 2011 · Viewed 8.2k times · Source

I working with programmatically working with certificates and communicating with a Certificate Authority. I have been working with the CertClient and CertEnroll COM objects in C# on Windows 2008R2.

I can generate a request and get back a cert from the CA. I started with this example: http://blogs.msdn.com/b/alejacma/archive/2008/09/05/how-to-create-a-certificate-request-with-certenroll-and-net-c.aspx

I am having two issues that I can not figure out. First is, how can I get access to the private key that was used to generate the cert? The methods that are part of the IX509PrivateKey interface don't seem to work on my test env. The request I give the CA is different from the private key, correct?

The second issue is I can't seem to figure out to supply an enrollment agent cert when requesting a cert. The older versions of this API had a method, SetSignerCertificate, that was used. I can't find an equivalent in the new API.

Answer

Mitch picture Mitch · May 13, 2015

An example using an Enrollment Agent certificate is found on MSDN under Create Enroll on Behalf of Another User Request. The certificate will be installed into the certificate store you specify, and you can utilize the private key as you would any other installed certificate.

Full example:

// Add references to CERTENROLL (CertEnroll 1.0 Type Library) 
// and CERTCLILib (CertCli 1.0 Type Library)
using CERTCLILib;
using CERTENROLLLib;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using X509KeyUsageFlags = CERTENROLLLib.X509KeyUsageFlags;

namespace TestSubmitEnrollment
{
    class Program
    {
        static void Main(string[] args)
        {
            string requesterName = @"DOMAIN\otherUser";
            string caName = @"CA1.DOMAIN.LOCAL\DOMAIN-CA1-CA";
            string template = "User";
            // signerCertificate's private key must be accessible to this process
            var signerCertificate = FindCertificateByThumbprint("3f817d138f32a9a8df2aa6e43b8aed76eb93a932");

            // create a new private key for the certificate
            CX509PrivateKey privateKey = new CX509PrivateKey();
            // http://blogs.technet.com/b/pki/archive/2009/08/05/how-to-create-a-web-server-ssl-certificate-manually.aspx
            privateKey.ProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";
            privateKey.MachineContext = false;
            privateKey.Length = 2048;
            privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
            privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_NONE;
            privateKey.Create();

            // PKCS 10 Request
            // we use v1 to avoid compat issues on w2k8
            IX509CertificateRequestPkcs10 req = (IX509CertificateRequestPkcs10)new CX509CertificateRequestPkcs10();
            req.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, privateKey, template);

            // PKCS 7 Wrapper
            var signer = new CSignerCertificate();
            signer.Initialize(false, X509PrivateKeyVerify.VerifyAllowUI, EncodingType.XCN_CRYPT_STRING_BASE64_ANY, 
                Convert.ToBase64String(signerCertificate.GetRawCertData()));

            var wrapper = new CX509CertificateRequestPkcs7();
            wrapper.InitializeFromInnerRequest(req);
            wrapper.RequesterName = requesterName;
            wrapper.SignerCertificate = signer;

            // get CSR
            var enroll = new CX509Enrollment();
            enroll.InitializeFromRequest(wrapper);
            var csr = enroll.CreateRequest();
            //File.WriteAllText("csr.p7b", csr);

            // submit
            const int CR_IN_BASE64 = 1, CR_OUT_BASE64 = 1;
            const int CR_IN_PKCS7 = 0x300;
            ICertRequest2 liveCsr = new CCertRequest();
            var disposition = (RequestDisposition)liveCsr.Submit(CR_IN_BASE64 | CR_IN_PKCS7, csr, null, caName);

            if (disposition == RequestDisposition.CR_DISP_ISSUED)
            {
                string resp = liveCsr.GetCertificate(CR_OUT_BASE64);
                //File.WriteAllText("resp.cer", resp);

                // install the response
                var install = new CX509Enrollment();
                install.Initialize(X509CertificateEnrollmentContext.ContextUser);

                install.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedRoot,
                    resp, EncodingType.XCN_CRYPT_STRING_BASE64_ANY, null);
            }
            else
            {
                Console.WriteLine("disp: " + disposition.ToString());
            }
            Console.WriteLine("done");
            Console.ReadLine();
        }

        private enum RequestDisposition
        {
            CR_DISP_INCOMPLETE = 0,
            CR_DISP_ERROR = 0x1,
            CR_DISP_DENIED = 0x2,
            CR_DISP_ISSUED = 0x3,
            CR_DISP_ISSUED_OUT_OF_BAND = 0x4,
            CR_DISP_UNDER_SUBMISSION = 0x5,
            CR_DISP_REVOKED = 0x6,
            CCP_DISP_INVALID_SERIALNBR = 0x7,
            CCP_DISP_CONFIG = 0x8,
            CCP_DISP_DB_FAILED = 0x9
        }

        private static X509Certificate2 FindCertificateByThumbprint(string sslCertThumbprint)
        {
            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            try
            {
                store.Open(OpenFlags.ReadOnly);

                var certs = store.Certificates.Find(X509FindType.FindByThumbprint, sslCertThumbprint, true);
                if (certs.Count > 0)
                {
                    return certs[0];
                }
                else
                {
                    throw new KeyNotFoundException();
                }
            }
            finally
            {
                store.Close();
            }
        }

        // we re-declare this to account for backcompat to 2k8
        [ComImport, Guid("884E2042-217D-11DA-B2A4-000E7BBB2B09")]
        class CX509CertificateRequestPkcs10
        {
        }
    }
}