Categories
Mastering Development

Generate a certificate request and submit to a CA using only .Net

I am trying to use only .Net code to create a certificate request and submit the request to our on premise Active Directory PKI certificate authority, and get a certificate back. I have a solution that has been working for a few years, but it uses CERTCLILib and CERTENROLLLib, and I would like to shed these dependencies and port this code over to .Net 5.

These certificates are then imported onto a Yubikey device. We generate the key pair on the Yubikey and then use the public key with the CSR.

This question here Generate and Sign Certificate Request using pure .net Framework has been very helpful in getting a DER encoded CSR, but I still have a few questions that I haven’t been able to figure out.

  1. How do I specify the CA and the template to use in the CertificateRequest object?
  2. I have a public key that is a RSAParameters object. How can I get that into an RSA object to use with the CertificateRequst constructor?
  3. Once I have the DER encoded CSR, how do I submit that to the CA? I can’t find any classes or methods in the System.Security.Cryptography.X509Certificates namespace that accomplishes that.

Here is my current code that is working that I want to port to .NET 5. Note that DeviceDetails contains properties about the Yubikey device and the CA and template. This code is part of a larger app that provisions Yubikey devices.

    {
        //private const int CC_UIPICKCONFIG = 0x1;
        private const int CR_IN_BASE64 = 0x1;
        private const int CR_IN_FORMATANY = 0;
        private const int CR_DISP_ISSUED = 0x3;
        private const int CR_DISP_UNDER_SUBMISSION = 0x5;
        private const int CR_OUT_BASE64 = 0x1;

        public static string GenerateRequest(DeviceDetails deviceDetails)
        {
            //  Create all the objects that will be required
            var objPkcs10 = new CX509CertificateRequestPkcs10();
            var objDN = new CX500DistinguishedName();
            var objObjectIds = new CObjectIds();
            var objObjectId = new CObjectId();
            var objExtensionKeyUsage = new CX509ExtensionKeyUsage();
            var objX509ExtensionEnhancedKeyUsage = new CX509ExtensionEnhancedKeyUsage();
            var objPublicKey = new CX509PublicKey();

            try
            {
                var publicKey = Utilities.ExportPublicKeyToPEMFormat(deviceDetails.PublicKey);

                publicKey = string.Join("", publicKey.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).Where(s => !s.StartsWith("--")));

                objPublicKey.InitializeFromEncodedPublicKeyInfo(publicKey, EncodingType.XCN_CRYPT_STRING_BASE64);

                var sha512 = new CObjectId();
                sha512.InitializeFromValue("2.16.840.1.101.3.4.2.3");

                // Initialize the PKCS#10 certificate request object based on the public key.
                objPkcs10.InitializeFromPublicKey(X509CertificateEnrollmentContext.ContextUser, objPublicKey, "");
                objPkcs10.HashAlgorithm = sha512;

                //Key Usage Extension
                objExtensionKeyUsage.InitializeEncode(
                    X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE |
                    X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE |
                    X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE |
                    X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE
                );

                objPkcs10.X509Extensions.Add((CX509Extension)objExtensionKeyUsage);

                // Enhanced Key Usage Extension
                objObjectId.InitializeFromValue("1.3.6.1.5.5.7.3.2"); // OID for Client Authentication usage
                objObjectIds.Add(objObjectId);
                objX509ExtensionEnhancedKeyUsage.InitializeEncode(objObjectIds);
                objPkcs10.X509Extensions.Add((CX509Extension)objX509ExtensionEnhancedKeyUsage);

                //  Encode the name in using the Distinguished Name object
                objDN.Encode(
                    deviceDetails.CertificateDetails.Subject,
                    X500NameFlags.XCN_CERT_NAME_STR_NONE
                );

                //  Adding the subject name by using the Distinguished Name object initialized above
                objPkcs10.Subject = objDN;

                //  Adding the Subject Alternate Names
                var strRfc822Name = deviceDetails.UserDetails.OUN + "@corp.com";
                var strUpn = deviceDetails.UserDetails.OUN + "@corp.com";

                var objRfc822Name = new CAlternativeName();
                var objUserPrincipalName = new CAlternativeName();
                var objAlternativeNames = new CAlternativeNames();
                var objExtensionAlternativeNames = new CX509ExtensionAlternativeNames();

                // Set Alternative RFC822 Name
                objRfc822Name.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_RFC822_NAME, strRfc822Name);
                

                // Set Alternative UPN
                objUserPrincipalName.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_USER_PRINCIPLE_NAME, strUpn);

                // Set Alternative Names 
                objAlternativeNames.Add(objRfc822Name);
                
                objAlternativeNames.Add(objUserPrincipalName);
                objExtensionAlternativeNames.InitializeEncode(objAlternativeNames);
                objPkcs10.X509Extensions.Add((CX509Extension)objExtensionAlternativeNames);

                // Create a CMC outer request and initialize
                var cmcReq = new CX509CertificateRequestCmc();
                cmcReq.InitializeFromInnerRequestTemplateName(objPkcs10, deviceDetails.CertificateDetails.TemplateName);              

                // encode the request
                cmcReq.Encode();
                var strRequest = cmcReq.RawData[EncodingType.XCN_CRYPT_STRING_BASE64];
                return strRequest;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        // Submit request to CA and get response
        public static X509Certificate2 SendRequest(DeviceDetails deviceDetails, string request, out string error)
        {
            error = "";

            //  Create all the objects that will be required
            //var objCertConfig = new CCertConfig();
            var objCertRequest = new CCertRequest();

            try
            {
                // Submit the request
                var iDisposition = objCertRequest.Submit(
                    CR_IN_BASE64 | CR_IN_FORMATANY,
                    request,
                    null,
                    deviceDetails.CertificateDetails.IssuingCa
                );

                // Check the submission status
                if (CR_DISP_ISSUED != iDisposition) // Not enrolled
                {
                    var strDisposition = objCertRequest.GetDispositionMessage();

                    if (CR_DISP_UNDER_SUBMISSION == iDisposition) // Pending
                    {
                        error = "The submission is pending: " + strDisposition;
                        return null;
                    }

                    else // Failed
                    {
                        error = $"The submission failed: {strDisposition}. Last status: {objCertRequest.GetLastStatus()}";
                        return null;
                    }
                }

                // Get the certificate
                var strCert = objCertRequest.GetCertificate(CR_OUT_BASE64);
                var rawCert = Convert.FromBase64String(strCert);

                return new X509Certificate2(rawCert);
            }

            catch (Exception ex)
            {
                throw new Exception($"Error sending the request. {ex.Message}");
            }
        }

Leave a Reply

Your email address will not be published. Required fields are marked *