Skip to content

Commit ef5c92e

Browse files
feat(rust/signed-doc): Use Catalyst ID as KID (#194)
* fix(rust/signed-doc): add content type validation * fix(rust/signed-doc): content encoding field is optional * fix(rust/signed-doc): add 'half' feature from minicbor crate * wip(rust/signed_doc): implement verification method * verify signatures with public (verification) key * todo: verify UUIDs * fix(rust/signed-doc): fix content-type verification * wip(rust/signed_doc): implement verification method * verify doc type, id and ver * wip(rust/signed_doc): implement verification method * verify extra fields * feat(rust/signed-doc): add signatures to Catalyst Signed Document * fix(rust/signed-doc): refactor cli tool to sign * fix(rust/signed-doc): add verify command to cli tool * fix(rust/catalyst-types): refactor sigining logic into builder * chore(docs): fix spelling * fix(rust/signed-doc): cleanup * fix content validation, add unit test * remove clippy * fix * feat(rust/signed-doc): test verify signatures * fix(rust/signed-doc): update catalyst-types tag * fix(rust/signed-doc): update KID to use catalyst-id type * fix(rust/signed-doc): simpler test closure * feat(rust/signed-doc): add kids and authors methods for signed documents * fix(rust/signed-doc): use rbac-registration type for public key and algorithm verification * chore(docs): fix markdown * fix(rust/signed-doc): add more assertions to signature verification * fix(rust/signed-doc): code cleanup --------- Co-authored-by: Mr-Leshiy <[email protected]>
1 parent e2a2f89 commit ef5c92e

File tree

6 files changed

+94
-49
lines changed

6 files changed

+94
-49
lines changed

rust/signed_doc/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ license.workspace = true
1111
workspace = true
1212

1313
[dependencies]
14-
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250124-00" }
14+
rbac-registration = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250128-01" }
15+
catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250128-01" }
1516
anyhow = "1.0.95"
1617
serde = { version = "1.0.217", features = ["derive"] }
1718
serde_json = "1.0.134"

rust/signed_doc/README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,29 @@ Catalyst signed document crate implementation based on this
77

88
## Example
99

10-
Generate a `ed25519` private and public keys
10+
### Generate a `ed25519` private and public keys
1111

1212
```shell
1313
openssl genpkey -algorithm=ED25519 -out=private.pem -outpubkey=public.pem
1414
```
1515

16-
Prepare non-signed document,
16+
### Prepare non-signed document
17+
1718
`meta.json` file should follow the [`meta.schema.json`](./meta.schema.json).
1819

1920
```shell
2021
cargo run -p catalyst-signed-doc --example mk_signed_doc build signed_doc/doc.json signed_doc/doc.cose signed_doc/meta.json
2122
```
2223

23-
Inspect document
24+
### Sign document
25+
26+
`KID` is a valid Catalyst ID URI.
27+
28+
```shell
29+
cargo run -p catalyst-signed-doc --example mk_signed_doc sign signed_doc/doc.cose signed_doc/meta.json <KID>
30+
```
31+
32+
### Inspect document
2433

2534
```shell
2635
cargo run -p catalyst-signed-doc --example mk_signed_doc inspect signed_doc/doc.cose

rust/signed_doc/examples/mk_signed_doc.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
path::PathBuf,
99
};
1010

11-
use catalyst_signed_doc::{Builder, CatalystSignedDocument, KidUri, Metadata};
11+
use catalyst_signed_doc::{Builder, CatalystSignedDocument, IdUri, Metadata, SimplePublicKeyType};
1212
use clap::Parser;
1313
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey};
1414

@@ -39,7 +39,7 @@ enum Cli {
3939
/// Path to the secret key in PEM format
4040
sk: PathBuf,
4141
/// Signer kid
42-
kid: KidUri,
42+
kid: IdUri,
4343
},
4444
/// Inspects Catalyst Signed Document
4545
Inspect {
@@ -59,7 +59,7 @@ enum Cli {
5959
/// Path to the verifying key in PEM format
6060
pk: PathBuf,
6161
/// Signer kid
62-
kid: KidUri,
62+
kid: IdUri,
6363
},
6464
}
6565

@@ -107,9 +107,9 @@ impl Cli {
107107
signed_doc
108108
.verify(|k| {
109109
if k.to_string() == kid.to_string() {
110-
pk
110+
SimplePublicKeyType::Ed25519(pk)
111111
} else {
112-
k.role0_pk()
112+
SimplePublicKeyType::Undefined
113113
}
114114
})
115115
.map_err(|e| anyhow::anyhow!("Catalyst Document Verification failed: {e}"))?;

rust/signed_doc/src/builder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Catalyst Signed Document Builder.
2-
use catalyst_types::kid_uri::KidUri;
2+
use catalyst_types::id_uri::IdUri;
33
use ed25519_dalek::{ed25519::signature::Signer, SecretKey};
44

55
use crate::{CatalystSignedDocument, Content, InnerCatalystSignedDocument, Metadata, Signatures};
@@ -50,7 +50,7 @@ impl Builder {
5050
/// Fails if a `CatalystSignedDocument` cannot be created due to missing metadata or
5151
/// content, due to malformed data, or when the signed document cannot be
5252
/// converted into `coset::CoseSign`.
53-
pub fn add_signature(self, sk: SecretKey, kid: KidUri) -> anyhow::Result<Self> {
53+
pub fn add_signature(self, sk: SecretKey, kid: IdUri) -> anyhow::Result<Self> {
5454
let cose_sign = self
5555
.clone()
5656
.build()

rust/signed_doc/src/lib.rs

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ use catalyst_types::problem_report::ProblemReport;
2020
pub use catalyst_types::uuid::{UuidV4, UuidV7};
2121
pub use content::Content;
2222
use coset::{CborSerializable, Header};
23-
use ed25519_dalek::VerifyingKey;
2423
use error::CatalystSignedDocError;
2524
pub use metadata::{DocumentRef, ExtraFields, Metadata};
2625
pub use minicbor::{decode, encode, Decode, Decoder, Encode};
27-
pub use signature::{KidUri, Signatures};
26+
pub use rbac_registration::cardano::cip509::SimplePublicKeyType;
27+
pub use signature::{IdUri, Signatures};
2828
use utils::context::DecodeSignDocCtx;
2929

3030
/// Inner type that holds the Catalyst Signed Document with parsing errors.
@@ -110,41 +110,74 @@ impl CatalystSignedDocument {
110110
&self.inner.signatures
111111
}
112112

113+
/// Return a list of Document's Catalyst IDs.
114+
#[must_use]
115+
pub fn kids(&self) -> Vec<IdUri> {
116+
self.inner.signatures.kids()
117+
}
118+
119+
/// Return a list of Document's author IDs (short form of Catalyst IDs).
120+
#[must_use]
121+
pub fn authors(&self) -> Vec<IdUri> {
122+
self.inner
123+
.signatures
124+
.kids()
125+
.into_iter()
126+
.map(|k| k.as_short_id())
127+
.collect()
128+
}
129+
113130
/// Verify document signatures.
114131
///
115132
/// # Errors
116133
///
117134
/// Returns a report of verification failures and the source error.
118135
#[allow(clippy::indexing_slicing)]
119136
pub fn verify<P>(&self, pk_getter: P) -> Result<(), CatalystSignedDocError>
120-
where P: Fn(&KidUri) -> VerifyingKey {
137+
where P: Fn(&IdUri) -> SimplePublicKeyType {
121138
let error_report = ProblemReport::new("Catalyst Signed Document Verification");
122139

123140
match self.as_cose_sign() {
124141
Ok(cose_sign) => {
125142
let signatures = self.signatures().cose_signatures();
126-
for (idx, kid) in self.signatures().kids().iter().enumerate() {
127-
let pk = pk_getter(kid);
128-
let signature = &signatures[idx];
129-
let tbs_data = cose_sign.tbs_data(&[], signature);
130-
match signature.signature.as_slice().try_into() {
131-
Ok(signature_bytes) => {
132-
let signature = ed25519_dalek::Signature::from_bytes(signature_bytes);
133-
if let Err(e) = pk.verify_strict(&tbs_data, &signature) {
134-
error_report.functional_validation(
135-
&format!(
136-
"Verification failed for signature with Key ID {kid}: {e}"
137-
),
138-
"During signature validation with verifying key",
139-
);
143+
for (idx, kid) in self.kids().iter().enumerate() {
144+
match pk_getter(kid) {
145+
SimplePublicKeyType::Ed25519(pk) => {
146+
let signature = &signatures[idx];
147+
let tbs_data = cose_sign.tbs_data(&[], signature);
148+
match signature.signature.as_slice().try_into() {
149+
Ok(signature_bytes) => {
150+
let signature =
151+
ed25519_dalek::Signature::from_bytes(signature_bytes);
152+
if let Err(e) = pk.verify_strict(&tbs_data, &signature) {
153+
error_report.functional_validation(
154+
&format!(
155+
"Verification failed for signature with Key ID {kid}: {e}"
156+
),
157+
"During signature validation with verifying key",
158+
);
159+
}
160+
},
161+
Err(_) => {
162+
error_report.invalid_value(
163+
"cose signature",
164+
&format!("{}", signature.signature.len()),
165+
&format!("must be {}", ed25519_dalek::Signature::BYTE_SIZE),
166+
"During encoding cose signature to bytes",
167+
);
168+
},
140169
}
141170
},
142-
Err(_) => {
143-
error_report.invalid_value(
144-
"cose signature",
145-
&format!("{}", signature.signature.len()),
146-
&format!("must be {}", ed25519_dalek::Signature::BYTE_SIZE),
147-
"During encoding cose signature to bytes",
171+
SimplePublicKeyType::Deleted => {
172+
error_report.other(
173+
&format!("Public key for {kid} has been deleted."),
174+
"During public key extraction",
175+
);
176+
},
177+
SimplePublicKeyType::Undefined => {
178+
error_report.other(
179+
&format!("Public key for {kid} is undefined."),
180+
"During public key extraction",
148181
);
149182
},
150183
}
@@ -389,11 +422,11 @@ mod tests {
389422
let pk = sk.verifying_key();
390423

391424
let kid_str = format!(
392-
"kid.catalyst-rbac://cardano/{}/0/0",
425+
"id.catalyst://cardano/{}/0/0",
393426
base64_url::encode(pk.as_bytes())
394427
);
395428

396-
let kid = KidUri::from_str(&kid_str).unwrap();
429+
let kid = IdUri::from_str(&kid_str).unwrap();
397430
let (_, _, metadata) = test_metadata().unwrap();
398431
let signed_doc = Builder::new()
399432
.with_decoded_content(content)
@@ -404,13 +437,15 @@ mod tests {
404437
.unwrap();
405438

406439
assert!(signed_doc
407-
.verify(|k| {
408-
if k.to_string() == kid.to_string() {
409-
pk
410-
} else {
411-
k.role0_pk()
412-
}
413-
})
440+
.verify(|_| { SimplePublicKeyType::Ed25519(pk) })
414441
.is_ok());
442+
443+
assert!(signed_doc
444+
.verify(|_| { SimplePublicKeyType::Undefined })
445+
.is_err());
446+
447+
assert!(signed_doc
448+
.verify(|_| { SimplePublicKeyType::Deleted })
449+
.is_err());
415450
}
416451
}

rust/signed_doc/src/signature/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
//! Catalyst Signed Document COSE Signature information.
22
33
use anyhow::bail;
4-
pub use catalyst_types::kid_uri::KidUri;
4+
pub use catalyst_types::id_uri::IdUri;
55
use catalyst_types::problem_report::ProblemReport;
66
use coset::CoseSignature;
77

88
/// Catalyst Signed Document COSE Signature.
99
#[derive(Debug, Clone)]
1010
pub struct Signature {
1111
/// Key ID
12-
kid: KidUri,
12+
kid: IdUri,
1313
/// COSE Signature
1414
signature: CoseSignature,
1515
}
@@ -27,7 +27,7 @@ impl Signatures {
2727

2828
/// List of signature Key IDs.
2929
#[must_use]
30-
pub fn kids(&self) -> Vec<KidUri> {
30+
pub fn kids(&self) -> Vec<IdUri> {
3131
self.0.iter().map(|sig| sig.kid.clone()).collect()
3232
}
3333

@@ -38,7 +38,7 @@ impl Signatures {
3838
}
3939

4040
/// Add a new signature
41-
pub fn push(&mut self, kid: KidUri, signature: CoseSignature) {
41+
pub fn push(&mut self, kid: IdUri, signature: CoseSignature) {
4242
self.0.push(Signature { kid, signature });
4343
}
4444

@@ -65,14 +65,14 @@ impl Signatures {
6565
.cloned()
6666
.enumerate()
6767
.for_each(|(idx, signature)| {
68-
match KidUri::try_from(signature.protected.header.key_id.as_ref()) {
68+
match IdUri::try_from(signature.protected.header.key_id.as_ref()) {
6969
Ok(kid) => signatures.push(Signature { kid, signature }),
7070
Err(e) => {
7171
error_report.conversion_error(
7272
&format!("COSE signature protected header key ID at id {idx}"),
7373
&format!("{:?}", &signature.protected.header.key_id),
7474
&format!("{e:?}"),
75-
"Converting COSE signature header key ID to KidUri",
75+
"Converting COSE signature header key ID to IdUri",
7676
);
7777
},
7878
}

0 commit comments

Comments
 (0)