Skip to content

Commit ad8bb30

Browse files
committed
fix(rust/signed_doc): decode and validate signatures
* WIP: implement temporary Error type that will be replaced with a ProblemReport
1 parent 5c74471 commit ad8bb30

File tree

7 files changed

+128
-19
lines changed

7 files changed

+128
-19
lines changed

rust/signed_doc/examples/cat-signed-doc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use std::{
1111
path::PathBuf,
1212
};
1313

14+
use catalyst_signed_doc::CatalystSignedDocument;
1415
use clap::Parser;
15-
use signed_doc::CatalystSignedDocument;
1616

1717
/// Hermes cli commands
1818
#[derive(clap::Parser)]
@@ -42,7 +42,7 @@ impl Cli {
4242
Self::InspectBytes { cose_sign_str } => hex::decode(&cose_sign_str)?,
4343
};
4444
println!("Bytes read:\n{}\n", hex::encode(&cose_bytes));
45-
let cat_signed_doc: CatalystSignedDocument = cose_bytes.try_into()?;
45+
let cat_signed_doc: CatalystSignedDocument = cose_bytes.as_slice().try_into()?;
4646
println!("{cat_signed_doc}");
4747
Ok(())
4848
}

rust/signed_doc/examples/mk_signed_doc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ use std::{
88
path::PathBuf,
99
};
1010

11+
use catalyst_signed_doc::{DocumentRef, KidUri, Metadata, UuidV7};
1112
use clap::Parser;
1213
use coset::{iana::CoapContentFormat, CborSerializable};
1314
use ed25519_dalek::{
1415
ed25519::signature::Signer,
1516
pkcs8::{DecodePrivateKey, DecodePublicKey},
1617
};
17-
use signed_doc::{DocumentRef, KidUri, Metadata, UuidV7};
1818

