dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.24k stars 4.73k forks source link

Cannot export RSACng private parameters #48472

Closed qidydl closed 3 years ago

qidydl commented 3 years ago

Description

For context: we're trying to use https://github.com/aws/amazon-s3-encryption-client-dotnet to do client-side encryption and decryption of S3 objects. The new version of that library needs to be able to export the private parameters from an RSA object in order to initialize a cipher. The problem can be easily reproduced without using that library at all, so it's not a problem with the AWS code.

If I try to export the private parameters from an RSA private key, I get the error "Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException : The requested operation is not supported". Using X509KeyStorageFlags.Exportable does not help, and the suggested workarounds in #36899 don't work either; trying to set the "Export Policy" property produces the same error, and several of the methods discussed in that issue don't seem to even exist (e.g. ExportPkcs8PrivateKey).

Configuration

Windows 10 x64 Visual Studio Professional 2019 version 16.8.5 .NET SDK 5.0.103 .NET Core runtime 2.1.25

Regression?

Unclear, I don't have any other environments to test in.

Other information

Certificate was generated using PowerShell:

$cert = New-SelfSignedCertificate `
    -CertStoreLocation Cert:\CurrentUser\My `
    -FriendlyName "Unit Testing" `
    -HashAlgorithm sha512 `
    -KeyExportPolicy Exportable `
    -KeyLength 4096 `
    -KeyUsage DigitalSignature `
    -NotAfter (Get-Date -Date "01/01/2300") `
    -Provider "Microsoft Software Key Storage Provider" `
    -Subject "Tests" `
    -Type DocumentEncryptionCert

$password = ConvertTo-SecureString "xxxxxx" -AsPlainText -Force
Export-PfxCertificate -Password $password -FilePath CNGTest.pfx -Cert $cert

Code to reproduce:

using (var privateCert = new X509Certificate2(privateCertFile, "xxxxxx", X509KeyStorageFlags.Exportable))
{
    var rsa = privateCert.GetRSAPrivateKey();
    var parms = rsa.ExportParameters(true);
}

Stack trace:

Message: 
    Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException : The requested operation is not supported
  Stack Trace: 
    CngKey.Export(CngKeyBlobFormat format)
    RSACng.ExportKeyBlob(Boolean includePrivateParameters)
    RSACng.ExportParameters(Boolean includePrivateParameters)
    AwsClientCryptoTests.TestClientSideEncryption() line 69
ghost commented 3 years ago

Tagging subscribers to this area: @bartonjs, @vcsjones, @krwq, @GrabYourPitchForks See info in area-owners.md if you want to be subscribed.

Issue Details
### Description For context: we're trying to use https://github.com/aws/amazon-s3-encryption-client-dotnet to do client-side encryption and decryption of S3 objects. The new version of that library needs to be able to export the private parameters from an RSA object in order to initialize a cipher. The problem can be easily reproduced without using that library at all, so it's not a problem with the AWS code. If I try to export the private parameters from an RSA private key, I get the error "Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException : The requested operation is not supported". Using `X509KeyStorageFlags.Exportable` does not help, and the suggested workarounds in #36899 don't work either; trying to set the "Export Policy" property produces the same error, and several of the methods discussed in that issue don't seem to even exist (e.g. `ExportPkcs8PrivateKey`). ### Configuration Windows 10 x64 Visual Studio Professional 2019 version 16.8.5 .NET SDK 5.0.103 .NET Core runtime 2.1.25 ### Regression? Unclear, I don't have any other environments to test in. ### Other information Certificate was generated using PowerShell: ```powershell $cert = New-SelfSignedCertificate ` -CertStoreLocation Cert:\CurrentUser\My ` -FriendlyName "Unit Testing" ` -HashAlgorithm sha512 ` -KeyExportPolicy Exportable ` -KeyLength 4096 ` -KeyUsage DigitalSignature ` -NotAfter (Get-Date -Date "01/01/2300") ` -Provider "Microsoft Software Key Storage Provider" ` -Subject "Tests" ` -Type DocumentEncryptionCert $password = ConvertTo-SecureString "xxxxxx" -AsPlainText -Force Export-PfxCertificate -Password $password -FilePath CNGTest.pfx -Cert $cert ``` Code to reproduce: ```C# using (var privateCert = new X509Certificate2(privateCertFile, "xxxxxx", X509KeyStorageFlags.Exportable)) { var rsa = privateCert.GetRSAPrivateKey(); var parms = rsa.ExportParameters(true); } ``` Stack trace: ``` Message: Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException : The requested operation is not supported Stack Trace: CngKey.Export(CngKeyBlobFormat format) RSACng.ExportKeyBlob(Boolean includePrivateParameters) RSACng.ExportParameters(Boolean includePrivateParameters) AwsClientCryptoTests.TestClientSideEncryption() line 69 ```
Author: qidydl
Assignees: -
Labels: `area-System.Security`, `untriaged`
Milestone: -
bartonjs commented 3 years ago

This is just a duplicate of #36899. PFX loading only sets the CNG "exportable" bit, it doesn't set "plaintext exportable".

ExportPkcs8PrivateKey was added in .NET Core 3.0. FWIW, 2.1 goes out of support in August (https://dotnet.microsoft.com/platform/support/policy/dotnet-core), upgrading will allow this scenario to work.

qidydl commented 3 years ago

It was not clear that this scenario is unsupported in .NET Core 2.1. Thanks for the information. If there are no workable alternatives then this issue can be closed.

bartonjs commented 3 years ago

Once you know you have an RSACng you can do some... messy... P/Invokes to work around things, in particular you can overwrite the key to make it be plaintext exportable:

https://stackoverflow.com/questions/57269726/x509certificate2-import-with-ncrypt-allow-plaintext-export-flag/57330499#57330499

vcsjones commented 3 years ago

For what it's worth, if you import in to the EphemeralKeySet, then changing the export policy with RSACng works while staying in netcoreapp2.1, at least on Windows 10.

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using (var privateCert = new X509Certificate2("CNGTest.pfx", "xxxxxx", X509KeyStorageFlags.EphemeralKeySet))
{
    var rsa = privateCert.GetRSAPrivateKey();

    if (rsa is RSACng { Key: CngKey key })
    {
        byte[] exportValue = new byte[] { 0x02, 0x00, 0x00, 0x00 }; // 0x02 DWORD in little endian
        key.SetProperty(new CngProperty("Export Policy", exportValue, CngPropertyOptions.None));
    }

    RSAParameters parameters = rsa.ExportParameters(true);
    Console.WriteLine(BitConverter.ToString(parameters.D));
}
bartonjs commented 3 years ago

Oh, yeah, or use EphemeralKeySet. ::whistles innocently::