Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4e5ff19
fix(rust/signed-doc): add content type validation
saibatizoku Jan 27, 2025
15741c6
fix(rust/signed-doc): content encoding field is optional
saibatizoku Jan 27, 2025
3d34e47
fix(rust/signed-doc): add 'half' feature from minicbor crate
saibatizoku Jan 27, 2025
3a66559
wip(rust/signed_doc): implement verification method
saibatizoku Jan 28, 2025
70440b5
fix(rust/signed-doc): fix content-type verification
saibatizoku Jan 28, 2025
bd4561d
wip(rust/signed_doc): implement verification method
saibatizoku Jan 28, 2025
2392ed5
wip(rust/signed_doc): implement verification method
saibatizoku Jan 28, 2025
dd60045
Merge branch 'main' into feat/doc-signing-logic
saibatizoku Jan 28, 2025
22c46f0
feat(rust/signed-doc): add signatures to Catalyst Signed Document
saibatizoku Jan 28, 2025
0f85057
fix(rust/signed-doc): refactor cli tool to sign
saibatizoku Jan 29, 2025
ee0657c
fix(rust/signed-doc): add verify command to cli tool
saibatizoku Jan 29, 2025
262b1ad
fix(rust/catalyst-types): refactor sigining logic into builder
saibatizoku Jan 29, 2025
a1c11ea
Merge branch 'main' into feat/doc-signing-logic
saibatizoku Jan 29, 2025
56b0509
chore(docs): fix spelling
saibatizoku Jan 29, 2025
70cae9f
fix(rust/signed-doc): cleanup
saibatizoku Jan 31, 2025
0329541
fix content validation, add unit test
Mr-Leshiy Feb 2, 2025
3d1b044
remove clippy
Mr-Leshiy Feb 2, 2025
5682dd5
fix
Mr-Leshiy Feb 2, 2025
a2af6f1
feat(rust/signed-doc): test verify signatures
saibatizoku Feb 2, 2025
ba6eb18
fix(rust/signed-doc): update catalyst-types tag
saibatizoku Feb 3, 2025
6bf6274
fix(rust/signed-doc): update KID to use catalyst-id type
saibatizoku Feb 3, 2025
ae94111
fix(rust/signed-doc): simpler test closure
saibatizoku Feb 3, 2025
0897cd6
feat(rust/signed-doc): add kids and authors methods for signed documents
saibatizoku Feb 3, 2025
2cc67e1
fix(rust/signed-doc): use rbac-registration type for public key and a…
saibatizoku Feb 3, 2025
ad99056
chore(docs): fix markdown
saibatizoku Feb 3, 2025
55edc1b
fix(rust/signed-doc): add more assertions to signature verification
saibatizoku Feb 3, 2025
9c54aa2
Merge remote-tracking branch 'origin/main' into feat/use-catalyst-id
saibatizoku Feb 3, 2025
90880b6
fix(rust/signed-doc): code cleanup
saibatizoku Feb 3, 2025
013c3bd
Merge branch 'main' into feat/use-catalyst-id
saibatizoku Feb 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion rust/signed_doc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
15 changes: 12 additions & 3 deletions rust/signed_doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <KID>
```

### Inspect document

```shell
cargo run -p catalyst-signed-doc --example mk_signed_doc inspect signed_doc/doc.cose
Expand Down
10 changes: 5 additions & 5 deletions rust/signed_doc/examples/mk_signed_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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 {
Expand All @@ -59,7 +59,7 @@ enum Cli {
/// Path to the verifying key in PEM format
pk: PathBuf,
/// Signer kid
kid: KidUri,
kid: IdUri,
},
}

Expand Down Expand Up @@ -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}"))?;
Expand Down
4 changes: 2 additions & 2 deletions rust/signed_doc/src/builder.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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<Self> {
pub fn add_signature(self, sk: SecretKey, kid: IdUri) -> anyhow::Result<Self> {
let cose_sign = self
.clone()
.build()
Expand Down
99 changes: 67 additions & 32 deletions rust/signed_doc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -110,41 +110,74 @@ impl CatalystSignedDocument {
&self.inner.signatures
}

/// Return a list of Document's Catalyst IDs.
#[must_use]
pub fn kids(&self) -> Vec<IdUri> {
self.inner.signatures.kids()
}

/// Return a list of Document's author IDs (short form of Catalyst IDs).
#[must_use]
pub fn authors(&self) -> Vec<IdUri> {
self.inner
.signatures
.kids()
.into_iter()
.map(|k| k.as_short_id())
.collect()
}

/// Verify document signatures.
///
/// # Errors
///
/// Returns a report of verification failures and the source error.
#[allow(clippy::indexing_slicing)]
pub fn verify<P>(&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",
);
},
}
Expand Down Expand Up @@ -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)
Expand All @@ -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());
}
}
12 changes: 6 additions & 6 deletions rust/signed_doc/src/signature/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//! 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;

/// Catalyst Signed Document COSE Signature.
#[derive(Debug, Clone)]
pub struct Signature {
/// Key ID
kid: KidUri,
kid: IdUri,
/// COSE Signature
signature: CoseSignature,
}
Expand All @@ -27,7 +27,7 @@ impl Signatures {

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

Expand All @@ -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 });
}

Expand All @@ -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",
);
},
}
Expand Down