Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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::new(cose_bytes) {
Ok(cat_signed_doc) => {
println!("This is a valid Catalyst Document.");
println!("{cat_signed_doc}");
Expand Down
29 changes: 0 additions & 29 deletions rust/signed_doc/src/error.rs

This file was deleted.

136 changes: 108 additions & 28 deletions rust/signed_doc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

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

use std::{
convert::TryFrom,
fmt::{Display, Formatter},
fmt::{self, 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};
pub use metadata::{DocumentRef, ExtraFields, Metadata, UuidV4, UuidV7};
pub use minicbor::{decode, encode, Decode, Decoder, Encode};
pub use signature::{KidUri, Signatures};
use utils::context::SignDocContext;

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

/// Catalyst Signed Document Error
#[derive(Debug)]
pub struct CatalystSignedDocError {
/// List of errors during processing.
report: ProblemReport,
/// Actual error.
error: anyhow::Error,
}

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
)
}
}

impl CatalystSignedDocument {
/// Create a new Catalyst Signed Document from a COSE Sign document bytes.
///
/// # Arguments
///
/// * `cose_bytes` - COSE Sign document bytes.
///
/// # Returns
///
/// A new Catalyst Signed Document.
///
/// # Errors
///
/// Returns an error if the COSE Sign document bytes are invalid or decode error.
pub fn new(cose_bytes: &[u8]) -> anyhow::Result<Self, CatalystSignedDocError> {
let error_report = ProblemReport::new("Catalyst Signed Document");
let mut ctx = SignDocContext { error_report };
let decoded: CatalystSignedDocument =
minicbor::decode_with(cose_bytes, &mut ctx).map_err(|e| {
CatalystSignedDocError {
report: ctx.error_report,
error: e.into(),
}
})?;
Ok(decoded)
}

// A bunch of getters to access the contents, or reason through the document, such as.

/// Return Document Type `UUIDv4`.
Expand Down Expand Up @@ -104,8 +153,8 @@ impl CatalystSignedDocument {
}
}

impl Decode<'_, ()> for CatalystSignedDocument {
fn decode(d: &mut Decoder<'_>, (): &mut ()) -> Result<Self, decode::Error> {
impl Decode<'_, SignDocContext> for CatalystSignedDocument {
fn decode(d: &mut Decoder<'_>, ctx: &mut SignDocContext) -> Result<Self, decode::Error> {
let start = d.position();
d.skip()?;
let end = d.position();
Expand All @@ -115,40 +164,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 +234,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 +318,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::new(&bytes).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