Add fail-closed HCM manifest signature verification (ed25519) - #19
Merged
Conversation
The HCM matcher previously checked only a manifest's internal consistency and freshness, never its authenticity, so a forged manifest could claim any support tier (security review finding #1). The unused ArtifactPin signing_key_id hook is now backed by real cryptography. - Add a pure-Rust ed25519-dalek dependency (no key-generation/RNG feature). - Add an optional signature { key_id, sig } field to HcmManifest and a new signing module: canonical_signing_bytes (typed-model canonicalization with the signature field removed and object keys sorted), a TrustedKeyring mapping key_id -> verifying key, verify_manifest_signature, and a deterministic ManifestSigningKey helper for tests and offline tooling. - Add keyring-aware matcher entry points evaluate_manifest_verified and evaluate_manifest_at_verified. With a keyring, an unsigned manifest, an unknown key_id, a malformed signature, or a failed verification drives effective_tier to Blocked, and every artifact pin's signing_key_id must resolve in the same keyring; without a keyring the historical advisory behavior is unchanged. - Cover the crypto and wiring with deterministic fixed-seed tests: valid signature retains the tier; tamper, unknown key, and unsigned-with-keyring are Blocked; no keyring stays advisory. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the new manifest-embedded authenticity field in the schema: an optional top-level `signature` (object or null) with a `$defs/signature` requiring `key_id` and a 128-hex-char ed25519 `sig`. The field is optional, so the repository example manifest and all existing documents still validate; unknown or malformed signature objects are rejected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Describe the detached ed25519 signing model in the hardware-compatibility and certification-test-plan docs: the signature field, the canonicalization rules, the TrustedKeyring, the fail-closed enforcement semantics, and the manifest-level vs artifact-level signing relationship. State plainly that `andromeda hardware check` stays advisory until a keyring is wired in, that Supported/Certified manifests must be signed and evaluated with a keyring, and that key generation, distribution, and production signing are deployment concerns while the code supplies verification plus a signing procedure. Flag a CLI `--trusted-keys` flag as the follow-up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes security review finding #1: HCM manifests had no authenticity/signature verification, so a forged manifest could claim
certified. PR #12 added theArtifactVerifiertrait, sha256 artifact verification, and an unusedArtifactPin.signing_key_idfield; this PR makessigning_key_idmean something by adding real detached ed25519 signature verification, fail-closed.What is genuinely enforced (in code)
signature { key_id, sig }field onHcmManifest.sigis a detached ed25519 signature (64 bytes / 128 hex chars) over the manifest's canonical bytes — the typed model serialized with thesignaturefield removed and every object key sorted, so JSON whitespace/key order/null-vs-omitted never change the signed bytes. Canonicalization is the single source of truth incrates/andromeda-hardware/src/signing.rs::canonical_signing_bytes.TrustedKeyringmapskey_id -> ed25519 verifying key.evaluate_manifest_verified/evaluate_manifest_at_verified. With a keyring, an unsigned manifest, unknownkey_id, malformed signature, or failed verification driveseffective_tier = Blocked; additionally every artifact pin'ssigning_key_idmust resolve in the same keyring. Without a keyring, behavior is unchanged (advisory) for back-compat.sha256andsigning_key_id. The artifactsigning_key_ididentifies which trusted key vouches for a pinned digest (checked against the keyring); anArtifactVerifierstill separately confirms local bytes hash to the pinnedsha256.verify_strict(rejects signature malleability / small-order keys). No runtime RNG: the ed25519 dependency is built without the key-generation feature; the signing helper takes a fixed 32-byte seed.Documented deployment concern (not code)
Key generation, distribution, rotation, and the signing of production manifests are ops concerns. The code provides the verification path, a deterministic
ManifestSigningKeysigning helper, and a documented signing + canonicalization procedure (docs/development/hardware-compatibility.md,hardware-certification-test-plan.md).andromeda hardware checkremains advisory on its default (no-keyring) path — the docs now state plainly it must not be used as a trust decision until a keyring is wired in.Tests (real crypto, deterministic fixed seeds)
Signed manifest with a trusted key retains its tier; tampered manifest, unknown
key_id, and unsigned-manifest-with-keyring areBlocked; no-keyring stays advisory; artifact key outside the keyring blocks even when the manifest signature is valid; keyring + artifact verifier engage together; canonicalization survives a JSON round-trip. Plus unit tests for the keyring, hex codec, and canonicalization. Hardware crate: 61 tests pass.New dependencies
ed25519-dalek = 2.2.0(default features off;std+zeroize, norand_core), pulling the standard RustCrypto tree (curve25519-dalek, ed25519, signature, subtle, zeroize, etc.). Declared on theandromeda-hardwarecrate only;Cargo.lockupdated.Validation
cargo fmt --all --checkcleancargo clippy --workspace --all-targets --locked -- -D warningscleancargo test --workspace --lockedall greensignatureoptional); existing matcher tests unchanged.Follow-up (out of scope here: CLI owned by a sibling)
Add a minimal
andromeda hardware check --trusted-keys <path>option to load a{ "<key_id>": "<verifying-key-hex>" }file and route throughevaluate_manifest_verified, so the CLI exit code carries an authenticity guarantee. The library API is ready for it; the default CLI path stays advisory.🤖 Generated with Claude Code