This commit adds: - Database migrations for app_delta fingerprints and client certificates - Scripts for certificate generation and fingerprint backfilling - Comprehensive security audit reports and findings tracking - Implementation documentation for certificate authentication - Test utilities for certificate renewal and fingerprinting - GE-ENFORCE collector fix documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
134 lines
5.6 KiB
PowerShell
134 lines
5.6 KiB
PowerShell
param(
|
|
[Parameter(Mandatory=$false)]
|
|
[string]$Hostname = $env:COMPUTERNAME,
|
|
|
|
[Parameter(Mandatory=$false)]
|
|
[int]$ValidityYears = 2
|
|
)
|
|
|
|
# SECURITY: Validate hostname matches actual machine name to prevent cert for wrong host
|
|
$actualHostname = [System.Net.Dns]::GetHostName()
|
|
if ($Hostname -ne $actualHostname) {
|
|
Write-Warning "Hostname mismatch detected!"
|
|
Write-Warning " Requested: $Hostname"
|
|
Write-Warning " Actual machine: $actualHostname"
|
|
Write-Warning "Using actual hostname to prevent certificate mismatch."
|
|
$Hostname = $actualHostname
|
|
}
|
|
|
|
# Generate self-signed cert for this endpoint
|
|
$cert = New-SelfSignedCertificate `
|
|
-Subject "CN=$Hostname" `
|
|
-DnsName $Hostname `
|
|
-CertStoreLocation "Cert:\LocalMachine\My" `
|
|
-KeyExportPolicy NonExportable `
|
|
-KeyUsage DigitalSignature, KeyEncipherment `
|
|
-NotAfter (Get-Date).AddYears($ValidityYears) `
|
|
-HashAlgorithm SHA256
|
|
|
|
# SECURITY WARNING: Grant Users group read access to the private key so the tray
|
|
# (running as interactive user) can use it when shelling out to shopdbagent.exe --once.
|
|
# The service already works because it runs as SYSTEM.
|
|
#
|
|
# RISK: This grants ALL interactive users on the machine read access to the private
|
|
# key, which means any user with console/RDP access can extract the key and impersonate
|
|
# this endpoint. Only acceptable in environments where ALL interactive users are trusted.
|
|
#
|
|
# MITIGATION OPTIONS:
|
|
# 1. Best: Remove tray dependency on cert (tray calls service API, service makes HTTP)
|
|
# 2. Acceptable: Grant only to specific user account running the tray
|
|
# 3. Document: Only deploy in environments where all console users are trusted operators
|
|
try {
|
|
$rsaKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
|
|
$keyPath = $rsaKey.Key.UniqueName
|
|
|
|
# CNG keys are in SystemKeys, not MachineKeys
|
|
$keyFile = Join-Path "$env:ProgramData\Microsoft\Crypto\SystemKeys" $keyPath
|
|
if (-not (Test-Path $keyFile)) {
|
|
$keyFile = Join-Path "$env:ProgramData\Microsoft\Crypto\Keys" $keyPath
|
|
}
|
|
if (-not (Test-Path $keyFile)) {
|
|
$keyFile = Join-Path "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys" $keyPath
|
|
}
|
|
|
|
if (Test-Path $keyFile) {
|
|
# Grant Read + Execute to BUILTIN\Users
|
|
$acl = Get-Acl $keyFile
|
|
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
"BUILTIN\Users", "Read,ReadAndExecute", "Allow")
|
|
$acl.AddAccessRule($rule)
|
|
Set-Acl -Path $keyFile -AclObject $acl
|
|
Write-Host " Granted Users group access to private key"
|
|
} else {
|
|
Write-Host " WARNING: Could not find private key file to set permissions"
|
|
}
|
|
} catch {
|
|
Write-Host " WARNING: Could not set private key permissions: $($_.Exception.Message)"
|
|
}
|
|
|
|
Write-Host "Generated certificate:"
|
|
Write-Host " Thumbprint: $($cert.Thumbprint)"
|
|
Write-Host " Subject: $($cert.Subject)"
|
|
Write-Host " Expires: $($cert.NotAfter)"
|
|
|
|
# Compute SHA1 fingerprint to match nginx $ssl_client_fingerprint variable
|
|
# SECURITY NOTE: Nginx only provides SHA1 via $ssl_client_fingerprint variable.
|
|
# SHA1 is deprecated for cryptographic signing but remains acceptable for certificate
|
|
# fingerprinting in air-gapped environments with:
|
|
# - Short-lived certificates (2 years max)
|
|
# - Certificate revocation support (cert_revoked_at column)
|
|
# - No external CA trust (self-signed only)
|
|
# Future migration to SHA256 requires: nginx module for SHA256, or IIS deployment
|
|
# which exposes full PEM cert for server-side hashing.
|
|
$certBytes = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
|
|
$sha1 = [System.Security.Cryptography.SHA1]::Create()
|
|
$fingerprintBytes = $sha1.ComputeHash($certBytes)
|
|
$fingerprint = ($fingerprintBytes | ForEach-Object { $_.ToString("x2") }) -join ""
|
|
|
|
Write-Host ""
|
|
Write-Host "========================================="
|
|
Write-Host "CERTIFICATE GENERATED SUCCESSFULLY"
|
|
Write-Host "========================================="
|
|
Write-Host "Fingerprint (SHA1): $fingerprint"
|
|
Write-Host "Length: 40 hex chars (nginx limitation)"
|
|
Write-Host ""
|
|
Write-Host "Register this certificate in ShopDB database:"
|
|
Write-Host ""
|
|
Write-Host " UPDATE assets"
|
|
Write-Host " SET client_cert_fingerprint = '$fingerprint',"
|
|
Write-Host " cert_issued_at = CURRENT_TIMESTAMP,"
|
|
Write-Host " cert_expires_at = '$($cert.NotAfter.ToString("yyyy-MM-dd HH:mm:ss"))',"
|
|
Write-Host " cert_revoked_at = NULL,"
|
|
Write-Host " cert_revocation_reason = NULL"
|
|
Write-Host " WHERE assetnumber = '(REPLACE_WITH_ASSET_NUMBER)';"
|
|
Write-Host ""
|
|
Write-Host "To revoke this certificate later (compromised/lost endpoint):"
|
|
Write-Host ""
|
|
Write-Host " UPDATE assets"
|
|
Write-Host " SET cert_revoked_at = CURRENT_TIMESTAMP,"
|
|
Write-Host " cert_revocation_reason = 'Compromised - TICKET-123'"
|
|
Write-Host " WHERE client_cert_fingerprint = '$fingerprint';"
|
|
Write-Host ""
|
|
Write-Host "========================================="
|
|
|
|
# Update agent config
|
|
$configPath = "C:\ProgramData\ShopDB\config.json"
|
|
if (Test-Path $configPath) {
|
|
$config = Get-Content $configPath | ConvertFrom-Json
|
|
$config | Add-Member -NotePropertyName clientCertThumbprint -NotePropertyValue $cert.Thumbprint -Force
|
|
$config | ConvertTo-Json | Set-Content $configPath
|
|
Write-Host ""
|
|
Write-Host "Updated $configPath with thumbprint"
|
|
} else {
|
|
Write-Host ""
|
|
Write-Host "WARNING: $configPath not found - agent may not be configured"
|
|
}
|
|
|
|
# Output for automation
|
|
Write-Host ""
|
|
Write-Host "---MACHINE-READABLE---"
|
|
Write-Host "THUMBPRINT=$($cert.Thumbprint)"
|
|
Write-Host "FINGERPRINT=$fingerprint"
|
|
Write-Host "HOSTNAME=$Hostname"
|
|
Write-Host "EXPIRES=$($cert.NotAfter.ToString('yyyy-MM-dd HH:mm:ss'))"
|