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

发布于 2024-12-08 09:57:42 字数 2627 浏览 0 评论 0原文

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

我希望能够以编程方式查看密钥容器的 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()
   ...

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

我正在使用的代码如下:

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

这里有类似问题的问题,但我看不到任何方法将答案应用于我的情况。

根据 MSDN CspKeyContainerInfo.CryptoKeySecurity 属性:

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

我想要一个代表访问权限但不代表审核规则的对象(因为我没有审核规则的权限)。

更新

我找到了一个解决方法,即找到包含密钥容器的文件并检查其 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
}

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
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

追我者格杀勿论 2024-12-15 09:57:42

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

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

选项 1:从头开始重新实现

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

用法:

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

实现:

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
}

选项 2:反射

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

用法:

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

实施:

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

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 });
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文