#![cfg(test)]
use super::*;
#[test]
fn a_hand_built_invalid_kind_role_pair_encodes_and_is_then_refused() {
let mut refused = 0;
for (kind, role) in [
(DelegationSubjectKind::Domain, DelegationRole::SpeaksFor),
(DelegationSubjectKind::Seat, DelegationRole::Operational),
] {
let mut delegation = sample();
delegation.claim.subject_kind = kind;
delegation.claim.role = role;
let bytes = delegation.to_cose_bytes();
assert!(
ciborium::de::from_reader::<ciborium::value::Value, _>(bytes.as_slice()).is_ok(),
"the mutant must be well-formed CBOR for this test to mean anything"
);
assert!(
matches!(
Delegation::from_cose_bytes(&bytes),
Err(TrustError::DelegationVerification)
),
"({kind:?}, {role:?}) parsed back"
);
refused += 1;
}
assert_eq!(refused, 2, "both invalid pairs must have been tried");
Delegation::from_cose_bytes(&sample().to_cose_bytes()).unwrap();
Delegation::from_cose_bytes(&seat_sample().to_cose_bytes()).unwrap();
}
#[test]
fn the_envelope_malleability_sweep_is_refused() {
let good = sample().to_cose_bytes();
let protected = encoding::protected_bytes(CONTENT_TYPE, &ROOT_KEY);
let payload = encoding::payload_bytes(&sample().claim);
assert_eq!(&good[..4], &[0xd2, 0x84, 0x58, 0x4f]);
let mut mutants: Vec<(&str, Vec<u8>)> = Vec::new();
let mut m = vec![0xd8, 0x12];
m.extend_from_slice(&good[1..]);
mutants.push(("non-minimal tag head", m));
let mut m = vec![0xd2, 0x98, 0x04];
m.extend_from_slice(&good[2..]);
mutants.push(("non-minimal outer-array head", m));
let mut m = vec![0xd2, 0x9f];
m.extend_from_slice(&good[2..]);
m.push(0xff);
mutants.push(("indefinite outer array", m));
let mut m = vec![0xd2, 0x84, 0x5f, 0x58, 0x4f];
m.extend_from_slice(&protected);
m.push(0xff);
m.extend_from_slice(&good[PROTECTED_AT + PROTECTED_LEN..]);
mutants.push(("indefinite protected bstr", m));
let mut m = vec![0xd2, 0x84, 0x59, 0x00, 0x4f];
m.extend_from_slice(&protected);
m.extend_from_slice(&good[PROTECTED_AT + PROTECTED_LEN..]);
mutants.push(("non-minimal protected bstr head", m));
let mut m = good[..UNPROTECTED_AT].to_vec();
m.extend_from_slice(&[0xbf, 0xff]);
m.extend_from_slice(&good[UNPROTECTED_AT + 1..]);
mutants.push(("indefinite unprotected map", m));
let mut m = good[..=UNPROTECTED_AT].to_vec();
m.push(0x5f);
m.push(0x58);
m.push(u8::try_from(payload.len()).unwrap());
m.extend_from_slice(&payload);
m.push(0xff);
m.extend_from_slice(&[0x58, 0x40]);
m.extend_from_slice(&SIGNATURE);
mutants.push(("indefinite payload bstr", m));
let mut m = good[..=UNPROTECTED_AT].to_vec();
m.extend_from_slice(&[0x59, 0x00, u8::try_from(payload.len()).unwrap()]);
m.extend_from_slice(&payload);
m.extend_from_slice(&[0x58, 0x40]);
m.extend_from_slice(&SIGNATURE);
mutants.push(("non-minimal payload bstr head", m));
let mut m = good[..good.len() - 66].to_vec();
m.extend_from_slice(&[0x59, 0x00, 0x40]);
m.extend_from_slice(&SIGNATURE);
mutants.push(("non-minimal signature bstr head", m));
let mut m = good[..good.len() - 66].to_vec();
m.extend_from_slice(&[0x5f, 0x58, 0x40]);
m.extend_from_slice(&SIGNATURE);
m.push(0xff);
mutants.push(("indefinite signature bstr", m));
let mut m = good.clone();
m.push(0x00);
mutants.push(("trailing garbage", m));
let mut m = good.clone();
m.extend_from_slice(&[0xff; 16]);
mutants.push(("trailing garbage, 16 bytes", m));
let mut refused = 0;
for (name, mutant) in &mutants {
assert_ne!(mutant, &good, "{name} did not change the artifact");
assert!(
ciborium::de::from_reader::<ciborium::value::Value, _>(mutant.as_slice()).is_ok(),
"{name} is not well-formed CBOR, so its rejection would prove nothing"
);
assert!(
matches!(
Delegation::from_cose_bytes(mutant),
Err(TrustError::DelegationVerification)
),
"{name} was accepted"
);
refused += 1;
}
assert_eq!(
refused, 12,
"every malleability mutant must have been tried"
);
Delegation::from_cose_bytes(&good).unwrap();
}
#[test]
fn an_oversized_integer_head_in_the_payload_is_refused() {
let payload = encoding::payload_bytes(&sample().claim);
let role_at = payload.len() - 4 - 10 - 2;
assert_eq!(
payload[role_at], 0x04,
"label 4 must be where the tail puts it"
);
assert_eq!(payload[role_at + 1], 0x02, "operational is wire value 2");
assert_eq!(payload[role_at + 2], 0x05, "label 5 follows the role value");
let mut fat = payload[..role_at].to_vec();
fat.extend_from_slice(&[0x04, 0x19, 0x00, 0x02]);
fat.extend_from_slice(&payload[role_at + 2..]);
let mutant = artifact_with_payload(&fat);
assert!(
ciborium::de::from_reader::<ciborium::value::Value, _>(mutant.as_slice()).is_ok(),
"the mutant must be well-formed CBOR for this test to mean anything"
);
assert!(Delegation::from_cose_bytes(&mutant).is_err());
}
#[test]
fn a_reordered_protected_map_is_refused() {
let good = sample().to_cose_bytes();
let protected = &good[PROTECTED_AT..PROTECTED_AT + PROTECTED_LEN];
let mut reordered = vec![0xa3, 0x01, 0x27];
reordered.extend_from_slice(&[0x04, 0x58, 0x20]);
reordered.extend_from_slice(&ROOT_KEY);
reordered.extend_from_slice(&protected[3..3 + 3 + CONTENT_TYPE.len()]);
assert_eq!(reordered.len(), PROTECTED_LEN);
assert_ne!(reordered.as_slice(), protected);
let mut mutant = good[..PROTECTED_AT].to_vec();
mutant.extend_from_slice(&reordered);
mutant.extend_from_slice(&good[PROTECTED_AT + PROTECTED_LEN..]);
assert_eq!(mutant.len(), good.len());
assert!(Delegation::from_cose_bytes(&mutant).is_err());
}
#[test]
fn a_detached_nil_payload_is_not_a_delegation() {
let protected = encoding::protected_bytes(CONTENT_TYPE, &ROOT_KEY);
let mut mutant = vec![0xd2, 0x84, 0x58, 0x4f];
mutant.extend_from_slice(&protected);
mutant.push(0xa0);
mutant.push(0xf6);
mutant.extend_from_slice(&[0x58, 0x40]);
mutant.extend_from_slice(&SIGNATURE);
assert!(
ciborium::de::from_reader::<ciborium::value::Value, _>(mutant.as_slice()).is_ok(),
"the mutant must be well-formed CBOR for this test to mean anything"
);
assert!(Delegation::from_cose_bytes(&mutant).is_err());
}
#[test]
fn stripping_the_tag_is_refused() {
let good = sample().to_cose_bytes();
assert_eq!(good[0], 0xd2);
assert!(Delegation::from_cose_bytes(&good[1..]).is_err());
}
#[test]
fn a_wrong_tag_number_is_refused() {
let mut mutant = sample().to_cose_bytes();
mutant[0] = 0xd8;
mutant.insert(1, 98);
assert!(Delegation::from_cose_bytes(&mutant).is_err());
}
#[test]
fn a_receipt_content_type_in_the_protected_bucket_is_refused_by_the_parser() {
let confused = encoding::envelope_bytes(
&encoding::protected_bytes("application/vnd.lys.receipt.v1+cbor", &ROOT_KEY),
&encoding::payload_bytes(&sample().claim),
&SIGNATURE,
);
assert!(Delegation::from_cose_bytes(&confused).is_err());
}
#[test]
fn a_ten_thousand_character_subject_parses_whole() {
let subject = "o".repeat(10_000);
let delegation = Delegation {
root_public_key: ROOT_KEY,
claim: claim_for(&subject, 0, 0),
signature: SIGNATURE,
};
let bytes = delegation.to_cose_bytes();
assert!(bytes.len() > 10_000);
let parsed = Delegation::from_cose_bytes(&bytes).unwrap();
assert_eq!(parsed.claim.subject_value, subject);
assert_eq!(parsed, delegation);
}
#[test]
fn every_parse_failure_is_the_same_error() {
let good = sample().to_cose_bytes();
Delegation::from_cose_bytes(&good).unwrap();
let mut trailing = good.clone();
trailing.push(0x00);
let mut truncated = good.clone();
truncated.truncate(good.len() / 2);
let mut untagged = good.clone();
untagged.remove(0);
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..]);
assert!(
ciborium::de::from_reader::<ciborium::value::Value, _>(non_empty_unprotected.as_slice())
.is_ok(),
"the non-empty-bucket mutant must be well-formed CBOR"
);
let cases = [
("trailing garbage", trailing),
("truncated", truncated),
("untagged", untagged),
("non-empty unprotected", non_empty_unprotected),
("empty input", vec![]),
("noise", vec![0xff; 10]),
];
let mut refused = 0;
for (name, mutant) in cases {
let err = Delegation::from_cose_bytes(&mutant).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, 6, "every mutant must have been rejected");
}