2

我们目前有一个使用 Microsoft Active Directory 证书服务生成 CA 证书的解决方案。

我们有一个您可以登录的应用程序,您可以发送 CA 证书请求。

这会在后台加载一个 iframe(指向 MS CA 证书服务上的表单)使用 jQuery 复制数据负载并提交表单。

表单提交使用 ActiveX 和 CertEnroll 创建一个 CSR,并将其发布到另一个 ASP 页。(整个MS AD证书服务都是ASP页面)

问题是这只适用于IE(显然)

我的建议是移动 CSR 生成服务器端,这样它就不会依赖 IE 并且可以从任何浏览器中使用。

我有一个可行的解决方案,它将数据发送到 api,并在 api 中生成 CSR 并将其转发到 MS CA 证书服务器,这会生成有效的证书。

问题

问题是,一旦我安装了证书,就会丢失“您拥有与此证书对应的私钥”的信息。在现有系统上,此消息显示如下:

预期证书

我的假设是这是因为尚未为私钥设置 KeyProtection。不幸的是,它在现有 VBScript 实现中使用的标志需要用户输入:XCN_NCRYPT_UI_PROTECT_KEY_FLAG。每次请求证书时,都会提示用户保护密钥

对于我的 .Net Framework 实现,我尝试了两种不同的 CSR 生成器,一种使用 Bouncy Castle,另一种使用 CERTENROLLLib 的互操作。

我将在下面显示的代码将是 CERTENROLLLib 实现,但如果有人对这两种解决方案有任何想法,我会很高兴听到他们

CERTENROLLLib CSR 一代

        var values = new Dictionary<string, string>
        {
            { "E", emailAddress },
            { "CN", commonName },
            { "OU", organizationalUnit },
            { "L", locality }
        };

        var privateKey = new CX509PrivateKeyClass
        {
            Length = 1024,
            MachineContext = false,
            CspInformations = GetCryptographicProvider(),
            // This can't be set as it requires UI feedback
            //KeyProtection = X509PrivateKeyProtection.XCN_NCRYPT_UI_PROTECT_KEY_FLAG,
            ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_NONE,
        };


        //  Create the actual key pair
        privateKey.Create();

        //  Initialize the PKCS#10 certificate request object based on the private key.
        //  Using the context, indicate that this is a user certificate request and don't
        //  provide a template name
        var certificateRequest = new CX509CertificateRequestPkcs10Class();

        certificateRequest.InitializeFromPrivateKey(
            X509CertificateEnrollmentContext.ContextUser,
            privateKey,
            string.Empty
        );

        var extension = new CX509ExtensionsClass
        {
            GetExtensionKeyUsage(),
            GetExtensionEnhancedKeyUsage()
        };

        certificateRequest.X509Extensions.AddRange(extension);

        //  Encode the name in using the Distinguished Name object
        var valueCollection = values.Select(v => $"{v.Key}={v.Value}").Reverse();

        var subject = new CX500DistinguishedNameClass();
        subject.Encode(
            string.Join(";", valueCollection), X500NameFlags.XCN_CERT_NAME_STR_ENABLE_PUNYCODE_FLAG
        );

        //  Assigning the subject name by using the Distinguished Name object initialized above
        certificateRequest.Subject = subject;

        // Create enrollment request
        var enrollmentRequest = new CX509EnrollmentClass();
        enrollmentRequest.InitializeFromRequest(certificateRequest);

        var csr = enrollmentRequest.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64REQUESTHEADER);

所以我的问题是如何为私钥添加密钥保护,如屏幕截图所示?

由于这是服务器端,我不能指望任何用户反馈,所以还有另一种方法。

任何指导都将受到欢迎,因为可能很清楚,我对 CSR 的产生还很陌生。

最终这将被重写,我们可能会生成我们自己的证书,但我只是试图改善目前的情况。

非常感谢

4

1 回答 1

0

首先,这不是一个答案,而是分享一些可能有所帮助的想法和经验。

我之前在 CERTENROLLLib 库的 1.0.0.0 版本中使用了类似的代码(这是要走的路,因为它有大多数用于生成证书的设置,而不是 Bouncy Castle)。

我认为您的请求是正确的,但不知何故,MS CA 正在颁发没有私钥的证书,因此您导入它并最终丢失“您有一个与此证书相对应的私钥”。

我这样说是因为我的代码没有设置KeyProtection并保留默认值X509PrivateKeyProtection.XCN_NCRYPT_UI_NO_PROTECTION_FLAG,但我能够生成包含私钥的证书(我实际上再次使用 CERTENROLLLib 来生成证书而不是 MS CA,所以这是一个主要区别)。

这就像以 .cer 格式(不带 PK)和 .pfx 格式(带 PK)导出证书。

因此,MS CA 的注册请求必须有一些特殊设置。

这是我的参考代码,它创建一个证书并将其添加到本地计算机上的个人证书中。

    // Create enrollment request
    var enrollmentRequest = new CX509Enrollment();
    enrollmentRequest.InitializeFromRequest(certificateRequest); 

    string csr = enrollmentRequest.CreateRequest();

    // and install it back as the response
    enrollmentRequest.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
        csr, EncodingType.XCN_CRYPT_STRING_BASE64, ""); // no password
    // output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
    var base64encoded = enrollmentRequest.CreatePFX("", // no password, this is for internal consumption
        PFXExportOptions.PFXExportChainWithRoot);

    // instantiate the target class with the PKCS#12 data (and the empty password)
    var certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(
        System.Convert.FromBase64String(base64encoded), "",
        // mark the private key as exportable (this is usually what you want to do)
        System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable
    );

我也决定发这个,因为你说

我们可能会生成自己的证书

这实际上是我的代码所做的,它包括 PK,所以也许这篇文章可能会有所帮助。

于 2020-06-02T11:54:15.003 回答