| 1 | param( |
| 2 | [Parameter(Mandatory = $true, ParameterSetName = 'Payload')] |
| 3 | [string]$PayloadDirectory, |
| 4 | [Parameter(Mandatory = $true, ParameterSetName = 'File')] |
| 5 | [string]$FilePath |
| 6 | ) |
| 7 | |
| 8 | $ErrorActionPreference = 'Stop' |
| 9 | Set-StrictMode -Version Latest |
| 10 | $thumbprint = $env:CERTUM_KEY_ID |
| 11 | if ($thumbprint -notmatch '^[0-9a-fA-F]{40}$') { throw 'Invalid CERTUM_KEY_ID' } |
| 12 | $tool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" | |
| 13 | Sort-Object FullName -Descending | Select-Object -First 1 |
| 14 | if (-not $tool) { throw 'Windows SDK SignTool not found' } |
| 15 | |
| 16 | if ($PSCmdlet.ParameterSetName -eq 'Payload') { |
| 17 | $root = (Resolve-Path -LiteralPath $PayloadDirectory).Path |
| 18 | $prefix = $root.TrimEnd('\', '/') + [IO.Path]::DirectorySeparatorChar |
| 19 | $entries = @(Get-Content -LiteralPath (Join-Path $root 'signing-files.txt') | |
| 20 | ForEach-Object { $_.Trim() } | Where-Object { $_ -and -not $_.StartsWith('#') }) |
| 21 | if ($entries.Count -eq 0) { throw 'Empty signing manifest' } |
| 22 | $files = @($entries | ForEach-Object { |
| 23 | $candidate = [IO.Path]::GetFullPath((Join-Path $root $_)) |
| 24 | if (-not $candidate.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { |
| 25 | throw 'Signing manifest path escapes payload directory' |
| 26 | } |
| 27 | $candidate |
| 28 | }) |
| 29 | } else { |
| 30 | $files = @((Resolve-Path -LiteralPath $FilePath).Path) |
| 31 | } |
| 32 | |
| 33 | # Validate the entire manifest before the first signing operation. |
| 34 | foreach ($file in $files) { |
| 35 | if (-not (Test-Path -LiteralPath $file -PathType Leaf)) { throw "Missing signing input: $file" } |
| 36 | } |
| 37 | foreach ($file in $files) { |
| 38 | & $tool.FullName sign /sha1 $thumbprint /fd SHA256 /tr http://timestamp.certum.pl /td SHA256 $file |
| 39 | if ($LASTEXITCODE -ne 0) { throw "Authenticode signing failed: $file" } |
| 40 | & $tool.FullName verify /pa /all /tw $file |
| 41 | if ($LASTEXITCODE -ne 0) { throw "Authenticode verification failed: $file" } |
| 42 | $signature = Get-AuthenticodeSignature -LiteralPath $file |
| 43 | if ($signature.Status -ne 'Valid' -or $signature.SignerCertificate.Thumbprint -ne $thumbprint -or -not $signature.TimeStamperCertificate) { |
| 44 | throw "Unexpected signer, untrusted signature, or missing timestamp: $file" |
| 45 | } |
| 46 | } |
| 47 |