Skip to content

Commit 5767aaa

Browse files
committed
fix(sign-doc): err report for sign doc decode
Signed-off-by: bkioshn <[email protected]>
1 parent eaf071e commit 5767aaa

File tree

11 files changed

+361
-197
lines changed

11 files changed

+361
-197
lines changed

rust/signed_doc/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ license.workspace = true
1111
workspace = true
1212

1313
[dependencies]
14-
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250122-00" }
14+
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250124-00" }
1515
anyhow = "1.0.95"
1616
serde = { version = "1.0.217", features = ["derive"] }
1717
serde_json = "1.0.134"
@@ -22,6 +22,7 @@ ed25519-dalek = { version = "2.1.1", features = ["pem", "rand_core"] }
2222
uuid = { version = "1.11.0", features = ["v4", "v7", "serde"] }
2323
hex = "0.4.3"
2424
thiserror = "2.0.9"
25+
strum = { version = "0.26.3", features = ["derive"] }
2526

2627
[dev-dependencies]
2728
clap = { version = "4.5.23", features = ["derive", "env"] }

rust/signed_doc/examples/mk_signed_doc.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
path::PathBuf,
99
};
1010

11-
use catalyst_signed_doc::{Builder, CatalystSignedDocument, Decode, Decoder, KidUri, Metadata};
11+
use catalyst_signed_doc::{Builder, CatalystSignedDocument, KidUri, Metadata};
1212
use clap::Parser;
1313
use coset::CborSerializable;
1414
use ed25519_dalek::{ed25519::signature::Signer, pkcs8::DecodePrivateKey};
@@ -107,7 +107,8 @@ fn decode_signed_doc(cose_bytes: &[u8]) {
107107
cose_bytes.len(),
108108
hex::encode(cose_bytes)
109109
);
110-
match CatalystSignedDocument::decode(&mut Decoder::new(cose_bytes), &mut ()) {
110+
111+
match CatalystSignedDocument::new(cose_bytes) {
111112
Ok(cat_signed_doc) => {
112113
println!("This is a valid Catalyst Document.");
113114
println!("{cat_signed_doc}");

rust/signed_doc/src/error.rs

Lines changed: 0 additions & 29 deletions
This file was deleted.

rust/signed_doc/src/lib.rs

Lines changed: 108 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,24 @@
22
33
mod builder;
44
mod content;
5-
mod error;
65
mod metadata;
76
mod signature;
7+
mod utils;
88

99
use std::{
1010
convert::TryFrom,
11-
fmt::{Display, Formatter},
11+
fmt::{self, Display, Formatter},
1212
sync::Arc,
1313
};
1414

15-
use anyhow::anyhow;
1615
pub use builder::Builder;
16+
use catalyst_types::problem_report::ProblemReport;
1717
pub use content::Content;
1818
use coset::{CborSerializable, Header};
1919
pub use metadata::{DocumentRef, ExtraFields, Metadata, UuidV4, UuidV7};
2020
pub use minicbor::{decode, encode, Decode, Decoder, Encode};
2121
pub use signature::{KidUri, Signatures};
22+
use utils::context::SignDocContext;
2223

2324
/// Inner type that holds the Catalyst Signed Document with parsing errors.
2425
#[derive(Debug, Clone)]
@@ -64,7 +65,55 @@ impl From<InnerCatalystSignedDocument> for CatalystSignedDocument {
6465
}
6566
}
6667

68+
/// Catalyst Signed Document Error
69+
#[derive(Debug)]
70+
pub struct CatalystSignedDocError {
71+
/// List of errors during processing.
72+
report: ProblemReport,
73+
/// Actual error.
74+
error: anyhow::Error,
75+
}
76+
77+
impl fmt::Display for CatalystSignedDocError {
78+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
79+
let report_json = serde_json::to_string(&self.report)
80+
.unwrap_or_else(|_| String::from("Failed to serialize ProblemReport"));
81+
82+
write!(
83+
fmt,
84+
"CatalystSignedDocError {{ error: {}, report: {} }}",
85+
self.error, report_json
86+
)
87+
}
88+
}
89+
6790
impl CatalystSignedDocument {
91+
/// Create a new Catalyst Signed Document from a COSE Sign document bytes.
92+
///
93+
/// # Arguments
94+
///
95+
/// * `cose_bytes` - COSE Sign document bytes.
96+
///
97+
/// # Returns
98+
///
99+
/// A new Catalyst Signed Document.
100+
///
101+
/// # Errors
102+
///
103+
/// Returns an error if the COSE Sign document bytes are invalid or decode error.
104+
pub fn new(cose_bytes: &[u8]) -> anyhow::Result<Self, CatalystSignedDocError> {
105+
let error_report = ProblemReport::new("Catalyst Signed Document");
106+
let mut ctx = SignDocContext { error_report };
107+
let decoded: CatalystSignedDocument =
108+
minicbor::decode_with(cose_bytes, &mut ctx).map_err(|e| {
109+
CatalystSignedDocError {
110+
report: ctx.error_report,
111+
error: e.into(),
112+
}
113+
})?;
114+
Ok(decoded)
115+
}
116+
68117
// A bunch of getters to access the contents, or reason through the document, such as.
69118

70119
/// Return Document Type `UUIDv4`.
@@ -104,8 +153,8 @@ impl CatalystSignedDocument {
104153
}
105154
}
106155

107-
impl Decode<'_, ()> for CatalystSignedDocument {
108-
fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result<Self, decode::Error> {
156+
impl Decode<'_, SignDocContext> for CatalystSignedDocument {
157+
fn decode(d: &mut Decoder<'_>, ctx: &mut SignDocContext) -> Result<Self, decode::Error> {
109158
let start = d.position();
110159
d.skip()?;
111160
let end = d.position();
@@ -115,40 +164,67 @@ impl Decode<'_, ()> for CatalystSignedDocument {
115164
.ok_or(minicbor::decode::Error::end_of_input())?;
116165

117166
let cose_sign = coset::CoseSign::from_slice(cose_bytes).map_err(|e| {
167+
ctx.error_report.invalid_value(
168+
"COSE sign document bytes",
169+
&format!("{:?}", &cose_bytes),
170+
&format!("Cannot convert bytes to CoseSign {e:?}"),
171+
"Creating COSE Sign document",
172+
);
118173
minicbor::decode::Error::message(format!("Invalid COSE Sign document: {e}"))
119174
})?;
120175

121-
let mut errors = Vec::new();
122-
123-
let metadata = Metadata::try_from(&cose_sign.protected).map_or_else(
124-
|e| {
125-
errors.extend(e.0 .0);
126-
None
127-
},
128-
Some,
129-
);
130-
let signatures = Signatures::try_from(&cose_sign.signatures).map_or_else(
131-
|e| {
132-
errors.extend(e.0 .0);
133-
None
134-
},
135-
Some,
136-
);
176+
let metadata = Metadata::from_protected_header(&cose_sign.protected, &ctx.error_report)
177+
.map_or_else(
178+
|e| {
179+
ctx.error_report.conversion_error(
180+
"COSE sign protected header",
181+
&format!("{:?}", &cose_sign.protected),
182+
&format!("Expected Metadata: {e:?}"),
183+
"Converting COSE Sign protected header to Metadata",
184+
);
185+
None
186+
},
187+
Some,
188+
);
189+
let signatures = Signatures::from_cose_sig(&cose_sign.signatures, &ctx.error_report)
190+
.map_or_else(
191+
|e| {
192+
ctx.error_report.conversion_error(
193+
"COSE sign signatures",
194+
&format!("{:?}", &cose_sign.signatures),
195+
&format!("Expected Signatures {e:?}"),
196+
"Converting COSE Sign signatures to Signatures",
197+
);
198+
None
199+
},
200+
Some,
201+
);
137202

138203
if cose_sign.payload.is_none() {
139-
errors.push(anyhow!("Document Content is missing"));
204+
ctx.error_report
205+
.missing_field("COSE Sign Payload", "Missing document content (payload)");
140206
}
141207

142208
match (cose_sign.payload, metadata, signatures) {
143209
(Some(payload), Some(metadata), Some(signatures)) => {
144210
let content = Content::from_encoded(
145-
payload,
211+
payload.clone(),
146212
metadata.content_type(),
147213
metadata.content_encoding(),
148214
)
149215
.map_err(|e| {
150-
errors.push(anyhow!("Invalid Document Content: {e}"));
151-
minicbor::decode::Error::message(error::Error::from(errors))
216+
ctx.error_report.invalid_value(
217+
"Document Content",
218+
&format!(
219+
"Given value {:?}, {:?}, {:?}",
220+
payload,
221+
metadata.content_type(),
222+
metadata.content_encoding()
223+
),
224+
&format!("{e:?}"),
225+
"Creating document content",
226+
);
227+
minicbor::decode::Error::message("Failed to create Document Content")
152228
})?;
153229

154230
Ok(InnerCatalystSignedDocument {
@@ -158,7 +234,11 @@ impl Decode<'_, ()> for CatalystSignedDocument {
158234
}
159235
.into())
160236
},
161-
_ => Err(minicbor::decode::Error::message(error::Error::from(errors))),
237+
_ => {
238+
Err(minicbor::decode::Error::message(
239+
"Failed to decode Catalyst Signed Document",
240+
))
241+
},
162242
}
163243
}
164244
}
@@ -238,8 +318,8 @@ mod tests {
238318

239319
let mut bytes = Vec::new();
240320
minicbor::encode_with(doc, &mut bytes, &mut ()).unwrap();
241-
let decoded: CatalystSignedDocument =
242-
minicbor::decode_with(bytes.as_slice(), &mut ()).unwrap();
321+
322+
let decoded = CatalystSignedDocument::new(&bytes).unwrap();
243323

244324
assert_eq!(decoded.doc_type(), uuid_v4);
245325
assert_eq!(decoded.doc_id(), uuid_v7);

rust/signed_doc/src/metadata/algorithm.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Cryptographic Algorithm in COSE SIGN protected header.
22
3+
use strum::VariantArray;
4+
35
/// Cryptography Algorithm.
4-
#[derive(Copy, Clone, Debug, PartialEq, serde::Deserialize)]
6+
#[derive(Copy, Clone, Debug, PartialEq, serde::Deserialize, VariantArray)]
57
pub enum Algorithm {
68
/// `EdDSA`
79
EdDSA,
@@ -25,7 +27,12 @@ impl TryFrom<coset::iana::Algorithm> for Algorithm {
2527
fn try_from(value: coset::iana::Algorithm) -> Result<Self, Self::Error> {
2628
match value {
2729
coset::iana::Algorithm::EdDSA => Ok(Self::EdDSA),
28-
_ => anyhow::bail!("Unsupported algorithm: {value:?}"),
30+
_ => {
31+
anyhow::bail!(
32+
"Unsupported algorithm: {value:?}, Supported only: {:?}",
33+
Algorithm::VARIANTS
34+
)
35+
},
2936
}
3037
}
3138
}

rust/signed_doc/src/metadata/content_type.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ use std::{
77

88
use coset::iana::CoapContentFormat;
99
use serde::{de, Deserialize, Deserializer};
10+
use strum::VariantArray;
1011

1112
/// Payload Content Type.
12-
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
13+
#[derive(Debug, Copy, Clone, PartialEq, Eq, VariantArray)]
1314
pub enum ContentType {
1415
/// 'application/cbor'
1516
Cbor,
@@ -33,7 +34,12 @@ impl FromStr for ContentType {
3334
match s {
3435
"cbor" => Ok(Self::Cbor),
3536
"json" => Ok(Self::Json),
36-
_ => anyhow::bail!("Unsupported Content Type: {s:?}"),
37+
_ => {
38+
anyhow::bail!(
39+
"Unsupported Content Type: {s:?}, Supported only: {:?}",
40+
ContentType::VARIANTS
41+
)
42+
},
3743
}
3844
}
3945
}
@@ -62,7 +68,12 @@ impl TryFrom<&coset::ContentType> for ContentType {
6268
let content_type = match value {
6369
coset::ContentType::Assigned(CoapContentFormat::Json) => ContentType::Json,
6470
coset::ContentType::Assigned(CoapContentFormat::Cbor) => ContentType::Cbor,
65-
_ => anyhow::bail!("Unsupported Content Type {value:?}"),
71+
_ => {
72+
anyhow::bail!(
73+
"Unsupported Content Type {value:?}, Supported only: {:?}",
74+
ContentType::VARIANTS
75+
)
76+
},
6677
};
6778
Ok(content_type)
6879
}

0 commit comments

Comments
 (0)