diff --git a/rust/signed_doc/Cargo.toml b/rust/signed_doc/Cargo.toml index 8bbfb181e7..6bd2c874d4 100644 --- a/rust/signed_doc/Cargo.toml +++ b/rust/signed_doc/Cargo.toml @@ -11,7 +11,8 @@ license.workspace = true workspace = true [dependencies] -catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250124-00" } +rbac-registration = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250128-01" } +catalyst-types = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250128-01" } anyhow = "1.0.95" serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.134" diff --git a/rust/signed_doc/README.md b/rust/signed_doc/README.md index 93ff0f2f17..c31472e74b 100644 --- a/rust/signed_doc/README.md +++ b/rust/signed_doc/README.md @@ -7,20 +7,29 @@ Catalyst signed document crate implementation based on this ## Example -Generate a `ed25519` private and public keys +### Generate a `ed25519` private and public keys ```shell openssl genpkey -algorithm=ED25519 -out=private.pem -outpubkey=public.pem ``` -Prepare non-signed document, +### Prepare non-signed document + `meta.json` file should follow the [`meta.schema.json`](./meta.schema.json). ```shell cargo run -p catalyst-signed-doc --example mk_signed_doc build signed_doc/doc.json signed_doc/doc.cose signed_doc/meta.json ``` -Inspect document +### Sign document + +`KID` is a valid Catalyst ID URI. + +```shell +cargo run -p catalyst-signed-doc --example mk_signed_doc sign signed_doc/doc.cose signed_doc/meta.json +``` + +### Inspect document ```shell cargo run -p catalyst-signed-doc --example mk_signed_doc inspect signed_doc/doc.cose diff --git a/rust/signed_doc/examples/mk_signed_doc.rs b/rust/signed_doc/examples/mk_signed_doc.rs index 25215c2a70..8a96bd53f2 100644 --- a/rust/signed_doc/examples/mk_signed_doc.rs +++ b/rust/signed_doc/examples/mk_signed_doc.rs @@ -8,7 +8,7 @@ use std::{ path::PathBuf, }; -use catalyst_signed_doc::{Builder, CatalystSignedDocument, KidUri, Metadata}; +use catalyst_signed_doc::{Builder, CatalystSignedDocument, IdUri, Metadata, SimplePublicKeyType}; use clap::Parser; use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey}; @@ -39,7 +39,7 @@ enum Cli { /// Path to the secret key in PEM format sk: PathBuf, /// Signer kid - kid: KidUri, + kid: IdUri, }, /// Inspects Catalyst Signed Document Inspect { @@ -59,7 +59,7 @@ enum Cli { /// Path to the verifying key in PEM format pk: PathBuf, /// Signer kid - kid: KidUri, + kid: IdUri, }, } @@ -107,9 +107,9 @@ impl Cli { signed_doc .verify(|k| { if k.to_string() == kid.to_string() { - pk + SimplePublicKeyType::Ed25519(pk) } else { - k.role0_pk() + SimplePublicKeyType::Undefined } }) .map_err(|e| anyhow::anyhow!("Catalyst Document Verification failed: {e}"))?; diff --git a/rust/signed_doc/src/builder.rs b/rust/signed_doc/src/builder.rs index e16076d98c..1640c8a77f 100644 --- a/rust/signed_doc/src/builder.rs +++ b/rust/signed_doc/src/builder.rs @@ -1,5 +1,5 @@ //! Catalyst Signed Document Builder. -use catalyst_types::kid_uri::KidUri; +use catalyst_types::id_uri::IdUri; use ed25519_dalek::{ed25519::signature::Signer, SecretKey}; use crate::{CatalystSignedDocument, Content, InnerCatalystSignedDocument, Metadata, Signatures}; @@ -50,7 +50,7 @@ impl Builder { /// Fails if a `CatalystSignedDocument` cannot be created due to missing metadata or /// content, due to malformed data, or when the signed document cannot be /// converted into `coset::CoseSign`. - pub fn add_signature(self, sk: SecretKey, kid: KidUri) -> anyhow::Result { + pub fn add_signature(self, sk: SecretKey, kid: IdUri) -> anyhow::Result { let cose_sign = self .clone() .build() diff --git a/rust/signed_doc/src/lib.rs b/rust/signed_doc/src/lib.rs index 7e236937d8..2f9ab9e3fb 100644 --- a/rust/signed_doc/src/lib.rs +++ b/rust/signed_doc/src/lib.rs @@ -20,11 +20,11 @@ use catalyst_types::problem_report::ProblemReport; pub use catalyst_types::uuid::{UuidV4, UuidV7}; pub use content::Content; use coset::{CborSerializable, Header}; -use ed25519_dalek::VerifyingKey; use error::CatalystSignedDocError; pub use metadata::{DocumentRef, ExtraFields, Metadata}; pub use minicbor::{decode, encode, Decode, Decoder, Encode}; -pub use signature::{KidUri, Signatures}; +pub use rbac_registration::cardano::cip509::SimplePublicKeyType; +pub use signature::{IdUri, Signatures}; use utils::context::DecodeSignDocCtx; /// Inner type that holds the Catalyst Signed Document with parsing errors. @@ -110,6 +110,23 @@ impl CatalystSignedDocument { &self.inner.signatures } + /// Return a list of Document's Catalyst IDs. + #[must_use] + pub fn kids(&self) -> Vec { + self.inner.signatures.kids() + } + + /// Return a list of Document's author IDs (short form of Catalyst IDs). + #[must_use] + pub fn authors(&self) -> Vec { + self.inner + .signatures + .kids() + .into_iter() + .map(|k| k.as_short_id()) + .collect() + } + /// Verify document signatures. /// /// # Errors @@ -117,34 +134,50 @@ impl CatalystSignedDocument { /// Returns a report of verification failures and the source error. #[allow(clippy::indexing_slicing)] pub fn verify

