123 lines
4.8 KiB
PowerShell
123 lines
4.8 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Sign (or re-sign) manifests/apps.json and produce apps.json.sig.
|
|
|
|
.DESCRIPTION
|
|
Reads the private key from either:
|
|
-KeyPath (a PEM file path, default .\keys\manifest-private.pem)
|
|
or the env var MANIFEST_SIGNING_KEY (base64-encoded DER private key, used in CI)
|
|
|
|
Produces manifests/apps.json.sig — a file containing the base64-encoded
|
|
ed25519 signature of the raw apps.json bytes.
|
|
|
|
This detached-signature approach means the JSON is never modified and there
|
|
are no canonicalisation issues.
|
|
|
|
.EXAMPLE
|
|
# Local development
|
|
.\scripts\sign-manifest.ps1
|
|
|
|
# CI (key stored as base64 DER in an env var)
|
|
$env:MANIFEST_SIGNING_KEY = "<base64-der-private-key>"
|
|
.\scripts\sign-manifest.ps1
|
|
#>
|
|
|
|
param(
|
|
[string]$KeyPath = ".\keys\manifest-private.pem",
|
|
[string]$ManifestPath = ".\manifests\apps.json",
|
|
[string]$SigPath = ".\manifests\apps.json.sig"
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# ── Locate OpenSSL (probes common Windows install paths if not on PATH) ───────
|
|
. "$PSScriptRoot\_openssl.ps1"
|
|
|
|
# ── Resolve all paths to absolute using PowerShell's CWD ─────────────────────
|
|
# [IO.File] uses .NET's Environment.CurrentDirectory, which differs from
|
|
# PowerShell's $PWD when the session was started from another location.
|
|
# GetUnresolvedProviderPathFromPSPath works even for paths that don't exist yet.
|
|
$ManifestPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ManifestPath)
|
|
$SigPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($SigPath)
|
|
$KeyPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($KeyPath)
|
|
|
|
# ── Resolve private key ──────────────────────────────────────────────────────
|
|
$tempKeyFile = $null
|
|
|
|
if ($env:MANIFEST_SIGNING_KEY) {
|
|
Write-Host "Using MANIFEST_SIGNING_KEY from environment" -ForegroundColor Cyan
|
|
# CI provides the PEM content as an env var
|
|
$tempKeyFile = Join-Path $env:TEMP "psg-sign-key-$([System.Guid]::NewGuid()).pem"
|
|
[IO.File]::WriteAllText($tempKeyFile, $env:MANIFEST_SIGNING_KEY)
|
|
$resolvedKey = $tempKeyFile
|
|
} elseif (Test-Path $KeyPath) {
|
|
Write-Host "Using key file: $KeyPath" -ForegroundColor Cyan
|
|
$resolvedKey = Resolve-Path $KeyPath
|
|
} else {
|
|
Write-Error "No signing key found.`nRun scripts/keygen.ps1 first, or set the MANIFEST_SIGNING_KEY env var."
|
|
exit 1
|
|
}
|
|
|
|
try {
|
|
# ── Sign ─────────────────────────────────────────────────────────────────
|
|
$tempSig = Join-Path $env:TEMP "psg-manifest-sig-$([System.Guid]::NewGuid()).bin"
|
|
|
|
Write-Host "Signing $ManifestPath..." -ForegroundColor Yellow
|
|
& openssl pkeyutl `
|
|
-sign `
|
|
-inkey $resolvedKey `
|
|
-rawin `
|
|
-in (Resolve-Path $ManifestPath) `
|
|
-out $tempSig
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "OpenSSL signing failed (exit $LASTEXITCODE)"
|
|
exit 1
|
|
}
|
|
|
|
# Base64-encode the raw signature bytes
|
|
$sigBytes = [IO.File]::ReadAllBytes($tempSig)
|
|
$sigB64 = [Convert]::ToBase64String($sigBytes)
|
|
[IO.File]::WriteAllText($SigPath, $sigB64)
|
|
|
|
Remove-Item $tempSig -Force
|
|
|
|
Write-Host "Signature written to $SigPath" -ForegroundColor Green
|
|
Write-Host "Sig (base64): $sigB64" -ForegroundColor Gray
|
|
|
|
} finally {
|
|
# Clean up temp key file if we created one
|
|
if ($tempKeyFile -and (Test-Path $tempKeyFile)) {
|
|
Remove-Item $tempKeyFile -Force
|
|
}
|
|
}
|
|
|
|
# ── Verify round-trip ────────────────────────────────────────────────────────
|
|
Write-Host "`nVerifying round-trip..." -ForegroundColor Yellow
|
|
|
|
$tempVerifySig = Join-Path $env:TEMP "psg-verify-sig-$([System.Guid]::NewGuid()).bin"
|
|
$sigBytesBack = [Convert]::FromBase64String($sigB64)
|
|
[IO.File]::WriteAllBytes($tempVerifySig, $sigBytesBack)
|
|
|
|
# Use the public key for verification
|
|
$pubKeyPath = $KeyPath -replace '-private\.pem$', '-public.pem'
|
|
if (-not (Test-Path $pubKeyPath)) {
|
|
Write-Warning "Public key not found at $pubKeyPath — skipping verify step"
|
|
} else {
|
|
& openssl pkeyutl `
|
|
-verify `
|
|
-inkey $pubKeyPath `
|
|
-pubin `
|
|
-rawin `
|
|
-in (Resolve-Path $ManifestPath) `
|
|
-sigfile $tempVerifySig
|
|
|
|
if ($LASTEXITCODE -eq 0) {
|
|
Write-Host "Round-trip verification passed ✓" -ForegroundColor Green
|
|
} else {
|
|
Write-Error "Round-trip verification FAILED — signature is corrupt!"
|
|
}
|
|
}
|
|
|
|
Remove-Item $tempVerifySig -Force -ErrorAction SilentlyContinue
|