Skip to content

Commit c0d22c6

Browse files
Mr-Leshiyno30bit
andauthored
feat(rust/signed-doc): Catalyst signed document encoding using minicbor (#353)
* wip * wip * wip * wip * wip * fix * fix clippy * wip * cleanup * wip * wip * wip * wip * wip * wip * wip * update test * fix spelling * wip * wip * Update rust/signed_doc/src/lib.rs Co-authored-by: Artur Helmanau <[email protected]> * Revert "wip" This reverts commit 5b209bb. * fix clippy --------- Co-authored-by: Artur Helmanau <[email protected]>
1 parent fe82adb commit c0d22c6

File tree

18 files changed

+344
-450
lines changed

18 files changed

+344
-450
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,15 @@ impl TryFrom<&[u8]> for CatalystId {
676676
}
677677
}
678678

679+
impl minicbor::Encode<()> for CatalystId {
680+
fn encode<W: minicbor::encode::Write>(
681+
&self, e: &mut minicbor::Encoder<W>, _ctx: &mut (),
682+
) -> Result<(), minicbor::encode::Error<W::Error>> {
683+
e.bytes(self.to_string().into_bytes().as_slice())?;
684+
Ok(())
685+
}
686+
}
687+
679688
#[cfg(test)]
680689
mod tests {
681690
use chrono::{DateTime, Utc};

rust/signed_doc/bins/mk_signed_doc.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ 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(
87+
|message| sk.sign::<()>(&message).to_bytes().to_vec(),
88+
kid.clone(),
89+
)?
8790
.build();
8891
save_signed_doc(new_signed_doc, &doc)?;
8992
},

rust/signed_doc/src/builder.rs

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
use catalyst_types::{catalyst_id::CatalystId, problem_report::ProblemReport};
33

44
use crate::{
5-
signature::Signature, CatalystSignedDocument, Content, InnerCatalystSignedDocument, Metadata,
6-
Signatures, PROBLEM_REPORT_CTX,
5+
signature::{tbs_data, Signature},
6+
CatalystSignedDocument, Content, InnerCatalystSignedDocument, Metadata, Signatures,
7+
PROBLEM_REPORT_CTX,
78
};
89

