0
votes

I have the following code which is used for (en/de)crypting a key which in turn is used for encrypting strings that are placed in clients headers some of which the application may cache. (they contain senstive information). The application in this case is ASP .Net core 2.0 running on IIS.

The encryption using the public key works just fine but the exception is thrown when i try to decrypt.

    private string DecryptKey(string key)
    {
        if (this.Certificate == null || string.IsNullOrEmpty(key))
            throw new Exception("A x509 certificate and string for decryption must be provided");

        // Get the string as bytes
        var encryptedBytes = Convert.FromBase64String(key);

        if (!Certificate.HasPrivateKey)
            throw new Exception("x509 certificate does not contain a private key for decryption");

        using (var rsa = Certificate.GetRSAPrivateKey())
        {
            var result = rsa.Decrypt(encryptedBytes, RSAEncryptionPadding.OaepSHA512); // **Exception is thrown here**
            return ASCIIEncoding.ASCII.GetString(result);
        }
    }

    private string EncryptKey(string key)
    {
        if (this.Certificate == null || string.IsNullOrEmpty(key))
            throw new Exception("A x509 certificate and string for decryption must be provided");

        // Get the string as bytes
        var bytes = ASCIIEncoding.ASCII.GetBytes(key);

        if (!Certificate.HasPrivateKey)
            throw new Exception("x509 certificate does not contain a private key for decryption");

        using (var rsa = Certificate.GetRSAPrivateKey())
        {
            var result = rsa.Encrypt(bytes, RSAEncryptionPadding.OaepSHA512);
            return Convert.ToBase64String(result);
        }
    }

    /// <summary>
    /// Returns a X509Certificate2 with the given name
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    protected X509Certificate2 GetX509Certificate(string name)
    {
        // Get the certificate store for the current user.
        var store = new X509Store(StoreLocation.LocalMachine);

        try
        {
            store.Open(OpenFlags.ReadOnly);

            // Place all certificates in an X509Certificate2Collection object.
            var certCollection = store.Certificates;

            var currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
            var signingCert = currentCerts.Find(X509FindType.FindBySubjectDistinguishedName, name, false);

            if (signingCert.Count == 0)
                return null;

            return signingCert[0];
        }
        finally
        {
            store.Close();
        }
    }

Exception : Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException: 'Access denied'

Here is the stack:

        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.Decrypt(Byte[] data, RSAEncryptionPadding padding)
        at VmeApi.Extensions.KeyEncryptService.DecryptKey(String key) in C:\Users\kevom\Source\Repos\VME\VME Management\Management Core Api\Extensions\AppSettings\KeyEncryptService.cs:line 69
        at VmeApi.Extensions.KeyEncryptService..ctor(String certName, String encryptedKey) in C:\Users\kevom\Source\Repos\VME\VME Management\Management Core Api\Extensions\AppSettings\KeyEncryptService.cs:line 31
        at Management_Core_Api.Startup.ConfigureServices(IServiceCollection services) in C:\Users\kevom\Source\Repos\VME\VME Management\Management Core Api\Startup.cs:line 83

I have tried giving my account, IUser Account and network service full access to the certificate's private key using mmc and restarting the OS but that didn't do anything.

1
Are you able to see if the can find the private key or not? - mahlatse
Are you re-importing the certificate ever? If so, that will reset the permissions. Also, it's possible that the private key is marked as signature only (no decryption), which would (unfortunately) give the same "access denied" - bartonjs
@mahlatse It has a private key since 'var rsa = Certificate.GetRSAPrivateKey()' does not throw NullReferenceException. - kelvinmac
@bartonjs No i am not reimporting the certificate, its imported once at the start of the application and an instance is saved, (if thats what you mean). Otherwise, Its saved on to the Localmachine's cerficate collection; and no it is not marked as signature only. - kelvinmac
By "import" I mean "read it from some source and add it to an X509Store". - bartonjs

1 Answers

1
votes

This might be a bit long, so I will put it in as an answer, since the formatting as a comment might not be sufficient,

Try checking is the private key is accessible using the following code

private static string FindKeyLocation(string keyFileName)
        {
            string firstLocation =
            Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
            string secondLocation = firstLocation + @"\Microsoft\Crypto\RSA\MachineKeys";
            string[] textArray1 = Directory.GetFiles(secondLocation, keyFileName);
            if (textArray1.Length > 0)
            {
                return secondLocation;
            }
            string thirdLocation =
            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            string forthLocation = thirdLocation + @"\Microsoft\Crypto\RSA\";
            textArray1 = Directory.GetDirectories(forthLocation);
            if (textArray1.Length > 0)
            {
                foreach (string fifthLocation in textArray1)
                {
                    textArray1 = Directory.GetFiles(fifthLocation, keyFileName);
                    if (textArray1.Length != 0)
                    {
                        return fifthLocation;
                    }
                }
            }
            return "Private key exists but is not accessible";
        }

you would use the code in by specifying the certificate fileName as

RSACryptoServiceProvider rsa = cert.PrivateKey as RSACryptoServiceProvider;

            if (rsa != null)
            {
                string keyfilepath =
                    FindKeyLocation(rsa.CspKeyContainerInfo.UniqueKeyContainerName);
            }

This bit of code maybe can shed some light on why the decrypt might not work, the full method to add a user to a certificate in code would look something like this

private static void AddAccessToCertificate(X509Certificate2 cert, string user)
        {
            RSACryptoServiceProvider rsa = cert.PrivateKey as RSACryptoServiceProvider;

            if (rsa != null)
            {
                string keyfilepath =
                    FindKeyLocation(rsa.CspKeyContainerInfo.UniqueKeyContainerName);

                FileInfo file = new FileInfo(keyfilepath + "\\" +
                    rsa.CspKeyContainerInfo.UniqueKeyContainerName);

                FileSecurity fs = file.GetAccessControl();

                NTAccount account = new NTAccount(user);
                fs.AddAccessRule(new FileSystemAccessRule(account,
                FileSystemRights.FullControl, AccessControlType.Allow));

                file.SetAccessControl(fs);
            }
        }