Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions rust/signed_doc/src/signature/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,17 +203,17 @@ fn protected_header_decode(
)?
.into_iter();

let Some(entry) = map.next() else {
anyhow::bail!("COSE signature protected header must be at least one entry");
};

if map.len() > 1 {
ctx.report().functional_validation(
"COSE signature protected header must have only one `kid` field",
"COSE signature protected header must only include the `kid` field",
"COSE signature protected header decoding",
);
}

let Some(entry) = map.next() else {
anyhow::bail!("COSE signature protected header must include at least one entry");
};

// protected headers (kid field)
anyhow::ensure!(
matches!(
Expand Down
199 changes: 199 additions & 0 deletions rust/signed_doc/tests/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,202 @@ fn signed_doc_with_non_strict_deterministic_decoding_wrong_order() -> TestCase {
}
}

fn signed_doc_with_non_supported_metadata_invalid() -> TestCase {
let uuid_v7 = UuidV7::new();
let uuid_v4 = UuidV4::new();

TestCase {
name: "Catalyst Signed Doc with non-supported defined metadata fields is invalid."
.to_string(),
bytes_gen: Box::new({
move || {
let mut e = Encoder::new(Vec::new());
e.tag(Tag::new(98))?;
e.array(4)?;
// protected headers (metadata fields)
e.bytes({
let mut p_headers = Encoder::new(Vec::new());

p_headers.map(5)?;
p_headers.u8(3)?.encode(ContentType::Json)?;
p_headers
.str("type")?
.encode_with(uuid_v4, &mut catalyst_types::uuid::CborContext::Tagged)?;
p_headers
.str("id")?
.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?;
p_headers
.str("ver")?
.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?;
p_headers
.str("unsupported")?
.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?;
p_headers.into_writer().as_slice()
})?;
// empty unprotected headers
e.map(0)?;
// content
e.bytes(serde_json::to_vec(&serde_json::Value::Null)?.as_slice())?;
// signatures
// no signature
e.array(0)?;
Ok(e)
}
}),
policy: CompatibilityPolicy::Accept,
can_decode: true,
valid_doc: false,
post_checks: Some(Box::new({
move |doc| {
anyhow::ensure!(doc.doc_type()? == &DocType::from(uuid_v4));
anyhow::ensure!(doc.doc_id()? == uuid_v7);
anyhow::ensure!(doc.doc_ver()? == uuid_v7);
anyhow::ensure!(doc.doc_content_type()? == ContentType::Json);
anyhow::ensure!(
doc.encoded_content() == serde_json::to_vec(&serde_json::Value::Null)?
);
anyhow::ensure!(doc.kids().len() == 0);
Ok(())
}
})),
}
}

fn signed_doc_with_kid_in_id_form_invalid() -> TestCase {
let uuid_v7 = UuidV7::new();
let uuid_v4 = UuidV4::new();

TestCase {
name: "Catalyst Signed Doc with Signature KID in Id form, instead of URI form is invalid."
.to_string(),
bytes_gen: Box::new({
move || {
let (_, _, kid) = create_dummy_key_pair(RoleId::Role0)?;

let mut e = Encoder::new(Vec::new());
e.tag(Tag::new(98))?;
e.array(4)?;
// protected headers (metadata fields)
e.bytes({
let mut p_headers = Encoder::new(Vec::new());

p_headers.map(4)?;
p_headers.u8(3)?.encode(ContentType::Json)?;
p_headers
.str("type")?
.encode_with(uuid_v4, &mut catalyst_types::uuid::CborContext::Tagged)?;
p_headers
.str("id")?
.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?;
p_headers
.str("ver")?
.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?;
p_headers.into_writer().as_slice()
})?;
// empty unprotected headers
e.map(0)?;
// content
e.bytes(serde_json::to_vec(&serde_json::Value::Null)?.as_slice())?;
// signatures
// one signature
e.array(1)?;
e.array(3)?;
// protected headers (kid field)
let mut p_headers = minicbor::Encoder::new(Vec::new());
p_headers
.map(1)?
.u8(4)?
.bytes(Vec::<u8>::from(&kid.as_id()).as_slice())?;
e.bytes(p_headers.into_writer().as_slice())?;
e.map(0)?;
e.bytes(&[1, 2, 3])?;
Ok(e)
}
}),
policy: CompatibilityPolicy::Accept,
can_decode: true,
valid_doc: false,
post_checks: Some(Box::new({
move |doc| {
anyhow::ensure!(doc.doc_type()? == &DocType::from(uuid_v4));
anyhow::ensure!(doc.doc_id()? == uuid_v7);
anyhow::ensure!(doc.doc_ver()? == uuid_v7);
anyhow::ensure!(doc.doc_content_type()? == ContentType::Json);
anyhow::ensure!(
doc.encoded_content() == serde_json::to_vec(&serde_json::Value::Null)?
);
anyhow::ensure!(doc.kids().len() == 1);
Ok(())
}
})),
}
}

fn signed_doc_with_non_supported_protected_signature_header_invalid() -> TestCase {
let uuid_v7 = UuidV7::new();
let uuid_v4 = UuidV4::new();

TestCase {
name: "Catalyst Signed Doc with unsupported protected Signature header is invalid."
.to_string(),
bytes_gen: Box::new({
move || {
let (_, _, kid) = create_dummy_key_pair(RoleId::Role0)?;

let mut e = Encoder::new(Vec::new());
e.tag(Tag::new(98))?;
e.array(4)?;
// protected headers (metadata fields)
e.bytes({
let mut p_headers = Encoder::new(Vec::new());

p_headers.map(4)?;
p_headers.u8(3)?.encode(ContentType::Json)?;
p_headers
.str("type")?
.encode_with(uuid_v4, &mut catalyst_types::uuid::CborContext::Tagged)?;
p_headers
.str("id")?
.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?;
p_headers
.str("ver")?
.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?;
p_headers.into_writer().as_slice()
})?;
// empty unprotected headers
e.map(0)?;
// content
e.bytes(serde_json::to_vec(&serde_json::Value::Null)?.as_slice())?;
// signatures
e.array(1)?;
// signature
e.array(3)?;
// protected headers
e.bytes({
let mut s_headers = minicbor::Encoder::new(Vec::new());
s_headers.map(2)?;
// (kid field)
s_headers.u8(4)?.bytes(Vec::<u8>::from(&kid).as_slice())?;
// Unsupported label/value
s_headers
.str("unsupported")?
.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?;
s_headers.into_writer().as_slice()
})?;
// unprotected headers
e.map(0)?;
// signature bytes
e.bytes(&[1, 2, 3])?;
Ok(e)
}
}),
policy: CompatibilityPolicy::Accept,
can_decode: true,
valid_doc: false,
post_checks: None,
}
}

#[test]
fn catalyst_signed_doc_decoding_test() {
let test_cases = [
Expand Down Expand Up @@ -1223,6 +1419,9 @@ fn catalyst_signed_doc_decoding_test() {
signed_doc_with_signatures_non_empty_unprotected_headers(),
signed_doc_with_strict_deterministic_decoding_wrong_order(),
signed_doc_with_non_strict_deterministic_decoding_wrong_order(),
signed_doc_with_non_supported_metadata_invalid(),
signed_doc_with_kid_in_id_form_invalid(),
signed_doc_with_non_supported_protected_signature_header_invalid(),
];

for mut case in test_cases {
Expand Down