910
/// Catalyst Signed Document Builder.
@@ -56,23 +57,14 @@ impl Builder {
5657
/// content, due to malformed data, or when the signed document cannot be
5758
/// converted into `coset::CoseSign`.
5859
pub fn add_signature(
59-
mut self, sign_fn: impl FnOnce(Vec<u8>) -> Vec<u8>, kid: &CatalystId,
60+
mut self, sign_fn: impl FnOnce(Vec<u8>) -> Vec<u8>, kid: CatalystId,
6061
) -> anyhow::Result<Self> {
61-
let cose_sign = self
62-
.0
63-
.as_cose_sign()
64-
.map_err(|e| anyhow::anyhow!("Failed to sign: {e}"))?;
65-
66-
let protected_header = coset::HeaderBuilder::new().key_id(kid.to_string().into_bytes());
67-
68-
let mut signature = coset::CoseSignatureBuilder::new()
69-
.protected(protected_header.build())
70-
.build();
71-
let data_to_sign = cose_sign.tbs_data(&[], &signature);
72-
signature.signature = sign_fn(data_to_sign);
73-
if let Some(sign) = Signature::from_cose_sig(signature, &self.0.report) {
74-
self.0.signatures.push(sign);
62+
if kid.is_id() {
63+
anyhow::bail!("Provided kid should be in a uri format, kid: {kid}");
7564
}
65+
let data_to_sign = tbs_data(&kid, &self.0.metadata, &self.0.content)?;
66+
let sign_bytes = sign_fn(data_to_sign);
67+
self.0.signatures.push(Signature::new(kid, sign_bytes));
7668

7769
Ok(self)
7870
}

rust/signed_doc/src/lib.rs

Lines changed: 42 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ use std::{
1515
sync::Arc,
1616
};
1717

18-
use anyhow::Context;
1918
pub use builder::Builder;
2019
pub use catalyst_types::{
2120
problem_report::ProblemReport,
2221
uuid::{Uuid, UuidV4, UuidV7},
2322
};
2423
pub use content::Content;
25-
use coset::{CborSerializable, Header, TaggedCborSerializable};
24+
use coset::{CborSerializable, TaggedCborSerializable};
2625
use decode_context::{CompatibilityPolicy, DecodeContext};
2726
pub use metadata::{ContentEncoding, ContentType, DocType, DocumentRef, Metadata, Section};
2827
use minicbor::{decode, encode, Decode, Decoder, Encode};
@@ -68,7 +67,7 @@ impl Display for CatalystSignedDocument {
6867
if self.inner.signatures.is_empty() {
6968
writeln!(f, " This document is unsigned.")?;
7069
} else {
71-
for kid in &self.inner.signatures.kids() {
70+
for kid in &self.kids() {
7271
writeln!(f, " Signature Key ID: {kid}")?;
7372
}
7473
}
@@ -147,13 +146,21 @@ impl CatalystSignedDocument {
147146
/// Return a list of Document's Catalyst IDs.
148147
#[must_use]
149148
pub fn kids(&self) -> Vec<CatalystId> {
150-
self.inner.signatures.kids()
149+
self.inner
150+
.signatures
151+
.iter()
152+
.map(|s| s.kid().clone())
153+
.collect()
151154
}
152155

153156
/// Return a list of Document's author IDs (short form of Catalyst IDs).
154157
#[must_use]
155158
pub fn authors(&self) -> Vec<CatalystId> {
156-
self.inner.signatures.authors()
159+
self.inner
160+
.signatures
161+
.iter()
162+
.map(|s| s.kid().as_short_id())
163+
.collect()
157164
}
158165

159166
/// Returns a collected problem report for the document.
@@ -173,14 +180,6 @@ impl CatalystSignedDocument {
173180
&self.inner.report
174181
}
175182

176-
/// Convert Catalyst Signed Document into `coset::CoseSign`
177-
///
178-
/// # Errors
179-
/// Could fails if the `CatalystSignedDocument` object is not valid.
180-
pub(crate) fn as_cose_sign(&self) -> anyhow::Result<coset::CoseSign> {
181-
self.inner.as_cose_sign()
182-
}
183-
184183
/// Returns a signed document `Builder` pre-loaded with the current signed document's
185184
/// data.
186185
#[must_use]
@@ -189,39 +188,6 @@ impl CatalystSignedDocument {
189188
}
190189
}
191190

192-
impl InnerCatalystSignedDocument {
193-
/// Convert Catalyst Signed Document into `coset::CoseSign`
194-
///
195-
/// # Errors
196-
/// Could fails if the `CatalystSignedDocument` object is not valid.
197-
fn as_cose_sign(&self) -> anyhow::Result<coset::CoseSign> {
198-
if let Some(raw_bytes) = self.raw_bytes.clone() {
199-
let cose_sign = coset::CoseSign::from_tagged_slice(raw_bytes.as_slice())
200-
.or_else(|_| coset::CoseSign::from_slice(raw_bytes.as_slice()))
201-
.map_err(|e| {
202-
minicbor::decode::Error::message(format!("Invalid COSE Sign document: {e}"))
203-
})?;
204-
Ok(cose_sign)
205-
} else {
206-
let protected_header =
207-
Header::try_from(&self.metadata).context("Failed to encode Document Metadata")?;
208-
209-
let content = self
210-
.content
211-
.encoded_bytes(self.metadata.content_encoding())?;
212-
213-
let mut builder = coset::CoseSignBuilder::new()
214-
.protected(protected_header)
215-
.payload(content);
216-
217-
for signature in self.signatures.cose_signatures() {
218-
builder = builder.add_signature(signature);
219-
}
220-
Ok(builder.build())
221-
}
222-
}
223-
}
224-
225191
impl Decode<'_, ()> for CatalystSignedDocument {
226192
fn decode(d: &mut Decoder<'_>, _ctx: &mut ()) -> Result<Self, decode::Error> {
227193
let start = d.position();
@@ -264,18 +230,38 @@ impl Decode<'_, ()> for CatalystSignedDocument {
264230
}
265231
}
266232

267-
impl Encode<()> for CatalystSignedDocument {
233+
impl<C> Encode<C> for CatalystSignedDocument {
268234
fn encode<W: minicbor::encode::Write>(
269-
&self, e: &mut encode::Encoder<W>, _ctx: &mut (),
235+
&self, e: &mut encode::Encoder<W>, _ctx: &mut C,
270236
) -> Result<(), encode::Error<W::Error>> {
271-
let cose_sign = self.as_cose_sign().map_err(encode::Error::message)?;
272-
let cose_bytes = cose_sign.to_tagged_vec().map_err(|e| {
273-
minicbor::encode::Error::message(format!("Failed to encode COSE Sign document: {e}"))
274-
})?;
275-
276-
e.writer_mut()
277-
.write_all(&cose_bytes)
278-
.map_err(|_| minicbor::encode::Error::message("Failed to encode to CBOR"))
237+
if let Some(raw_bytes) = &self.inner.raw_bytes {
238+
e.writer_mut()
239+
.write_all(raw_bytes)
240+
.map_err(minicbor::encode::Error::write)?;
241+
} else {
242+
// COSE_Sign tag
243+
// <!https://datatracker.ietf.org/doc/html/rfc8152#page-9>
244+
e.tag(minicbor::data::Tag::new(98))?;
245+
e.array(4)?;
246+
// protected headers (metadata fields)
247+
e.bytes(
248+
minicbor::to_vec(self.doc_meta())
249+
.map_err(minicbor::encode::Error::message)?
250+
.as_slice(),
251+
)?;
252+
// empty unprotected headers
253+
e.map(0)?;
254+
// content
255+
let content = self
256+
.doc_content()
257+
.encoded_bytes(self.doc_content_encoding())
258+
.map_err(minicbor::encode::Error::message)?;
259+
e.bytes(content.as_slice())?;
260+
// signatures
261+
e.encode(self.signatures())?;
262+
}
263+
264+
Ok(())
279265
}
280266
}
281267

rust/signed_doc/src/metadata/content_encoding.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,12 @@ impl TryFrom<&coset::cbor::Value> for ContentEncoding {
8484
}
8585
}
8686
}
87+
88+
impl minicbor::Encode<()> for ContentEncoding {
89+
fn encode<W: minicbor::encode::Write>(
90+
&self, e: &mut minicbor::Encoder<W>, _ctx: &mut (),
91+
) -> Result<(), minicbor::encode::Error<W::Error>> {
92+
e.str(self.to_string().as_str())?;
93+
Ok(())
94+
}
95+
}

rust/signed_doc/src/metadata/content_type.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,33 +55,34 @@ impl<'de> Deserialize<'de> for ContentType {
5555
}
5656
}
5757

58-
impl From<ContentType> for CoapContentFormat {
59-
fn from(value: ContentType) -> Self {
60-
match value {
61-
ContentType::Cbor => Self::Cbor,
62-
ContentType::Json => Self::Json,
63-
}
64-
}
65-
}
66-
6758
impl TryFrom<&coset::ContentType> for ContentType {
6859
type Error = anyhow::Error;
6960

7061
fn try_from(value: &coset::ContentType) -> Result<Self, Self::Error> {
71-
let content_type = match value {
72-
coset::ContentType::Assigned(CoapContentFormat::Json) => ContentType::Json,
73-
coset::ContentType::Assigned(CoapContentFormat::Cbor) => ContentType::Cbor,
74-
_ => {
62+
match value {
63+
coset::ContentType::Assigned(CoapContentFormat::Json) => Ok(ContentType::Json),
64+
coset::ContentType::Assigned(CoapContentFormat::Cbor) => Ok(ContentType::Cbor),
65+
coset::ContentType::Text(str) => str.parse(),
66+
coset::RegisteredLabel::Assigned(_) => {
7567
anyhow::bail!(
76-
"Unsupported Content Type {value:?}, Supported only: {:?}",
68+
"Unsupported Content Type: {value:?}, Supported only: {:?}",
7769
ContentType::VARIANTS
7870
.iter()
7971
.map(ToString::to_string)
8072
.collect::<Vec<_>>()
8173
)
8274
},
83-
};
84-
Ok(content_type)
75+
}
76+
}
77+
}
78+
79+
impl minicbor::Encode<()> for ContentType {
80+
fn encode<W: minicbor::encode::Write>(
81+
&self, e: &mut minicbor::Encoder<W>, _ctx: &mut (),
82+
) -> Result<(), minicbor::encode::Error<W::Error>> {
83+
// encode as media types, not in CoAP Content-Formats
84+
e.str(self.to_string().as_str())?;
85+
Ok(())
8586
}
8687
}
8788

0 commit comments

Comments
 (0)