Skip to content

Commit

Permalink
fix(rust/signed_doc): decode and validate signatures
Browse files Browse the repository at this point in the history
* WIP: implement temporary Error type that will be replaced with a ProblemReport
  • Loading branch information
saibatizoku committed Jan 9, 2025
1 parent 5c74471 commit 2b9282f
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 20 deletions.
4 changes: 2 additions & 2 deletions rust/signed_doc/examples/cat-signed-doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use std::{
path::PathBuf,
};

use catalyst_signed_doc::CatalystSignedDocument;
use clap::Parser;
use signed_doc::CatalystSignedDocument;

/// Hermes cli commands
#[derive(clap::Parser)]
Expand Down Expand Up @@ -42,7 +42,7 @@ impl Cli {
Self::InspectBytes { cose_sign_str } => hex::decode(&cose_sign_str)?,
};
println!("Bytes read:\n{}\n", hex::encode(&cose_bytes));
let cat_signed_doc: CatalystSignedDocument = cose_bytes.try_into()?;
let cat_signed_doc: CatalystSignedDocument = cose_bytes.as_slice().try_into()?;
println!("{cat_signed_doc}");
Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion rust/signed_doc/examples/mk_signed_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ use std::{
path::PathBuf,
};

use catalyst_signed_doc::{DocumentRef, KidUri, Metadata, UuidV7};
use clap::Parser;
use coset::{iana::CoapContentFormat, CborSerializable};
use ed25519_dalek::{
ed25519::signature::Signer,
pkcs8::{DecodePrivateKey, DecodePublicKey},
};
use signed_doc::{DocumentRef, KidUri, Metadata, UuidV7};

