9

In the C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys directory there's an enumeration of Key Containers. The naming convention is <uniqueGUID>_<staticGUID> and I presume the <staticGUID> to be a machine identifier. Ultimately I want to be able to pair the Key Container with it's respective cert so I can target specific Key Files for ACL's. To do that I need to know how the <uniqueGUID> is derived and how it relates to certificates.

The Microsoft resources I've checked so far haven't elucidated an answer, but are great for reference:

Understanding Machine-Level and User-Level RSA Key Containers (IIS reference)

How to: Change the Security Permissions for the MachineKeys Directory

nobody
  • 190
  • 8
Colyn1337
  • 2,387
  • 2
  • 22
  • 38

3 Answers3

14

To solve your problem of finding which certificate goes with which key file for the purpose of modifying file system ACLs on the private key files, use this:

PS C:\Users\Ryan> $Cert = Get-Item Cert:\LocalMachine\My\2F6CB7D56BAA752BCCC0829DD829C0E2662FA1C6    

PS C:\Users\Ryan> $Cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName

fad662b360941f26a1193357aab3c12d_03f917b5-cb8b-45bd-b884-41c139a66ff7

The file naming convention is x_y, where x is a random GUID to uniquely identify the key, and y is the machine GUID found at HKLM\SOFTWARE\Microsoft\Cryptography.

Some of these unique identifiers are well-known, such as some of these IIS ones:

6de9cb26d2b98c01ec4e9e8b34824aa2_GUID      iisConfigurationKey

d6d986f09a1ee04e24c949879fdb506c_GUID      NetFrameworkConfigurationKey

76944fb33636aeddb9590521c2e8815a_GUID      iisWasKey

But others are generated randomly.

Note that this information only applies to "Local Computer" or "Machine" certificates/keys. User certificates are stored in the corresponding user-specific locations on the file system and registry.

Ryan Ries
  • 55,011
  • 9
  • 138
  • 197
6

Ryan Ries provided only partial solution, because it won't work on CNG keys. The following code will retrieve container name (hence, file name too) for CNG keys:

