Лучший способ сохранить объект X509Certificate2 с его privateKey в CertStore
Я создал CSR и подписал его. У меня все еще есть закрытый ключ, который я использовал для создания CSR, и я хочу сохранить этот сертификат в Windows CertStores вместе с этим закрытым ключом.
Мой показатель успеха:
Когда я просматриваю сертификат в CertStore, он помечается как имеющий закрытый ключ. В частности, в верхнем левом углу значка сертификата есть маленький под-значок "ключ", и если вы откроете сертификат, он говорит: "У вас есть закрытый ключ, соответствующий этому сертификату" под информацией ValidDates.
Первоначально мы предположили, что .CopyWithPrivateKey(RSA key)
сделает это для нас, но, похоже, это не работает само по себе. Нам также нужно установить некоторые флаги keyStorage, но мы можем сделать это только.Export()
передача сертификата byte[]
array, а затем "импортировать" его с помощью другого вызова конструктора.
Я пробовал кучу вариантов, и это единственная действенная последовательность событий:
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 шага.
1 ответ
CopyWithPrivateKey
поддерживает состояние закрытого ключа. Если это эфемерный ключ, он остается эфемерным. Если это постоянный ключ, он остается постоянным. Когда вы создалиRSA privateKeyUsedToGenerateCsr
object вы создали эфемерный ключевой объект и получили поведение, которое вы видите.
Ваш 4-строчный правильный (за исключением того, что вы действительно хотите, чтобы каждый из объектов сертификата в using
заявление).
- Преобразуйте недавно подписанный сертификат в объект X509Certificate2
CopyWithPrivateKey
создает новый объект X509Certificate2 с привязкой к закрытому ключу (может сохраняться, может быть недолговечным).- Экспорт в формате PFX может завершиться ошибкой, если ключ сохраняется и не подлежит экспорту. Вам решать, хотите ли вы защититься от этого или нет. В противном случае это создает возможность более простого повторного импорта ключа в постоянный ключ.
- Повторный импорт PFX с
PersistKeySet
создает новую копию закрытого ключа (если он уже был сохранен, или "первую копию", если он был недолговечным), и делает так, чтобы ключ не стирался, когда объект сертификата собирает или удаляет мусор.
С другой стороны, вы можете создать ключ как постоянный ключ, и вам просто нужно будет использовать 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)
, вы можете создать постоянный ключ CAPI,
CspParameters cspParams = new CspParameters
{
KeyContainerName = Guid.NewGuid().ToString(),
Flags =
// If appropriate
CspProviderFlags.UseMachineKeyStore |
// If desired
CspProviderFlags.UseNonExportableKey
};
return new RSACryptoServiceProvider(2048, cspParams);