(&self, pk_getter: P) -> Result<(), CatalystSignedDocError> - where P: Fn(&KidUri) -> VerifyingKey { + where P: Fn(&IdUri) -> SimplePublicKeyType { let error_report = ProblemReport::new("Catalyst Signed Document Verification"); match self.as_cose_sign() { Ok(cose_sign) => { let signatures = self.signatures().cose_signatures(); - for (idx, kid) in self.signatures().kids().iter().enumerate() { - let pk = pk_getter(kid); - let signature = &signatures[idx]; - let tbs_data = cose_sign.tbs_data(&[], signature); - match signature.signature.as_slice().try_into() { - Ok(signature_bytes) => { - let signature = ed25519_dalek::Signature::from_bytes(signature_bytes); - if let Err(e) = pk.verify_strict(&tbs_data, &signature) { - error_report.functional_validation( - &format!( - "Verification failed for signature with Key ID {kid}: {e}" - ), - "During signature validation with verifying key", - ); + for (idx, kid) in self.kids().iter().enumerate() { + match pk_getter(kid) { + SimplePublicKeyType::Ed25519(pk) => { + let signature = &signatures[idx]; + let tbs_data = cose_sign.tbs_data(&[], signature); + match signature.signature.as_slice().try_into() { + Ok(signature_bytes) => { + let signature = + ed25519_dalek::Signature::from_bytes(signature_bytes); + if let Err(e) = pk.verify_strict(&tbs_data, &signature) { + error_report.functional_validation( + &format!( + "Verification failed for signature with Key ID {kid}: {e}" + ), + "During signature validation with verifying key", + ); + } + }, + Err(_) => { + error_report.invalid_value( + "cose signature", + &format!("{}", signature.signature.len()), + &format!("must be {}", ed25519_dalek::Signature::BYTE_SIZE), + "During encoding cose signature to bytes", + ); + }, } }, - Err(_) => { - error_report.invalid_value( - "cose signature", - &format!("{}", signature.signature.len()), - &format!("must be {}", ed25519_dalek::Signature::BYTE_SIZE), - "During encoding cose signature to bytes", + SimplePublicKeyType::Deleted => { + error_report.other( + &format!("Public key for {kid} has been deleted."), + "During public key extraction", + ); + }, + SimplePublicKeyType::Undefined => { + error_report.other( + &format!("Public key for {kid} is undefined."), + "During public key extraction", ); }, } @@ -389,11 +422,11 @@ mod tests { let pk = sk.verifying_key(); let kid_str = format!( - "kid.catalyst-rbac://cardano/{}/0/0", + "id.catalyst://cardano/{}/0/0", base64_url::encode(pk.as_bytes()) ); - let kid = KidUri::from_str(&kid_str).unwrap(); + let kid = IdUri::from_str(&kid_str).unwrap(); let (_, _, metadata) = test_metadata().unwrap(); let signed_doc = Builder::new() .with_decoded_content(content) @@ -404,13 +437,15 @@ mod tests { .unwrap(); assert!(signed_doc - .verify(|k| { - if k.to_string() == kid.to_string() { - pk - } else { - k.role0_pk() - } - }) + .verify(|_| { SimplePublicKeyType::Ed25519(pk) }) .is_ok()); + + assert!(signed_doc + .verify(|_| { SimplePublicKeyType::Undefined }) + .is_err()); + + assert!(signed_doc + .verify(|_| { SimplePublicKeyType::Deleted }) + .is_err()); } } diff --git a/rust/signed_doc/src/signature/mod.rs b/rust/signed_doc/src/signature/mod.rs index 6645ca3d23..91738ca4af 100644 --- a/rust/signed_doc/src/signature/mod.rs +++ b/rust/signed_doc/src/signature/mod.rs @@ -1,7 +1,7 @@ //! Catalyst Signed Document COSE Signature information. use anyhow::bail; -pub use catalyst_types::kid_uri::KidUri; +pub use catalyst_types::id_uri::IdUri; use catalyst_types::problem_report::ProblemReport; use coset::CoseSignature; @@ -9,7 +9,7 @@ use coset::CoseSignature; #[derive(Debug, Clone)] pub struct Signature { /// Key ID - kid: KidUri, + kid: IdUri, /// COSE Signature signature: CoseSignature, } @@ -27,7 +27,7 @@ impl Signatures { /// List of signature Key IDs. #[must_use] - pub fn kids(&self) -> Vec { + pub fn kids(&self) -> Vec { self.0.iter().map(|sig| sig.kid.clone()).collect() } @@ -38,7 +38,7 @@ impl Signatures { } /// Add a new signature - pub fn push(&mut self, kid: KidUri, signature: CoseSignature) { + pub fn push(&mut self, kid: IdUri, signature: CoseSignature) { self.0.push(Signature { kid, signature }); } @@ -65,14 +65,14 @@ impl Signatures { .cloned() .enumerate() .for_each(|(idx, signature)| { - match KidUri::try_from(signature.protected.header.key_id.as_ref()) { + match IdUri::try_from(signature.protected.header.key_id.as_ref()) { Ok(kid) => signatures.push(Signature { kid, signature }), Err(e) => { error_report.conversion_error( &format!("COSE signature protected header key ID at id {idx}"), &format!("{:?}", &signature.protected.header.key_id), &format!("{e:?}"), - "Converting COSE signature header key ID to KidUri", + "Converting COSE signature header key ID to IdUri", ); }, }