v5.6 batch: signed releases + stronger passwords enforced + relay address-proof
The everyone-must-update release. Four coordinated changes, each from the security discussion Ed approved 2026-07-27, plus the remembered-apps polish: 1. SIGNED RELEASES. build-release.ps1 now signs the release zip (ECDSA P-256 / SHA-256, --sign-update verb) with a private key that lives ONLY at Ed's chosen location outside the repo; the matching public key is embedded (UpdateSignature) and the updater REFUSES any release whose .sig asset is missing or does not verify - a compromised GitHub account can no longer ship code to users. The signing verb self-checks against the embedded key so a key/embed mismatch fails the pipeline, and the gate proves the on-disk key matches the embed when present. 2. STRONGER PASSWORDS, ENFORCED (BREAKING). PBKDF2 raised 100k -> 600k (both peers must derive the same key, so 5.6 cannot stream with pre-5.6 AT ALL - release notes lead with it). New PasswordStrength rule (>= 8 chars, not an infamous password) enforced at EVERY door: both password dialogs block weak NEW entries with concrete plain-English advice; the streaming gate walks an existing weak password through strengthening; and ForPlainPassword - the single derivation choke-point shared with the service - refuses weak outright, so no path streams on a guessable password. Headless service logs the why. Per Ed: painful once, and this coordinated-update release is the cheapest moment it will ever have. 3. RELAY ADDRESS-PROOF (watch-only). The relay sends every new client address a random cookie and marks it verified when echoed - a forged source address can never echo, killing the reflection attack. 5.6 clients echo automatically (AddrCheck type 10, verbatim, self-limiting); the relay ships watch-only (logs would-blocks) until the fleet updates, then one flag (--require-addr-check) enforces. Per-IP entry cap (4) enforced immediately. Relay changes are committed but NOT deployed to the Pi - they ride the v5.6 release moment. 4. Remembered-apps empty state teaches its lifecycle + manual sentence; About/ release notes written; version bumped to 5.6. New gate steps: signing round-trip/tamper/wrong-key/embed-match; password rules incl. the exact "Games" case; AddrCheck verbatim echo. Gate 69/69. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -94,6 +94,10 @@ internal static class CommandLine
|
||||
return WithConsole(() => SetLogging(ValueAfter(args, raw)));
|
||||
case "--close": case "--quit":
|
||||
return WithConsole(CloseRunning);
|
||||
case "--sign-update":
|
||||
// Publish-pipeline verb (build-release.ps1): sign a release zip with the private
|
||||
// key so the updater's signature enforcement accepts it. Not a user command.
|
||||
return WithConsole(() => SignUpdate(ValueAfter(args, raw)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +121,50 @@ internal static class CommandLine
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Where the release-signing PRIVATE key lives on the publisher's machine (chosen by
|
||||
/// Ed, 2026-07-27). Overridable via REMSOUND_SIGNING_KEY for a future move. The key is never
|
||||
/// in the repo or a release; the matching public key is embedded (UpdateSignature).</summary>
|
||||
private static string SigningKeyPath =>
|
||||
Environment.GetEnvironmentVariable("REMSOUND_SIGNING_KEY")
|
||||
?? @"D:\Dropbox\proj\rsound key\remsound-signing-key.pem";
|
||||
|
||||
/// <summary>--sign-update <zip>: write <zip>.sig (base64 ECDSA P-256 / SHA-256 over the
|
||||
/// zip bytes) and self-check it against the EMBEDDED public key before reporting success — so a
|
||||
/// key/embed mismatch fails the publish pipeline loudly instead of shipping a release every
|
||||
/// updater would refuse.</summary>
|
||||
private static int SignUpdate(string? zipPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(zipPath) || !File.Exists(zipPath))
|
||||
{
|
||||
Console.WriteLine($"sign-update: zip not found: [{zipPath}]");
|
||||
return 2;
|
||||
}
|
||||
if (!File.Exists(SigningKeyPath))
|
||||
{
|
||||
Console.WriteLine($"sign-update: signing key not found at [{SigningKeyPath}] (set REMSOUND_SIGNING_KEY to override)");
|
||||
return 3;
|
||||
}
|
||||
try
|
||||
{
|
||||
var bytes = File.ReadAllBytes(zipPath);
|
||||
var signature = UpdateSignature.SignWithKey(bytes, File.ReadAllText(SigningKeyPath));
|
||||
if (!UpdateSignature.Verify(bytes, signature))
|
||||
{
|
||||
Console.WriteLine("sign-update: FAILED self-check — the private key does not match the public key embedded in this build. Update UpdateSignature.PublicKeyPem or restore the right key file.");
|
||||
return 4;
|
||||
}
|
||||
var sigPath = zipPath + UpdateSignature.SignatureAssetSuffix;
|
||||
File.WriteAllText(sigPath, signature);
|
||||
Console.WriteLine($"sign-update: OK — wrote {sigPath} (verified against the embedded public key)");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"sign-update: FAILED — {ex.GetType().Name}: {ex.Message}");
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- console plumbing ----------------
|
||||
|
||||
/// <summary>Attach to the calling terminal (when launched from one), point Console.Out at the
|
||||
|
||||
Reference in New Issue
Block a user