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