【问题标题】:Best way to Store an X509Certificate2 object with it's privateKey into the CertStore将带有 privateKey 的 X509Certificate2 对象存储到 CertStore 的最佳方法
【发布时间】:2020-03-12 14:57:53
【问题描述】:

我已生成 CSR,并已对其进行签名。我仍然拥有用于创建 CSR 的私钥,并且我想将该证书与该私钥一起存储在 Windows CertStores 中。

我的成功标准是:

当我在 CertStore 中查看证书时,它被标记为具有私钥。具体来说,它在证书图标的左上角有一个小“密钥”子图标,如果你打开证书,它会在 ValidDates 信息下显示“你有一个与此证书相对应的私钥”。

我们最初认为.CopyWithPrivateKey(RSA key) 会为我们做到这一点,但它似乎并不能单独工作。我们还需要设置一些 keyStorage 标志,但我们只能通过 .Export() 将证书写入 byte[] 数组,然后使用另一个构造函数调用“导入”它。

我尝试了很多变体,这是唯一有效的事件序列:

public void  InstallCertOnNonUiThread(byte[] certificateDataFromCsrResponse, RSA privateKeyUsedToGenerateCsr)
{
    var keyStorageFlags = X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet;

    var originalCert = new X509Certificate2(certificateDataFromCsrResponse);
    var exportOfOriginalCert = originalCert.Export(X509ContentType.Pkcs12);

    var withFlagsCert = new X509Certificate2(certificateDataFromCsrResponse, (SecureString)null, keyStorageFlags);
    var exportOfWithFlagsCert = withFlagsCert.Export(X509ContentType.Pkcs12);

    var copiedWithPKCert = originalCert.CopyWithPrivateKey(privateKeyUsedToGenerateCsr);
    var exportOfCopiedWithPkCert = copiedWithPKCert.Export(X509ContentType.Pkcs12);

    var withFlagsReimportOfOriginal = new X509Certificate2(exportOfOriginalCert, (SecureString)null, keyStorageFlags);
    var withFlagsReimportOfWithFlags = new X509Certificate2(exportOfWithFlagsCert, (SecureString)null, keyStorageFlags);
    var withFlagsReimportOfCopiedWithPK = new X509Certificate2(exportOfCopiedWithPkCert, (SecureString)null, keyStorageFlags);

    InstallCertInStore(StoreLocation.LocalMachine, originalCert);                   // Doesn't work; no key in Store UI.
    InstallCertInStore(StoreLocation.LocalMachine, withFlagsCert);                  // Doesn't work; no key in Store UI.
    InstallCertInStore(StoreLocation.LocalMachine, copiedWithPKCert);               // Doesn't work; no key in Store UI.
    InstallCertInStore(StoreLocation.LocalMachine, withFlagsReimportOfOriginal);    // Doesn't work; no key in Store UI.
    InstallCertInStore(StoreLocation.LocalMachine, withFlagsReimportOfWithFlags);   // Doesn't work; no key in Store UI.
    InstallCertInStore(StoreLocation.LocalMachine, withFlagsReimportOfCopiedWithPK);// This one works. Cert has key icon, and text "You have a private key that corresponds to this certificate"
}

private static void InstallCertInStore(StoreLocation location, X509Certificate2 newCert)
{
    using (var store = new X509Store(StoreName.My, location))
    {
        store.Open(OpenFlags.ReadWrite);
        store.Add(newCert);
    }
}

所以我的最终代码将如下所示:

public Task<bool> InstallCertOnNonUiThread(byte[] certificateDataFromCsrResponse, RSA privateKeyUsedToGenerateCsr, string orgId)
{
    var keyStorageFlags = X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet;

    var originalCert = new X509Certificate2(certificateDataFromCsrResponse);
    var copiedWithPKCert = originalCert.CopyWithPrivateKey(privateKeyUsedToGenerateCsr);
    var exportOfCopiedWithPkCert = copiedWithPKCert.Export(X509ContentType.Pkcs12);
    var withFlagsReimportOfCopiedWithPK = new X509Certificate2(exportOfCopiedWithPkCert, (SecureString)null, keyStorageFlags);

    InstallCertInStore(StoreLocation.LocalMachine, withFlagsReimportOfCopiedWithPK);// This one works. Cert has key icon, and text "You have a private key that corresponds to this certificate"

    return Task.FromResult(true);
}

