Add private key file information to personal certificate enumeration

This commit is contained in:
itm4n
2025-12-25 12:57:45 +01:00
parent bae70eb111
commit 51bdb58ba3
6 changed files with 214 additions and 11 deletions
+3
View File
@@ -117,6 +117,7 @@
"IPHTTPS",
"ISATAP",
"jaredcatkinson",
"KEYEXCHANGE",
"Lanman",
"Ldarg",
"Ldtoken",
@@ -149,6 +150,7 @@
"msidb",
"msiexec",
"msrcincident",
"ncrypt",
"NDIS",
"netapi",
"NETLOGON",
@@ -203,6 +205,7 @@
"Sacl",
"SAERXCIT",
"Sandboxed",
"SCARD",
"SCCM",
"SCHTASKS",
"SDDL",
+1 -1
View File
@@ -1 +1 @@
373422768
380036237
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## 2025-12-25
### Added
- Attempt to determine private key file path when enumerating machine and user personal certificates (issue #77).
## 2025-12-24
### Added
+128 -7
View File
@@ -2501,7 +2501,8 @@ function Get-CertificateContextProperty {
[CmdletBinding()]
param (
[IntPtr] $ContextPtr,
[UInt32] $PropId
[UInt32] $PropId,
[String] $PropName
)
process {
@@ -2515,8 +2516,16 @@ function Get-CertificateContextProperty {
# CERT_KEY_PROV_INFO_PROP_ID
2 {
$ProviderInfo = [System.Runtime.InteropServices.Marshal]::PtrToStructure($PropertyPtr, [type] $script:CRYPT_KEY_PROV_INFO)
Write-Verbose $ProviderInfo.ContainerName
return $ProviderInfo.ProvName
if ([String]::IsNullOrEmpty($PropName)) {
return $ProviderInfo.ProvName
}
else {
switch ($PropName) {
"ProviderName" { return $ProviderInfo.ProvName }
"ContainerName" { return $ProviderInfo.ContainerName }
default { throw "Unhandled property name: $($PropName)"}
}
}
}
# CERT_HASH_PROP_ID
3 {
@@ -2559,6 +2568,117 @@ function Get-CertificateContextProperty {
}
}
function Get-CertificateNCryptProperty {
<#
.SYNOPSIS
Wrapper for NCryptGetProperty
Author: @itm4n
License: BSD 3-Clause
.DESCRIPTION
This cmdlet invokes the NCryptGetProperty CNG API to retrieve a property of a certificate.
.PARAMETER ContextPtr
A pointer to a certificate context.
.PARAMETER Property
The name of a certificate property to retrieve.
#>
[OutputType([String])]
[CmdletBinding()]
param (
[IntPtr] $ContextPtr,
[String] $Property
)
begin {
$FlagValues = @(
0x00000000,
# NCRYPT_MACHINE_KEY_FLAG
0x00000020
)
$LegacyKeySpecValues = @(
0x00000000,
# AT_KEYEXCHANGE
0x00000001,
# AT_SIGNATURE
0x00000002
)
}
process {
$ProviderName = Get-CertificateContextProperty -ContextPtr $ContextPtr -PropId 2 -PropName "ProviderName"
$ContainerName = Get-CertificateContextProperty -ContextPtr $ContextPtr -PropId 2 -PropName "ContainerName"
$ProviderHandle = [UIntPtr]::Zero
$Status = $script:Ncrypt::NCryptOpenStorageProvider([ref] $ProviderHandle, $ProviderName, 0)
if ($Status -eq 0) {
$KeyHandle = [UIntPtr]::Zero
foreach ($FlagValue in $FlagValues) {
foreach ($LegacyKeySpecValue in $LegacyKeySpecValues) {
# NCRYPT_SILENT_FLAG = 0x00000040
# Provide the "silent" flag to avoid interactive prompts that would block the execution.
# This is especially relevant for Smart Card readers.
$Flags = 0x00000040 + $FlagValue
$Status = $script:Ncrypt::NCryptOpenKey($ProviderHandle, [ref] $KeyHandle, $ContainerName, $LegacyKeySpecValue, $Flags)
# SCARD_E_NO_READERS_AVAILABLE = 0x8010002e
# Access to a Smart Card reader is required, so this case should be ignored.
if ($Status -eq 0x8010002e) { break }
if ($KeyHandle -ne [UIntPtr]::Zero) { break }
}
if ($KeyHandle -ne [UIntPtr]::Zero) { break }
}
if (($Status -ne 0) -and ($Status -ne 0x8010002e)) {
# NTE_BAD_KEYSET = 0x80090016
if ($Status -eq 0x80090016) {
Write-Warning "Key '$($ProviderName)' -> '$($ContainerName)' not found. Administrator privileges might be required to open it."
}
else {
Write-Warning "NCryptOpenKey('$($ContainerName)') - 0x$("{0:x8}" -f $Status)"
}
}
if ($KeyHandle -ne [UIntPtr]::Zero) {
$BufferSize = 0
$Status = $script:Ncrypt::NCryptGetProperty($KeyHandle, $Property, [IntPtr]::Zero, 0, [ref] $BufferSize, 0x00000040)
if ($Status -eq 0) {
$PropertyValuePtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($BufferSize)
$Status = $script:Ncrypt::NCryptGetProperty($KeyHandle, $Property, $PropertyValuePtr, $BufferSize, [ref] $BufferSize, 0x00000040)
if ($Status -eq 0) {
switch ($Property) {
"Unique Name" {
return [System.Runtime.InteropServices.Marshal]::PtrToStringUni($PropertyValuePtr)
}
default {
throw "Unhandled property name: $($Property)"
}
}
}
else {
Write-Warning "NCryptGetProperty('$($Property)') - 0x$("{0:x8}" -f $Status)"
}
$null = [System.Runtime.InteropServices.Marshal]::FreeHGlobal($PropertyValuePtr)
}
else {
Write-Warning "NCryptGetProperty('$($Property)') - 0x$("{0:x8}" -f $Status)"
}
$null = $script:Ncrypt::NCryptFreeObject($KeyHandle)
}
$null = $script:Ncrypt::NCryptFreeObject($ProviderHandle)
}
else {
Write-Warning "NCryptOpenStorageProvider('$($ProviderName)') - 0x$("{0:x8}" -f $Status)"
}
}
}
function Get-CertificateExtendedKeyUsage {
<#
.SYNOPSIS
@@ -2731,21 +2851,22 @@ function Get-SerializedCertificateInformation {
$CertInfo = [System.Runtime.InteropServices.Marshal]::PtrToStructure($CertContext.CertInfo, [type] $script:CERT_INFO)
$ProviderName = Get-CertificateContextProperty -ContextPtr $CertContextPtr -PropId 2
$ContainerName = Get-CertificateContextProperty -ContextPtr $CertContextPtr -PropId 2 -PropName "ContainerName"
$CertificateHash = Get-CertificateContextProperty -ContextPtr $CertContextPtr -PropId 3
$FriendlyName = Get-CertificateContextProperty -ContextPtr $CertContextPtr -PropId 11
$Description = Get-CertificateContextProperty -ContextPtr $CertContextPtr -PropId 13
$ExtendedKeyUsage = Get-CertificateExtendedKeyUsage -ContextPtr $CertContextPtr
$UniqueName = Get-CertificateNCryptProperty -ContextPtr $CertContextPtr -Property "Unique Name"
$Result = New-Object -TypeName PSObject
$Result | Add-Member -MemberType "NoteProperty" -Name "FriendlyName" -Value $FriendlyName
$Result | Add-Member -MemberType "NoteProperty" -Name "Description" -Value $Description
$Result | Add-Member -MemberType "NoteProperty" -Name "Subject" -Value $(Convert-CertNameBlob -EncodingType $CertContext.CertEncodingType -Blob $CertInfo.Subject)
$Result | Add-Member -MemberType "NoteProperty" -Name "Issuer" -Value $(Convert-CertNameBlob -EncodingType $CertContext.CertEncodingType -Blob $CertInfo.Issuer)
$Result | Add-Member -MemberType "NoteProperty" -Name "FriendlyName" -Value $FriendlyName
$Result | Add-Member -MemberType "NoteProperty" -Name "NotBefore" -Value $(Convert-FiletimeToDatetime -FileTime $CertInfo.NotBefore)
$Result | Add-Member -MemberType "NoteProperty" -Name "NotAfter" -Value $(Convert-FiletimeToDatetime -FileTime $CertInfo.NotAfter)
$Result | Add-Member -MemberType "NoteProperty" -Name "Version" -Value $($CertInfo.Version + 1)
$Result | Add-Member -MemberType "NoteProperty" -Name "Provider" -Value $ProviderName
$Result | Add-Member -MemberType "NoteProperty" -Name "Container" -Value $ContainerName
$Result | Add-Member -MemberType "NoteProperty" -Name "UniqueName" -Value $UniqueName
$Result | Add-Member -MemberType "NoteProperty" -Name "ExtendedKeyUsage" -Value $ExtendedKeyUsage
$Result | Add-Member -MemberType "NoteProperty" -Name "PublicKeyAlgorithm" -Value $(Convert-CertificateOid -Oid $CertInfo.SubjectPublicKeyInfo.Algorithm.ObjId | Select-Object -ExpandProperty Name)
$Result | Add-Member -MemberType "NoteProperty" -Name "SignatureAlgorithm" -Value $(Convert-CertificateOid -Oid $CertInfo.SignatureAlgorithm.ObjId | Select-Object -ExpandProperty Name)
+6
View File
@@ -55,6 +55,11 @@ $FunctionDefinitions = @(
(New-Function kernel32 SearchPathW ([UInt32]) @([IntPtr], [String], [String], [UInt32], [System.Text.StringBuilder], [IntPtr]) ([Runtime.InteropServices.CallingConvention]::Winapi) ([Runtime.InteropServices.CharSet]::Unicode) -SetLastError -EntryPoint SearchPathW),
(New-Function kernel32 GetProcAddress ([IntPtr]) @([IntPtr], [String]) ([Runtime.InteropServices.CallingConvention]::Winapi) ([Runtime.InteropServices.CharSet]::Ansi) -SetLastError -EntryPoint GetProcAddress),
(New-Function ncrypt NCryptOpenStorageProvider ([Int32]) @([UIntPtr].MakeByRefType(), [String], [UInt32]) -Charset Unicode -EntryPoint NCryptOpenStorageProvider),
(New-Function ncrypt NCryptOpenKey ([Int32]) @([UIntPtr], [UIntPtr].MakeByRefType(), [String], [UInt32], [UInt32]) -Charset Unicode -EntryPoint NCryptOpenKey),
(New-Function ncrypt NCryptGetProperty ([Int32]) @([UIntPtr], [String], [IntPtr], [UInt32], [UInt32].MakeByRefType(), [UInt32]) -Charset Unicode -EntryPoint NCryptGetProperty),
(New-Function ncrypt NCryptFreeObject ([Int32]) @([UIntPtr]) -EntryPoint NCryptFreeObject),
(New-Function netapi32 NetGetJoinInformation ([UInt32]) @([IntPtr], [IntPtr].MakeByRefType(), $script:NETSETUP_JOIN_STATUS.MakeByRefType()) -Charset Unicode -EntryPoint NetGetJoinInformation),
(New-Function netapi32 NetGetAadJoinInformation ([Int32]) @([String], [IntPtr].MakeByRefType()) -Charset Unicode -EntryPoint NetGetAadJoinInformation),
(New-Function netapi32 NetUserEnum ([UInt32]) @([IntPtr], [UInt32], [UInt32], [IntPtr].MakeByRefType(), [UInt32], [UInt32].MakeByRefType(), [UInt32].MakeByRefType(), [UInt32].MakeByRefType()) -EntryPoint NetUserEnum),
@@ -106,6 +111,7 @@ $script:Crypt32 = $Types['crypt32']
$script:Iphlpapi = $Types['iphlpapi']
$script:FirewallApi = $Types['firewallapi']
$script:Kernel32 = $Types['kernel32']
$script:Ncrypt = $Types['ncrypt']
$script:Netapi32 = $Types['netapi32']
$script:Ntdll = $Types['ntdll']
$script:Shell32 = $Types['shell32']
+70 -3
View File
@@ -1437,6 +1437,64 @@ function Get-NetworkEndpoint {
}
}
function Get-CertificatePrivateKeyFilePath {
<#
.SYNOPSIS
Get the path of a certificate's private key.
Author: @itm4n
License: BSD 3-Clause
.DESCRIPTION
This cmdlet attempts to retrieve the path of a certificate's private given its "Unique Name" property.
.PARAMETER UniqueName
The "Unique Name" property of a certificate.
#>
[OutputType([String])]
[CmdletBinding()]
param (
[String] $UniqueName
)
begin {
$CandidatePaths = @(
# Legacy CryptoAPI private key locations
"$($env:APPDATA)\Microsoft\Crypto\RSA",
"$($env:APPDATA)\Microsoft\Crypto\DSS",
"$($env:ALLUSERSPROFILE)\Application Data\Microsoft\Crypto\RSA\S-1-5-18",
"$($env:ALLUSERSPROFILE)\Application Data\Microsoft\Crypto\DSS\S-1-5-18",
"$($env:ALLUSERSPROFILE)\Application Data\Microsoft\Crypto\RSA\S-1-5-19",
"$($env:ALLUSERSPROFILE)\Application Data\Microsoft\Crypto\DSS\S-1-5-19",
"$($env:ALLUSERSPROFILE)\Application Data\Microsoft\Crypto\RSA\S-1-5-20",
"$($env:ALLUSERSPROFILE)\Application Data\Microsoft\Crypto\DSS\S-1-5-20",
"$($env:ALLUSERSPROFILE)\Application Data\Microsoft\Crypto\RSA\MachineKeys",
"$($env:ALLUSERSPROFILE)\Application Data\Microsoft\Crypto\DSS\MachineKeys",
# Cryptography API next generation private key locations
"$($env:APPDATA)\Microsoft\Crypto\Keys",
"$($env:ALLUSERSPROFILE)\Application Data\Microsoft\Crypto\SystemKeys",
"$($env:windir)\ServiceProfiles\LocalService",
"$($env:windir)\ServiceProfiles\NetworkService",
"$($env:ALLUSERSPROFILE)\Application Data\Microsoft\Crypto\Keys"
)
}
process {
$PrivateKeyFilePath = ""
if (-not [String]::IsNullOrEmpty($UniqueName)) {
foreach ($CandidatePath in $CandidatePaths) {
$FileItem = Get-ChildItem -Path $CandidatePath -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $UniqueName }
if ($FileItem) {
$PrivateKeyFilePath = $FileItem.FullName
break
}
}
}
return $PrivateKeyFilePath
}
}
function Get-PersonalCertificateInformation {
<#
.SYNOPSIS
@@ -1482,18 +1540,21 @@ function Get-PersonalCertificateInformation {
Path : C:\Users\Admin\AppData\Roaming\Microsoft\SystemCertificates\My\Certificates\B74755D3600AFFC32344F18BD854875F888981A8
#>
[OutputType([Object[]])]
[CmdletBinding()]
param ()
process {
$Certificates = @()
Get-ChildItem -Path "registry::HKLM\SOFTWARE\Microsoft\SystemCertificates\MY\Certificates" -ErrorAction SilentlyContinue | ForEach-Object {
$Blob = $_ | Get-ItemProperty -Name "Blob" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Blob
if ($null -ne $Blob) {
$Result = Get-SerializedCertificateInformation -Blob $Blob
$Result | Add-Member -MemberType "NoteProperty" -Name "Path" -Value $_.Name
$Result
$Certificates += $Result
}
}
@@ -1507,7 +1568,7 @@ function Get-PersonalCertificateInformation {
if ($null -ne $Blob) {
$Result = Get-SerializedCertificateInformation -Blob $Blob
$Result | Add-Member -MemberType "NoteProperty" -Name "Path" -Value $_.Name
$Result
$Certificates += $Result
}
}
}
@@ -1521,8 +1582,14 @@ function Get-PersonalCertificateInformation {
$Blob = [System.IO.File]::ReadAllBytes($_.FullName)
$Result = Get-SerializedCertificateInformation -Blob $Blob
$Result | Add-Member -MemberType "NoteProperty" -Name "Path" -Value $_.FullName
$Result
$Certificates += $Result
}
}
$Certificates | ForEach-Object {
$PrivateKeyFilePath = Get-CertificatePrivateKeyFilePath -UniqueName $_.UniqueName
$_ | Add-Member -MemberType "NoteProperty" -Name "PrivateKeyFilePath" -Value $PrivateKeyFilePath
$_
}
}
}