#![cfg(test)]
use super::*;
#[test]
fn signature_verification_runs_for_every_mismatch_kind() {
use crate::delegation::sign::SIGNATURE_VERIFICATIONS;
let key = root();
let expected = key.public_key_bytes();
let count = || SIGNATURE_VERIFICATIONS.with(std::cell::Cell::get);
let wrong_kid = sign_delegation(&attacker(), &claim()).unwrap();
let wrong_origin = sign_delegation(&key, &claim_for(OTHER_ORIGIN)).unwrap();
let mut bad_signature = sign_delegation(&key, &claim()).unwrap();
let last = bad_signature.len() - 1;
bad_signature[last] ^= 0x01;
let honest = sign_delegation(&key, &claim()).unwrap();
let seat_at_a_domain_verifier = sign_delegation(&key, &seat_claim_for(ORIGIN)).unwrap();
let mut measured = 0;
for (name, artifact, should_verify) in [
("wrong kid", wrong_kid, false),
("wrong subject value", wrong_origin, false),
("wrong subject kind", seat_at_a_domain_verifier, false),
("bad signature", bad_signature, false),
("honest", honest, true),
] {
let before = count();
let outcome = verify_delegation(&artifact, &expected, DOMAIN, ORIGIN);
let after = count();
assert_eq!(
after - before,
1,
"{name}: the signature verification must run exactly once — a count \
of 0 means an early return was restored and the 32.8x timing oracle \
is back"
);
assert_eq!(outcome.is_ok(), should_verify, "{name}: wrong outcome");
measured += 1;
}
assert_eq!(
measured, 5,
"every arm must have been measured — one per comparison in \
`verify_delegation` plus the honest case. Widening that function \
without widening this count leaves the new comparison unguarded, which \
is exactly how the subject-kind arm came to be missing."
);
let before = count();
assert!(verify_delegation(b"not cbor at all", &expected, DOMAIN, ORIGIN).is_err());
assert_eq!(
count() - before,
0,
"a parse failure must not reach the signature check — if this counts, \
the instrument is incrementing somewhere that proves nothing"
);
}
#[test]
fn a_wrong_kid_does_not_skip_the_signature_check() {
let key = root();
let evil = attacker();
let mut both_wrong = sign_delegation(&evil, &claim()).unwrap();
let sig_at = both_wrong.len() - 64;
both_wrong[sig_at..].copy_from_slice(&[0u8; 64]);
assert!(matches!(
verify_delegation(&both_wrong, &key.public_key_bytes(), DOMAIN, ORIGIN),
Err(TrustError::DelegationVerification)
));
assert!(matches!(
verify_delegation(&both_wrong, &evil.public_key_bytes(), DOMAIN, ORIGIN),
Err(TrustError::DelegationVerification)
));
let intact = sign_delegation(&evil, &claim()).unwrap();
verify_delegation(&intact, &evil.public_key_bytes(), DOMAIN, ORIGIN).unwrap();
}
#[test]
fn the_verifier_does_the_same_work_for_every_kind_of_mismatch() {
let key = root();
let expected = key.public_key_bytes();
let wrong_kid = sign_delegation(&attacker(), &claim()).unwrap();
let wrong_origin = sign_delegation(&key, &claim_for(OTHER_ORIGIN)).unwrap();
let mut bad_signature = sign_delegation(&key, &claim()).unwrap();
let last = bad_signature.len() - 1;
bad_signature[last] ^= 0x01;
let mut refused = 0;
for (name, mutant, valid_signature) in [
("wrong kid", wrong_kid, true),
("wrong origin", wrong_origin, true),
("bad signature", bad_signature, false),
] {
let parsed = Delegation::from_cose_bytes(&mutant).unwrap();
let preimage = delegation_preimage(&parsed.root_public_key, &parsed.claim);
let signature_ok =
Ed25519Identity::verify(&parsed.root_public_key, &preimage, &parsed.signature).is_ok();
assert_eq!(
signature_ok, valid_signature,
"{name} does not have the signature status this test assumes"
);
assert!(matches!(
verify_delegation(&mutant, &expected, DOMAIN, ORIGIN),
Err(TrustError::DelegationVerification)
));
refused += 1;
}
assert_eq!(refused, 3, "every mismatch kind must have been tried");
}
#[test]
fn every_verification_failure_is_the_same_error() {
let key = root();
let expected = key.public_key_bytes();
let good = sign_delegation(&key, &claim()).unwrap();
verify_delegation(&good, &expected, DOMAIN, ORIGIN).unwrap();
let wrong_root = sign_delegation(&attacker(), &claim()).unwrap();
let wrong_origin = sign_delegation(&key, &claim_for(OTHER_ORIGIN)).unwrap();
let mut bad_signature = good.clone();
let last = bad_signature.len() - 1;
bad_signature[last] ^= 0x01;
let relabelled = signed_envelope(
&encoding::protected_bytes("application/vnd.lys.receipt.v1+cbor", &expected),
&encoding::payload_bytes(&claim()),
&key,
);
let canonical_payload = encoding::payload_bytes(&claim());
let role_at = role_offset(&canonical_payload);
let mut unknown_role_payload = canonical_payload.clone();
unknown_role_payload[role_at + 1] = 7;
let unknown_role = signed_envelope(&protected_for(&key), &unknown_role_payload, &key);
let mut invalid_pair_payload = canonical_payload;
invalid_pair_payload[role_at + 1] = 0x03;
let invalid_pair = signed_envelope(&protected_for(&key), &invalid_pair_payload, &key);
let wrong_kind = sign_delegation(&key, &seat_claim_for(ORIGIN)).unwrap();
let unprotected_at = 4 + encoding::PROTECTED_LEN;
let mut non_empty_unprotected = good[..unprotected_at].to_vec();
non_empty_unprotected.extend_from_slice(&[0xa1, 0x01, 0x01]);
non_empty_unprotected.extend_from_slice(&good[unprotected_at + 1..]);
let mut trailing = good;
trailing.push(0x00);
let cases = [
("wrong root key", wrong_root),
("wrong subject value", wrong_origin),
("forged signature", bad_signature),
("relabelled as a receipt", relabelled),
("unknown role", unknown_role),
("invalid (kind, role) pair", invalid_pair),
("wrong subject kind", wrong_kind),
("non-empty unprotected", non_empty_unprotected),
("trailing garbage", trailing),
("empty input", vec![]),
];
let mut refused = 0;
for (name, mutant) in cases {
let err = verify_delegation(&mutant, &expected, DOMAIN, ORIGIN).unwrap_err();
assert!(
matches!(err, TrustError::DelegationVerification),
"{name} produced a distinguishable error: {err:?}"
);
assert_eq!(
format!("{err}"),
format!("{}", TrustError::DelegationVerification),
"{name} produced a distinguishable message"
);
refused += 1;
}
assert_eq!(refused, 10, "every case must have been rejected");
}