mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-14 19:27:07 -04:00
Co-authored-by: Brandon Thomas <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
103 lines
5.2 KiB
PowerShell
103 lines
5.2 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Authenticode-sign files with signtool, using whichever signing material is configured.
|
|
|
|
.DESCRIPTION
|
|
Tries, in order:
|
|
1. A code-signing certificate file: WINDOWS_CERTIFICATE (base64 .pfx), WINDOWS_CERTIFICATE_PASSWORD
|
|
2. Azure Trusted Signing: AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET,
|
|
AZURE_SIGNING_ENDPOINT (e.g. https://eus.codesigning.azure.net),
|
|
AZURE_SIGNING_ACCOUNT, AZURE_CERT_PROFILE
|
|
With neither, it prints a warning and leaves the files unsigned (exit code 0), so test builds
|
|
still produce installers.
|
|
|
|
This is the one place to adapt when the signing setup changes (an HSM/cloud key, a different
|
|
timestamp server, ...). Optional overrides: WINDOWS_TIMESTAMP_URL, SIGNTOOL (path to signtool.exe).
|
|
|
|
.EXAMPLE
|
|
pwsh packaging/windows/sign.ps1 dist/designcraft.exe dist/Designcraft.msi
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory = $true, ValueFromRemainingArguments = $true)]
|
|
[string[]] $Files
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Write-Warn([string] $Message) {
|
|
if ($env:GITHUB_ACTIONS) { Write-Output "::warning::$Message" } else { Write-Warning $Message }
|
|
}
|
|
|
|
function Find-SignTool {
|
|
if ($env:SIGNTOOL) { return $env:SIGNTOOL }
|
|
$onPath = Get-Command signtool.exe -ErrorAction SilentlyContinue
|
|
if ($onPath) { return $onPath.Source }
|
|
$kits = Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\bin'
|
|
$found = Get-ChildItem -Path $kits -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName -match '\\x64\\signtool\.exe$' } |
|
|
Sort-Object FullName -Descending | Select-Object -First 1
|
|
if (-not $found) { throw 'signtool.exe not found (install the Windows SDK)' }
|
|
return $found.FullName
|
|
}
|
|
|
|
function Invoke-SignTool([string[]] $Arguments) {
|
|
& $script:SignTool @Arguments
|
|
if ($LASTEXITCODE -ne 0) { throw "signtool $($Arguments[0]) failed with exit code $LASTEXITCODE" }
|
|
}
|
|
|
|
$Files = $Files | ForEach-Object { (Resolve-Path $_).Path }
|
|
$haveCert = [bool]$env:WINDOWS_CERTIFICATE
|
|
$azureVars = 'AZURE_TENANT_ID', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET', 'AZURE_SIGNING_ENDPOINT', 'AZURE_SIGNING_ACCOUNT', 'AZURE_CERT_PROFILE'
|
|
$haveAzure = -not ($azureVars | Where-Object { -not [Environment]::GetEnvironmentVariable($_) })
|
|
|
|
if (-not $haveCert -and -not $haveAzure) {
|
|
Write-Warn "Windows signing secrets not set (WINDOWS_CERTIFICATE or AZURE_*): leaving unsigned: $($Files -join ', ')"
|
|
exit 0
|
|
}
|
|
|
|
$script:SignTool = Find-SignTool
|
|
$common = @('sign', '/v', '/fd', 'SHA256', '/td', 'SHA256', '/d', 'DesignCraft', '/du', 'https://github.com/storytold/designcraft')
|
|
$tmp = Join-Path ([IO.Path]::GetTempPath()) "designcraft-sign-$([IO.Path]::GetRandomFileName())"
|
|
New-Item -ItemType Directory -Path $tmp | Out-Null
|
|
|
|
try {
|
|
if ($haveCert) {
|
|
Write-Output "Signing with WINDOWS_CERTIFICATE: $($Files -join ', ')"
|
|
$pfx = Join-Path $tmp 'cert.pfx'
|
|
[IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE))
|
|
$ts = if ($env:WINDOWS_TIMESTAMP_URL) { $env:WINDOWS_TIMESTAMP_URL } else { 'http://timestamp.digicert.com' }
|
|
$signArgs = $common + @('/tr', $ts, '/f', $pfx)
|
|
if ($env:WINDOWS_CERTIFICATE_PASSWORD) { $signArgs += @('/p', $env:WINDOWS_CERTIFICATE_PASSWORD) }
|
|
Invoke-SignTool ($signArgs + $Files)
|
|
}
|
|
else {
|
|
Write-Output "Signing with Azure Trusted Signing ($env:AZURE_SIGNING_ACCOUNT / $env:AZURE_CERT_PROFILE): $($Files -join ', ')"
|
|
# The dlib authenticates with DefaultAzureCredential, which reads AZURE_TENANT_ID,
|
|
# AZURE_CLIENT_ID and AZURE_CLIENT_SECRET from the environment.
|
|
$trustedSigningVersion = '1.0.95'
|
|
$trustedSigningSha256 = '3bfcf1e0a3cb42af1692f0a8ed45c15de070c2de86f28a59b2795d904d8a920f'
|
|
$pkg = Join-Path $tmp 'trusted-signing.zip'
|
|
$uri = "https://api.nuget.org/v3-flatcontainer/microsoft.trusted.signing.client/$trustedSigningVersion/microsoft.trusted.signing.client.$trustedSigningVersion.nupkg"
|
|
Invoke-WebRequest -Uri $uri -OutFile $pkg
|
|
$actualHash = (Get-FileHash -Path $pkg -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
if ($actualHash -ne $trustedSigningSha256) {
|
|
throw "Microsoft.Trusted.Signing.Client $trustedSigningVersion checksum mismatch"
|
|
}
|
|
Expand-Archive -Path $pkg -DestinationPath (Join-Path $tmp 'client') -Force
|
|
$dlib = Get-ChildItem -Path (Join-Path $tmp 'client') -Recurse -Filter Azure.CodeSigning.Dlib.dll |
|
|
Where-Object { $_.FullName -match '\\x64\\' } | Select-Object -First 1
|
|
if (-not $dlib) { throw 'Azure.CodeSigning.Dlib.dll not found in Microsoft.Trusted.Signing.Client' }
|
|
$metadata = Join-Path $tmp 'metadata.json'
|
|
@{
|
|
Endpoint = $env:AZURE_SIGNING_ENDPOINT
|
|
CodeSigningAccountName = $env:AZURE_SIGNING_ACCOUNT
|
|
CertificateProfileName = $env:AZURE_CERT_PROFILE
|
|
} | ConvertTo-Json | Set-Content -Path $metadata -Encoding utf8
|
|
$ts = if ($env:WINDOWS_TIMESTAMP_URL) { $env:WINDOWS_TIMESTAMP_URL } else { 'http://timestamp.acs.microsoft.com' }
|
|
Invoke-SignTool ($common + @('/tr', $ts, '/dlib', $dlib.FullName, '/dmdf', $metadata) + $Files)
|
|
}
|
|
Invoke-SignTool (@('verify', '/pa', '/v') + $Files)
|
|
}
|
|
finally {
|
|
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
|
|
}
|