如何查看RSA密钥容器的权限

时间:2022-10-20 18:22:55

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.

我以非管理员身份创建了一个RSA机器商店容器,并为自己分配了完全控制权,以及对其他帐户的读取权限。

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:

我希望能够以编程方式查看密钥容器的ACL。当我尝试使用下面的代码执行此操作时,即使我是密钥容器的所有者并具有完全控制权,我也会收到以下异常:

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.

我可以使用Windows资源管理器或CACLS查看权限,以查看C:\ Documents and Settings \ All Users \ ... \ Crypto \ RSA \ MachineKeys中的密钥文件,因此我的帐户似乎具有所需的权限。

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:

根据MSDN,CspKeyContainerInfo.CryptoKeySecurity属性:

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

获取一个CryptoKeySecurity对象,该对象表示容器的访问权限和审核规则。

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.

我找到了一种解决方法,即找到包含密钥容器的文件并检查其ACL。我对此并不完全满意,因为它取决于密码学类的未记录的实现细节(例如可能不适用于Mono),并且仍然对更好的解决方案感兴趣。

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

1 个解决方案

#1


1  

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

正如您在.NET Framework参考源中看到的那样,CspKeyContainerInfo.CryptoKeySecurity被硬编码以请求AccessControlSections.All。

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

除了使用CSP的内部实现细节来定位文件,这是可以理解的不可取的,你有两个选择。

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.

例如,这与列出关键容器必须采用相同的方法。这是理想的解决方案,因为它不依赖于加密服务提供程序或.NET Framework和运行时的内部实现细节。这和选项2之间的唯一区别是它不会尝试断言SeSecurityPrivilege(你会得到一个InvalidOperationException,好像该对象没有安全描述符),并且它将为所有其他错误抛出Win32Exception。我保持简单。

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.

您可以模拟CspKeyContainerInfo.CryptoKeySecurity的功能,但指定所需的AccessControlSections值。这依赖于.NET Framework和CLR内部的实现细节,很可能不适用于其他BCL实现和CLR,例如.NET Core。 .NET Framework和桌面CLR的未来更新也可能导致此选项中断。那说,它确实有效。

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 });
    }
}

#1


1  

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

正如您在.NET Framework参考源中看到的那样,CspKeyContainerInfo.CryptoKeySecurity被硬编码以请求AccessControlSections.All。

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

除了使用CSP的内部实现细节来定位文件,这是可以理解的不可取的,你有两个选择。

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.

例如,这与列出关键容器必须采用相同的方法。这是理想的解决方案,因为它不依赖于加密服务提供程序或.NET Framework和运行时的内部实现细节。这和选项2之间的唯一区别是它不会尝试断言SeSecurityPrivilege(你会得到一个InvalidOperationException,好像该对象没有安全描述符),并且它将为所有其他错误抛出Win32Exception。我保持简单。

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.

您可以模拟CspKeyContainerInfo.CryptoKeySecurity的功能,但指定所需的AccessControlSections值。这依赖于.NET Framework和CLR内部的实现细节,很可能不适用于其他BCL实现和CLR,例如.NET Core。 .NET Framework和桌面CLR的未来更新也可能导致此选项中断。那说,它确实有效。

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 });
    }
}