1

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

我衡量成功的标准是:

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

我们最初认为这.CopyWithPrivateKey(RSA key)会为我们做到这一点,但它似乎并不能单独工作。我们还需要设置一些 keyStorage 标志,但我们只能.Export()通过将 cert放入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

1 回答 1

1

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

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

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

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

创建一个持久的 CNG 密钥

假设您将密钥创建为new RSACng(2048)or 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),您可以通过以下方式制作持久的 CAPI 密钥

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

return new RSACryptoServiceProvider(2048, cspParams);
于 2020-04-20T17:23:25.650 回答