Skip to content

Add fail-closed HCM manifest signature verification (ed25519) - #19

Merged
oratis merged 4 commits into
mainfrom
followup/hcm-signing
Aug 2, 2026
Merged

Add fail-closed HCM manifest signature verification (ed25519)#19
oratis merged 4 commits into
mainfrom
followup/hcm-signing

Conversation

@oratis

@oratis oratis commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

Closes security review finding #1: HCM manifests had no authenticity/signature verification, so a forged manifest could claim certified. PR #12 added the ArtifactVerifier trait, sha256 artifact verification, and an unused ArtifactPin.signing_key_id field; this PR makes signing_key_id mean something by adding real detached ed25519 signature verification, fail-closed.

What is genuinely enforced (in code)

  • Manifest-embedded signature. New optional signature { key_id, sig } field on HcmManifest. sig is a detached ed25519 signature (64 bytes / 128 hex chars) over the manifest's canonical bytes — the typed model serialized with the signature field 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 in crates/andromeda-hardware/src/signing.rs::canonical_signing_bytes.
  • Trusted keyring. TrustedKeyring maps key_id -> ed25519 verifying key.
  • Fail-closed matcher gate. New evaluate_manifest_verified / evaluate_manifest_at_verified. With a keyring, an unsigned manifest, unknown key_id, malformed signature, or failed verification drives effective_tier = Blocked; additionally every artifact pin's signing_key_id must resolve in the same keyring. Without a keyring, behavior is unchanged (advisory) for back-compat.
  • Manifest-level vs artifact-level signing. The manifest signature is the authenticity gate — verifying it authenticates every declared sha256 and signing_key_id. The artifact signing_key_id identifies which trusted key vouches for a pinned digest (checked against the keyring); an ArtifactVerifier still separately confirms local bytes hash to the pinned sha256.
  • Crypto uses 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 ManifestSigningKey signing helper, and a documented signing + canonicalization procedure (docs/development/hardware-compatibility.md, hardware-certification-test-plan.md). andromeda hardware check remains 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 are Blocked; 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, no rand_core), pulling the standard RustCrypto tree (curve25519-dalek, ed25519, signature, subtle, zeroize, etc.). Declared on the andromeda-hardware crate only; Cargo.lock updated.

Validation

  • cargo fmt --all --check clean
  • cargo clippy --workspace --all-targets --locked -- -D warnings clean
  • cargo test --workspace --locked all green
  • Repository example manifest still validates against the updated schema (signature optional); 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 through evaluate_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

oratis and others added 4 commits August 1, 2026 21:54
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>
@oratis
oratis merged commit 50e7347 into main Aug 2, 2026
5 checks passed
@oratis
oratis deleted the followup/hcm-signing branch August 2, 2026 04:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant