Certificate Store Deep Dive

Deep dive into the Windows Certificate Store architecture and advanced operations using the PowerShell Cert: PSDrive.

Cert: PSDrive Architecture

The Cert: drive is a PowerShell provider that exposes the Windows certificate store as a navigable filesystem.

Structure

Cert:\
├── CurrentUser\                    # Per-user certificates
│   ├── Root\                       # Trusted Root CAs
│   ├── CA\                         # Intermediate CAs
│   ├── My\                         # Personal certificates
│   ├── Trust\                      # Enterprise Trust
│   ├── Disallowed\                 # Untrusted certs
│   ├── AuthRoot\                   # Third-party Root CAs
│   ├── TrustedPublisher\           # Trusted software publishers
│   └── TrustedPeople\              # Trusted users/computers
│
└── LocalMachine\                   # System-wide certificates
    ├── Root\                       # Trusted Root CAs
    ├── CA\                         # Intermediate CAs
    ├── My\                         # Computer certificates
    ├── Trust\                      # Enterprise Trust
    ├── Disallowed\                 # Untrusted certs
    ├── AuthRoot\                   # Third-party Root CAs
    ├── TrustedPublisher\           # Trusted software publishers
    └── TrustedPeople\              # Trusted users/computers

Navigation

# Navigate like a filesystem
Set-Location Cert:\LocalMachine\Root
Get-ChildItem | Select-Object -First 5

# Return to filesystem
Set-Location C:\

# Or use full paths
Get-ChildItem Cert:\LocalMachine\Root
Get-ChildItem Cert:\CurrentUser\My

Certificate Object Properties

Full Property List

# Get all properties of a certificate
Get-ChildItem Cert:\LocalMachine\Root | Select-Object -First 1 | Format-List *

# Key properties
Get-ChildItem Cert:\LocalMachine\Root | Select-Object -First 1 | Select-Object `
    Subject,           # DN of the certificate subject
    Issuer,            # DN of the issuing CA
    Thumbprint,        # SHA1 hash (unique identifier)
    SerialNumber,      # CA-assigned serial
    NotBefore,         # Valid from date
    NotAfter,          # Expiration date
    HasPrivateKey,     # Whether private key is present
    PrivateKey,        # Private key object (if present)
    PublicKey,         # Public key object
    SignatureAlgorithm,# e.g., sha256RSA
    Version,           # X.509 version
    Extensions         # Certificate extensions

Extension Details

# List all extensions
$cert = Get-ChildItem Cert:\LocalMachine\Root | Select-Object -First 1
$cert.Extensions | ForEach-Object \{
    [PSCustomObject]@\{
        Oid = $_.Oid.FriendlyName
        Critical = $_.Critical
        Value = $_.Format($true)
    }
}

# Key Usage extension
$cert.Extensions | Where-Object \{ $_.Oid.FriendlyName -eq "Key Usage" }

# Subject Alternative Names
$cert.Extensions | Where-Object \{ $_.Oid.FriendlyName -eq "Subject Alternative Name" }

# Enhanced Key Usage (EKU)
$cert.EnhancedKeyUsageList | Format-Table FriendlyName, ObjectId

Advanced Queries

By Purpose (EKU)

# Client Authentication certificates
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{
    $_.EnhancedKeyUsageList.FriendlyName -contains "Client Authentication"
}

# Server Authentication certificates
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{
    $_.EnhancedKeyUsageList.FriendlyName -contains "Server Authentication"
}

# Code Signing certificates
Get-ChildItem Cert:\CurrentUser\My | Where-Object \{
    $_.EnhancedKeyUsageList.FriendlyName -contains "Code Signing"
}

# Smart Card Logon
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{
    $_.EnhancedKeyUsageList.FriendlyName -contains "Smart Card Logon"
}

By Key Properties

# Certificates with private keys
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{ $_.HasPrivateKey }

# Certificates with exportable private keys
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{
    $_.HasPrivateKey -and $_.PrivateKey.CspKeyContainerInfo.Exportable
}

# RSA certificates
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{
    $_.PublicKey.Key.GetType().Name -eq "RSACryptoServiceProvider"
}

# Specific key size
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{
    $_.PublicKey.Key.KeySize -ge 2048
}

By Date Range

# Expired certificates
Get-ChildItem Cert:\LocalMachine\Root | Where-Object \{ $_.NotAfter -lt (Get-Date) }

# Expiring within 30 days
$threshold = (Get-Date).AddDays(30)
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{
    $_.NotAfter -gt (Get-Date) -and $_.NotAfter -lt $threshold
} | Format-Table Subject, NotAfter

# Issued in last 7 days
$weekAgo = (Get-Date).AddDays(-7)
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{ $_.NotBefore -gt $weekAgo }

# Valid for at least 1 year
$nextYear = (Get-Date).AddYears(1)
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{ $_.NotAfter -gt $nextYear }

By Certificate Chain

# Find certificates issued by specific CA
$issuerPattern = "CN=DigiCert"
Get-ChildItem Cert:\LocalMachine\My | Where-Object \{ $_.Issuer -match $issuerPattern }

# Build and verify chain
$cert = Get-ChildItem Cert:\LocalMachine\My | Select-Object -First 1
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.Build($cert)
$chain.ChainStatus
$chain.ChainElements | ForEach-Object \{ $_.Certificate.Subject }

# Self-signed certificates (Issuer == Subject)
Get-ChildItem Cert:\LocalMachine\Root | Where-Object \{ $_.Issuer -eq $_.Subject }

Bulk Operations

Export All Certificates

# Export all root CAs to individual files
$exportPath = "C:\temp\certs"
New-Item -ItemType Directory -Path $exportPath -Force | Out-Null

Get-ChildItem Cert:\LocalMachine\Root | ForEach-Object \{
    $filename = $_.Thumbprint + ".cer"
    Export-Certificate -Cert $_ -FilePath "$exportPath\$filename" -Type CERT
}

# Export all to single PEM bundle
$bundle = ""
Get-ChildItem Cert:\LocalMachine\Root | ForEach-Object \{
    $bundle += "# $($_.Subject)`n"
    $bundle += "-----BEGIN CERTIFICATE-----`n"
    $bundle += [Convert]::ToBase64String($_.RawData, 'InsertLineBreaks')
    $bundle += "`n-----END CERTIFICATE-----`n`n"
}
[System.IO.File]::WriteAllText("$exportPath\root-ca-bundle.pem", $bundle)

