use corim::builder::{ComidBuilder, CorimBuilder};
use corim::cbor;
use corim::cbor::value::Value;
use corim::types::common::{MeasuredElement, TagIdChoice};
use corim::types::corim::{CorimId, CorimMetaMap, CorimSignerMap};
use corim::types::environment::{ClassMap, EnvironmentMap};
use corim::types::measurement::{Digest, MeasurementMap, MeasurementValuesMap};
use corim::types::signed::*;
use corim::types::tags::TAG_SIGNED_CORIM;
use corim::types::triples::ReferenceTriple;
use corim::Validate;
fn build_sample_corim_bytes() -> Vec<u8> {
let env = EnvironmentMap {
class: Some(ClassMap {
class_id: None,
vendor: Some("TestVendor".into()),
model: Some("TestModel".into()),
layer: None,
index: None,
}),
instance: None,
group: None,
};
let meas = MeasurementMap {
mkey: Some(MeasuredElement::Text("firmware".into())),
mval: MeasurementValuesMap {
digests: Some(vec![Digest::new(7, vec![0xBB; 48])]),
..MeasurementValuesMap::default()
},
authorized_by: None,
};
let comid = ComidBuilder::new(TagIdChoice::Text("signed-test-comid".into()))
.add_reference_triple(ReferenceTriple::new(env, vec![meas]))
.build()
.unwrap();
CorimBuilder::new(CorimId::Text("signed-test-corim".into()))
.add_comid_tag(comid)
.unwrap()
.build_bytes()
.unwrap()
}
fn make_corim_meta() -> CorimMetaMap {
CorimMetaMap {
signer: CorimSignerMap {
signer_name: "ACME Corp".into(),
signer_uri: Some("https://acme.example.com".into()),
},
signature_validity: None,
}
}
fn make_cwt_claims() -> CwtClaims {
CwtClaims::new("ACME Corp")
.with_nbf(1700000000)
.with_exp(1800000000)
}
#[test]
fn cwt_claims_round_trip() {
let claims = CwtClaims::new("Test Issuer")
.with_sub("test-doc")
.with_nbf(1700000000)
.with_exp(1800000000);
let bytes = cbor::encode(&claims).unwrap();
let decoded: CwtClaims = cbor::decode(&bytes).unwrap();
assert_eq!(decoded.iss, "Test Issuer");
assert_eq!(decoded.sub.as_deref(), Some("test-doc"));
assert_eq!(decoded.nbf, Some(1700000000));
assert_eq!(decoded.exp, Some(1800000000));
}
#[test]
fn cwt_claims_minimal() {
let claims = CwtClaims::new("Minimal");
let bytes = cbor::encode(&claims).unwrap();
let decoded: CwtClaims = cbor::decode(&bytes).unwrap();
assert_eq!(decoded.iss, "Minimal");
assert_eq!(decoded.sub, None);
assert_eq!(decoded.exp, None);
assert_eq!(decoded.nbf, None);
assert!(decoded.extra.is_empty());
}
#[test]
fn cwt_claims_with_extra_fields() {
let mut claims = CwtClaims::new("Test");
claims.extra.insert(100, Value::Text("custom".into()));
let bytes = cbor::encode(&claims).unwrap();
let decoded: CwtClaims = cbor::decode(&bytes).unwrap();
assert_eq!(decoded.extra.get(&100), Some(&Value::Text("custom".into())));
}
#[test]
fn cwt_claims_missing_iss_fails() {
let val = Value::Map(vec![(Value::Integer(2), Value::Text("sub".into()))]);
let bytes = cbor::encode(&val).unwrap();
let result = cbor::decode::<CwtClaims>(&bytes);
assert!(result.is_err());
}
#[test]
fn protected_header_with_corim_meta_round_trip() {
let meta = make_corim_meta();
let header = ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es256,
content_type: Some("application/rim+cbor".into()),
payload_hash_alg: None,
payload_preimage_content_type: None,
payload_location: None,
corim_meta: Some(meta.clone()),
cwt_claims: None,
extra: std::collections::BTreeMap::new(),
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
};
let bytes = cbor::encode(&header).unwrap();
let decoded: ProtectedCorimHeaderMap = cbor::decode(&bytes).unwrap();
assert_eq!(decoded.alg, CoseAlgorithm::Es256);
assert_eq!(
decoded.content_type.as_deref(),
Some("application/rim+cbor")
);
assert!(decoded.corim_meta.is_some());
let dm = decoded.corim_meta.unwrap();
assert_eq!(dm.signer.signer_name, "ACME Corp");
assert_eq!(
dm.signer.signer_uri.as_deref(),
Some("https://acme.example.com")
);
}
#[test]
fn protected_header_with_cwt_claims_round_trip() {
let claims = make_cwt_claims();
let header = ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es384,
content_type: Some("application/rim+cbor".into()),
payload_hash_alg: None,
payload_preimage_content_type: None,
payload_location: None,
corim_meta: None,
cwt_claims: Some(claims),
extra: std::collections::BTreeMap::new(),
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
};
let bytes = cbor::encode(&header).unwrap();
let decoded: ProtectedCorimHeaderMap = cbor::decode(&bytes).unwrap();
assert_eq!(decoded.alg, CoseAlgorithm::Es384);
assert!(decoded.cwt_claims.is_some());
let dc = decoded.cwt_claims.unwrap();
assert_eq!(dc.iss, "ACME Corp");
assert_eq!(dc.nbf, Some(1700000000));
assert_eq!(dc.exp, Some(1800000000));
}
#[test]
fn protected_header_with_both_meta_and_cwt() {
let header = ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es256,
content_type: Some("application/rim+cbor".into()),
payload_hash_alg: None,
payload_preimage_content_type: None,
payload_location: None,
corim_meta: Some(make_corim_meta()),
cwt_claims: Some(make_cwt_claims()),
extra: std::collections::BTreeMap::new(),
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
};
assert!(header.valid().is_ok());
let bytes = cbor::encode(&header).unwrap();
let decoded: ProtectedCorimHeaderMap = cbor::decode(&bytes).unwrap();
assert!(decoded.corim_meta.is_some());
assert!(decoded.cwt_claims.is_some());
}
#[test]
fn protected_header_missing_both_meta_and_cwt_fails_decode() {
let val = Value::Map(vec![
(Value::Integer(1), Value::Integer(-7)),
(
Value::Integer(3),
Value::Text("application/rim+cbor".into()),
),
]);
let bytes = cbor::encode(&val).unwrap();
let result = cbor::decode::<ProtectedCorimHeaderMap>(&bytes);
assert!(result.is_err());
}
#[test]
fn protected_header_missing_alg_fails_decode() {
let meta_bytes = cbor::encode(&make_corim_meta()).unwrap();
let val = Value::Map(vec![
(
Value::Integer(3),
Value::Text("application/rim+cbor".into()),
),
(Value::Integer(8), Value::Bytes(meta_bytes)),
]);
let bytes = cbor::encode(&val).unwrap();
let result = cbor::decode::<ProtectedCorimHeaderMap>(&bytes);
assert!(result.is_err());
}
#[test]
fn protected_header_validate_inline_mode() {
let header = ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es256,
content_type: Some("application/rim+cbor".into()),
payload_hash_alg: None,
payload_preimage_content_type: None,
payload_location: None,
corim_meta: Some(make_corim_meta()),
cwt_claims: None,
extra: std::collections::BTreeMap::new(),
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
};
assert!(header.valid().is_ok());
}
#[test]
fn protected_header_validate_inline_missing_content_type() {
let header = ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es256,
content_type: None,
payload_hash_alg: None,
payload_preimage_content_type: None,
payload_location: None,
corim_meta: Some(make_corim_meta()),
cwt_claims: None,
extra: std::collections::BTreeMap::new(),
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
};
assert!(header.valid().is_err());
}
#[test]
fn protected_header_validate_hash_envelope_mode() {
let header = ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es256,
content_type: None,
payload_hash_alg: Some(1), payload_preimage_content_type: Some("application/rim+cbor".into()),
payload_location: Some("https://example.com/corim.cbor".into()),
corim_meta: Some(make_corim_meta()),
cwt_claims: None,
extra: std::collections::BTreeMap::new(),
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
};
assert!(header.valid().is_ok());
assert!(header.is_hash_envelope());
}
#[test]
fn protected_header_validate_meta_cwt_mismatch() {
let mut meta = make_corim_meta();
meta.signer.signer_name = "Different Corp".into();
let header = ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es256,
content_type: Some("application/rim+cbor".into()),
payload_hash_alg: None,
payload_preimage_content_type: None,
payload_location: None,
corim_meta: Some(meta),
cwt_claims: Some(make_cwt_claims()),
extra: std::collections::BTreeMap::new(),
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
};
let result = header.valid();
assert!(result.is_err());
assert!(result.unwrap_err().contains("signer-name"));
}
#[test]
fn signed_corim_builder_with_cwt_claims() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("Builder Test"));
let tbs = builder.to_be_signed(&[]).unwrap();
assert!(!tbs.is_empty());
let tbs_val: Value = cbor::decode(&tbs).unwrap();
if let Value::Array(arr) = tbs_val {
assert_eq!(arr.len(), 4);
assert_eq!(arr[0], Value::Text("Signature1".into()));
assert!(matches!(arr[1], Value::Bytes(_)));
assert_eq!(arr[2], Value::Bytes(vec![]));
assert!(matches!(arr[3], Value::Bytes(_)));
} else {
panic!("TBS must be a CBOR array");
}
}
#[test]
fn signed_corim_builder_with_corim_meta() {
let corim_bytes = build_sample_corim_bytes();
let mut builder = SignedCorimBuilder::new(-35, corim_bytes).set_corim_meta(make_corim_meta());
let tbs = builder.to_be_signed(&[]).unwrap();
assert!(!tbs.is_empty());
let fake_signature = vec![0xAA; 64];
let signed_bytes = builder
.build_with_signature(fake_signature.clone())
.unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
assert_eq!(signed.protected.alg, CoseAlgorithm::Es384);
assert!(signed.protected.corim_meta.is_some());
assert_eq!(
signed
.protected
.corim_meta
.as_ref()
.unwrap()
.signer
.signer_name,
"ACME Corp"
);
assert_eq!(signed.signature, fake_signature);
assert!(signed.payload.is_some());
}
#[test]
fn signed_corim_builder_with_both_meta_and_cwt() {
let corim_bytes = build_sample_corim_bytes();
let mut builder = SignedCorimBuilder::new(-7, corim_bytes)
.set_corim_meta(make_corim_meta())
.set_cwt_claims(make_cwt_claims());
let tbs = builder.to_be_signed(&[]).unwrap();
assert!(!tbs.is_empty());
let fake_sig = vec![0xCC; 64];
let signed_bytes = builder.build_with_signature(fake_sig).unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
assert!(signed.protected.corim_meta.is_some());
assert!(signed.protected.cwt_claims.is_some());
}
#[test]
fn signed_corim_builder_missing_meta_and_cwt_fails() {
let corim_bytes = build_sample_corim_bytes();
let mut builder = SignedCorimBuilder::new(-7, corim_bytes);
let result = builder.to_be_signed(&[]);
assert!(result.is_err());
}
#[test]
fn signed_corim_builder_with_external_aad() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("AAD Test"));
let aad = b"some-external-aad";
let tbs_with_aad = builder.to_be_signed(aad).unwrap();
let tbs_val: Value = cbor::decode(&tbs_with_aad).unwrap();
if let Value::Array(arr) = tbs_val {
assert_eq!(arr[2], Value::Bytes(aad.to_vec()));
} else {
panic!("TBS must be a CBOR array");
}
}
#[test]
fn signed_corim_builder_with_unprotected_header() {
let corim_bytes = build_sample_corim_bytes();
let mut builder = SignedCorimBuilder::new(-7, corim_bytes)
.set_cwt_claims(CwtClaims::new("Unprotected Test"))
.add_unprotected(Value::Integer(33), Value::Text("x5chain".into()));
let tbs = builder.to_be_signed(&[]).unwrap();
assert!(!tbs.is_empty());
let signed_bytes = builder.build_with_signature(vec![0xDD; 64]).unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
assert!(!signed.unprotected.is_empty());
}
#[test]
fn decode_signed_corim_valid() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("Decode Test"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let signed_bytes = builder.build_with_signature(vec![0x42; 64]).unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
assert_eq!(signed.protected.alg, CoseAlgorithm::Es256);
assert!(signed.payload.is_some());
assert_eq!(signed.signature, vec![0x42; 64]);
}
#[test]
fn decode_signed_corim_not_a_tag() {
let val = Value::Array(vec![]);
let bytes = cbor::encode(&val).unwrap();
let result = decode_signed_corim(&bytes);
assert!(result.is_err());
}
#[test]
fn decode_signed_corim_wrong_tag() {
let val = Value::Tag(99, Box::new(Value::Array(vec![])));
let bytes = cbor::encode(&val).unwrap();
let result = decode_signed_corim(&bytes);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(format!("{}", err).contains("expected CBOR tag 18"));
}
#[test]
fn decode_signed_corim_wrong_array_length() {
let val = Value::Tag(
TAG_SIGNED_CORIM,
Box::new(Value::Array(vec![Value::Bytes(vec![]), Value::Map(vec![])])),
);
let bytes = cbor::encode(&val).unwrap();
let result = decode_signed_corim(&bytes);
assert!(result.is_err());
}
#[test]
fn decode_signed_corim_not_an_array() {
let val = Value::Tag(TAG_SIGNED_CORIM, Box::new(Value::Text("bad".into())));
let bytes = cbor::encode(&val).unwrap();
let result = decode_signed_corim(&bytes);
assert!(result.is_err());
}
#[test]
fn decode_signed_corim_nil_payload() {
let meta_bytes = cbor::encode(&make_corim_meta()).unwrap();
let protected = Value::Map(vec![
(Value::Integer(1), Value::Integer(-7)),
(
Value::Integer(3),
Value::Text("application/rim+cbor".into()),
),
(Value::Integer(8), Value::Bytes(meta_bytes)),
]);
let protected_bytes = cbor::encode(&protected).unwrap();
let cose_arr = Value::Array(vec![
Value::Bytes(protected_bytes),
Value::Map(vec![]),
Value::Null,
Value::Bytes(vec![0xFF; 32]),
]);
let tagged = Value::Tag(TAG_SIGNED_CORIM, Box::new(cose_arr));
let bytes = cbor::encode(&tagged).unwrap();
let signed = decode_signed_corim(&bytes).unwrap();
assert!(signed.payload.is_none());
assert_eq!(signed.signature, vec![0xFF; 32]);
}
#[test]
fn tbs_sig_structure1_format() {
let protected_bytes = vec![0xA1, 0x01, 0x26]; let payload = vec![0xDE, 0xAD];
let external_aad = vec![];
let tbs = build_sig_structure1(&protected_bytes, &external_aad, &payload).unwrap();
let val: Value = cbor::decode(&tbs).unwrap();
match val {
Value::Array(arr) => {
assert_eq!(arr.len(), 4);
assert_eq!(arr[0], Value::Text("Signature1".into()));
assert_eq!(arr[1], Value::Bytes(protected_bytes));
assert_eq!(arr[2], Value::Bytes(vec![]));
assert_eq!(arr[3], Value::Bytes(vec![0xDE, 0xAD]));
}
_ => panic!("expected array"),
}
}
#[test]
fn tbs_with_external_aad() {
let protected_bytes = vec![0xA0]; let payload = vec![0x01];
let external_aad = b"extra-context".to_vec();
let tbs = build_sig_structure1(&protected_bytes, &external_aad, &payload).unwrap();
let val: Value = cbor::decode(&tbs).unwrap();
match val {
Value::Array(arr) => {
assert_eq!(arr[2], Value::Bytes(external_aad));
}
_ => panic!("expected array"),
}
}
#[test]
fn cose_sign1_corim_tbs_method() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes.clone()).set_cwt_claims(CwtClaims::new("TBS Test"));
let tbs_from_builder = builder.to_be_signed(&[]).unwrap();
let signed_bytes = builder.build_with_signature(vec![0x11; 64]).unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
let tbs_from_struct = signed.to_be_signed(&[]).unwrap();
assert_eq!(tbs_from_builder, tbs_from_struct);
}
#[test]
fn encode_decode_round_trip() {
let corim_bytes = build_sample_corim_bytes();
let protected = ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es256,
content_type: Some("application/rim+cbor".into()),
payload_hash_alg: None,
payload_preimage_content_type: None,
payload_location: None,
corim_meta: Some(make_corim_meta()),
cwt_claims: None,
extra: std::collections::BTreeMap::new(),
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
};
let protected_header_bytes = cbor::encode(&protected).unwrap();
let signed = CoseSign1Corim {
protected_header_bytes: protected_header_bytes.clone(),
protected,
unprotected: vec![],
payload: Some(corim_bytes),
signature: vec![0xEE; 64],
};
let encoded = encode_signed_corim(&signed).unwrap();
let decoded = decode_signed_corim(&encoded).unwrap();
assert_eq!(decoded.protected.alg, CoseAlgorithm::Es256);
assert_eq!(decoded.protected_header_bytes, protected_header_bytes);
assert_eq!(decoded.signature, vec![0xEE; 64]);
assert!(decoded.payload.is_some());
}
#[test]
fn validate_signed_corim_payload_valid() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("Validate Test"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let signed_bytes = builder.build_with_signature(vec![0x55; 64]).unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
let result = validate_signed_corim_payload(&signed, 0);
assert!(result.is_ok());
let validated = result.unwrap();
assert_eq!(validated.comids.len(), 1);
}
#[test]
fn validate_signed_corim_payload_detached_fails() {
let meta_bytes = cbor::encode(&make_corim_meta()).unwrap();
let protected = Value::Map(vec![
(Value::Integer(1), Value::Integer(-7)),
(
Value::Integer(3),
Value::Text("application/rim+cbor".into()),
),
(Value::Integer(8), Value::Bytes(meta_bytes)),
]);
let protected_bytes = cbor::encode(&protected).unwrap();
let cose_arr = Value::Array(vec![
Value::Bytes(protected_bytes),
Value::Map(vec![]),
Value::Null,
Value::Bytes(vec![0x00; 32]),
]);
let tagged = Value::Tag(TAG_SIGNED_CORIM, Box::new(cose_arr));
let bytes = cbor::encode(&tagged).unwrap();
let signed = decode_signed_corim(&bytes).unwrap();
let result = validate_signed_corim_payload(&signed, 0);
assert!(result.is_err());
assert!(format!("{}", result.unwrap_err()).contains("detached"));
}
#[test]
fn cose_header_constants() {
assert_eq!(COSE_HEADER_ALG, 1);
assert_eq!(COSE_HEADER_CONTENT_TYPE, 3);
assert_eq!(COSE_HEADER_CORIM_META, 8);
assert_eq!(COSE_HEADER_CWT_CLAIMS, 15);
assert_eq!(COSE_HEADER_PAYLOAD_HASH_ALG, 258);
assert_eq!(COSE_HEADER_PAYLOAD_PREIMAGE_CT, 259);
assert_eq!(COSE_HEADER_PAYLOAD_LOCATION, 260);
}
#[test]
fn full_signed_corim_workflow() {
let corim_bytes = build_sample_corim_bytes();
let mut builder = SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(
CwtClaims::new("Workflow Test")
.with_nbf(0)
.with_exp(2000000000),
);
let tbs = builder.to_be_signed(&[]).unwrap();
assert!(!tbs.is_empty());
let fake_signature = vec![0xAB; 64];
let signed_bytes = builder
.build_with_signature(fake_signature.clone())
.unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
assert_eq!(signed.protected.alg, CoseAlgorithm::Es256);
assert!(signed.protected.cwt_claims.is_some());
assert_eq!(signed.signature, fake_signature);
let tbs2 = signed.to_be_signed(&[]).unwrap();
assert_eq!(tbs, tbs2);
let validated = validate_signed_corim_payload(&signed, 1000000000).unwrap();
assert_eq!(validated.comids.len(), 1);
assert_eq!(
validated.corim.id,
CorimId::Text("signed-test-corim".into())
);
}
#[test]
fn full_workflow_with_corim_meta() {
let corim_bytes = build_sample_corim_bytes();
let mut builder = SignedCorimBuilder::new(-35, corim_bytes).set_corim_meta(CorimMetaMap {
signer: CorimSignerMap {
signer_name: "Meta Signer".into(),
signer_uri: None,
},
signature_validity: None,
});
let _tbs = builder.to_be_signed(&[]).unwrap();
let signed_bytes = builder.build_with_signature(vec![0x99; 48]).unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
assert_eq!(signed.protected.alg, CoseAlgorithm::Es384);
let meta = signed.protected.corim_meta.as_ref().unwrap();
assert_eq!(meta.signer.signer_name, "Meta Signer");
let validated = validate_signed_corim_payload(&signed, 0).unwrap();
assert_eq!(validated.comids.len(), 1);
}
#[test]
fn protected_header_extra_fields_preserved() {
let header = ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es256,
content_type: Some("application/rim+cbor".into()),
payload_hash_alg: None,
payload_preimage_content_type: None,
payload_location: None,
corim_meta: Some(make_corim_meta()),
cwt_claims: None,
extra: {
let mut m = std::collections::BTreeMap::new();
m.insert(99, Value::Text("custom-value".into()));
m
},
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
};
let bytes = cbor::encode(&header).unwrap();
let decoded: ProtectedCorimHeaderMap = cbor::decode(&bytes).unwrap();
assert_eq!(
decoded.extra.get(&99),
Some(&Value::Text("custom-value".into()))
);
}
#[test]
fn signed_corim_large_signature() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-36, corim_bytes).set_cwt_claims(CwtClaims::new("Large Sig Test"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let large_sig = vec![0xFE; 132]; let signed_bytes = builder.build_with_signature(large_sig.clone()).unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
assert_eq!(signed.signature, large_sig);
}
#[test]
fn signed_corim_empty_signature() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("Empty Sig Test"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let signed_bytes = builder.build_with_signature(vec![]).unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
assert!(signed.signature.is_empty());
}
#[test]
fn builder_caches_protected_bytes() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("Cache Test"));
let tbs1 = builder.to_be_signed(&[]).unwrap();
let tbs2 = builder.to_be_signed(&[]).unwrap();
assert_eq!(tbs1, tbs2);
}
#[test]
fn tag_18_magic_number() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("Magic Test"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let signed_bytes = builder.build_with_signature(vec![0x11; 64]).unwrap();
assert_eq!(signed_bytes[0], 0xD2);
assert_eq!(signed_bytes[1], 0x84);
}
#[test]
fn detached_builder_produces_nil_payload() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("Detached Test"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let signed_bytes = builder
.build_detached_with_signature(vec![0xDD; 64])
.unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
assert!(signed.is_detached());
assert!(signed.payload.is_none());
assert_eq!(signed.signature, vec![0xDD; 64]);
}
#[test]
fn detached_tbs_uses_actual_payload() {
let corim_bytes = build_sample_corim_bytes();
let mut builder_a =
SignedCorimBuilder::new(-7, corim_bytes.clone()).set_cwt_claims(CwtClaims::new("TBS Test"));
let tbs_from_builder = builder_a.to_be_signed(&[]).unwrap();
let signed_bytes = builder_a.build_with_signature(vec![0x00; 64]).unwrap();
let attached = decode_signed_corim(&signed_bytes).unwrap();
let tbs_from_attached = attached.to_be_signed(&[]).unwrap();
assert_eq!(tbs_from_builder, tbs_from_attached);
let mut builder_b =
SignedCorimBuilder::new(-7, corim_bytes.clone()).set_cwt_claims(CwtClaims::new("TBS Test"));
let tbs_before_detach = builder_b.to_be_signed(&[]).unwrap();
let detached_bytes = builder_b
.build_detached_with_signature(vec![0x00; 64])
.unwrap();
let detached = decode_signed_corim(&detached_bytes).unwrap();
assert!(detached.to_be_signed(&[]).is_err());
let tbs_from_detached = detached.to_be_signed_detached(&corim_bytes, &[]).unwrap();
assert_eq!(tbs_before_detach, tbs_from_detached);
}
#[test]
fn detached_to_be_signed_errors_without_payload() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("Error Test"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let detached_bytes = builder
.build_detached_with_signature(vec![0xAA; 64])
.unwrap();
let signed = decode_signed_corim(&detached_bytes).unwrap();
let err = signed.to_be_signed(&[]).unwrap_err();
assert!(
err.to_string().contains("detached"),
"error should mention detached: {}",
err
);
}
#[test]
fn detached_to_be_signed_detached_with_external_aad() {
let corim_bytes = build_sample_corim_bytes();
let mut builder = SignedCorimBuilder::new(-7, corim_bytes.clone())
.set_cwt_claims(CwtClaims::new("AAD Detach Test"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let detached_bytes = builder
.build_detached_with_signature(vec![0xBB; 64])
.unwrap();
let signed = decode_signed_corim(&detached_bytes).unwrap();
let aad = b"my-external-aad";
let tbs = signed.to_be_signed_detached(&corim_bytes, aad).unwrap();
let tbs_val: Value = cbor::decode(&tbs).unwrap();
if let Value::Array(arr) = tbs_val {
assert_eq!(arr[2], Value::Bytes(aad.to_vec()));
assert_eq!(arr[3], Value::Bytes(corim_bytes.clone()));
} else {
panic!("TBS must be a CBOR array");
}
}
#[test]
fn detached_validate_fails_for_attached_api() {
let corim_bytes = build_sample_corim_bytes();
let mut builder =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("Validate Detach"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let detached_bytes = builder
.build_detached_with_signature(vec![0xCC; 64])
.unwrap();
let signed = decode_signed_corim(&detached_bytes).unwrap();
let result = validate_signed_corim_payload(&signed, 0);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("detached"));
}
#[test]
fn detached_validate_with_supplied_payload() {
let corim_bytes = build_sample_corim_bytes();
let mut builder = SignedCorimBuilder::new(-7, corim_bytes.clone())
.set_cwt_claims(CwtClaims::new("Validate Detach OK"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let detached_bytes = builder
.build_detached_with_signature(vec![0xEE; 64])
.unwrap();
let signed = decode_signed_corim(&detached_bytes).unwrap();
let validated = validate_signed_corim_payload_detached(&signed, &corim_bytes, 0).unwrap();
assert_eq!(validated.comids.len(), 1);
}
#[test]
fn is_detached_flag() {
let corim_bytes = build_sample_corim_bytes();
let mut builder_a = SignedCorimBuilder::new(-7, corim_bytes.clone())
.set_cwt_claims(CwtClaims::new("Flag Test"));
let _tbs = builder_a.to_be_signed(&[]).unwrap();
let attached_bytes = builder_a.build_with_signature(vec![0x11; 64]).unwrap();
let attached = decode_signed_corim(&attached_bytes).unwrap();
assert!(!attached.is_detached());
let mut builder_b =
SignedCorimBuilder::new(-7, corim_bytes).set_cwt_claims(CwtClaims::new("Flag Test"));
let _tbs = builder_b.to_be_signed(&[]).unwrap();
let detached_bytes = builder_b
.build_detached_with_signature(vec![0x22; 64])
.unwrap();
let detached = decode_signed_corim(&detached_bytes).unwrap();
assert!(detached.is_detached());
}
#[test]
fn full_detached_workflow() {
let corim_bytes = build_sample_corim_bytes();
let mut builder = SignedCorimBuilder::new(-35, corim_bytes.clone())
.set_cwt_claims(CwtClaims::new("Detached Workflow"));
let tbs = builder.to_be_signed(&[]).unwrap();
assert!(!tbs.is_empty());
let fake_signature = vec![0xFE; 48];
let envelope_bytes = builder
.build_detached_with_signature(fake_signature.clone())
.unwrap();
let envelope = decode_signed_corim(&envelope_bytes).unwrap();
assert!(envelope.is_detached());
assert_eq!(envelope.protected.alg, CoseAlgorithm::Es384);
let tbs2 = envelope.to_be_signed_detached(&corim_bytes, &[]).unwrap();
assert_eq!(tbs, tbs2);
let validated = validate_signed_corim_payload_detached(&envelope, &corim_bytes, 0).unwrap();
assert_eq!(validated.comids.len(), 1);
}
#[test]
fn to_be_signed_detached_works_on_attached_too() {
let corim_bytes = build_sample_corim_bytes();
let mut builder = SignedCorimBuilder::new(-7, corim_bytes.clone())
.set_cwt_claims(CwtClaims::new("Override Test"));
let _tbs = builder.to_be_signed(&[]).unwrap();
let signed_bytes = builder.build_with_signature(vec![0x11; 64]).unwrap();
let signed = decode_signed_corim(&signed_bytes).unwrap();
assert!(!signed.is_detached());
let tbs_attached = signed.to_be_signed(&[]).unwrap();
let tbs_detached = signed.to_be_signed_detached(&corim_bytes, &[]).unwrap();
assert_eq!(tbs_attached, tbs_detached);
}
#[test]
fn cose_algorithm_all_variants_round_trip_i64() {
let cases: Vec<(i64, &str)> = vec![
(-7, "ES256 (deprecated)"),
(-8, "EdDSA (deprecated)"),
(-9, "ESP256"),
(-19, "Ed25519"),
(-35, "ES384 (deprecated)"),
(-36, "ES512 (deprecated)"),
(-37, "PS256"),
(-38, "PS384"),
(-39, "PS512"),
(-51, "ESP384"),
(-52, "ESP512"),
(-53, "Ed448"),
(-999, "Unknown"),
];
for (id, expected_name) in &cases {
let alg = CoseAlgorithm::from_i64(*id);
assert_eq!(alg.to_i64(), *id, "round-trip failed for {}", id);
assert_eq!(alg.name(), *expected_name, "name mismatch for {}", id);
}
}
#[test]
fn cose_algorithm_deprecated_flag_marks_legacy_es_eddsa() {
assert!(CoseAlgorithm::Es256.is_deprecated());
assert!(CoseAlgorithm::EdDsa.is_deprecated());
assert!(CoseAlgorithm::Es384.is_deprecated());
assert!(CoseAlgorithm::Es512.is_deprecated());
assert!(!CoseAlgorithm::Esp256.is_deprecated());
assert!(!CoseAlgorithm::Ed25519.is_deprecated());
assert!(!CoseAlgorithm::Ps256.is_deprecated());
assert!(!CoseAlgorithm::Ps384.is_deprecated());
assert!(!CoseAlgorithm::Ps512.is_deprecated());
assert!(!CoseAlgorithm::Esp384.is_deprecated());
assert!(!CoseAlgorithm::Esp512.is_deprecated());
assert!(!CoseAlgorithm::Ed448.is_deprecated());
assert!(!CoseAlgorithm::Unknown(42).is_deprecated());
}
#[test]
fn cose_algorithm_display_includes_id_in_parens() {
for alg in [
CoseAlgorithm::Esp256,
CoseAlgorithm::Ed25519,
CoseAlgorithm::Ps256,
CoseAlgorithm::Ps384,
CoseAlgorithm::Ps512,
CoseAlgorithm::Esp384,
CoseAlgorithm::Esp512,
CoseAlgorithm::Ed448,
CoseAlgorithm::Es256,
CoseAlgorithm::EdDsa,
CoseAlgorithm::Es384,
CoseAlgorithm::Es512,
] {
let s = format!("{}", alg);
assert!(!s.is_empty());
assert!(s.contains('('));
}
assert_eq!(format!("{}", CoseAlgorithm::Unknown(-999)), "Unknown(-999)");
}
#[test]
fn cose_algorithm_into_i64_and_back() {
let alg: CoseAlgorithm = (-7i64).into();
assert_eq!(alg, CoseAlgorithm::Es256);
let n: i64 = alg.into();
assert_eq!(n, -7);
}
#[test]
fn cose_x509_single_exposes_one_cert() {
let x = CoseX509::Single(vec![0x30, 0x82, 0x01]);
assert_eq!(x.certs().len(), 1);
assert_eq!(x.end_entity(), &[0x30, 0x82, 0x01]);
}
#[test]
fn cose_x509_chain_exposes_all_with_first_as_end_entity() {
let x = CoseX509::Chain(vec![vec![0x30, 0x01], vec![0x30, 0x02], vec![0x30, 0x03]]);
assert_eq!(x.certs().len(), 3);
assert_eq!(x.end_entity(), &[0x30, 0x01]);
}
fn coverage_meta() -> CorimMetaMap {
CorimMetaMap {
signer: CorimSignerMap {
signer_name: "ACME Ltd.".into(),
signer_uri: None,
},
signature_validity: None,
}
}
fn coverage_header_with_meta() -> ProtectedCorimHeaderMap {
ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es256,
content_type: Some("application/rim+cbor".into()),
payload_hash_alg: None,
payload_preimage_content_type: None,
payload_location: None,
corim_meta: Some(coverage_meta()),
cwt_claims: None,
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
extra: std::collections::BTreeMap::new(),
}
}
#[test]
fn protected_header_with_x509_fields_round_trip() {
let mut header = coverage_header_with_meta();
header.kid = Some(vec![0x01, 0x02, 0x03]);
header.x5bag = Some(CoseX509::Single(vec![0x30, 0x82]));
header.x5chain = Some(CoseX509::Chain(vec![vec![0x30, 0x01], vec![0x30, 0x02]]));
header.x5t = Some(CoseCertHash {
hash_alg: corim::types::DigestAlg::Int(1),
hash_value: vec![0xAA; 32],
});
header.x5u = Some("https://example.com/cert.pem".into());
let bytes = cbor::encode(&header).unwrap();
let decoded: ProtectedCorimHeaderMap = cbor::decode(&bytes).unwrap();
assert_eq!(decoded.kid, header.kid);
assert!(decoded.x5bag.is_some());
assert!(decoded.x5chain.is_some());
assert!(decoded.x5t.is_some());
assert_eq!(decoded.x5u, header.x5u);
}
#[test]
fn protected_header_hash_envelope_round_trip() {
let header = ProtectedCorimHeaderMap {
alg: CoseAlgorithm::Es256,
content_type: None,
payload_hash_alg: Some(1),
payload_preimage_content_type: Some("application/rim+cbor".into()),
payload_location: Some("https://example.com/payload".into()),
corim_meta: Some(coverage_meta()),
cwt_claims: None,
kid: None,
x5bag: None,
x5chain: None,
x5t: None,
x5u: None,
extra: std::collections::BTreeMap::new(),
};
let bytes = cbor::encode(&header).unwrap();
let decoded: ProtectedCorimHeaderMap = cbor::decode(&bytes).unwrap();
assert!(decoded.is_hash_envelope());
assert_eq!(decoded.payload_hash_alg, Some(1));
assert_eq!(
decoded.payload_preimage_content_type.as_deref(),
Some("application/rim+cbor")
);
assert_eq!(
decoded.payload_location.as_deref(),
Some("https://example.com/payload")
);
}
#[test]
fn encode_decode_signed_corim_attached_round_trip() {
let header = coverage_header_with_meta();
let protected_bytes = cbor::encode(&header).unwrap();
let signed = CoseSign1Corim {
protected_header_bytes: protected_bytes,
protected: header,
unprotected: vec![],
payload: Some(vec![0xD9, 0x01, 0xF5, 0xA0]),
signature: vec![0xDE; 64],
};
let encoded = encode_signed_corim(&signed).unwrap();
let decoded = decode_signed_corim(&encoded).unwrap();
assert_eq!(decoded.protected.alg, CoseAlgorithm::Es256);
assert!(!decoded.is_detached());
assert_eq!(decoded.signature.len(), 64);
}
#[test]
fn encode_decode_signed_corim_detached_round_trip() {
let header = coverage_header_with_meta();
let protected_bytes = cbor::encode(&header).unwrap();
let signed = CoseSign1Corim {
protected_header_bytes: protected_bytes,
protected: header,
unprotected: vec![],
payload: None,
signature: vec![0xAB; 64],
};
let encoded = encode_signed_corim(&signed).unwrap();
let decoded = decode_signed_corim(&encoded).unwrap();
assert!(decoded.is_detached());
}
#[test]
fn decode_signed_corim_rejects_wrong_outer_tag() {
let val = Value::Tag(
501,
Box::new(Value::Array(vec![
Value::Bytes(vec![0xA0]),
Value::Map(vec![]),
Value::Bytes(vec![]),
Value::Bytes(vec![]),
])),
);
let bytes = cbor::encode(&val).unwrap();
let err = decode_signed_corim(&bytes).unwrap_err();
assert!(format!("{}", err).contains("tag"), "got: {}", err);
}
#[test]
fn decode_signed_corim_rejects_non_tag_input() {
let val = Value::Array(vec![Value::Integer(1)]);
let bytes = cbor::encode(&val).unwrap();
let err = decode_signed_corim(&bytes).unwrap_err();
assert!(format!("{}", err).contains("tag 18"), "got: {}", err);
}
#[test]
fn decode_signed_corim_rejects_wrong_array_length() {
let val = Value::Tag(
18,
Box::new(Value::Array(vec![Value::Bytes(vec![]), Value::Map(vec![])])),
);
let bytes = cbor::encode(&val).unwrap();
let err = decode_signed_corim(&bytes).unwrap_err();
assert!(format!("{}", err).contains("4-element"), "got: {}", err);
}
#[test]
fn decode_signed_corim_rejects_non_array_payload() {
let val = Value::Tag(18, Box::new(Value::Text("not-array".into())));
let bytes = cbor::encode(&val).unwrap();
let err = decode_signed_corim(&bytes).unwrap_err();
assert!(format!("{}", err).contains("array"), "got: {}", err);
}
#[test]
fn decode_signed_corim_rejects_non_bstr_protected() {
let val = Value::Tag(
18,
Box::new(Value::Array(vec![
Value::Text("not-bstr".into()),
Value::Map(vec![]),
Value::Null,
Value::Bytes(vec![]),
])),
);
let bytes = cbor::encode(&val).unwrap();
let err = decode_signed_corim(&bytes).unwrap_err();
assert!(format!("{}", err).contains("bstr"), "got: {}", err);
}
#[test]
fn cose_sign1_to_be_signed_attached_differs_from_detached() {
let header = coverage_header_with_meta();
let prot_bytes = cbor::encode(&header).unwrap();
let signed = CoseSign1Corim {
protected_header_bytes: prot_bytes,
protected: header,
unprotected: vec![],
payload: Some(vec![0x01, 0x02]),
signature: vec![],
};
let tbs = signed.to_be_signed(&[]).unwrap();
assert!(!tbs.is_empty());
let tbs_d = signed.to_be_signed_detached(&[0x03, 0x04], &[]).unwrap();
assert_ne!(tbs, tbs_d);
}
#[test]
fn cose_sign1_to_be_signed_errors_on_detached_envelope() {
let header = coverage_header_with_meta();
let prot_bytes = cbor::encode(&header).unwrap();
let signed = CoseSign1Corim {
protected_header_bytes: prot_bytes,
protected: header,
unprotected: vec![],
payload: None,
signature: vec![],
};
assert!(signed.to_be_signed(&[]).is_err());
}
fn coverage_corim_payload() -> Vec<u8> {
let comid = ComidBuilder::new(TagIdChoice::Text("t".into()))
.add_reference_triple(ReferenceTriple::new(
EnvironmentMap::for_class("V", "M"),
vec![MeasurementMap {
mkey: None,
mval: MeasurementValuesMap {
svn: Some(corim::types::measurement::SvnChoice::ExactValue(1)),
..Default::default()
},
authorized_by: None,
}],
))
.build()
.unwrap();
CorimBuilder::new(CorimId::Text("test".into()))
.add_comid_tag(comid)
.unwrap()
.build_bytes()
.unwrap()
}
#[test]
fn signed_corim_builder_with_meta_caches_tbs() {
let mut builder = SignedCorimBuilder::new(-7i64, coverage_corim_payload())
.set_corim_meta(coverage_meta())
.set_content_type("application/rim+cbor")
.add_unprotected(Value::Integer(99), Value::Text("info".into()))
.add_protected(100, Value::Bool(true));
let tbs = builder.to_be_signed(&[]).unwrap();
assert!(!tbs.is_empty());
let tbs2 = builder.to_be_signed(&[]).unwrap();
assert_eq!(tbs, tbs2);
let signed_bytes = builder.build_with_signature(vec![0xAB; 64]).unwrap();
let decoded = decode_signed_corim(&signed_bytes).unwrap();
assert!(!decoded.is_detached());
}
#[test]
fn signed_corim_builder_detached_round_trip() {
let mut builder = SignedCorimBuilder::new(CoseAlgorithm::Es256, coverage_corim_payload())
.set_corim_meta(coverage_meta());
let _tbs = builder.to_be_signed(&[]).unwrap();
let signed_bytes = builder
.build_detached_with_signature(vec![0xCD; 64])
.unwrap();
let decoded = decode_signed_corim(&signed_bytes).unwrap();
assert!(decoded.is_detached());
}
#[test]
fn signed_corim_builder_with_cwt_claims_round_trip() {
let mut builder = SignedCorimBuilder::new(CoseAlgorithm::Esp256, coverage_corim_payload())
.set_cwt_claims(CwtClaims::new("ACME Corp").with_exp(9999999999).with_nbf(0));
let _tbs = builder.to_be_signed(&[]).unwrap();
let signed_bytes = builder.build_with_signature(vec![0; 64]).unwrap();
let decoded = decode_signed_corim(&signed_bytes).unwrap();
let cwt = decoded.protected.cwt_claims.unwrap();
assert_eq!(cwt.iss, "ACME Corp");
assert_eq!(cwt.exp, Some(9999999999));
}
#[test]
fn signed_corim_builder_without_meta_or_cwt_fails() {
let mut builder = SignedCorimBuilder::new(-7i64, vec![0xA0]);
assert!(builder.to_be_signed(&[]).is_err());
}
#[test]
fn cwt_claims_builder_methods_set_each_field() {
let c = CwtClaims::new("iss")
.with_sub("sub")
.with_exp(9999)
.with_nbf(1000);
assert_eq!(c.iss, "iss");
assert_eq!(c.sub, Some("sub".into()));
assert_eq!(c.exp, Some(9999));
assert_eq!(c.nbf, Some(1000));
}
#[test]
fn validate_signed_corim_payload_errors_when_detached() {
let header = coverage_header_with_meta();
let prot_bytes = cbor::encode(&header).unwrap();
let signed = CoseSign1Corim {
protected_header_bytes: prot_bytes,
protected: header,
unprotected: vec![],
payload: None,
signature: vec![0; 64],
};
assert!(validate_signed_corim_payload(&signed, 0).is_err());
}
#[test]
fn build_sig_structure1_emits_4_element_array_with_context() {
let tbs = build_sig_structure1(&[0xA0], &[], &[0x01, 0x02]).unwrap();
let val: Value = cbor::decode(&tbs).unwrap();
match val {
Value::Array(arr) => {
assert_eq!(arr.len(), 4);
assert_eq!(arr[0], Value::Text("Signature1".into()));
}
_ => panic!("expected array"),
}
}
#[test]
fn build_sig_structure1_with_aad_differs_from_without() {
let tbs1 = build_sig_structure1(&[0xA0], &[], &[0x01]).unwrap();
let tbs2 = build_sig_structure1(&[0xA0], &[0xFF], &[0x01]).unwrap();
assert_ne!(tbs1, tbs2);
}