Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to view permissions for RSA Key Container

I've created an RSA Machine-Store container as a non-administrator, and assigned Full Control to myself, as well as read access to other accounts.

I want to be able to programatically view the ACL for the key container. When I attempt to do so with the code below, I get the following exception even though I am owner of the key container and have Full Control:

System.Security.AccessControl.PrivilegeNotHeldException: The process does not possess the 'SeSecurityPrivilege' privilege which is required for this operation.
   at System.Security.AccessControl.Privilege.ToggleState(Boolean enable)
   at System.Security.Cryptography.Utils.GetKeySetSecurityInfo(SafeProvHandle hProv, AccessControlSections accessControlSections)
   at System.Security.Cryptography.CspKeyContainerInfo.get_CryptoKeySecurity()
   ...

I can view the privileges by using Windows Explorer or CACLS to view the key file in C:\Documents and Settings\All Users\...\Crypto\RSA\MachineKeys, so it appears that my account has the required privilege.

The code I'm using is as follows:

CspParameters cp = new CspParameters();
cp.Flags = CspProviderFlags.NoPrompt | CspProviderFlags.UseExistingKey | CspProviderFlags.UseMachineKeyStore;
cp.KeyContainerName = containerName;

using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp))
{
    // PrivilegeNotHeldException thrown at next line while
    // dereferencing CspKeyContainerInfo.CryptoKeySecurity
    if (rsa.CspKeyContainerInfo.CryptoKeySecurity != null)
    {
        foreach (CryptoKeyAccessRule rule in rsa.CspKeyContainerInfo.CryptoKeySecurity.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)))
        {
           ... process rule
        }
    }
}

There's a question with a similar problem here, but I can't see any way to apply the answer to my situation.

According to MSDN, the CspKeyContainerInfo.CryptoKeySecurity property:

Gets a CryptoKeySecurity object that represents access rights and audit rules for a container.

I want an object that represents access rights but not audit rules (as I don't have the privilege for audit rules).

UPDATE

I've found a workaround, which is to locate the file containing the key container and inspect its ACL. I'm not entirely happy with this as it depends on undocumented implementation details of the cryptography classes (e.g. presumably wouldn't work on Mono), and would still be interested in a better solution.

... Initialize cp as above

CspKeyContainerInfo info = new CspKeyContainerInfo(cp);
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), 
    "Microsoft\\Crypto\\RSA\\MachineKeys\\" + info.UniqueKeyContainerName);
FileSecurity fs = new FileInfo(path).GetAccessControl(AccessControlSections.Access);
foreach (FileSystemAccessRule rule in fs.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)))
{
    ... process rules
}
like image 358
Joe Avatar asked Oct 05 '11 09:10

Joe


People also ask

Where is the RSA key container location?

Windows has a cryptographic key store, and it is simply located in a folder on your hard drive. On my Windows 10 machine, this path is C:\ProgramData\Microsoft\Crypto and inside that folder, there are various other folders for each key type. In this example, we will be looking at the RSA\MachineKeys subfolders.

How do I export an RSA key container?

Exporting an RSA Key Container To export an RSA key container to an XML file, you can use the Aspnet_regiis.exe tool with the –px switch. You can use the XML file as backup for the RSA key container or to import the RSA key container on a different server.


1 Answers

As you can see in the .NET Framework reference source, CspKeyContainerInfo.CryptoKeySecurity is hardcoded to ask for AccessControlSections.All.

Besides using internal implementation details of CSP to locate the file, which is understandably undesirable, you have two options.

Option 1: Reimplement from scratch

