I'm displaying to the user every available certificates in his personal store, fetched with the following method :
public List<X509Certificate2> GetPersonalCertificates()
{
var certificates = new List<X509Certificate2>();
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.MaxAllowed);
foreach (var certificate in store.Certificates)
{
if (certificate != null && certificate.HasPrivateKey)
{
certificates.Add(certificate);
}
}
}
return certificates;
}
When the user select one of those certificates, a sample file is loaded and given to the following method :
public byte[] EncryptFile(X509Certificate2 certificate, byte[] fileToEncrypt)
{
if (certificate == null || hashToSign == null) return null;
byte[] encryptedFile = null;
try
{
using (var privateKey = certificate.GetRSAPrivateKey())
{
encryptedFile = privateKey.Encrypt(hashToSign, System.Security.Cryptography.RSAEncryptionPadding.OaepSHA256);
}
}
catch (Exception e)
{
// Error management
}
return encryptedFile;
}
This method is supposed to encrypt the data with the RSA private key using the SHA256 algorithm, but it keeps falling into the catch statement.
The error that I get depends on the certificate that I use :
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException:
Invalid parameter
at System.Security.Cryptography.RSACng.EncryptOrDecrypt(SafeNCryptKeyHandle key, Byte[] input, AsymmetricPaddingMode paddingMode, Void* paddingInfo, EncryptOrDecryptAction encryptOrDecrypt)
at System.Security.Cryptography.RSACng.EncryptOrDecrypt(Byte[] data, RSAEncryptionPadding padding, EncryptOrDecryptAction encryptOrDecrypt)
at System.Security.Cryptography.RSACng.Encrypt(Byte[] data, RSAEncryptionPadding padding)
at My method
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException:
The requested operation is not supported
at Security.Cryptography.RSACng.EncryptOrDecrypt(SafeNCryptKeyHandle key, Byte[] input, AsymmetricPaddingMode paddingMode, Void* paddingInfo, EncryptOrDecryptAction encryptOrDecrypt)
at System.Security.Cryptography.RSACng.EncryptOrDecrypt(Byte[] data, RSAEncryptionPadding padding, EncryptOrDecryptAction encryptOrDecrypt)
at System.Security.Cryptography.RSACng.Encrypt(Byte[] data, RSAEncryptionPadding padding)
at My method
I've also tried the private key's SignData method, resulting in the same exceptions :
encryptedFile = privateKey.SignData(hashToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
Am I missing something ?
Please note that, as I'm working in an UWP application, I can only access some of the System.Security namespace.
I found a solution to my issues, it was due to these two mistakes :
First, the encryption method to use here isn't Encrypt but SignData, since I want to electronically sign the data.
Second, in order to make a valid signature, the certificate needs to contain the Non-Repudation extension. To check that, I've added this method :
private bool _canUsingCertificateForSignData(X509ExtensionCollection extensions)
{
if (extensions != null)
{
foreach (var ext in extensions)
{
if (ext.GetType().Equals(typeof(X509KeyUsageExtension)))
{
if (((X509KeyUsageExtension)ext).KeyUsages.HasFlag(X509KeyUsageFlags.NonRepudiation))
{
return true;
}
}
}
}
return false;
}
I now fetch the certificates using the following :
public List<X509Certificate2> GetPersonalCertificates()
{
var certificates = new List<X509Certificate2>();
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.MaxAllowed);
foreach (var certificate in store.Certificates)
{
if (certificate != null && certificate.HasPrivateKey && _canUsingCertificateForSignData(certificate.Extensions))
{
certificates.Add(certificate);
}
}
}
return certificates;
}
One thing that I still don't understand is why the SignData method doesn't throw any exception and did sign the data when the certificate is fetched from a pfx file and not from the store.