Skip to content

Commit 8998fc6

Browse files
authored
Merge branch 'main' into fix/extra-fields-build
2 parents cc91834 + 4f6462a commit 8998fc6

File tree

8 files changed

+295
-83
lines changed

8 files changed

+295
-83
lines changed

rust/signed_doc/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ anyhow = "1.0.95"
1616
serde = { version = "1.0.217", features = ["derive"] }
1717
serde_json = "1.0.134"
1818
coset = "0.3.8"
19-
minicbor = "0.25.1"
19+
minicbor = { version = "0.25.1", features = ["half"] }
2020
brotli = "7.0.0"
2121
ed25519-dalek = { version = "2.1.1", features = ["pem", "rand_core"] }
2222
uuid = { version = "1.11.0", features = ["v4", "v7", "serde"] }
@@ -26,9 +26,10 @@ clap = { version = "4.5.23", features = ["derive", "env"] }
2626

2727

2828
[dev-dependencies]
29+
base64-url = "3.0.0"
2930
rand = "0.8.5"
3031

3132

3233
[[bin]]
3334
name = "signed-docs"
34-
path = "examples/mk_signed_doc.rs"
35+
path = "examples/mk_signed_doc.rs"

rust/signed_doc/examples/mk_signed_doc.rs

Lines changed: 66 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ use std::{
1010

1111
use catalyst_signed_doc::{Builder, CatalystSignedDocument, KidUri, Metadata};
1212
use clap::Parser;
13-
use coset::CborSerializable;
14-
use ed25519_dalek::{ed25519::signature::Signer, pkcs8::DecodePrivateKey};
13+
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey};
1514

1615
fn main() {
1716
if let Err(err) = Cli::parse().exec() {
@@ -52,6 +51,16 @@ enum Cli {
5251
/// Hex-formatted COSE SIGN Bytes
5352
cose_sign_hex: String,
5453
},
54+
/// Validates a signature by Key ID and verifying key
55+
Verify {
56+
/// Path to the formed (could be empty, without any signatures) COSE document
57+
/// This exact file would be modified and new signature would be added
58+
path: PathBuf,
59+
/// Path to the verifying key in PEM format
60+
pk: PathBuf,
61+
/// Signer kid
62+
kid: KidUri,
63+
},
5564
}
5665

5766
impl Cli {
@@ -68,53 +77,80 @@ impl Cli {
6877
let payload = serde_json::to_vec(&json_doc)?;
6978
// Start with no signatures.
7079
let signed_doc = Builder::new()
71-
.with_content(payload)
80+
.with_decoded_content(payload)
7281
.with_metadata(metadata)
7382
.build()?;
74-
let mut bytes: Vec<u8> = Vec::new();
75-
minicbor::encode(signed_doc, &mut bytes)
76-
.map_err(|e| anyhow::anyhow!("Failed to encode document: {e}"))?;
77-
78-
write_bytes_to_file(&bytes, &output)?;
83+
save_signed_doc(signed_doc, &output)?;
7984
},
8085
Self::Sign { sk, doc, kid } => {
8186
let sk = load_secret_key_from_file(&sk)
8287
.map_err(|e| anyhow::anyhow!("Failed to load SK FILE: {e}"))?;
83-
let mut cose = load_cose_from_file(&doc)
84-
.map_err(|e| anyhow::anyhow!("Failed to load COSE FROM FILE: {e}"))?;
85-
add_signature_to_cose(&mut cose, &sk, kid.to_string());
86-
store_cose_file(cose, &doc)?;
88+
let cose_bytes = read_bytes_from_file(&doc)?;
89+
let signed_doc = signed_doc_from_bytes(cose_bytes.as_slice())?;
90+
let builder = signed_doc.into_builder();
91+
let new_signed_doc = builder.add_signature(sk.to_bytes(), kid)?.build()?;
92+
save_signed_doc(new_signed_doc, &doc)?;
8793
},
8894
Self::Inspect { path } => {
89-
let mut cose_file = File::open(path)?;
90-
let mut cose_bytes = Vec::new();
91-
cose_file.read_to_end(&mut cose_bytes)?;
92-
decode_signed_doc(&cose_bytes);
95+
let cose_bytes = read_bytes_from_file(&path)?;
96+
inspect_signed_doc(&cose_bytes)?;
9397
},
9498
Self::InspectBytes { cose_sign_hex } => {
9599
let cose_bytes = hex::decode(&cose_sign_hex)?;
96-
decode_signed_doc(&cose_bytes);
100+
inspect_signed_doc(&cose_bytes)?;
101+
},
102+
Self::Verify { path, pk, kid } => {
103+
let pk = load_public_key_from_file(&pk)
104+
.map_err(|e| anyhow::anyhow!("Failed to load PK FILE {pk:?}: {e}"))?;
105+
let cose_bytes = read_bytes_from_file(&path)?;
106+
let signed_doc = signed_doc_from_bytes(cose_bytes.as_slice())?;
107+
signed_doc
108+
.verify(|k| {
109+
if k.to_string() == kid.to_string() {
110+
pk
111+
} else {
112+
k.role0_pk()
113+
}
114+
})
115+
.map_err(|e| anyhow::anyhow!("Catalyst Document Verification failed: {e}"))?;
116+
println!("Catalyst Signed Document is Verified.");
97117
},
98118
}
99119
println!("Done");
100120
Ok(())
101121
}
102122
}
103123