This is the same tack you must take to list key containers, for example. This is the ideal solution because it does not rely on internal implementation details of either the cryptographic service provider or the .NET Framework and runtime. The only difference between this and option 2 is that it will not attempt to assert the SeSecurityPrivilege (you'll get an InvalidOperationException as though the object has no security descriptor), and it will throw Win32Exception for all other errors. I kept it simple.

Usage:

var cryptoKeySecurity =
    GetCryptoKeySecurity(containerName, true, AccessControlSections.All & ~AccessControlSections.Audit);

Implementation:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using Microsoft.Win32.SafeHandles;

public static class CryptographicUtils
{
    public static CryptoKeySecurity GetCryptoKeySecurity(string containerName, bool machine, AccessControlSections sections)
    {
        var securityInfo = (SecurityInfos)0;

        if ((sections & AccessControlSections.Owner) != 0) securityInfo |= SecurityInfos.Owner;
        if ((sections & AccessControlSections.Group) != 0) securityInfo |= SecurityInfos.Group;
        if ((sections & AccessControlSections.Access) != 0) securityInfo |= SecurityInfos.DiscretionaryAcl;
        if ((sections & AccessControlSections.Audit) != 0) securityInfo |= SecurityInfos.SystemAcl;

        if (!CryptAcquireContext(
            out CryptoServiceProviderHandle provider,
            containerName,
            null,
            PROV.RSA_FULL,
            machine ? CryptAcquireContextFlags.MACHINE_KEYSET : 0))
        {
            throw new Win32Exception();
        }
        using (provider)
        {
            var size = 0;
            if (!CryptGetProvParam(provider, PP.KEYSET_SEC_DESCR, null, ref size, securityInfo))
                throw new Win32Exception();

            if (size == 0) throw new InvalidOperationException("No security descriptor available.");

            var buffer = new byte[size];

            if (!CryptGetProvParam(provider, PP.KEYSET_SEC_DESCR, buffer, ref size, securityInfo))
                throw new Win32Exception();

            return new CryptoKeySecurity(new CommonSecurityDescriptor(false, false, buffer, 0));
        }
    }


    #region P/invoke
    // ReSharper disable UnusedMember.Local
    // ReSharper disable ClassNeverInstantiated.Local
    // ReSharper disable InconsistentNaming

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool CryptAcquireContext(out CryptoServiceProviderHandle hProv, string pszContainer, string pszProvider, PROV dwProvType, CryptAcquireContextFlags dwFlags);

    private sealed class CryptoServiceProviderHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private CryptoServiceProviderHandle() : base(true)
        {
        }

        protected override bool ReleaseHandle()
        {
            return CryptReleaseContext(handle, 0);
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags);
    }

    private enum PROV : uint
    {
        RSA_FULL = 1
    }

    [Flags]
    private enum CryptAcquireContextFlags : uint
    {
        VERIFYCONTEXT = 0xF0000000,
        NEWKEYSET = 0x8,
        DELETEKEYSET = 0x10,
        MACHINE_KEYSET = 0x20,
        SILENT = 0x40,
        DEFAULT_CONTAINER_OPTIONAL = 0x80
    }


    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    private static extern bool CryptGetProvParam(CryptoServiceProviderHandle hProv, PP dwParam, [Out] byte[] pbData, ref int dwDataLen, SecurityInfos dwFlags);

    private enum PP : uint
    {
        KEYSET_SEC_DESCR = 8
    }

    // ReSharper restore UnusedMember.Local
    // ReSharper restore ClassNeverInstantiated.Local
    // ReSharper restore InconsistentNaming
    #endregion
}

Option 2: Reflection

You could simulate what the CspKeyContainerInfo.CryptoKeySecurity does, but specify whatever value of AccessControlSections you want. This relies on implementation details internal to the .NET Framework and CLR and will very likely not work on other BCL implementations and CLRs, such as .NET Core's. Future updates to the .NET Framework and desktop CLR could also render this option broken. That said, it does work.

Usage:

var cryptoKeySecurity =
    GetCryptoKeySecurity(cp, AccessControlSections.All & ~AccessControlSections.Audit);

Implementation:

public static CryptoKeySecurity GetCryptoKeySecurity(CspParameters parameters, AccessControlSections sections)
{
    var mscorlib = Assembly.Load("mscorlib");
    var utilsType = mscorlib.GetType("System.Security.Cryptography.Utils", true);

    const uint silent = 0x40;
    var args = new[]
    {
        parameters,
        silent,
        mscorlib.GetType("System.Security.Cryptography.SafeProvHandle", true)
            .GetMethod("get_InvalidHandle", BindingFlags.Static | BindingFlags.NonPublic)
            .Invoke(null, null)
    };

    if ((int)utilsType
            .GetMethod("_OpenCSP", BindingFlags.Static | BindingFlags.NonPublic)
            .Invoke(null, args) != 0)
    {
        throw new CryptographicException("Cannot open the cryptographic service provider with the given parameters.");
    }
    using ((SafeHandle)args[2])
    {
        return (CryptoKeySecurity)utilsType
            .GetMethod("GetKeySetSecurityInfo", BindingFlags.Static | BindingFlags.NonPublic)
            .Invoke(null, new[] { args[2], sections });
    }
}
like image 151
jnm2 Avatar answered Oct 24 '22 04:10

jnm2