Лучший способ сохранить объект X509Certificate2 с его privateKey в CertStore - PullRequest
1 голос
/ 12 марта 2020

Я создал CSR и подписал его. У меня все еще есть закрытый ключ, который я использовал для создания CSR, и я хочу сохранить этот сертификат в Windows CertStores вместе с этим закрытым ключом.

Мой показатель c для успеха:

Когда я просматриваю сертификат в 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 шага.

1 Ответ

1 голос
/ 20 апреля 2020

CopyWithPrivateKey поддерживает состояние закрытого ключа. Если это эфемерный ключ, он остается эфемерным. Если это постоянный ключ, он остается постоянным. Когда вы создали объект RSA privateKeyUsedToGenerateCsr, вы создали эфемерный ключевой объект, и поэтому вы получили поведение, которое видите.

Ваш 4-строчный корректно (за исключением того, что вы действительно хотите, чтобы каждый из объектов сертификата в using Statement).

  • Гидратирование вновь подписанного сертификата в объект 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

В идеале, нет. Вместо этого создайте постоянный КПГ. Но если вам необходимо:

Предполагая, что вы создали ключ как 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...