最后一个选项确实有效,但它似乎比必要的步骤多得多,这表明我即将定义自己的扩展方法:.ActuallyCopyWithPrivateKey,以替换它的 .NET 框架版本方法。这似乎是错误的。

有没有更好的方法来实现这一点,还是真的需要全部 4 个步骤。

【问题讨论】:

  • 这 4 个步骤有什么问题?对我来说看起来不错,我同意你的解决方案。
  • 将其导出为字节,然后立即重新导入它,感觉很讨厌。我希望有一个.SetKeyStorageFlags() 方法,或者我可以将它们传递给.CopyWithPK 方法,或者在原始构造函数上设置标志会起作用,或者类似的东西。
  • 我当然认为是 KeyStorageFlags 有所作为?如果不是,那么这有点说明我的意思......不清楚为什么我们要执行后两个步骤以及为什么结果对象不同。
  • 我相信这行:var copiedWithPKCert = originalCert.CopyWithPrivateKey(privateKeyUsedToGenerateCsr); 应该可以工作。当您将证书添加到存储时,CryptoAPI 应将密钥添加到私钥对象中指定的 CSP,将其放在正确的位置并通过存储附加属性进行更新。
  • 好的,我现在明白你的问题了。如果在那段时间没有人回答,明天再看看。

标签: private-key x509certificate2 certificate-store


【解决方案1】:

CopyWithPrivateKey 维护私钥的状态。如果它是临时密钥,它将保持临时状态。如果它是持久键,它会保持持久。当您创建 RSA privateKeyUsedToGenerateCsr 对象时,您创建了一个临时密钥对象,因此您得到了您所看到的行为。

您的 4-liner 是正确的(除非您确实希望 using 语句中的每个证书对象)。

  • 将新签名的证书合并到 X509Certificate2 对象中
  • CopyWithPrivateKey 使用绑定的私钥创建一个新的 X509Certificate2 对象(可以持久化,也可以是短暂的)。
  • 如果密钥被持久化且不可导出,则导出为 PFX 可能会失败。是否要防止这种情况取决于您。否则,这可能会更轻松地将密钥重新导入到持久密钥。
  • 使用PersistKeySet 重新导入 PFX 会生成私钥的新副本(如果它已经被持久化,或者如果它是短暂的,则为“第一个副本”),并使其在证书时不会擦除密钥对象被垃圾收集或处置。

另一方面,您可以将密钥创建为持久密钥,并且只需要使用 CopyWithPrivateKey 一次。当然,这个概念只存在于 Windows 上。

创建一个持久的 CNG 密钥

假设您将密钥创建为new RSACng(2048)RSA.Create(2048),您可以通过

CngKeyCreationParameters keyParameters = new CngKeyCreationParameters
{
    // Or whatever.
    ExportPolicy = CngExportPolicies.None,

    // If applicable.
    KeyCreationOptions = CngKeyCreationOptions.MachineKey,

    Parameters =
    {
        new CngProperty("Key Length", BitConverter.GetBytes(2048), CngPropertyOptions.Persist),
    },
};

using (CngKey key = CngKey.Create(CngAlgorithm.Rsa, Guid.NewGuid().ToString(), keyParameters))
{
    return new RSACng(key);
}

创建一个持久的 CAPI 密钥

理想情况下,不要。而是创建一个持久的 CNG。但是,如果您必须:

假设您将密钥创建为new RSACryptoServiceProvider(2048),您可以通过

CspParameters cspParams = new CspParameters
{
    KeyContainerName = Guid.NewGuid().ToString(),
    Flags =
        // If appropriate
        CspProviderFlags.UseMachineKeyStore |
        // If desired
        CspProviderFlags.UseNonExportableKey
};

return new RSACryptoServiceProvider(2048, cspParams);

【讨论】:

    猜你喜欢
    • 2021-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-07
    • 2010-11-30
    • 1970-01-01
    • 2013-12-10
    • 2018-09-11
    相关资源
    最近更新 更多