fn main() {
if let Err(err) = Cli::parse().exec() {
Expand Down
19 changes: 19 additions & 0 deletions rust/signed_doc/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Catalyst Signed Document errors.
/// Catalyst Signed Document error.
#[derive(thiserror::Error, Debug)]
#[error("Catalyst Signed Document Error: {0:#?}")]
pub struct Error(pub(crate) Vec<anyhow::Error>);

impl From<Vec<anyhow::Error>> for Error {
fn from(e: Vec<anyhow::Error>) -> Self {
Error(e)
}
}

impl Error {
/// List of errors.
pub fn errors(&self) -> &Vec<anyhow::Error> {
&self.0
}
}
44 changes: 41 additions & 3 deletions rust/signed_doc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ use std::{
};

use anyhow::anyhow;
use coset::CborSerializable;
use coset::{CborSerializable, CoseSignature};

mod error;
mod metadata;
mod payload;
mod signature;

pub use metadata::{DocumentRef, Metadata, UuidV7};
use payload::JsonContent;
pub use signature::KidUri;
use signature::Signatures;

/// Inner type that holds the Catalyst Signed Document with parsing errors.
#[derive(Default)]
Expand All @@ -23,6 +25,8 @@ struct InnerCatalystSignedDocument {
metadata: Metadata,
/// Document Payload viewed as JSON Content
payload: JsonContent,
/// Signatures
signatures: Signatures,
/// Raw COSE Sign data
cose_sign: coset::CoseSign,
}
Expand Down Expand Up @@ -54,7 +58,7 @@ impl Display for CatalystSignedDocument {
}

impl TryFrom<&[u8]> for CatalystSignedDocument {
type Error = Vec<anyhow::Error>;
type Error = error::Error;

fn try_from(cose_bytes: &[u8]) -> Result<Self, Self::Error> {
// Try reading as a tagged COSE SIGN, otherwise try reading as untagged.
Expand All @@ -67,7 +71,7 @@ impl TryFrom<&[u8]> for CatalystSignedDocument {
let mut payload = JsonContent::default();

if let Some(bytes) = &cose_sign.payload {
match JsonContent::try_from((bytes, metadata.content_encoding())) {
match JsonContent::try_from((bytes.as_ref(), metadata.content_encoding())) {
Ok(c) => payload = c,
Err(e) => {
content_errors.push(anyhow!("Invalid Payload: {e}"));
Expand All @@ -77,11 +81,27 @@ impl TryFrom<&[u8]> for CatalystSignedDocument {
content_errors.push(anyhow!("COSE payload is empty"));
};

let mut signatures = Signatures::default();
match Signatures::try_from(&cose_sign.signatures) {
Ok(s) => signatures = s,
Err(errors) => {
for e in errors.errors() {
content_errors.push(anyhow!("{e}"));
}
},
}

let inner = InnerCatalystSignedDocument {
metadata,
payload,
signatures,
cose_sign,
};

if !content_errors.is_empty() {
return Err(error::Error(content_errors));
}

Ok(CatalystSignedDocument {
inner: Arc::new(inner),
})
Expand Down Expand Up @@ -132,4 +152,22 @@ impl CatalystSignedDocument {
pub fn doc_section(&self) -> Option<String> {
self.inner.metadata.doc_section()
}

/// Return Raw COSE SIGN bytes.
#[must_use]
pub fn cose_sign_bytes(&self) -> Vec<u8> {
self.inner.cose_sign.clone().to_vec().unwrap_or_default()
}

/// Return a list of signature KIDs.
#[must_use]
pub fn signature_kids(&self) -> Vec<KidUri> {
self.inner.signatures.kids()
}

/// Return a list of signatures.
#[must_use]
pub fn signatures(&self) -> Vec<CoseSignature> {
self.inner.signatures.signatures()
}
}
8 changes: 4 additions & 4 deletions rust/signed_doc/src/metadata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ pub struct Metadata {
}

impl Metadata {
/// Are there any validation errors (as opposed to structural errors).
/// Returns true if metadata has no validation errors.
#[must_use]
pub fn is_valid(&self) -> bool {
!self.content_errors.is_empty()
self.content_errors.is_empty()
}

/// Return Document Type `UUIDv4`.
Expand Down Expand Up @@ -144,7 +144,7 @@ impl Default for Metadata {
}

impl TryFrom<&coset::ProtectedHeader> for Metadata {
type Error = Vec<anyhow::Error>;
type Error = crate::error::Error;

#[allow(clippy::too_many_lines)]
fn try_from(protected: &coset::ProtectedHeader) -> Result<Self, Self::Error> {
Expand Down Expand Up @@ -254,7 +254,7 @@ impl TryFrom<&coset::ProtectedHeader> for Metadata {
if errors.is_empty() {
Ok(metadata)
} else {
Err(errors)
Err(errors.into())
}
}
}
Expand Down
17 changes: 7 additions & 10 deletions rust/signed_doc/src/payload/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,19 @@ impl TryFrom<&[u8]> for Content<serde_json::Value> {
}
}

impl TryFrom<(&Vec<u8>, Option<ContentEncoding>)> for Content<serde_json::Value> {
impl TryFrom<(&[u8], Option<ContentEncoding>)> for Content<serde_json::Value> {
type Error = anyhow::Error;

fn try_from(
(value, encoding): (&Vec<u8>, Option<ContentEncoding>),
) -> Result<Self, Self::Error> {
fn try_from((value, encoding): (&[u8], Option<ContentEncoding>)) -> Result<Self, Self::Error> {
if let Some(content_encoding) = encoding {
match content_encoding.decode(value) {
Ok(decompressed) => {
return Self::try_from(decompressed.as_slice());
},
match content_encoding.decode(&value.to_vec()) {
Ok(decompressed) => Self::try_from(decompressed.as_slice()),
Err(e) => {
anyhow::bail!("Failed to decode {encoding:?}: {e}");
anyhow::bail!("Failed to decode {encoding:?} content: {e}");
},
}
} else {
Self::try_from(value)
}
Self::try_from(value.as_ref())
}
}
55 changes: 55 additions & 0 deletions rust/signed_doc/src/signature/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,57 @@
//! Catalyst Signed Document COSE Signature information.
pub use catalyst_types::kid_uri::KidUri;
use coset::CoseSignature;

/// Catalyst Signed Document COSE Signature.
#[derive(Debug)]
pub struct Signature {
/// Key ID
kid: KidUri,
/// COSE Signature
signature: CoseSignature,
}

/// List of Signatures.
#[derive(Default)]
pub struct Signatures(Vec<Signature>);

impl Signatures {
/// List of signature Key IDs.
pub fn kids(&self) -> Vec<KidUri> {
self.0.iter().map(|sig| sig.kid.clone()).collect()
}

/// List of signatures.
pub fn signatures(&self) -> Vec<CoseSignature> {
self.0.iter().map(|sig| sig.signature.clone()).collect()
}
}

impl TryFrom<&Vec<CoseSignature>> for Signatures {
type Error = crate::error::Error;

fn try_from(value: &Vec<CoseSignature>) -> Result<Self, Self::Error> {
let mut signatures = Vec::new();
let mut errors = Vec::new();
value
.iter()
.cloned()
.enumerate()
.for_each(|(idx, signature)| {
match KidUri::try_from(signature.protected.header.key_id.as_ref()) {
Ok(kid) => signatures.push(Signature { kid, signature }),
Err(e) => {
errors.push(anyhow::anyhow!(
"Signature at index {idx} has valid Catalyst Key Id: {e}"
));
},
}
});
if errors.is_empty() {
Err(errors.into())
} else {
Ok(Signatures(signatures))
}
}
}

0 comments on commit 2b9282f

Please sign in to comment.