Skip to content

Commit 70c611c

Browse files
apskhemMr-Leshiy
andauthored
feat(rust/signed-doc): Add a function to check deprecated version below v0.04 for a signed doc (#441)
* initial * chore: minor comment * wip tests * wip * wip * wip * chore: minor test to check non-deterministic * test: minor * chore: add error doc to the function * chore: fmtfix --------- Co-authored-by: Mr-Leshiy <leshiy12345678@gmail.com>
1 parent 5687017 commit 70c611c

File tree

4 files changed

+141
-14
lines changed

4 files changed

+141
-14
lines changed

rust/signed_doc/src/lib.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub use metadata::{
2929
use minicbor::{decode, encode, Decode, Decoder, Encode};
3030
pub use signature::{CatalystId, Signatures};
3131

32-
use crate::builder::SignaturesBuilder;
32+
use crate::{builder::SignaturesBuilder, metadata::SupportedLabel};
3333

3434
/// `COSE_Sign` object CBOR tag <https://datatracker.ietf.org/doc/html/rfc8152#page-8>
3535
const COSE_SIGN_CBOR_TAG: minicbor::data::Tag = minicbor::data::Tag::new(98);
@@ -180,6 +180,38 @@ impl CatalystSignedDocument {
180180
.collect()
181181
}
182182

183+
/// Checks if the CBOR body of the signed doc is in the older version format before
184+
/// v0.04.
185+
///
186+
/// # Errors
187+
///
188+
/// Errors from CBOR decoding.
189+
pub fn is_deprecated(&self) -> anyhow::Result<bool> {
190+
let mut e = minicbor::Encoder::new(Vec::new());
191+
192+
let e = e.encode(self.inner.metadata.clone())?;
193+
let e = e.to_owned().into_writer();
194+
195+
for entry in cbork_utils::map::Map::decode(
196+
&mut minicbor::Decoder::new(e.as_slice()),
197+
&mut cbork_utils::decode_context::DecodeCtx::non_deterministic(),
198+
)? {
199+
match minicbor::Decoder::new(&entry.key_bytes).decode::<SupportedLabel>()? {
200+
SupportedLabel::Template
201+
| SupportedLabel::Ref
202+
| SupportedLabel::Reply
203+
| SupportedLabel::Parameters => {
204+
if DocumentRefs::is_deprecated_cbor(&entry.value)? {
205+
return Ok(true);
206+
}
207+
},
208+
_ => {},
209+
}
210+
}
211+
212+
Ok(false)
213+
}
214+
183215
/// Returns a collected problem report for the document.
184216
/// It accumulates all kind of errors, collected during the decoding, type based
185217
/// validation and signature verification.

rust/signed_doc/src/metadata/document_refs/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,28 @@ use crate::CompatibilityPolicy;
1717
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
1818
pub struct DocumentRefs(Vec<DocumentRef>);
1919

20+
impl DocumentRefs {
21+
/// Returns true if provided `cbor` bytes is a valid old format.
22+
/// ```cddl
23+
/// old_format = [id, ver]
24+
/// ```
25+
/// Returns false if provided `cbor` bytes is a valid new format.
26+
/// ```cddl
27+
/// new_format = [ +[id, ver, cid] ]
28+
/// ```
29+
pub(crate) fn is_deprecated_cbor(cbor: &[u8]) -> Result<bool, minicbor::decode::Error> {
30+
let mut d = minicbor::Decoder::new(cbor);
31+
d.array()?;
32+
match d.datatype()? {
33+
// new_format = [ +[id, ver, cid] ]
34+
minicbor::data::Type::Array => Ok(false),
35+
// old_format = [id, ver]
36+
minicbor::data::Type::Tag => Ok(true),
37+
ty => Err(minicbor::decode::Error::type_mismatch(ty)),
38+
}
39+
}
40+
}
41+
2042
/// Document reference error.
2143
#[derive(Debug, Clone, thiserror::Error)]
2244
pub enum DocRefError {

rust/signed_doc/src/metadata/supported_field.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -208,19 +208,10 @@ impl minicbor::Decode<'_, crate::decode_context::DecodeContext> for Option<Suppo
208208
) -> Result<Self, minicbor::decode::Error> {
209209
const REPORT_CONTEXT: &str = "Metadata field decoding";
210210

211-
let label = Label::decode(d, &mut ())?;
212-
let Some(key) = SupportedLabel::from_cose(label) else {
213-
let value_start = d.position();
214-
d.skip()?;
215-
let value_end = d.position();
216-
// Since the high level type isn't know, the value CBOR is tokenized and reported as
217-
// such.
218-
let value = minicbor::decode::Tokenizer::new(
219-
d.input().get(value_start..value_end).unwrap_or_default(),
220-
)
221-
.to_string();
222-
ctx.report()
223-
.unknown_field(&label.to_string(), &value, REPORT_CONTEXT);
211+
let Ok(key) = d
212+
.decode::<SupportedLabel>()
213+
.inspect_err(|e| ctx.report().other(e.to_string().as_str(), REPORT_CONTEXT))
214+
else {
224215
return Ok(None);
225216
};
226217

@@ -272,6 +263,17 @@ impl minicbor::Decode<'_, crate::decode_context::DecodeContext> for Option<Suppo
272263
}
273264
}
274265

266+
impl<C> minicbor::Decode<'_, C> for SupportedLabel {
267+
fn decode(
268+
d: &mut minicbor::Decoder<'_>, _ctx: &mut C,
269+
) -> Result<Self, minicbor::decode::Error> {
270+
let label = d.decode()?;
271+
Self::from_cose(label).ok_or(minicbor::decode::Error::message(format!(
272+
"Unsupported key {label}"
273+
)))
274+
}
275+
}
276+
275277
impl minicbor::Encode<()> for SupportedField {
276278
fn encode<W: minicbor::encode::Write>(
277279
&self, e: &mut minicbor::Encoder<W>, ctx: &mut (),

rust/signed_doc/tests/decoding.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,68 @@ struct TestCase {
2222
post_checks: Option<Box<PostCheck>>,
2323
}
2424

25+
fn signed_doc_deprecated_doc_ref_case(field_name: &'static str) -> TestCase {
26+
let uuid_v7 = UuidV7::new();
27+
let doc_type = DocType::from(UuidV4::new());
28+
let doc_ref = DocumentRef::new(UuidV7::new(), UuidV7::new(), DocLocator::default());
29+
TestCase {
30+
name: format!(
31+
"Catalyst Signed Doc with deprecated {field_name} version before v0.04 validating."
32+
),
33+
bytes_gen: Box::new({
34+
move || {
35+
let mut e = Encoder::new(Vec::new());
36+
e.tag(Tag::new(98))?;
37+
e.array(4)?;
38+
39+
// protected headers (metadata fields)
40+
e.bytes({
41+
let mut p_headers = Encoder::new(Vec::new());
42+
p_headers.map(5)?;
43+
p_headers.u8(3)?.encode(ContentType::Json)?;
44+
p_headers
45+
.str("id")?
46+
.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?;
47+
p_headers
48+
.str("ver")?
49+
.encode_with(uuid_v7, &mut catalyst_types::uuid::CborContext::Tagged)?;
50+
p_headers.str("type")?.encode(&doc_type)?;
51+
p_headers.str(field_name)?;
52+
p_headers.array(2)?;
53+
p_headers.encode_with(
54+
doc_ref.id(),
55+
&mut catalyst_types::uuid::CborContext::Tagged,
56+
)?;
57+
p_headers.encode_with(
58+
doc_ref.ver(),
59+
&mut catalyst_types::uuid::CborContext::Tagged,
60+
)?;
61+
62+
p_headers.into_writer().as_slice()
63+
})?;
64+
65+
// empty unprotected headers
66+
e.map(0)?;
67+
// content
68+
e.bytes(serde_json::to_vec(&serde_json::Value::Null)?.as_slice())?;
69+
// zero signatures
70+
e.array(0)?;
71+
72+
Ok(e)
73+
}
74+
}),
75+
policy: CompatibilityPolicy::Accept,
76+
can_decode: true,
77+
valid_doc: true,
78+
post_checks: Some(Box::new({
79+
move |doc| {
80+
anyhow::ensure!(doc.is_deprecated()?);
81+
Ok(())
82+
}
83+
})),
84+
}
85+
}
86+
2587
fn signed_doc_with_valid_alias_case(alias: &'static str) -> TestCase {
2688
let uuid_v7 = UuidV7::new();
2789
let doc_type = DocType::from(UuidV4::new());
@@ -537,6 +599,7 @@ fn signed_doc_with_minimal_metadata_fields_case() -> TestCase {
537599
doc.encoded_content() == serde_json::to_vec(&serde_json::Value::Null)?
538600
);
539601
anyhow::ensure!(doc.kids().len() == 1);
602+
anyhow::ensure!(!doc.is_deprecated()?);
540603
Ok(())
541604
}
542605
})),
@@ -622,6 +685,7 @@ fn signed_doc_with_complete_metadata_fields_case() -> TestCase {
622685
anyhow::ensure!(doc.doc_content_type()? == ContentType::Json);
623686
anyhow::ensure!(doc.encoded_content() == serde_json::to_vec(&serde_json::Value::Null)?);
624687
anyhow::ensure!(doc.kids().len() == 1);
688+
anyhow::ensure!(!doc.is_deprecated()?);
625689
Ok(())
626690
}
627691
})),
@@ -1240,6 +1304,13 @@ fn signed_doc_with_non_supported_protected_signature_header_invalid() -> TestCas
12401304
fn catalyst_signed_doc_decoding_test() {
12411305
let test_cases = [
12421306
decoding_empty_bytes_case(),
1307+
signed_doc_deprecated_doc_ref_case("template"),
1308+
signed_doc_deprecated_doc_ref_case("ref"),
1309+
signed_doc_deprecated_doc_ref_case("reply"),
1310+
signed_doc_deprecated_doc_ref_case("parameters"),
1311+
signed_doc_deprecated_doc_ref_case("category_id"),
1312+
signed_doc_deprecated_doc_ref_case("brand_id"),
1313+
signed_doc_deprecated_doc_ref_case("campaign_id"),
12431314
signed_doc_with_minimal_metadata_fields_case(),
12441315
signed_doc_with_complete_metadata_fields_case(),
12451316
signed_doc_valid_null_as_no_content(),

0 commit comments

Comments
 (0)