1919
fn main() {
2020
if let Err(err) = Cli::parse().exec() {

rust/signed_doc/src/error.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//! Catalyst Signed Document errors.
2+
3+
/// Catalyst Signed Document error.
4+
#[derive(thiserror::Error, Debug)]
5+
#[error("Catalyst Signed Document Error: {0:#?}")]
6+
pub struct Error(pub(crate) Vec<anyhow::Error>);
7+
8+
impl From<Vec<anyhow::Error>> for Error {
9+
fn from(e: Vec<anyhow::Error>) -> Self {
10+
Error(e)
11+
}
12+
}
13+
14+
impl Error {
15+
/// List of errors.
16+
pub fn errors(&self) -> &Vec<anyhow::Error> {
17+
&self.0
18+
}
19+
}

rust/signed_doc/src/lib.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ use std::{
66
};
77

88
use anyhow::anyhow;
9-
use coset::CborSerializable;
9+
use coset::{CborSerializable, CoseSignature};
1010

11+
mod error;
1112
mod metadata;
1213
mod payload;
1314
mod signature;
1415

1516
pub use metadata::{DocumentRef, Metadata, UuidV7};
1617
use payload::JsonContent;
1718
pub use signature::KidUri;
19+
use signature::Signatures;
1820

1921
/// Inner type that holds the Catalyst Signed Document with parsing errors.
2022
#[derive(Default)]
@@ -23,6 +25,8 @@ struct InnerCatalystSignedDocument {
2325
metadata: Metadata,
2426
/// Document Payload viewed as JSON Content
2527
payload: JsonContent,
28+
/// Signatures
29+
signatures: Signatures,
2630
/// Raw COSE Sign data
2731
cose_sign: coset::CoseSign,
2832
}
@@ -54,7 +58,7 @@ impl Display for CatalystSignedDocument {
5458
}
5559

5660
impl TryFrom<&[u8]> for CatalystSignedDocument {
57-
type Error = Vec<anyhow::Error>;
61+
type Error = error::Error;
5862

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

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

84+
let mut signatures = Signatures::default();
85+
match Signatures::try_from(&cose_sign.signatures) {
86+
Ok(s) => signatures = s,
87+
Err(errors) => {
88+
for e in errors {
89+
content_errors.push(anyhow!("{e}"));
90+
}
91+
},
92+
}
93+
8094
let inner = InnerCatalystSignedDocument {
8195
metadata,
8296
payload,
97+
signatures,
8398
cose_sign,
8499
};
100+
101+
if !content_errors.is_empty() {
102+
return Err(error::Error(content_errors));
103+
}
104+
85105
Ok(CatalystSignedDocument {
86106
inner: Arc::new(inner),
87107
})
@@ -132,4 +152,22 @@ impl CatalystSignedDocument {
132152
pub fn doc_section(&self) -> Option<String> {
133153
self.inner.metadata.doc_section()
134154
}
155+
156+
/// Return Raw COSE SIGN bytes.
157+
#[must_use]
158+
pub fn cose_sign_bytes(&self) -> Vec<u8> {
159+
self.inner.cose_sign.clone().to_vec().unwrap_or_default()
160+
}
161+
162+
/// Return a list of signature KIDs.
163+
#[must_use]
164+
pub fn signature_kids(&self) -> Vec<KidUri> {
165+
self.inner.signatures.kids()
166+
}
167+
168+
/// Return a list of signatures.
169+
#[must_use]
170+
pub fn signatures(&self) -> Vec<CoseSignature> {
171+
self.inner.signatures.signatures()
172+
}
135173
}

rust/signed_doc/src/metadata/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ pub struct Metadata {
4949
}
5050

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

5858
/// Return Document Type `UUIDv4`.
@@ -144,7 +144,7 @@ impl Default for Metadata {
144144
}
145145

146146
impl TryFrom<&coset::ProtectedHeader> for Metadata {
147-
type Error = Vec<anyhow::Error>;
147+
type Error = crate::error::Error;
148148

149149
#[allow(clippy::too_many_lines)]
150150
fn try_from(protected: &coset::ProtectedHeader) -> Result<Self, Self::Error> {

rust/signed_doc/src/payload/json.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,19 @@ impl TryFrom<&[u8]> for Content<serde_json::Value> {
2020
}
2121
}
2222

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

26-
fn try_from(
27-
(value, encoding): (&Vec<u8>, Option<ContentEncoding>),
28-
) -> Result<Self, Self::Error> {
26+
fn try_from((value, encoding): (&[u8], Option<ContentEncoding>)) -> Result<Self, Self::Error> {
2927
if let Some(content_encoding) = encoding {
30-
match content_encoding.decode(value) {
31-
Ok(decompressed) => {
32-
return Self::try_from(decompressed.as_slice());
33-
},
28+
match content_encoding.decode(&value.to_vec()) {
29+
Ok(decompressed) => Self::try_from(decompressed.as_slice()),
3430
Err(e) => {
35-
anyhow::bail!("Failed to decode {encoding:?}: {e}");
31+
anyhow::bail!("Failed to decode {encoding:?} content: {e}");
3632
},
3733
}
34+
} else {
35+
Self::try_from(value)
3836
}
39-
Self::try_from(value.as_ref())
4037
}
4138
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,57 @@
11
//! Catalyst Signed Document COSE Signature information.
2+
23
pub use catalyst_types::kid_uri::KidUri;
4+
use coset::CoseSignature;
5+
6+
/// Catalyst Signed Document COSE Signature.
7+
#[derive(Debug)]
8+
pub struct Signature {
9+
/// Key ID
10+
kid: KidUri,
11+
/// COSE Signature
12+
signature: CoseSignature,
13+
}
14+
15+
/// List of Signatures.
16+
#[derive(Default)]
17+
pub struct Signatures(Vec<Signature>);
18+
19+
impl Signatures {
20+
/// List of signature Key IDs.
21+
pub fn kids(&self) -> Vec<KidUri> {
22+
self.0.iter().map(|sig| sig.kid.clone()).collect()
23+
}
24+
25+
/// List of signatures.
26+
pub fn signatures(&self) -> Vec<CoseSignature> {
27+
self.0.iter().map(|sig| sig.signature.clone()).collect()
28+
}
29+
}
30+
31+
impl TryFrom<&Vec<CoseSignature>> for Signatures {
32+
type Error = crate::error::Error;
33+
34+
fn try_from(value: &Vec<CoseSignature>) -> Result<Self, Self::Error> {
35+
let mut signatures = Vec::new();
36+
let mut errors = Vec::new();
37+
value
38+
.iter()
39+
.cloned()
40+
.enumerate()
41+
.for_each(|(idx, signature)| {
42+
match KidUri::try_from(signature.protected.header.key_id.as_ref()) {
43+
Ok(kid) => signatures.push(Signature { kid, signature }),
44+
Err(e) => {
45+
errors.push(anyhow::anyhow!(
46+
"Signature at index {idx} has valid Catalyst Key Id: {e}"
47+
));
48+
},
49+
}
50+
});
51+
if errors.is_empty() {
52+
Err(errors.into())
53+
} else {
54+
Ok(Signatures(signatures))
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)