104-
fn decode_signed_doc(cose_bytes: &[u8]) {
124+
fn read_bytes_from_file(path: &PathBuf) -> anyhow::Result<Vec<u8>> {
125+
let mut cose_file = File::open(path)?;
126+
let mut cose_bytes = Vec::new();
127+
cose_file.read_to_end(&mut cose_bytes)?;
128+
Ok(cose_bytes)
129+
}
130+
131+
fn inspect_signed_doc(cose_bytes: &[u8]) -> anyhow::Result<()> {
105132
println!(
106-
"Decoding {} bytes: {}",
133+
"Decoding {} bytes:\n{}",
107134
cose_bytes.len(),
108135
hex::encode(cose_bytes)
109136
);
137+
let cat_signed_doc = signed_doc_from_bytes(cose_bytes)?;
138+
println!("This is a valid Catalyst Document.");
139+
println!("{cat_signed_doc}");
140+
Ok(())
141+
}
110142

111-
match CatalystSignedDocument::try_from(cose_bytes) {
112-
Ok(cat_signed_doc) => {
113-
println!("This is a valid Catalyst Document.");
114-
println!("{cat_signed_doc}");
115-
},
116-
Err(e) => eprintln!("Invalid Catalyst Document, err: {e}"),
117-
}
143+
fn save_signed_doc(signed_doc: CatalystSignedDocument, path: &PathBuf) -> anyhow::Result<()> {
144+
let mut bytes: Vec<u8> = Vec::new();
145+
minicbor::encode(signed_doc, &mut bytes)
146+
.map_err(|e| anyhow::anyhow!("Failed to encode document: {e}"))?;
147+
148+
write_bytes_to_file(&bytes, path)
149+
}
150+
151+
fn signed_doc_from_bytes(cose_bytes: &[u8]) -> anyhow::Result<CatalystSignedDocument> {
152+
CatalystSignedDocument::try_from(cose_bytes)
153+
.map_err(|e| anyhow::anyhow!("Invalid Catalyst Document: {e}"))
118154
}
119155

120156
fn load_json_from_file<T>(path: &PathBuf) -> anyhow::Result<T>
@@ -124,45 +160,20 @@ where T: for<'de> serde::Deserialize<'de> {
124160
Ok(json)
125161
}
126162

127-
fn load_cose_from_file(cose_path: &PathBuf) -> anyhow::Result<coset::CoseSign> {
128-
let cose_file_bytes = read_bytes_from_file(cose_path)?;
129-
let cose = coset::CoseSign::from_slice(&cose_file_bytes).map_err(|e| anyhow::anyhow!("{e}"))?;
130-
Ok(cose)
131-
}
132-
133-
fn read_bytes_from_file(path: &PathBuf) -> anyhow::Result<Vec<u8>> {
134-
let mut file_bytes = Vec::new();
135-
File::open(path)?.read_to_end(&mut file_bytes)?;
136-
Ok(file_bytes)
137-
}
138-
139163
fn write_bytes_to_file(bytes: &[u8], output: &PathBuf) -> anyhow::Result<()> {
140164
File::create(output)?
141165
.write_all(bytes)
142166
.map_err(|e| anyhow::anyhow!("Failed to write to file {output:?}: {e}"))
143167
}
144168

145-
fn store_cose_file(cose: coset::CoseSign, output: &PathBuf) -> anyhow::Result<()> {
146-
let cose_bytes = cose
147-
.to_vec()
148-
.map_err(|e| anyhow::anyhow!("Failed to Store COSE SIGN: {e}"))?;
149-
write_bytes_to_file(&cose_bytes, output)
150-
}
151-
152169
fn load_secret_key_from_file(sk_path: &PathBuf) -> anyhow::Result<ed25519_dalek::SigningKey> {
153170
let sk_str = read_to_string(sk_path)?;
154171
let sk = ed25519_dalek::SigningKey::from_pkcs8_pem(&sk_str)?;
155172
Ok(sk)
156173
}
157174

158-
fn add_signature_to_cose(cose: &mut coset::CoseSign, sk: &ed25519_dalek::SigningKey, kid: String) {
159-
let protected_header = coset::HeaderBuilder::new()
160-
.key_id(kid.into_bytes())
161-
.algorithm(coset::iana::Algorithm::EdDSA);
162-
let mut signature = coset::CoseSignatureBuilder::new()
163-
.protected(protected_header.build())
164-
.build();
165-
let data_to_sign = cose.tbs_data(&[], &signature);
166-
signature.signature = sk.sign(&data_to_sign).to_vec();
167-
cose.signatures.push(signature);
175+
fn load_public_key_from_file(pk_path: &PathBuf) -> anyhow::Result<ed25519_dalek::VerifyingKey> {
176+
let pk_str = read_to_string(pk_path)?;
177+
let pk = ed25519_dalek::VerifyingKey::from_public_key_pem(&pk_str)?;
178+
Ok(pk)
168179
}

rust/signed_doc/src/builder.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
//! Catalyst Signed Document Builder.
2+
use catalyst_types::kid_uri::KidUri;
3+
use ed25519_dalek::{ed25519::signature::Signer, SecretKey};
4+
25
use crate::{CatalystSignedDocument, Content, InnerCatalystSignedDocument, Metadata, Signatures};
36

47
/// Catalyst Signed Document Builder.
5-
#[derive(Debug, Default)]
8+
#[derive(Debug, Default, Clone)]
69
pub struct Builder {
710
/// Document Metadata
811
metadata: Option<Metadata>,
@@ -26,13 +29,58 @@ impl Builder {
2629
self
2730
}
2831

29-
/// Set document content
32+
/// Set decoded (original) document content bytes
3033
#[must_use]
31-
pub fn with_content(mut self, content: Vec<u8>) -> Self {
34+
pub fn with_decoded_content(mut self, content: Vec<u8>) -> Self {
3235
self.content = Some(content);
3336
self
3437
}
3538

39+
/// Set document signatures
40+
#[must_use]
41+
pub fn with_signatures(mut self, signatures: Signatures) -> Self {
42+
self.signatures = signatures;
43+
self
44+
}
45+
46+
/// Add a signature to the document
47+
///
48+
/// # Errors
49+
///
50+
/// Fails if a `CatalystSignedDocument` cannot be created due to missing metadata or
51+
/// content, due to malformed data, or when the signed document cannot be
52+
/// converted into `coset::CoseSign`.
53+
pub fn add_signature(self, sk: SecretKey, kid: KidUri) -> anyhow::Result<Self> {
54+
let cose_sign = self
55+
.clone()
56+
.build()
57+
.map_err(|e| anyhow::anyhow!("Failed to sign: {e}"))?
58+
.as_cose_sign()
59+
.map_err(|e| anyhow::anyhow!("Failed to sign: {e}"))?;
60+
let Self {
61+
metadata: Some(metadata),
62+
content: Some(content),
63+
mut signatures,
64+
} = self
65+
else {
66+
anyhow::bail!("Metadata and Content are needed for signing");
67+
};
68+
let sk = ed25519_dalek::SigningKey::from_bytes(&sk);
69+
let protected_header = coset::HeaderBuilder::new()
70+
.key_id(kid.to_string().into_bytes())
71+
.algorithm(metadata.algorithm().into());
72+
let mut signature = coset::CoseSignatureBuilder::new()
73+
.protected(protected_header.build())
74+
.build();
75+
let data_to_sign = cose_sign.tbs_data(&[], &signature);
76+
signature.signature = sk.sign(&data_to_sign).to_vec();
77+
signatures.push(kid, signature);
78+
Ok(Self::new()
79+
.with_decoded_content(content)
80+
.with_metadata(metadata)
81+
.with_signatures(signatures))
82+
}
83+
3684
/// Build a signed document
3785
///
3886
/// ## Errors

rust/signed_doc/src/content.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ impl Content {
2828
.decode(&data)
2929
.map_err(|e| anyhow::anyhow!("Failed to decode {encoding} content: {e}"))?;
3030
}
31+
content_type.validate(&data)?;
3132

3233
Ok(Self {
3334
data,
@@ -41,11 +42,10 @@ impl Content {
4142
///
4243
/// # Errors
4344
/// Returns an error if content is not correctly encoded
44-
#[allow(clippy::unnecessary_wraps)]
4545
pub(crate) fn from_decoded(
4646
data: Vec<u8>, content_type: ContentType, content_encoding: Option<ContentEncoding>,
4747
) -> anyhow::Result<Self> {
48-
// TODO add content_type verification
48+
content_type.validate(&data)?;
4949
Ok(Self {
5050
data,
5151
content_type,
@@ -87,13 +87,7 @@ impl Content {
8787

8888
/// Return content byte size
8989
#[must_use]
90-
pub fn len(&self) -> usize {
90+
pub fn size(&self) -> usize {
9191
self.data.len()
9292
}
93-
94-
/// Return `true` if content is empty
95-
#[must_use]
96-
pub fn is_empty(&self) -> bool {
97-
self.data.is_empty()
98-
}
9993
}

0 commit comments

Comments
 (0)