Примечание. Ответ может быть получен либо на VB.NET, либо на C#. У меня нет предпочтений в отношении этих вопросов и ответов.
Я использую приведенный ниже код на основе Certes и Bouncy Castle для завершения, создания и импорта сертификата TLS Let's Encrypt в мое хранилище CurrentUser. Проблема в том, что, хотя я помечаю закрытый ключ как экспортируемый, он не найден в полученном сертификате.
Как мы видим, возможность экспорта закрытого ключа отключена, хотя в магазине указано, что ключ присутствует.
Я проработал этот пример, и код, кажется, делает почти то же самое, что и мой. Единственное заметное отличие заключается в том, что он загружает RsaKeyPairGenerator для создания новой пары ключей. В моем случае это неприменимо, поскольку я не создаю собственный сертификат и у меня уже есть массивы сертификатов и ключей Byte.
Я что-то упустил или делаю что-то не так в своем коде? Я этого не замечаю. Как создать и сохранить экземпляр X509Certificate2, закрытый ключ которого виден и доступен для экспорта?
Вот мой код:
Public Class Tls
Public Shared Async Function ImportAsync(NewCertificate As X509Certificate2) As Task(Of Result)
Dim oCollection As X509Certificate2Collection
Dim oQuery As Func(Of X509Certificate2, Boolean)
Await Task.CompletedTask
oQuery = Function(OldCertificate) OldCertificate.Subject = NewCertificate.Subject
Using oStore As New X509Store(StoreName.My, StoreLocation.CurrentUser)
oStore.Open(OpenFlags.ReadWrite)
oCollection = New X509Certificate2Collection(oStore.Certificates.Where(oQuery).ToArray)
oStore.RemoveRange(oCollection)
oStore.Add(NewCertificate)
End Using
Return Result.Ok
End Function
Public Shared Async Function GenerateAsync(Order As IOrderContext, CityCode As String) As Task(Of Result(Of X509Certificate2))
Dim oCertificateChain As CertificateChain
Dim oCertificate As X509Certificate2
Dim oPrivateKey As IKey
Dim oInfo As CsrInfo
oInfo = New CsrInfo With {
.OrganizationUnit = "MyUnit",
.Organization = "MyOrg",
.CountryName = "MyCountry",
.Locality = "MyTown",
.State = "MyState"
}
oPrivateKey = KeyFactory.NewKey(KeyAlgorithm.RS256)
oCertificateChain = Await Order.Generate(oInfo, oPrivateKey)
oCertificate = GetSignedCertificate(oCertificateChain.Certificate.ToDer, oPrivateKey.ToDer, CityCode)
Return oCertificate.ToResult
End Function
Private Shared Function GetSignedCertificate(Certificate As Byte(), PrivateKey As Byte(), Password As String) As X509Certificate2
Dim oUnsignedCertificate As X509.X509Certificate
Dim oSignedCertificate As X509Certificate2
Dim oCertificateParser As X509CertificateParser
Dim oCertificateEntry As X509CertificateEntry
Dim oPrivateKey As PrivateKeyInfo
Dim oParameter As AsymmetricKeyParameter
Dim oKeyEntry As AsymmetricKeyEntry
Dim oPfxStore As Pkcs12Store
Dim sSubject As String
Dim oRandom As SecureRandom
Dim eFlags As X509KeyStorageFlags
' Prepare the private key
oPrivateKey = PrivateKeyInfo.GetInstance(PrivateKey)
oParameter = PrivateKeyFactory.CreateKey(oPrivateKey)
oKeyEntry = New AsymmetricKeyEntry(oParameter)
oRandom = New SecureRandom
eFlags = X509KeyStorageFlags.Exportable Or X509KeyStorageFlags.PersistKeySet Or X509KeyStorageFlags.UserKeySet
' Convert to PKCS#12 format
oCertificateParser = New X509CertificateParser
oUnsignedCertificate = oCertificateParser.ReadCertificate(Certificate)
oCertificateEntry = New X509CertificateEntry(oUnsignedCertificate)
sSubject = oUnsignedCertificate.SubjectDN.ToString
oPfxStore = New Pkcs12Store
oPfxStore.SetCertificateEntry(sSubject, oCertificateEntry)
oPfxStore.SetKeyEntry($"{sSubject}_key", oKeyEntry, {oCertificateEntry})
Using oStream As New MemoryStream
oPfxStore.Save(oStream, Password.ToCharArray, oRandom)
oSignedCertificate = New X509Certificate2(oStream.ToArray, Password, eFlags)
End Using
Return oSignedCertificate
End Function
End Class

После долгого сбора фрагментов из различных источников (слишком много, чтобы их можно было запомнить) я пришел к следующему. Он не включает в себя так много вещей из «Надувного замка», но он работает.
Private Shared Function GetSignedCertificate(Certificate As Byte(), PrivateKey As Byte()) As X509Certificate2
Dim oSecondaryRsa As RSACryptoServiceProvider
Dim oCertificate As X509Certificate2
Dim oPrimaryRsa As RSACryptoServiceProvider
Dim oParameters As CspParameters
Dim oPrivateKey As PrivateKeyInfo
Dim oParameter As AsymmetricKeyParameter
Dim oKeyEntry As AsymmetricKeyEntry
Dim eFlags As X509KeyStorageFlags
oPrivateKey = PrivateKeyInfo.GetInstance(PrivateKey)
oParameter = PrivateKeyFactory.CreateKey(oPrivateKey)
oKeyEntry = New AsymmetricKeyEntry(oParameter)
oSecondaryRsa = DotNetUtilities.ToRSA(DirectCast(oKeyEntry.Key, RsaPrivateCrtKeyParameters))
oParameters = New CspParameters With
{
.KeyContainerName = Guid.NewGuid.ToString.ToUpperInvariant,
.ProviderType = 1,
.Flags = CspProviderFlags.UseMachineKeyStore
}
oPrimaryRsa = New RSACryptoServiceProvider(oParameters)
oPrimaryRsa.ImportCspBlob(oSecondaryRsa.ExportCspBlob(True))
oPrimaryRsa.PersistKeyInCsp = True
eFlags = X509KeyStorageFlags.Exportable Or X509KeyStorageFlags.PersistKeySet Or X509KeyStorageFlags.UserKeySet
oCertificate = New X509Certificate2(Certificate, String.Empty, eFlags)
Return oCertificate.CopyWithPrivateKey(oPrimaryRsa)
End Function