Files
2026-11-04 14:29:22 -06:00

124 lines
4.7 KiB
PowerShell

#Requires -Version 5.1
<#
Signs the LiveWall binaries and installer with Authenticode.
Why: an unsigned download is the single biggest reason antivirus engines and SmartScreen treat a
small installer as suspicious. Signing does not guarantee a clean scan, but it is what the vendors
ask for when they answer a false-positive report, and it fixes SmartScreen's "unknown publisher".
You need a code-signing certificate:
* EV certificate - immediate SmartScreen/reputation trust, hardware token, ~$300-500/year.
* OV certificate - works, but reputation builds up over downloads and weeks.
* Self-signed - useless for this. Do not bother: an untrusted signature is no better than none.
Since June 2023 all public code-signing keys must live on FIPS 140-2 hardware, so most setups mean
a token/HSM in the machine and the certificate visible in the store:
.\installer\sign.ps1 -Thumbprint 1A2B3C... # cert from the Windows certificate store
.\installer\sign.ps1 -Pfx .\codesign.pfx # file-based cert (asks for the password)
.\installer\sign.ps1 -Thumbprint ... -WhatIf # show the commands only
Everything is signed with SHA-256 and an RFC 3161 timestamp, so the signature stays valid after the
certificate expires.
#>
param(
[string]$Thumbprint,
[string]$Pfx,
[SecureString]$Password,
[string]$TimestampUrl = 'http://timestamp.digicert.com',
[string]$Signtool,
[switch]$WhatIf
)
$ErrorActionPreference = 'Stop'
$root = $PSScriptRoot | Split-Path -Parent
function Find-Signtool {
if ($Signtool) { return $Signtool }
$cmd = Get-Command signtool.exe -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
$roots = @(
"${env:ProgramFiles(x86)}\Windows Kits\10\bin",
"$env:ProgramFiles\Windows Kits\10\bin"
) | Where-Object { Test-Path $_ }
foreach ($r in $roots) {
$candidate = Get-ChildItem $r -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match 'x64' } |
Sort-Object FullName -Descending |
Select-Object -First 1
if ($candidate) { return $candidate.FullName }
}
throw 'signtool.exe not found. Install the Windows SDK ("Windows SDK Signing Tools") or pass -Signtool <path>.'
}
$signtool = Find-Signtool
# Files that end up on the user's machine. LiveWall.exe and the SDK matter most: those are what an
# engine inspects after the installer has run.
$files = @(
(Join-Path $root 'prebuilt\LiveWall.exe'),
(Join-Path $root 'prebuilt\LiveWall.dll'),
(Join-Path $root 'prebuilt\LiveWall.Sdk.dll'),
(Join-Path $root 'prebuilt\WebView2Loader.dll')
) | Where-Object { Test-Path $_ }
$installers = Get-ChildItem $root -Filter 'LiveWall-Setup-*.exe' |
Where-Object { $_.Name -notlike '*-old*' }
if (-not $files -and -not $installers) {
throw "Nothing to sign: publish first (.\build.ps1 -Task publish) and build the installer."
}
if ($Pfx) {
if (-not (Test-Path $Pfx)) { throw "Certificate file not found: $Pfx" }
if (-not $Password) {
$Password = Read-Host -AsSecureString "Password for $Pfx"
}
$plain = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))
$certArgs = @('/f', (Resolve-Path $Pfx).Path, '/p', $plain)
}
elseif ($Thumbprint) {
$certArgs = @('/sha1', ($Thumbprint -replace '\s', ''))
}
else {
throw 'Pass -Thumbprint <sha1 of the code-signing cert> or -Pfx <file>. Run with -WhatIf to preview.'
}
Write-Host "signtool : $signtool"
Write-Host "files : $($files.Count) binaries, $($installers.Count) installer(s)"
Write-Host ''
foreach ($file in @($files) + @($installers.FullName)) {
$signArgs = @('sign', '/fd', 'sha256', '/tr', $TimestampUrl, '/td', 'sha256') + $certArgs + @($file)
if ($WhatIf) {
Write-Host "would run: signtool $($signArgs -join ' ')"
continue
}
Write-Host "signing $([IO.Path]::GetFileName($file))"
& $signtool @signArgs
if ($LASTEXITCODE -ne 0) { throw "signtool failed for $file (exit $LASTEXITCODE)" }
}
if (-not $WhatIf) {
Write-Host ''
Write-Host 'Verifying…'
foreach ($file in @($files) + @($installers.FullName)) {
& $signtool verify /pa /q $file
if ($LASTEXITCODE -ne 0) { throw "Signature verification failed for $file" }
}
Write-Host ''
Write-Host 'Signed and verified. Rebuild the hashes for your release notes:' -ForegroundColor Green
foreach ($file in @($files) + @($installers.FullName)) {
$hash = (Get-FileHash $file -Algorithm SHA256).Hash.ToLower()
Write-Host (" {0} {1}" -f $hash, [IO.Path]::GetFileName($file))
}
}