124 lines
4.7 KiB
PowerShell
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))
|
|
}
|
|
}
|