3

Given a certificate thumbprint, I want to find the absolute file path to the certificate on the local file-system.

This snippet almost always works.

$thumb    = <mythumbprint string>

$cert     = Get-ChildItem "Cert:\LocalMachine\My" | Where-Object {$_.Thumbprint -eq $thumb};
$keyName  = (($cert.PrivateKey).CspKeyContainerInfo).UniqueKeyContainerName
$keyPath  = $env:ProgramData + "\Microsoft\Crypto\RSA\MachineKeys\"
$fullPath = $keyPath+$keyName

It fails on certs where the PrivateKey is not Exportable. When marked as NOT Exportable, the $cert.PrivateKey is no longer accessible and consequently unable to build the certificate's complete path.

Is there a powershell alternative for determining a certificate's UniqueKeyContainerName?

UPDATE: All the certs I am concerned about have private keys. The problem is that I cannot assume they are marked as exportable. If NOT marked exportable, .PrivateKey cannot be referenced. I need another way to get UniqueKeyContainerName without chaining through .Privatekey

bartonm
  • 1,600
  • 3
  • 18
  • 30
  • Ever figure this out? I am trying to script cert permissions for a deployment and my keys aren't exportable and I can't find a way to do it. – Nick Schroeder Jul 28 '17 at 14:59

3 Answers3

1

There seems to be very little useful information on this topic, let alone for PowerShell 5.1 and 7.1 (.NET Core). Hopefully this helps someone.

I've adapted this from Vadims Podāns 'Retrieve CNG key container name and unique name'

Please note:

  • Example use of Cmdlet: Get-Item 'Cert:\LocalMachine\My\FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' | Get-CertificateContainerAbsolutePath
  • I cheated and just searched for key file recursively under Microsoft Crypto directory hierarchy instead of searching correct specific sub-directories
  • Despite use of CNG Provider, this seems to work against a test CryptoAPI provided key I created
  • I tested this on Powershell 5.1 and 7.1 on Windows.
if(-not ('EmbeddedTemporary.PKITools' -as [type])) {
    $PKIToolsDefinition = @"
[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 $PKIToolsDefinition -Namespace 'EmbeddedTemporary' -Name 'PKITools'
}

Function Get-CertificateContainerAbsolutePath {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory,ValuefromPipeline)]
        $Certificate
    )
    $CERT_KEY_PROV_INFO_PROP_ID = 0x2 # from Wincrypt.h header file
    $pcbData = 0
    [EmbeddedTemporary.PKITools]::CertGetCertificateContextProperty($Certificate.Handle, $CERT_KEY_PROV_INFO_PROP_ID, [IntPtr]::Zero, [ref]$pcbData) | Out-Null
    $pvData = [Runtime.InteropServices.Marshal]::AllocHGlobal($pcbData)
    [EmbeddedTemporary.PKITools]::CertGetCertificateContextProperty($Certificate.Handle, $CERT_KEY_PROV_INFO_PROP_ID, $pvData, [ref]$pcbData) | Out-Null
    $keyProv = [Runtime.InteropServices.Marshal]::PtrToStructure($pvData, [type][EmbeddedTemporary.PKITools+CRYPT_KEY_PROV_INFO])
    [Runtime.InteropServices.Marshal]::FreeHGlobal($pvData) | Out-Null

    $CngProvider = [System.Security.Cryptography.CngProvider]::new($keyProv.pwszProvName)
    $CngKey = [System.Security.Cryptography.CngKey]::Open($keyProv.pwszContainerName, $CngProvider, [System.Security.Cryptography.CngKeyOpenOptions]::MachineKey)
    $UniqueName = $CngKey.UniqueName
    $CngKey.Dispose()

    $Certificates = Get-ChildItem -Path (Join-Path -Path $Env:ProgramData -ChildPath 'Microsoft\Crypto') -Recurse -Filter $UniqueName
    $Certificates.FullName
}
Vjz
  • 61
  • 5
  • This is also relevant but Answer is not helpful for PowerShell 7.1 https://stackoverflow.com/questions/7659402/how-to-view-permissions-for-rsa-key-container – Vjz Aug 02 '21 at 04:17
  • I dug around on this topic and found the following discussion which spoke to the changes in [.net vs. core](https://github.com/dotnet/runtime/issues/35920) – Jim May 04 '22 at 15:18
0

This works in PowerShell 5.1 only.

    $cert = Get-Item -Path Cert:/LocalMachine/My/<thumbprint>
    $fileName = $cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
    $path = "$env:ALLUSERSPROFILE\Microsoft\Crypto\RSA\MachineKeys\$fileName"

7.1 implements the PrivateKey property using a different type:
PowerShell 5.1

    PS C:\WINDOWS\system32> $cert.PrivateKey.GetType()

    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     False    RSACryptoServiceProvider                 
    System.Security.Cryptography.RSA

PowerShell 7.x

    PS C:\Windows\System32> $cert.PrivateKey.GetType()

    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     False    RSACng                                   
    System.Security.Cryptography.RSA
Jim
  • 692
  • 7
  • 15
  • Mentioned above in a comment, a [discussion](https://github.com/dotnet/runtime/issues/35920) that talks about the different types – Jim May 04 '22 at 15:20
-1

If I'm following you, you can filter certs that have a HasPrivateKey:

Get-ChildItem Cert:\LocalMachine\my | 
Where-Object {$_.Thumbprint -eq $thumb -and $_.HasPrivateKey} | ForEach-Object {  
    Join-Path $env:ProgramData\Microsoft\Crypto\RSA\MachineKeys $_.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
}
Shay Levy
  • 121,444
  • 32
  • 184
  • 206
  • 1
    I am afraid this does not help. HasPrivateKey will evaluate to $true. The problem is that if the cert was NOT marked as Exportable when it was generated then .PrivateKey fails. – bartonm Dec 05 '13 at 07:49