Skip to content

Commit 7ab08a0

Browse files
authored
fix(rust/signed-doc): Added missing validation of the kid field (#292)
* add catalyst id validation for signed doc signature * add tests * fix spelling * fix clippy
1 parent 54663da commit 7ab08a0

File tree

9 files changed

+90
-32
lines changed

9 files changed

+90
-32
lines changed

rust/catalyst-types/src/id_uri/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Catalyst ID URI.
2+
//! <https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/rbac_id_uri/catalyst-id-uri/>
23
34
// cspell: words userinfo rngs Fftx csprng
45

@@ -25,6 +26,7 @@ use key_rotation::KeyRotation;
2526
use role_index::RoleIndex;
2627

2728
/// Catalyst ID
29+
/// <https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/rbac_id_uri/catalyst-id-uri/>
2830
///
2931
/// Identity of Catalyst Registration.
3032
/// Optionally also identifies a specific Signed Document Key
@@ -66,7 +68,7 @@ impl IdUri {
6668
/// * Wednesday, January 1, 2025 12:00:00 AM
6769
const MIN_NONCE: i64 = 1_735_689_600;
6870
/// URI scheme for Catalyst
69-
const SCHEME: &Scheme = Scheme::new_or_panic("id.catalyst");
71+
pub const SCHEME: &Scheme = Scheme::new_or_panic("id.catalyst");
7072

7173
/// Get the cosmetic username from the URI.
7274
#[must_use]
@@ -144,6 +146,12 @@ impl IdUri {
144146
self.id
145147
}
146148

149+
/// Was `IdUri` formatted as an uri when it was parsed.
150+
#[must_use]
151+
pub fn is_uri(&self) -> bool {
152+
!self.id
153+
}
154+
147155
/// Add or change the username in a Catalyst ID URI.
148156
#[must_use]
149157
pub fn with_username(self, name: &str) -> Self {

rust/signed_doc/bins/mk_signed_doc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl Cli {
8383

8484
let new_signed_doc = signed_doc
8585
.into_builder()
86-
.add_signature(|message| sk.sign::<()>(&message).to_bytes().to_vec(), kid)?
86+
.add_signature(|message| sk.sign::<()>(&message).to_bytes().to_vec(), &kid)?
8787
.build();
8888
save_signed_doc(new_signed_doc, &doc)?;
8989
},

rust/signed_doc/src/builder.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
use catalyst_types::{id_uri::IdUri, problem_report::ProblemReport};
33

44
use crate::{
5-
CatalystSignedDocument, Content, InnerCatalystSignedDocument, Metadata, Signatures,
6-
PROBLEM_REPORT_CTX,
5+
signature::Signature, CatalystSignedDocument, Content, InnerCatalystSignedDocument, Metadata,
6+
Signatures, PROBLEM_REPORT_CTX,
77
};
88

99
/// Catalyst Signed Document Builder.
@@ -55,7 +55,7 @@ impl Builder {
5555
/// content, due to malformed data, or when the signed document cannot be
5656
/// converted into `coset::CoseSign`.
5757
pub fn add_signature(
58-
mut self, sign_fn: impl FnOnce(Vec<u8>) -> Vec<u8>, kid: IdUri,
58+
mut self, sign_fn: impl FnOnce(Vec<u8>) -> Vec<u8>, kid: &IdUri,
5959
) -> anyhow::Result<Self> {
6060
let cose_sign = self
6161
.0
@@ -69,7 +69,9 @@ impl Builder {
6969
.build();
7070
let data_to_sign = cose_sign.tbs_data(&[], &signature);
7171
signature.signature = sign_fn(data_to_sign);
72-
self.0.signatures.push(kid, signature);
72+
if let Some(sign) = Signature::from_cose_sig(signature, &self.0.report) {
73+
self.0.signatures.push(sign);
74+
}
7375

7476
Ok(self)
7577
}

rust/signed_doc/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ impl Decode<'_, ()> for CatalystSignedDocument {
223223

224224
let report = ProblemReport::new(PROBLEM_REPORT_CTX);
225225
let metadata = Metadata::from_protected_header(&cose_sign.protected, &report);
226-
let signatures = Signatures::from_cose_sig(&cose_sign.signatures, &report);
226+
let signatures = Signatures::from_cose_sig_list(&cose_sign.signatures, &report);
227227

228228
let content = if let Some(payload) = cose_sign.payload {
229229
Content::from_encoded(payload, metadata.content_encoding(), &report)

rust/signed_doc/src/signature/mod.rs

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,35 @@ pub struct Signature {
1313
signature: CoseSignature,
1414
}
1515

16+
impl Signature {
17+
/// Convert COSE Signature to `Signature`.
18+
pub(crate) fn from_cose_sig(signature: CoseSignature, report: &ProblemReport) -> Option<Self> {
19+
match IdUri::try_from(signature.protected.header.key_id.as_ref()) {
20+
Ok(kid) if kid.is_uri() => Some(Self { kid, signature }),
21+
Ok(kid) => {
22+
report.invalid_value(
23+
"COSE signature protected header key ID",
24+
&kid.to_string(),
25+
&format!(
26+
"COSE signature protected header key ID must be a Catalyst Id URI, missing URI schema {}", IdUri::SCHEME
27+
),
28+
"Converting COSE signature header key ID to IdUri",
29+
);
30+
None
31+
},
32+
Err(e) => {
33+
report.conversion_error(
34+
"COSE signature protected header key ID",
35+
&format!("{:?}", &signature.protected.header.key_id),
36+
&format!("{e:?}"),
37+
"Converting COSE signature header key ID to IdUri",
38+
);
39+
None
40+
},
41+
}
42+
}
43+
}
44+
1645
/// List of Signatures.
1746
#[derive(Debug, Clone, Default)]
1847
pub struct Signatures(Vec<Signature>);
@@ -42,9 +71,9 @@ impl Signatures {
4271
self.0.iter().map(|sig| sig.signature.clone())
4372
}
4473

45-
/// Add a new signature
46-
pub(crate) fn push(&mut self, kid: IdUri, signature: CoseSignature) {
47-
self.0.push(Signature { kid, signature });
74+
/// Add a `Signature` object into the list
75+
pub(crate) fn push(&mut self, sign: Signature) {
76+
self.0.push(sign);
4877
}
4978

5079
/// Number of signatures.
@@ -60,27 +89,19 @@ impl Signatures {
6089
}
6190

6291
/// Convert list of COSE Signature to `Signatures`.
63-
pub(crate) fn from_cose_sig(cose_sigs: &[CoseSignature], error_report: &ProblemReport) -> Self {
64-
let mut signatures = Vec::new();
65-
66-
cose_sigs
92+
pub(crate) fn from_cose_sig_list(cose_sigs: &[CoseSignature], report: &ProblemReport) -> Self {
93+
let res = cose_sigs
6794
.iter()
6895
.cloned()
6996
.enumerate()
70-
.for_each(|(idx, signature)| {
71-
match IdUri::try_from(signature.protected.header.key_id.as_ref()) {
72-
Ok(kid) => signatures.push(Signature { kid, signature }),
73-
Err(e) => {
74-
error_report.conversion_error(
75-
&format!("COSE signature protected header key ID at id {idx}"),
76-
&format!("{:?}", &signature.protected.header.key_id),
77-
&format!("{e:?}"),
78-
"Converting COSE signature header key ID to IdUri",
79-
);
80-
},
97+
.filter_map(|(idx, signature)| {
98+
let sign = Signature::from_cose_sig(signature, report);
99+
if sign.is_none() {
100+
report.other(&format!("COSE signature protected header key ID at id {idx}"), "Converting COSE signatures list to Catalyst Signed Documents signatures list",);
81101
}
82-
});
102+
sign
103+
}).collect();
83104

84-
Self(signatures)
105+
Self(res)
85106
}
86107
}

rust/signed_doc/src/validator/rules/signature_kid.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ mod tests {
6868
"content-type": ContentType::Json.to_string(),
6969
}))
7070
.unwrap()
71-
.add_signature(|m| sk.sign(&m).to_vec(), kid)
71+
.add_signature(|m| sk.sign(&m).to_vec(), &kid)
7272
.unwrap()
7373
.build();
7474

rust/signed_doc/tests/common/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ pub fn create_dummy_signed_doc(
8585
let signed_doc = Builder::new()
8686
.with_decoded_content(content)
8787
.with_json_metadata(with_metadata.unwrap_or(metadata))?
88-
.add_signature(|m| sk.sign(&m).to_vec(), kid.clone())?
88+
.add_signature(|m| sk.sign(&m).to_vec(), &kid)?
8989
.build();
9090

9191
Ok((signed_doc, pk, kid))

rust/signed_doc/tests/decoding.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
//! Integration test for COSE decoding part.
22
33
use catalyst_signed_doc::*;
4+
use catalyst_types::id_uri::role_index::RoleIndex;
5+
use common::create_dummy_key_pair;
6+
use ed25519_dalek::ed25519::signature::Signer;
47

58
mod common;
69

710
#[test]
811
fn catalyst_signed_doc_cbor_roundtrip_test() {
912
let (uuid_v7, uuid_v4, metadata_fields) = common::test_metadata();
13+
let (sk, _, kid) = create_dummy_key_pair(RoleIndex::ROLE_0).unwrap();
14+
1015
let content = serde_json::to_vec(&serde_json::Value::Null).unwrap();
1116

1217
let doc = Builder::new()
1318
.with_json_metadata(metadata_fields.clone())
1419
.unwrap()
1520
.with_decoded_content(content.clone())
21+
.add_signature(|m| sk.sign(&m).to_vec(), &kid)
22+
.unwrap()
1623
.build();
1724

1825
assert!(!doc.problem_report().is_problematic());
@@ -27,3 +34,23 @@ fn catalyst_signed_doc_cbor_roundtrip_test() {
2734
assert_eq!(decoded.doc_content().decoded_bytes().unwrap(), &content);
2835
assert_eq!(decoded.doc_meta(), &extra_fields);
2936
}
37+
38+
#[test]
39+
fn catalyst_signed_doc_cbor_roundtrip_kid_as_id_test() {
40+
let (_, _, metadata_fields) = common::test_metadata();
41+
let (sk, _, kid) = create_dummy_key_pair(RoleIndex::ROLE_0).unwrap();
42+
// transform Catalyst ID URI form to the ID form
43+
let kid = kid.as_id();
44+
45+
let content = serde_json::to_vec(&serde_json::Value::Null).unwrap();
46+
47+
let doc = Builder::new()
48+
.with_json_metadata(metadata_fields.clone())
49+
.unwrap()
50+
.with_decoded_content(content.clone())
51+
.add_signature(|m| sk.sign(&m).to_vec(), &kid)
52+
.unwrap()
53+
.build();
54+
55+
assert!(doc.problem_report().is_problematic());
56+
}

rust/signed_doc/tests/signature.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ async fn multiple_signatures_validation_test() {
3737
.with_decoded_content(serde_json::to_vec(&serde_json::Value::Null).unwrap())
3838
.with_json_metadata(common::test_metadata().2)
3939
.unwrap()
40-
.add_signature(|m| sk1.sign(&m).to_vec(), kid1.clone())
40+
.add_signature(|m| sk1.sign(&m).to_vec(), &kid1)
4141
.unwrap()
42-
.add_signature(|m| sk2.sign(&m).to_vec(), kid2.clone())
42+
.add_signature(|m| sk2.sign(&m).to_vec(), &kid2)
4343
.unwrap()
44-
.add_signature(|m| sk3.sign(&m).to_vec(), kid3.clone())
44+
.add_signature(|m| sk3.sign(&m).to_vec(), &kid3)
4545
.unwrap()
4646
.build();
4747

0 commit comments

Comments
 (0)