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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rust/signed_doc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ brotli = "7.0.0"
ed25519-dalek = { version = "2.1.1", features = ["pem", "rand_core"] }
uuid = { version = "1.11.0", features = ["v4", "v7", "serde"] }
hex = "0.4.3"
thiserror = "2.0.9"
strum = { version = "0.26.3", features = ["derive"] }

[dev-dependencies]
clap = { version = "4.5.23", features = ["derive", "env"] }
Expand Down
5 changes: 3 additions & 2 deletions rust/signed_doc/examples/mk_signed_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{
path::PathBuf,
};

use catalyst_signed_doc::{Builder, CatalystSignedDocument, Decode, Decoder, KidUri, Metadata};
use catalyst_signed_doc::{Builder, CatalystSignedDocument, KidUri, Metadata};
use clap::Parser;
use coset::CborSerializable;
use ed25519_dalek::{ed25519::signature::Signer, pkcs8::DecodePrivateKey};
Expand Down Expand Up @@ -107,7 +107,8 @@ fn decode_signed_doc(cose_bytes: &[u8]) {
cose_bytes.len(),
hex::encode(cose_bytes)
);
match CatalystSignedDocument::decode(&mut Decoder::new(cose_bytes), &mut ()) {

match CatalystSignedDocument::try_from(cose_bytes) {
Ok(cat_signed_doc) => {
println!("This is a valid Catalyst Document.");
println!("{cat_signed_doc}");
Expand Down
55 changes: 37 additions & 18 deletions rust/signed_doc/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,48 @@
//! Catalyst Signed Document errors.
//! Catalyst Signed Document Error

/// Catalyst Signed Document error.
#[derive(thiserror::Error, Debug)]
#[error("Catalyst Signed Document Error: {0:?}")]
pub struct Error(pub(crate) List);
use std::fmt;

/// List of errors.
use catalyst_types::problem_report::ProblemReport;

/// Catalyst Signed Document Error
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub(crate) struct List(pub(crate) Vec<anyhow::Error>);
pub struct CatalystSignedDocError {
/// List of errors during processing.
report: ProblemReport,
/// Actual error.
error: anyhow::Error,
}

impl From<Vec<anyhow::Error>> for List {
fn from(e: Vec<anyhow::Error>) -> Self {
Self(e)
impl CatalystSignedDocError {
/// Create a new `CatalystSignedDocError`.
#[must_use]
pub fn new(report: ProblemReport, error: anyhow::Error) -> Self {
Self { report, error }
}
}

impl From<Vec<anyhow::Error>> for Error {
fn from(e: Vec<anyhow::Error>) -> Self {
Self(e.into())
/// Get the error report.
#[must_use]
pub fn report(&self) -> &ProblemReport {
&self.report
}

/// Get the actual error.
#[must_use]
pub fn error(&self) -> &anyhow::Error {
&self.error
}
}

impl Error {
/// List of errors.
pub fn errors(&self) -> &Vec<anyhow::Error> {
&self.0 .0
impl fmt::Display for CatalystSignedDocError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let report_json = serde_json::to_string(&self.report)
.unwrap_or_else(|_| String::from("Failed to serialize ProblemReport"));

write!(
fmt,
"CatalystSignedDocError {{ error: {}, report: {} }}",
self.error, report_json
)
}
}
100 changes: 73 additions & 27 deletions rust/signed_doc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@

mod builder;
mod content;
mod error;
pub mod error;
mod metadata;
mod signature;
mod utils;

use std::{
convert::TryFrom,
fmt::{Display, Formatter},
sync::Arc,
};

use anyhow::anyhow;
pub use builder::Builder;
use catalyst_types::problem_report::ProblemReport;
pub use content::Content;
use coset::{CborSerializable, Header};
use error::CatalystSignedDocError;
pub use metadata::{DocumentRef, ExtraFields, Metadata, UuidV4, UuidV7};
pub use minicbor::{decode, encode, Decode, Decoder, Encode};
pub use signature::{KidUri, Signatures};
use utils::context::DecodeSignDocCtx;

/// Inner type that holds the Catalyst Signed Document with parsing errors.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -104,8 +107,20 @@ impl CatalystSignedDocument {
}
}

impl Decode<'_, ()> for CatalystSignedDocument {
fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result<Self, decode::Error> {
impl TryFrom<&[u8]> for CatalystSignedDocument {
type Error = CatalystSignedDocError;

fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let error_report = ProblemReport::new("Catalyst Signed Document");
let mut ctx = DecodeSignDocCtx { error_report };
let decoded: CatalystSignedDocument = minicbor::decode_with(value, &mut ctx)
.map_err(|e| CatalystSignedDocError::new(ctx.error_report, e.into()))?;
Ok(decoded)
}
}

impl Decode<'_, DecodeSignDocCtx> for CatalystSignedDocument {
fn decode(d: &mut Decoder<'_>, ctx: &mut DecodeSignDocCtx) -> Result<Self, decode::Error> {
let start = d.position();
d.skip()?;
let end = d.position();
Expand All @@ -115,40 +130,67 @@ impl Decode<'_, ()> for CatalystSignedDocument {
.ok_or(minicbor::decode::Error::end_of_input())?;

let cose_sign = coset::CoseSign::from_slice(cose_bytes).map_err(|e| {
ctx.error_report.invalid_value(
"COSE sign document bytes",
&format!("{:?}", &cose_bytes),
&format!("Cannot convert bytes to CoseSign {e:?}"),
"Creating COSE Sign document",
);
minicbor::decode::Error::message(format!("Invalid COSE Sign document: {e}"))
})?;

let mut errors = Vec::new();

let metadata = Metadata::try_from(&cose_sign.protected).map_or_else(
|e| {
errors.extend(e.0 .0);
None
},
Some,
);
let signatures = Signatures::try_from(&cose_sign.signatures).map_or_else(
|e| {
errors.extend(e.0 .0);
None
},
Some,
);
let metadata = Metadata::from_protected_header(&cose_sign.protected, &ctx.error_report)
.map_or_else(
|e| {
ctx.error_report.conversion_error(
"COSE sign protected header",
&format!("{:?}", &cose_sign.protected),
&format!("Expected Metadata: {e:?}"),
"Converting COSE Sign protected header to Metadata",
);
None
},
Some,
);
let signatures = Signatures::from_cose_sig(&cose_sign.signatures, &ctx.error_report)
.map_or_else(
|e| {
ctx.error_report.conversion_error(
"COSE sign signatures",
&format!("{:?}", &cose_sign.signatures),
&format!("Expected Signatures {e:?}"),
"Converting COSE Sign signatures to Signatures",
);
None
},
Some,
);

if cose_sign.payload.is_none() {
errors.push(anyhow!("Document Content is missing"));
ctx.error_report
.missing_field("COSE Sign Payload", "Missing document content (payload)");
}

match (cose_sign.payload, metadata, signatures) {
(Some(payload), Some(metadata), Some(signatures)) => {
let content = Content::from_encoded(
payload,
payload.clone(),
metadata.content_type(),
metadata.content_encoding(),
)
.map_err(|e| {
errors.push(anyhow!("Invalid Document Content: {e}"));
minicbor::decode::Error::message(error::Error::from(errors))
ctx.error_report.invalid_value(
"Document Content",
&format!(
"Given value {:?}, {:?}, {:?}",
payload,
metadata.content_type(),
metadata.content_encoding()
),
&format!("{e:?}"),
"Creating document content",
);
minicbor::decode::Error::message("Failed to create Document Content")
})?;

Ok(InnerCatalystSignedDocument {
Expand All @@ -158,7 +200,11 @@ impl Decode<'_, ()> for CatalystSignedDocument {
}
.into())
},
_ => Err(minicbor::decode::Error::message(error::Error::from(errors))),
_ => {
Err(minicbor::decode::Error::message(
"Failed to decode Catalyst Signed Document",
))
},
}
}
}
Expand Down Expand Up @@ -238,8 +284,8 @@ mod tests {

let mut bytes = Vec::new();
minicbor::encode_with(doc, &mut bytes, &mut ()).unwrap();
let decoded: CatalystSignedDocument =
minicbor::decode_with(bytes.as_slice(), &mut ()).unwrap();

let decoded: CatalystSignedDocument = bytes.as_slice().try_into().unwrap();

assert_eq!(decoded.doc_type(), uuid_v4);
assert_eq!(decoded.doc_id(), uuid_v7);
Expand Down
11 changes: 9 additions & 2 deletions rust/signed_doc/src/metadata/algorithm.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Cryptographic Algorithm in COSE SIGN protected header.

use strum::VariantArray;

/// Cryptography Algorithm.
#[derive(Copy, Clone, Debug, PartialEq, serde::Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, serde::Deserialize, VariantArray)]
pub enum Algorithm {
/// `EdDSA`
EdDSA,
Expand All @@ -25,7 +27,12 @@ impl TryFrom<coset::iana::Algorithm> for Algorithm {
fn try_from(value: coset::iana::Algorithm) -> Result<Self, Self::Error> {
match value {
coset::iana::Algorithm::EdDSA => Ok(Self::EdDSA),
_ => anyhow::bail!("Unsupported algorithm: {value:?}"),
_ => {
anyhow::bail!(
"Unsupported algorithm: {value:?}, Supported only: {:?}",
Algorithm::VARIANTS
)
},
}
}
}
17 changes: 14 additions & 3 deletions rust/signed_doc/src/metadata/content_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ use std::{

use coset::iana::CoapContentFormat;
use serde::{de, Deserialize, Deserializer};
use strum::VariantArray;

/// Payload Content Type.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, VariantArray)]
pub enum ContentType {
/// 'application/cbor'
Cbor,
Expand All @@ -33,7 +34,12 @@ impl FromStr for ContentType {
match s {
"cbor" => Ok(Self::Cbor),
"json" => Ok(Self::Json),
_ => anyhow::bail!("Unsupported Content Type: {s:?}"),
_ => {
anyhow::bail!(
"Unsupported Content Type: {s:?}, Supported only: {:?}",
ContentType::VARIANTS
)
},
}
}
}
Expand Down Expand Up @@ -62,7 +68,12 @@ impl TryFrom<&coset::ContentType> for ContentType {
let content_type = match value {
coset::ContentType::Assigned(CoapContentFormat::Json) => ContentType::Json,
coset::ContentType::Assigned(CoapContentFormat::Cbor) => ContentType::Cbor,
_ => anyhow::bail!("Unsupported Content Type {value:?}"),
_ => {
anyhow::bail!(
"Unsupported Content Type {value:?}, Supported only: {:?}",
ContentType::VARIANTS
)
},
};
Ok(content_type)
}
Expand Down
Loading
Loading