Как создать X509Certificate2 с PrivateKey в. NET Стандарт 2.0 / Core 2.1? - PullRequest
1 голос
/ 18 января 2020

Мы генерируем некоторые самозаверяющие сертификаты для тестирования с использованием BouncyCastle, но код выдает исключение, когда мы пытаемся добавить закрытый ключ к сертификату. Вот код, о котором идет речь:

private static X509Certificate2 CreateCertificate(string subject, DateTimeOffset notBefore, DataTimeOffset notAfter, string issuer, AsymmetricKeyParamter issuerPrivateKey)
{
        // Setup
        X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
        SecureRandom random = new SecureRandom(new CryptoApiRandomGenerator());

        RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
        keyPairGenerator.Init(new KeyGenerationParameters(random, KeyStrength));

        // Randomly generate a serial number
        BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
        certGenerator.SetSerialNumber(serialNumber);

        // Set the issuer and subject names
        X509Name issuerName = new X509Name(issuer);
        X509Name subjectName = new X509Name(subject);
        certGenerator.SetIssuerDN(issuerName);
        certGenerator.SetSubjectDN(subjectName);

        // Set the validity period
        certGenerator.SetNotBefore(notBefore.UtcDateTime);
        certGenerator.SetNotAfter(notAfter.UtcDateTime);

        // Randomly generate the public key
        AsymmetricCipherKeyPair subjectKeyPair = keyPairGenerator.GenerateKeyPair();
        certGenerator.SetPublicKey(subjectKeyPair.Public);

        // Generate the signed certificate
        ISignatureFactory signatureFactory = new Asn1SignatureFactory(SHA256RSASignatureAlgorithm, issuerPrivateKey ?? subjectKeyPair.Private, random);
        X509Certificate2 certificate = new X509Certificate2(certGenerator.Generate(signatureFactory).GetEncoded());

        // Include the private key with the response
        // ERROR HERE!
        certificate.PrivateKey = DotNetUtilities.ToRSA(subjectKeyPair.Private as RsaPrivateCrtKeyParameters);

        return certificate;
}

Этот код находится в библиотеке, предназначенной для. NET Standard 2.0, и библиотека является зависимостью двух разных приложений: одно целевое. NET Core 2.1 и другой таргетинг. NET Framework 4.7.2. Я считаю, что это нормально работает в приложении. NET Framework, но в приложении. NET Core Я получаю исключение с этим сообщением в указанной строке выше:

Операция не поддерживается на этой платформе.

Видимо это ожидаемое поведение в. NET Core . Мне известен метод CopyWithPrivateKey, упомянутый в этого вопроса , который, по идее, мне следует использовать. Однако этот метод не поддерживается в. NET Стандарт 2.0 (обратите внимание на ошибку в верхней части страницы, указывающую на перенаправление). Кроме того, приложение. NET Framework не может быть преобразовано в. NET Core в настоящий момент из-за некоторых других зависимостей. NET Framework. Согласно эта матрица ,. NET Стандарт 2.1 не поддерживается. NET Framework вообще, что означает Я не могу перейти на. NET Стандарт 2.1 и использовать CopyWithPrivateKey!

Как создать X509Certificate2 с закрытым ключом. NET Standard 2.0 таким образом, чтобы это было совместимо с. NET Core?

1 Ответ

1 голос
/ 22 января 2020

После долгих раскопок единственное решение, которое я нашел, - преобразовать сертификат в байтовый массив в формате PKCS12, добавить закрытый ключ и затем прочитать его обратно в объект X509Certificate2. Это отстой, но из того, что я могу сказать. NET Ядро единственный способ получить его с закрытым ключом - это либо позвонить CopyWithPrivateKey (недоступно в. NET Standard 2.0, как упомянуто в вопросе), либо загрузить данные PKCS12, которые содержат закрытый ключ. Следующий код может сделать это:

    private static X509Certificate2 AddPrivateKeyPlatformIndependent(Org.BouncyCastle.X509.X509Certificate bouncyCastleCert, AsymmetricKeyParameter privateKey)
    {
        string alias = bouncyCastleCert.SubjectDN.ToString();
        Pkcs12Store store = new Pkcs12StoreBuilder().Build();

        X509CertificateEntry certEntry = new X509CertificateEntry(bouncyCastleCert);
        store.SetCertificateEntry(alias, certEntry);

        // TODO: This needs extra logic to support a certificate chain
        AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(privateKey);
        store.SetKeyEntry(alias, keyEntry, new X509CertificateEntry[] { certEntry });

        byte[] certificateData;
        string password = GenerateRandomString();
        using (MemoryStream memoryStream = new MemoryStream())
        {
            store.Save(memoryStream, password.ToCharArray(), new SecureRandom());
            memoryStream.Flush();
            certificateData = memoryStream.ToArray();
        }

        return new X509Certificate2(certificateData, password, X509KeyStorageFlags.Exportable);
    }

Используйте это вместо двух последних строк в коде вопроса, и вместо создания X509Certificate2 с certGenerator используйте это для создания эквивалентного Bouncy Тип замка, чтобы передать ему: Org.BouncyCastle.X509.X509Certificate bouncyCastleCert = certGenerator.Generate(signatureFactory);


Поскольку документация по Bouncy Castle и сертификатам. NET в лучшем случае редкая, вот некоторые причуды, которые я нашел во время этого процесса:

  • Псевдонимы для записи сертификата и записи ключа в контейнере PKCS12 должны быть одинаковыми. В противном случае вы получите исключение «Набор ключей не существует» при попытке использовать закрытый ключ.

  • Вы должны использовать X509KeyStorageFlags.Exportable при загрузке массива байтов, в противном случае вы можете получить Исключение «Ключ недействителен для использования в указанном состоянии» в зависимости от того, как вы используете сертификат. Это также означает, что вы должны ввести пароль, потому что без него нет перегрузки, но вы можете использовать любую старую строку, поскольку это временный пароль.

...