在 C# 中允许 CX509PrivateKeyClass 的 KeyProtection

Posted

技术标签:

【中文标题】在 C# 中允许 CX509PrivateKeyClass 的 KeyProtection【英文标题】:Allowing KeyProtection for CX509PrivateKeyClass in C# 【发布时间】:2020-06-02 10:46:47 【问题描述】:

我们目前有一个使用 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 生成还很陌生。

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

非常感谢

【问题讨论】:

【参考方案1】:

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

我之前在 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,所以也许这篇文章可能会有所帮助。

【讨论】:

我开始认为这可能需要尽早完成,这看起来是一个非常好的起点,非常感谢!

以上是关于在 C# 中允许 CX509PrivateKeyClass 的 KeyProtection的主要内容,如果未能解决你的问题,请参考以下文章

请解释为啥在 Cpp、Java 和 C# 中允许或不允许 List<A> 强制转换为 List<A.super> [关闭]

在jetbrains骑手中允许不安全的代码?

如何在 JavaScript 文件中允许空格?

在 symfony 访问控制中允许控制器操作

如何在 JetBrains Rider 中允许不安全的代码?

为啥列表中允许使用尾随逗号?