The tree adds the installer, service and test paths admitted by this source commit. Nothing else changes about what may cross. Source-Sha: 3e4a3aa4ce092e9e3f51bc78daf3ddc14d9638f8 Policy-Sha: 3e4a3aa4ce092e9e3f51bc78daf3ddc14d9638f8 Tree-Digest: 89d6fce3828a0e0de0c8eeb41ee6750f4462217e2a04b5190297134d7f4a50c3
84 lines
4.5 KiB
PowerShell
84 lines
4.5 KiB
PowerShell
# Run in elevated Windows PowerShell 5.1 and PowerShell 7 with operator-installed
|
|
# OpenSSL 3 on PATH. Loads only verifier functions; never installs an agent.
|
|
$ErrorActionPreference = "Stop"
|
|
$Template = Join-Path $PSScriptRoot "../../server/internal/services/templates/install/scripts/windows.ps1.tmpl"
|
|
$Tokens = $null
|
|
$ParseErrors = $null
|
|
$AST = [System.Management.Automation.Language.Parser]::ParseFile(
|
|
(Resolve-Path $Template).Path, [ref]$Tokens, [ref]$ParseErrors)
|
|
if ($ParseErrors.Count -ne 0) { throw ($ParseErrors | Out-String) }
|
|
$Names = @("Convert-HexToBytes", "Stop-Install", "Remove-InstallerScratchDirectory",
|
|
"New-InstallerScratchDirectory", "Test-Ed25519Signature", "Assert-Ed25519Verifier")
|
|
foreach ($Name in $Names) {
|
|
$Function = $AST.Find({ param($Node)
|
|
$Node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $Node.Name -eq $Name
|
|
}, $true)
|
|
if (-not $Function) { throw "Missing function: $Name" }
|
|
. ([scriptblock]::Create($Function.Extent.Text))
|
|
}
|
|
|
|
Assert-Ed25519Verifier
|
|
$Key = Convert-HexToBytes "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c"
|
|
$Sig = Convert-HexToBytes "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00"
|
|
$Message = [byte[]]@(0x72)
|
|
$WrongKey = Convert-HexToBytes "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
|
|
if (Test-Ed25519Signature $Message $Sig $WrongKey) { throw "Wrong key accepted" }
|
|
$Sig[0] = $Sig[0] -bxor 1
|
|
if (Test-Ed25519Signature $Message $Sig $Key) { throw "Tampered signature accepted" }
|
|
|
|
$Rejected = $false
|
|
try { Test-Ed25519Signature $Message ([byte[]]@(1)) $Key | Out-Null } catch { $Rejected = $true }
|
|
if (-not $Rejected) { throw "Malformed signature accepted" }
|
|
$Rejected = $false
|
|
try { Test-Ed25519Signature $Message $Sig ([byte[]]@(1)) | Out-Null } catch { $Rejected = $true }
|
|
if (-not $Rejected) { throw "Malformed key accepted" }
|
|
$script:Ed25519OpenSSL = $null
|
|
$Rejected = $false
|
|
try { Test-Ed25519Signature $Message $Sig $Key | Out-Null } catch { $Rejected = $true }
|
|
if (-not $Rejected) { throw "Missing verifier accepted" }
|
|
# Cleanup must report its own trouble as a warning and leave the real failure
|
|
# standing. Removing an absent directory is the cheapest way to force that path.
|
|
$Warnings = @()
|
|
Remove-InstallerScratchDirectory -Path (Join-Path ([System.IO.Path]::GetTempPath()) "redflag-absent-$([guid]::NewGuid().ToString('N'))") -WarningVariable +Warnings -WarningAction SilentlyContinue
|
|
if ($Warnings.Count -eq 0) { throw "Cleanup of a missing directory raised no warning" }
|
|
|
|
$script:Ed25519OpenSSL = $null
|
|
$Primary = $null
|
|
try {
|
|
try { throw "PRIMARY FAILURE" } finally {
|
|
Remove-InstallerScratchDirectory -Path (Join-Path ([System.IO.Path]::GetTempPath()) "redflag-absent-$([guid]::NewGuid().ToString('N'))") -WarningAction SilentlyContinue
|
|
}
|
|
} catch { $Primary = $_.Exception.Message }
|
|
if ($Primary -ne "PRIMARY FAILURE") { throw "Cleanup masked the primary failure: got '$Primary'" }
|
|
|
|
# Stop-Install exits; run it in a child PowerShell so this script survives.
|
|
# The regression it guards: with $ErrorActionPreference = "Stop", a bare
|
|
# Write-Error inside the desktop try/catch threw, was caught by that catch,
|
|
# printed as a skip warning, and the installer exited 0 on a hash mismatch.
|
|
$Self = (Get-Process -Id $PID).Path
|
|
$Probe = @"
|
|
`$ErrorActionPreference = "Stop"
|
|
$($AST.Find({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $n.Name -eq "Stop-Install" }, $true).Extent.Text)
|
|
try {
|
|
Stop-Install @("REFUSAL LINE 1", "REFUSAL LINE 2", "REFUSAL LINE 3")
|
|
} catch {
|
|
Write-Host "SWALLOWED BY CATCH"
|
|
}
|
|
Write-Host "CONTINUED PAST REFUSAL"
|
|
"@
|
|
$ProbeFile = Join-Path ([System.IO.Path]::GetTempPath()) "redflag-probe-$([guid]::NewGuid().ToString('N')).ps1"
|
|
Set-Content -LiteralPath $ProbeFile -Value $Probe
|
|
try {
|
|
$Output = & $Self -NoProfile -File $ProbeFile 2>&1 | Out-String
|
|
$Code = $LASTEXITCODE
|
|
} finally {
|
|
Remove-InstallerScratchDirectory -Path $ProbeFile
|
|
}
|
|
if ($Code -ne 1) { throw "Refusal exited $Code, expected 1" }
|
|
if ($Output -match "SWALLOWED BY CATCH") { throw "An enclosing catch swallowed the refusal" }
|
|
if ($Output -match "CONTINUED PAST REFUSAL") { throw "Installer continued past a refusal" }
|
|
foreach ($n in 1..3) {
|
|
if ($Output -notmatch "REFUSAL LINE $n") { throw "Diagnostic line $n never reached the operator" }
|
|
}
|
|
|
|
Write-Host "PASS: exact installer functions accept valid signatures and reject tampering, wrong keys, malformed inputs, and missing verifier; cleanup warns without masking."
|