$signature = @"
[DllImport("Crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool CertGetCertificateContextProperty(
    IntPtr pCertContext,
    uint dwPropId,
    IntPtr pvData,
    ref uint pcbData
);
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
public struct CRYPT_KEY_PROV_INFO {
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pwszContainerName;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pwszProvName;
    public uint dwProvType;
    public uint dwFlags;
    public uint cProvParam;
    public IntPtr rgProvParam;
    public uint dwKeySpec;
}
[DllImport("ncrypt.dll", SetLastError = true)]
public static extern int NCryptOpenStorageProvider(
    ref IntPtr phProvider,
    [MarshalAs(UnmanagedType.LPWStr)]
    string pszProviderName,
    uint dwFlags
);
[DllImport("ncrypt.dll", SetLastError = true)]
public static extern int NCryptOpenKey(
    IntPtr hProvider,
    ref IntPtr phKey,
    [MarshalAs(UnmanagedType.LPWStr)]
    string pszKeyName,
    uint dwLegacyKeySpec,
    uint dwFlags
);
[DllImport("ncrypt.dll", SetLastError = true)]
public static extern int NCryptGetProperty(
    IntPtr hObject,
    [MarshalAs(UnmanagedType.LPWStr)]
    string pszProperty,
    byte[] pbOutput,
    int cbOutput,
    ref int pcbResult,
    int dwFlags
);
[DllImport("ncrypt.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int NCryptFreeObject(
    IntPtr hObject
);
"@
Add-Type -MemberDefinition $signature -Namespace PKI -Name Tools

$CERT_KEY_PROV_INFO_PROP_ID = 0x2 # from Wincrypt.h header file
$cert = dir cert:\currentuser\my\C541C66F490413302C845A440AFA24E98A231C3C
$pcbData = 0
[void][PKI.Tools]::CertGetCertificateContextProperty($cert.Handle,$CERT_KEY_PROV_INFO_PROP_ID,[IntPtr]::Zero,[ref]$pcbData)
$pvData = [Runtime.InteropServices.Marshal]::AllocHGlobal($pcbData)
[PKI.Tools]::CertGetCertificateContextProperty($cert.Handle,$CERT_KEY_PROV_INFO_PROP_ID,$pvData,[ref]$pcbData)
$keyProv = [Runtime.InteropServices.Marshal]::PtrToStructure($pvData,[type][PKI.Tools+CRYPT_KEY_PROV_INFO])
[Runtime.InteropServices.Marshal]::FreeHGlobal($pvData)
$phProvider = [IntPtr]::Zero
[void][PKI.Tools]::NCryptOpenStorageProvider([ref]$phProvider,$keyProv.pwszProvName,0)
$phKey = [IntPtr]::Zero
[void][PKI.Tools]::NCryptOpenKey($phProvider,[ref]$phKey,$keyProv.pwszContainerName,0,0)
$pcbResult = 0
[void][PKI.Tools]::NCryptGetProperty($phKey,"Unique Name",$null,0,[ref]$pcbResult,0)
$pbOutput = New-Object byte[] -ArgumentList $pcbResult
[void][PKI.Tools]::NCryptGetProperty($phKey,"Unique Name",$pbOutput,$pbOutput.length,[ref]$pcbResult,0)
[Text.Encoding]::Unicode.GetString($pbOutput)
[void][PKI.Tools]::NCryptFreeObject($phProvider)
[void][PKI.Tools]::NCryptFreeObject($phKey)
Crypt32
  • 6,414
  • 1
  • 13
  • 32
  • 1
    +1. Fair enough. My answer covers only from a CryptoAPI perspective, and not CNG. But on the other hand, OP didn't ask about CNG. Had OP said something about %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\S-1-5-18\ in his question, then my answer would have been different. Nevertheless, completeness is always welcome! For OP, http://msdn.microsoft.com/en-us/library/windows/desktop/bb204778(v=vs.85).aspx – Ryan Ries Nov 05 '14 at 18:49
2

I used CryptoGuy's code, expanded upon it significantly, and turned it into a function. It still has room for improvement, however. Thanks, CryptoGuy!

function Get-KeyContainer {
  [CmdletBinding()]
  Param([Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)] [string]$Thumbprint,
        [Parameter(Position=1, Mandatory=$false, ValueFromPipeline=$false)] [switch]$MachineStore)

  $MemberDefinition=@"
  [DllImport("Crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)]
  public static extern bool CertGetCertificateContextProperty(
    IntPtr pCertContext,
    uint dwPropId,
    IntPtr pvData,
    ref uint pcbData);

  [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
  public struct CRYPT_KEY_PROV_INFO {
    [MarshalAs(UnmanagedType.LPWStr)] public string pwszContainerName;
    [MarshalAs(UnmanagedType.LPWStr)] public string pwszProvName;
    public uint dwProvType;
    public uint dwFlags;
    public uint cProvParam;
    public IntPtr rgProvParam;
    public uint dwKeySpec;}

  [DllImport("ncrypt.dll", SetLastError = true)]
  public static extern int NCryptOpenStorageProvider(
    ref IntPtr phProvider,
    [MarshalAs(UnmanagedType.LPWStr)] string pszProviderName,
    uint dwFlags);

  [DllImport("ncrypt.dll", SetLastError = true)]
  public static extern int NCryptOpenKey(
    IntPtr hProvider,
    ref IntPtr phKey,
    [MarshalAs(UnmanagedType.LPWStr)] string pszKeyName,
    uint dwLegacyKeySpec,
    uint dwFlags);

  [DllImport("ncrypt.dll", SetLastError = true)]
  public static extern int NCryptGetProperty(
    IntPtr hObject,
    [MarshalAs(UnmanagedType.LPWStr)] string pszProperty,
    byte[] pbOutput,
    int cbOutput,
    ref int pcbResult,
    int dwFlags);

  [DllImport("ncrypt.dll", CharSet=CharSet.Auto, SetLastError=true)]
  public static extern int NCryptFreeObject(IntPtr hObject);
"@
  Add-Type -MemberDefinition $MemberDefinition -Namespace PKI -Name Tools

  $CERT_KEY_PROV_INFO_PROP_ID = 0x2 # from Wincrypt.h header file
  # from Ncrypt.h header file
  if ($MachineStore.IsPresent) { $NCRYPT_MACHINE_KEY_FLAG = 0x20 }
  else { $NCRYPT_MACHINE_KEY_FLAG = 0 }

  $cert=Get-Item -Path ("Cert:\LocalMachine\My\"+$Thumbprint)
  $pcbData = 0
  $result=[PKI.Tools]::CertGetCertificateContextProperty($cert.Handle,$CERT_KEY_PROV_INFO_PROP_ID,[IntPtr]::Zero,[ref]$pcbData)
  if ($result -ne $true) {
    switch ($result) {
      -2146885628 { Write-Error "ERROR:  CRYPT_E_NOT_FOUND 0x80092004 (-2146885628)`r`nThe certificate does not have the specified property." }
      -2005270525 { Write-Error "ERROR:  ERROR_MORE_DATA 0x887A0003 (-2005270525)`r`nIf the buffer specified by the pvData parameter is not large enough to hold the returned data, the function sets the ERROR_MORE_DATA code and stores the required buffer size, in bytes, in the variable pointed to by pcbData." }
    }
    exit
  }
  $pvData = [Runtime.InteropServices.Marshal]::AllocHGlobal($pcbData)
  $result=[PKI.Tools]::CertGetCertificateContextProperty($cert.Handle,$CERT_KEY_PROV_INFO_PROP_ID,$pvData,[ref]$pcbData)
  if ($result -ne $true) {
    switch ($result) {
      -2146885628 { Write-Error "ERROR:  CRYPT_E_NOT_FOUND 0x80092004 (-2146885628)`r`nThe certificate does not have the specified property." }
      -2005270525 { Write-Error "ERROR:  ERROR_MORE_DATA 0x887A0003 (-2005270525)`r`nIf the buffer specified by the pvData parameter is not large enough to hold the returned data, the function sets the ERROR_MORE_DATA code and stores the required buffer size, in bytes, in the variable pointed to by pcbData." }
    }
    exit
  }
  $keyProv = [Runtime.InteropServices.Marshal]::PtrToStructure($pvData,[type][PKI.Tools+CRYPT_KEY_PROV_INFO])
  [Runtime.InteropServices.Marshal]::FreeHGlobal($pvData)
  $phProvider = [IntPtr]::Zero
  $result=[PKI.Tools]::NCryptOpenStorageProvider([ref]$phProvider,$keyProv.pwszProvName,0)
  if ($result -ne 0) {
    switch ($result) {
      -2146893815 { Write-Error "ERROR:  NTE_BAD_FLAGS 0x80090009 (-2146893815)`r`nInvalid flags specified" }
      -2146893785 { Write-Error "ERROR:  NTE_INVALID_PARAMETER 0x80090027 (-2146893785)`r`nThe parameter is incorrect" }
      -2146893810 { Write-Error "ERROR:  NTE_NO_MEMORY 0x8009000E (-2146893810)`r`nInsufficient memory available for the operation" }
    }
    exit
  }
  $phKey = [IntPtr]::Zero
  $result=[PKI.Tools]::NCryptOpenKey($phProvider,[ref]$phKey,$keyProv.pwszContainerName,0,$NCRYPT_MACHINE_KEY_FLAG)
  if ($result -ne 0) {
    switch ($result) {
      -2146893815 { Write-Error "ERROR:  NTE_BAD_FLAGS 0x80090009 (-2146893815)`r`nThe dwFlags parameter contains a value that is not valid." }
      -2146893802 { Write-Error "ERROR:  NTE_BAD_KEYSET 0x80090016 (-2146893802)`r`nThe specified key was not found.  Try using the -MachineKey flag to look in the Machine's store instead of the User's store." }
      -2146893786 { Write-Error "ERROR:  NTE_INVALID_HANDLE 0x80090026 (-2146893786)`r`nThe hProvider parameter is not valid." }
      -2146893785 { Write-Error "ERROR:  NTE_INVALID_PARAMETER 0x80090027 (-2146893785)`r`nThe parameter is incorrect" }
      -2146893810 { Write-Error "ERROR:  NTE_NO_MEMORY 0x8009000E (-2146893810)`r`nInsufficient memory available for the operation" }
    }
    exit
  }
  $pcbResult = 0
  $result=[PKI.Tools]::NCryptGetProperty($phKey,"Unique Name",$null,0,[ref]$pcbResult,0)
  if ($result -ne 0) {
    switch ($result) {
      -2146893815 { Write-Error "ERROR:  NTE_BAD_FLAGS 0x80090009 (-2146893815)`r`nThe dwFlags parameter contains a value that is not valid." }
      -2146893786 { Write-Error "ERROR:  NTE_INVALID_HANDLE 0x80090026 (-2146893786)`r`nThe hProvider parameter is not valid." }
      -2146893785 { Write-Error "ERROR:  NTE_INVALID_PARAMETER 0x80090027 (-2146893785)`r`nThe parameter is incorrect" }
      -2146893810 { Write-Error "ERROR:  NTE_NO_MEMORY 0x8009000E (-2146893810)`r`nInsufficient memory available for the operation" }
      -2146893783 { Write-Error "ERROR:  NTE_NOT_SUPPORTED 0x80090029 (-2146893783)`r`nThe specified property is not supported for the object." }
    }
    exit
  }
  $pbOutput = New-Object byte[] -ArgumentList $pcbResult
  $result=[PKI.Tools]::NCryptGetProperty($phKey,"Unique Name",$pbOutput,$pbOutput.length,[ref]$pcbResult,0)
  if ($result -ne 0) {
    switch ($result) {
      -2146893815 { Write-Error "ERROR:  NTE_BAD_FLAGS 0x80090009 (-2146893815)`r`nThe dwFlags parameter contains a value that is not valid." }
      -2146893786 { Write-Error "ERROR:  NTE_INVALID_HANDLE 0x80090026 (-2146893786)`r`nThe hProvider parameter is not valid." }
      -2146893785 { Write-Error "ERROR:  NTE_INVALID_PARAMETER 0x80090027 (-2146893785)`r`nThe parameter is incorrect" }
      -2146893810 { Write-Error "ERROR:  NTE_NO_MEMORY 0x8009000E (-2146893810)`r`nInsufficient memory available for the operation" }
      -2146893783 { Write-Error "ERROR:  NTE_NOT_SUPPORTED 0x80090029 (-2146893783)`r`nThe specified property is not supported for the object." }
    }
    exit
  }
  [Text.Encoding]::Unicode.GetString($pbOutput)
  $result=[PKI.Tools]::NCryptFreeObject($phProvider)
  if ($result -ne 0) { Write-Error "ERROR:  NTE_INVALID_HANDLE 0x80090026 (-2146893786)`r`nThe hProvider parameter is not valid."; exit }
  [void][PKI.Tools]::NCryptFreeObject($phKey)
  if ($result -ne 0) { Write-Error "ERROR:  NTE_INVALID_HANDLE 0x80090026 (-2146893786)`r`nThe hProvider parameter is not valid."; exit }
}

Get-KeyContainer -Thumbprint "xxxxxxxxx" -MachineStore
  • Even so many years later, this was a lifesaver, thanks. I couldn't figure out why the first solution kept crashing my powershell session, but since it was a LocalMachine key I wanted, I think the secret sauce was `$NCRYPT_MACHINE_KEY_FLAG `. There is no way I could debugged that. And now I can grant perms to a specific key rather than the entire folder! (why does MSFT make this so hard?) – LeeM Jan 06 '22 at 10:20