Thomas Ferro
Thomas Ferro

Reputation: 2442

Encrypt data with X509 certificate's private key in UWP

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.

Upvotes: 0

Views: 1381

Answers (1)

Thomas Ferro
Thomas Ferro

Reputation: 2442

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.

Upvotes: 1

Related Questions