{"record":{"id":"c90c46d16c7ae5e8","repo":"astrid-runtime/astrid","slug":"distro-signature-verification-failed","errorCode":null,"errorMessage":"distro signature verification failed","messagePattern":"distro signature verification failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/distro/sign.rs","lineNumber":105,"sourceCode":"}\n\n/// Verify a hex `Distro.sig` against a lock and a public key.\n///\n/// # Errors\n///\n/// Returns an error if the signature is malformed (not 64 hex bytes) or\n/// does not verify against the lock's signing digest under `pubkey`.\npub(crate) fn verify_lock(\n    lock: &DistroLock,\n    sig_hex: &str,\n    pubkey: &PublicKey,\n) -> anyhow::Result<()> {\n    let sig = Signature::from_hex(sig_hex.trim())\n        .map_err(|e| anyhow::anyhow!(\"malformed Distro.sig (expected 64-byte hex): {e}\"))?;\n    let digest = lock_signing_digest(lock)?;\n    pubkey\n        .verify(&digest, &sig)\n        .map_err(|_| anyhow::anyhow!(\"distro signature verification failed\"))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::commands::distro::lock::{DistroLock, DistroLockMeta, LockedCapsule};\n\n    fn sample_lock() -> DistroLock {\n        DistroLock {\n            schema_version: 1,\n            distro: DistroLockMeta {\n                id: \"test\".into(),\n                version: \"0.1.0\".into(),\n                resolved_at: \"2026-01-01T00:00:00Z\".into(),\n            },\n            capsules: vec![LockedCapsule {\n                name: \"astrid-capsule-cli\".into(),\n                version: \"0.1.0\".into(),","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/sign.rs#L87-L123","documentation":"verify_lock signs the distro lock over a canonical digest (lock_signing_digest) and checks the ed25519-style signature from Distro.sig against the distro's declared public key. This error is the blanket rejection from pubkey.verify: the signature bytes parsed fine (they were valid 64-byte hex) but do not match the digest under that key. It deliberately returns no cryptographic detail to avoid oracle leaks.","triggerScenarios":"verify_lock(lock, sig_hex, pubkey) when: the Distro.sig was made over a different lock file/revision; the lock was edited after signing; the sig was generated with a different private key than the manifest's [distro.signing].pubkey; or the sig hex was corrupted/transcoded (e.g. re-saved via a tool that altered whitespace-to-content).","commonSituations":"Distro maintainers re-signing after a lock edit but shipping the old .sig; shipping a sig from a stale branch; CI building a lock deterministically but signing a different serialization; malicious tampering with the artifact in transit.","solutions":["Re-sign the lock with the private key matching the manifest pubkey and redeploy Distro.sig.","Verify you have the exact lock the sig was produced from (diff lock contents/hashes; re-fetch the distro).","Check the manifest's [distro.signing].pubkey matches the key that produced the sig (key rotation?).","Inspect Distro.sig for corruption (must be 64-byte hex, no wrapping/encoding changes); re-download it."],"exampleFix":"// before: lock edited after signing\n$ astrid distro sign --lock Distro.lock  # regenerate Distro.sig from the edited lock\n// after\n$ astrid distro apply --distro my-distro  # verify now passes","handlingStrategy":"try-catch","validationCode":"// Pre-check before trusting: hex decode length only; actual verify needs the key.\nlet sig_bytes = hex::decode(sig_hex.trim())?;\nif sig_bytes.len() != 64 { return Err(anyhow!(\"Distro.sig must be 64-byte hex\")); }","typeGuard":"fn is_wellformed_sig(sig_hex: &str) -> bool {\n    hex::decode(sig_hex.trim()).map(|b| b.len() == 64).unwrap_or(false)\n}","tryCatchPattern":"match sign::verify_lock(&lock, sig_hex, &pubkey) {\n    Ok(()) => install(),\n    Err(e) if e.to_string().contains(\"verification failed\") => {\n        eprintln!(\"signature mismatch: re-sign the lock or re-fetch the artifact\");\n    },\n    Err(e) => return Err(e),\n}","preventionTips":["Always regenerate Distro.sig in the same CI step that produces the lock.","Verify signatures in CI before publishing a distro release.","Keep the signing key and manifest pubkey in sync; rotate both together.","Never hand-edit a signed lock file."],"tags":["signature","cryptography","supply-chain","verification"],"backgroundTag":"checksum-mismatch","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}