initial commit
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
#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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user