ganvin
ganvin

Reputation: 179

Set Certificate PrivateKey Permissions in .NET 5

I had a requirement where I need to set permissions for the Certificate private key, I used below method (SetCertificatePrivateKeyPermissions) which was working fine with .Net framework 4.7.2 but now I had to migrate the project framework to .Net 5, Because of this project framework upgradation this existing code is breaking.

RSACryptoServiceProvider and CspParameters classes refers to System.Security.Cryptography.Csp.dll (C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\5.0.0\ref\net5.0\System.Security.Cryptography.Csp.dll), with this dll reference I am facing 2 issues with the existing code

  1. During the conversion of certificate.PrivateKey to RSACryptoServiceProvider it is returning NULL.

  2. While creating an instance of CspParameters I am not able to assign the CryptoKeySecurity value from rsa as this property is not available/supported in both RSACryptoServiceProvider and CspParameters classes of .NET 5 while it was supported in .NET 4.7.2 version.

Please let me know how to handle this issue ? or is there any alternative solution where I can set permissions for the Certificate private key in .NET 5 ?

Code snippet:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

public static void SetCertificatePrivateKeyPermissions(X509Certificate2 certificate, IdentityReference account, Operation operation)
{
  var rsa = certificate.PrivateKey as RSACryptoServiceProvider;
  if (rsa != null)//ISSUE 1: rsa is NULL
  {
    var cspParams = new CspParameters(rsa.CspKeyContainerInfo.ProviderType, rsa.CspKeyContainerInfo.ProviderName, rsa.CspKeyContainerInfo.KeyContainerName)
    {
      Flags = CspProviderFlags.UseExistingKey | CspProviderFlags.UseMachineKeyStore,
      CryptoKeySecurity = rsa.CspKeyContainerInfo.CryptoKeySecurity//ISSUE 2: There is no CryptoKeySecurity property present
    };

    switch (operation)
    {
      case Operation.Add:
        cspParams.CryptoKeySecurity.AddAccessRule(new CryptoKeyAccessRule(account, CryptoKeyRights.GenericRead, AccessControlType.Allow));
        break;
      case Operation.Remove:
        cspParams.CryptoKeySecurity.RemoveAccessRule(new CryptoKeyAccessRule(account, CryptoKeyRights.GenericAll, AccessControlType.Allow));
        break;
      default:
        throw new ArgumentException("Unhandled operation type");
    }

    using (var rsa2 = new RSACryptoServiceProvider(cspParams))
    {
      
    }
  }
}

Upvotes: 1

Views: 2928

Answers (2)

Crypt32
Crypt32

Reputation: 13974

.NET 5 doesn't have CryptoKeySecurity, because it is Windows-specific and hasn't ported yet (if ever planned to port). Couple words on your issues:

  1. var rsa = certificate.PrivateKey as RSACryptoServiceProvider; -- this construction can be considered obsolete and deprecated since .NET Framework 4.6. Under no condition you should use RSACryptoServiceProvider if you are on 4.6+. Instead, you should access X509Certificate2 class extension methods only to retrieve public/private key handles. More details in my blog post: Accessing and using certificate private keys in .NET Framework/.NET Core.

  2. When using X509Certificate2.GetRSAPrivateKey() extension method on Windows, it will return an instance of RSACng class that contains Key property which is of type CngKey. Then use GetProperty and SetProperty methods to read and write Security Descr property. You can check for Security Descr Support property if key supports ACL (1 if supports, any other value means the key doesn't support ACL).

    When reading and writing these properties, you have to include SecurityInfos in the options parameter. This is explained in the documentation for NCryptGetProperty (which is called by GetProperty under the covers).

    privateKey.Key.GetProperty("Security Descr", CngPropertyOptions.None | (CngPropertyOptions)SecurityInfos.DiscretionaryAcl)
    

Upvotes: 5

psurikov
psurikov

Reputation: 3478

It is possible to get the container name, and then based on that determine the full path to the file and apply the account permissions:

private static void SetupFileSecurity(X509Certificate2 certificate)
{
    var privateKey = RSACertificateExtensions.GetRSAPrivateKey(certificate);
    var rsaCngKey = privateKey as RSACng;
    if (rsaCngKey == null)
        throw new Exception("Expecting RSA CNG key.");
    var cngKey = rsaCngKey.Key;
    var keyName = cngKey.UniqueName;
    if (keyName == null)
        throw new Exception("The key cannot be ephemeral.");
    var keysDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"Microsoft\Crypto\Keys");
    var filePath = Path.Combine(keysDirectory, keyName);
    AddFileSystemPermissions(account, filePath, FileSystemRights.FullControl);
}

This worked well as a test, but I'd be cautious for anything else.

Upvotes: 1

Related Questions