Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Importing RSA public key from XML using RSACryptoServiceProvider sets property PublicOnly to false

I am creating an tool to manage RSA Key Pairs on Windows since there is no built-in functionality for this and we make extensive use of the RSA encryption features in .NET.

The tool provides various functions such as listing existing RSA key pairs, creating new keys, deleting keys, exporting keys and importing keys from a previously created XML export (exported using ASPNet_RegIIS.exe).

I have all the functionality working apart from importing only the public keys for encryption operations only. When the key pairs are originally created on the servers, both public and public/private exports are made to xml using aspnet_regiis.exe.

I am keen to provide the option to encrypt config files on other machines to prevent the private keys from being distributed unless absolutely necessary.

Each time I import the public key from a previoulsy exported XML block the PublicOnly property of the RSACryptoServiceProvider is set to false indicating that the key pair has a public and private key. I have confirmed the xml does not contain the private key information therefore the problem is not within the xml file.

The problem seems to occur when using the CspParameters object and specifying the Flags as CspProviderFlags.UseMachineKeyStore. If you construct the RSACryptoServiceProvider without specifying any Csp parameters and then import the key from xml, the PublicOnly property is correctly set to false.

String xmlText = File.ReadAllText(filePath);
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
csp.FromXmlString(xmlText);
//csp.PublicOnly equals true;

However since I need to give it the key container a name so it can be used later for encryption operations I am forced to use the CspParameters object when constructing a RSACryptoServiceProvider as there is no other way to name the key container.

String xmlText = File.ReadAllText(filePath);
CspParameters cspParams = new CspParameters();
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
cspParams.KeyContainerName = keyContainerName;
RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
csp.PersistKeyInCsp = true;
csp.FromXmlString(xmlText);
//csp.PublicOnly equals false;

I have tried a variety of versions of this code however the problem remains. I note there are similar questions such as RSA Encryption public key not returned from container? however I believe this issue is different and no satisfactory answer has ever been given.

The question is therefore how can I import an RSA public key from XML and give the key container a name whilst ensuring only a public key pair exists in the container?

EDIT

Further research around this area also shows a problem when importing the full key from xml and also setting the flags to allow exporting of the key.

CspProviderFlags.UseArchivableKey;

When this flag is specified on the CSP Parameter object an exception of type "Invalid flags specified" is thrown at the line csp.FromXmlString(xmlText);

I really cannot explain this. The key was created and previously exported to XML, surely if the key has been previously exported it should be possible to import it AND allow it to be exported again?

I have done a lot of research on this but just cannot see the answer to either of these problems.

I have tried changing the csp provider type from PROV_RSA_FULL to PROV_RSA_AES as I believe this is now the default and I considered the keys may have originally been created using this instead of PROV_RSA_FULL but this has made no difference.

like image 984
CarlR Avatar asked Jan 02 '15 14:01

CarlR


1 Answers

When importing key parameters (either from XML or from RSAParameters) RSACryptoServiceProvider will detach from the current key if public parameters are provided; it only replaces the contents of the key in the key container if private parameters are imported.

http://referencesource.microsoft.com/#mscorlib/system/security/cryptography/rsacryptoserviceprovider.cs,b027f6909aa0a6d1

The ImportCspBlob takes a different path, but ultimately hits the same conclusion: if a public-only blob is being imported it detaches to an ephemeral key. http://referencesource.microsoft.com/#mscorlib/system/security/cryptography/utils.cs,754f1f4055bba611

It seems like the underlying Windows Cryptography APIs don't allow the storage of public keys (aside from when they're being stored with their private keys).

The CNG documentation at https://msdn.microsoft.com/en-us/library/windows/desktop/bb427412(v=vs.85).aspx says

For BCryptExportKey to create a persisted key pair, the input key BLOB must contain a private key. Public keys are not persisted.

One assumes that they meant BCryptImportKey, but the "Public keys are not persisted" feels authoritative.

For CAPI I can't find anything quite as straightforward.

The best that I can find is the CAPI definition of 'key container' (https://msdn.microsoft.com/en-us/library/windows/desktop/ms721590(v=vs.85).aspx#_security_key_container_gly):

key container

A part of the key database that contains all the key pairs (exchange and signature key pairs) belonging to a specific user. Each container has a unique name that is used when calling the CryptAcquireContext function to get a handle to the container.

This definition only references "key pairs", suggesting that "public keys" might get rejected by the storage layer. Given the RSACryptoServiceProvider behaviors, this seems fairly likely.

like image 164
bartonjs Avatar answered Nov 06 '22 16:11

bartonjs