Certificate Inventory Report

# Generate inventory of all certificates
$report = @()

foreach ($store in @("Root", "CA", "My")) \{
    $path = "Cert:\LocalMachine\$store"
    Get-ChildItem $path | ForEach-Object \{
        $report += [PSCustomObject]@\{
            Store = $store
            Subject = $_.Subject
            Issuer = $_.Issuer
            Thumbprint = $_.Thumbprint
            NotBefore = $_.NotBefore
            NotAfter = $_.NotAfter
            HasPrivateKey = $_.HasPrivateKey
            DaysToExpire = ($_.NotAfter - (Get-Date)).Days
        }
    }
}

# Export to CSV
$report | Export-Csv -Path "C:\temp\cert-inventory.csv" -NoTypeInformation

# Show expiring soon
$report | Where-Object \{ $_.DaysToExpire -lt 90 -and $_.DaysToExpire -gt 0 } |
    Sort-Object DaysToExpire |
    Format-Table Store, Subject, DaysToExpire

Cleanup Operations

# Find and remove expired certificates (with confirmation)
$expired = Get-ChildItem Cert:\LocalMachine\Root | Where-Object \{ $_.NotAfter -lt (Get-Date) }
$expired | Format-Table Subject, NotAfter
$expired | Remove-Item -WhatIf  # Remove -WhatIf to actually delete

# Find duplicates (same thumbprint in multiple stores)
$allCerts = Get-ChildItem Cert:\LocalMachine\* -Recurse
$duplicates = $allCerts | Group-Object Thumbprint | Where-Object \{ $_.Count -gt 1 }
$duplicates | ForEach-Object \{
    Write-Host "Duplicate: $($_.Group[0].Subject)" -ForegroundColor Yellow
    $_.Group | ForEach-Object \{ Write-Host "  - $($_.PSPath)" }
}

Working with Private Keys

Key Container Information

# Get private key details
$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object \{ $_.HasPrivateKey } | Select-Object -First 1

if ($cert.HasPrivateKey) \{
    $key = $cert.PrivateKey
    [PSCustomObject]@\{
        KeySize = $key.KeySize
        Exportable = $key.CspKeyContainerInfo.Exportable
        MachineKeyStore = $key.CspKeyContainerInfo.MachineKeyStore
        ProviderName = $key.CspKeyContainerInfo.ProviderName
        UniqueKeyContainerName = $key.CspKeyContainerInfo.UniqueKeyContainerName
    } | Format-List
}

Protect Private Key

# Grant read access to private key for specific account
$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object \{ $_.Subject -match "MyMachine" }
$keyPath = $cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$fullPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\$keyPath"

# Get current permissions
Get-Acl $fullPath | Format-List

# Add permission for service account (example)
$acl = Get-Acl $fullPath
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    "NT SERVICE\MyService",
    "Read",
    "Allow"
)
$acl.AddAccessRule($rule)
Set-Acl -Path $fullPath -AclObject $acl

Registry Locations

Certificate stores are backed by registry (useful for troubleshooting):

Store Registry Path

LocalMachine\Root

HKLM:\SOFTWARE\Microsoft\SystemCertificates\Root

LocalMachine\CA

HKLM:\SOFTWARE\Microsoft\SystemCertificates\CA

LocalMachine\My

HKLM:\SOFTWARE\Microsoft\SystemCertificates\My

CurrentUser\Root

HKCU:\SOFTWARE\Microsoft\SystemCertificates\Root

CurrentUser\My

HKCU:\SOFTWARE\Microsoft\SystemCertificates\My

Group Policy (User)

HKCU:\SOFTWARE\Policies\Microsoft\SystemCertificates

Group Policy (Machine)

HKLM:\SOFTWARE\Policies\Microsoft\SystemCertificates