Подтвердить что ты не робот

Как просмотреть разрешения для контейнера ключей RSA

Я создал контейнер RSA Machine Store в качестве не-администратора и назначил полный контроль для себя, а также доступ для чтения к другим учетным записям.

Я хочу иметь возможность программно просматривать 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, который представляет права доступа и правила аудита для контейнера.

Я хочу объект, представляющий права доступа, но не правила аудита (поскольку у меня нет привилегии для правил аудита).

UPDATE

Я нашел обходное решение, которое заключается в том, чтобы найти файл, содержащий контейнер ключей, и проверить его 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
}
4b9b3361

Ответ 1

Как вы можете видеть в исходном источнике .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 });
    }
}