feat: initial PSG Launcher scaffold
This commit is contained in:
122
scripts/sign-manifest.ps1
Normal file
122
scripts/sign-manifest.ps1
Normal file
@@ -0,0 +1,122 @@
|
||||
<#
|
||||
.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
|
||||
Reference in New Issue
Block a user