Search code examples
c#encryptionuwpx509certificate2

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 :

  • When I use a certificate registered in the user's personal store :

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

  • When I use a certificate from a USB key :

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.


Solution

  • 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.