mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
ci(windows): Authenticode-sign HashWallet.exe with the hardware token
The Windows lane already runs on the machine that physically holds the Sectigo EV code-signing token, but the runner there is NT SERVICE\act_runner, which cannot use it: SafeNet holds the token login per user and an operator establishes that login interactively. So the build queues the launcher for the signing broker that Fleet installs (such-fleet roles/windows_code_signing) and waits for it to come back signed. CI never holds the private key -- it is non-exportable on FIPS 140-2 Level 2 hardware -- and never holds the token password. Sectigo allows three consecutive failed password attempts before the token needs vendor unlocking, and a job that never supplies a password cannot spend one. Only HashWallet.exe is signed. The bundled MSVC runtime DLLs are already Microsoft-signed and must not be re-signed, and the plugin DLLs are loaded by an already-trusted process; it is the launcher the user double-clicks that SmartScreen judges. The signature, its RFC 3161 timestamp and the expected signer are verified on the exe that actually goes into the zip, and any of the three failing fails the build. If the broker's circuit breaker is tripped the job says so and stops rather than shipping an unsigned binary. Procedure: docs platform/windows-hardware-token-signing.md.
This commit is contained in:
@@ -445,6 +445,66 @@ jobs:
|
||||
cp -v "$REDIST_DIR/msvcp140.dll" "$REDIST_DIR/vcruntime140.dll" "$REDIST_DIR/vcruntime140_1.dll" \
|
||||
build/windows/x64/runner/Release/
|
||||
|
||||
# ---- Authenticode sign the launcher ----------------------------------
|
||||
# This workflow already runs on the machine with the EV code-signing
|
||||
# token in it, but the runner (NT SERVICE\act_runner) cannot use the
|
||||
# token: SafeNet holds the login per user and an operator establishes it
|
||||
# interactively. So the exe is queued for the Fleet-installed signing
|
||||
# broker (such-fleet roles/windows_code_signing), which signs it in the
|
||||
# operator's session. CI never holds the key or the token password, so it
|
||||
# cannot spend one of the token's three password attempts.
|
||||
#
|
||||
# Only HashWallet.exe is signed. The bundled MSVC runtime DLLs are
|
||||
# already Microsoft-signed and must not be re-signed, and the plugin DLLs
|
||||
# are loaded by an already-trusted process -- it is the launcher the user
|
||||
# double-clicks that SmartScreen judges.
|
||||
- name: Sign HashWallet.exe with the hardware token
|
||||
shell: powershell
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$release = "build\windows\x64\runner\Release"
|
||||
$exe = Join-Path $release 'HashWallet.exe'
|
||||
if (-not (Test-Path $exe)) { throw "no HashWallet.exe at $exe" }
|
||||
|
||||
if (Test-Path 'C:\signing\SIGNING_LOCKED') {
|
||||
Get-Content 'C:\signing\SIGNING_LOCKED'
|
||||
throw "The signing broker's circuit breaker is tripped. An operator must clear it."
|
||||
}
|
||||
|
||||
$queued = "hashbags-$env:GITHUB_RUN_ID.exe"
|
||||
$inPath = "C:\signing\in\$queued"
|
||||
$outPath = "C:\signing\out\$queued"
|
||||
Copy-Item $exe $inPath -Force
|
||||
Set-Content -LiteralPath "$inPath.ready" -Value $env:GITHUB_RUN_ID
|
||||
Write-Host "queued $queued"
|
||||
|
||||
$deadline = (Get-Date).AddMinutes(10)
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
if (Test-Path "$outPath.done") { break }
|
||||
if (Test-Path 'C:\signing\SIGNING_LOCKED') {
|
||||
Get-Content 'C:\signing\SIGNING_LOCKED'
|
||||
throw "The broker tripped its circuit breaker while signing this artifact."
|
||||
}
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
if (-not (Test-Path "$outPath.done")) { throw "Timed out waiting for the signing broker." }
|
||||
|
||||
Move-Item $outPath $exe -Force
|
||||
Remove-Item "$outPath.done" -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Verify on the binary that actually ships. A bad signature or a
|
||||
# missing timestamp blocks the release.
|
||||
$sig = Get-AuthenticodeSignature $exe
|
||||
$sig | Format-List Status, StatusMessage,
|
||||
@{n='Signer';e={$_.SignerCertificate.Subject}},
|
||||
@{n='TimeStamper';e={$_.TimeStamperCertificate.Subject}}
|
||||
if ($sig.Status -ne 'Valid') { throw "Authenticode status is $($sig.Status)" }
|
||||
if (-not $sig.TimeStamperCertificate) { throw "Signature carries no RFC 3161 timestamp" }
|
||||
if ($sig.SignerCertificate.Subject -notmatch 'Such Software LLC') {
|
||||
throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
|
||||
}
|
||||
Write-Host "SHA256 $((Get-FileHash $exe -Algorithm SHA256).Hash)"
|
||||
|
||||
# ---- Package + upload ------------------------------------------------
|
||||
- name: List built artifacts (for debug visibility)
|
||||
run: |
|
||||
|
||||
Reference in New Issue
Block a user