仅使用 .Net 生成证书请求并提交给 CA

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

我正在尝试仅使用 .Net 代码来创建证书请求并将请求提交给我们的本地 Active Directory PKI 证书颁发机构,然后取回证书。我有一个已经工作了几年的解决方案,但它使用了 CERTCLILib 和 CERTENROLLLib,我想摆脱这些依赖并将此代码移植到 .Net 5。

然后将这些证书导入 Yubikey 设备。我们在 Yubikey 上生成密钥对,然后将 public 密钥与 CSR 一起使用。

这个问题对获得DER编码的CSR很有帮助,但我还有几个问题没弄清楚。

  1. 如何指定要在 CertificateRequest 对象中使用的 CA 和模板?
  2. 我有一个 public 键,它是一个 RSAParameters 对象。我如何才能将其放入 RSA 对象以与 CertificateRequst 构造函数一起使用?
  3. 一旦我有了 DER 编码的 CSR,我该如何将它提交给 CA?我在 System.Security.Cryptography.X509Certificates 命名空间中找不到任何 类 或方法来实现这一点。

这是我当前正在运行的代码,我想将其移植到 .NET 5。请注意,DeviceDetails 包含有关 Yubikey 设备以及 CA 和模板的属性。此代码是配置 Yubikey 设备的更大应用程序的一部分。

public static class CertificateUtilities
{
    //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}");
        }
    }
}

多部分问题很难,因为它们需要多部分答案。以下是我可以回答的部分:

How do I specify the CA and the template to use in the CertificateRequest object?

你不能,但没关系,因为你也不在 CertEnroll 代码中。 CertificateRequest 对象等同于您的 objPkcs10,CA 和模板用于您对 CreateSigningRequest 输出所做的操作。

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?

using (RSA key = RSA.Create())
{
    key.ImportParameters(rsaParameters);
    ...
}

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.

框中没有直接用于此的内容。根据CX509CertificateRequestclass的名字,好像是在用Certificate Management over CMS (CMC),但是后面还有认证和请求提交的部分要解决。