Skip to content

Commit 38104bb

Browse files
committed
wip
1 parent d60579c commit 38104bb

File tree

6 files changed

+131
-12
lines changed

6 files changed

+131
-12
lines changed

rust/signed_doc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ futures = "0.3.31"
3030
base64-url = "3.0.0"
3131
rand = "0.8.5"
3232
tokio = { version = "1.42.0", features = [ "macros" ] }
33+
ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
3334

3435
[[bin]]
3536
name = "signed-docs"

rust/signed_doc/src/builder.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,25 @@ impl Builder {
2626
}
2727

2828
/// Set document metadata
29+
///
30+
/// # Errors
31+
/// - Fails if it is invalid metadata JSON object.
2932
#[must_use]
3033
pub fn with_metadata(mut self, metadata: Metadata) -> Self {
3134
self.metadata = Some(metadata);
3235
self
3336
}
3437

38+
/// Set document metadata in JSON format
39+
///
40+
/// # Errors
41+
/// - Fails if it is invalid metadata JSON object.
42+
#[must_use]
43+
pub fn with_json_metadata(mut self, json: serde_json::Value) -> anyhow::Result<Self> {
44+
self.metadata = Some(serde_json::from_value(json)?);
45+
Ok(self)
46+
}
47+
3548
/// Set decoded (original) document content bytes
3649
#[must_use]
3750
pub fn with_decoded_content(mut self, content: Vec<u8>) -> Self {

rust/signed_doc/src/lib.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ use catalyst_types::problem_report::ProblemReport;
1919
pub use catalyst_types::uuid::{Uuid, UuidV4, UuidV7};
2020
pub use content::Content;
2121
use coset::{CborSerializable, Header};
22-
pub use metadata::{ContentEncoding, ContentType, DocumentRef, ExtraFields, Metadata, Section};
22+
pub use metadata::{
23+
Algorithm, ContentEncoding, ContentType, DocumentRef, ExtraFields, Metadata, Section,
24+
};
2325
use minicbor::{decode, encode, Decode, Decoder, Encode};
2426
use providers::VerifyingKeyProvider;
2527
pub use signature::{IdUri, Signatures};
@@ -226,9 +228,12 @@ impl CatalystSignedDocument {
226228
/// data.
227229
///
228230
/// # Errors
229-
/// Could fails if the `CatalystSignedDocument` object is not valid.
231+
/// Fails if the `CatalystSignedDocument` object is not valid.
230232
#[must_use]
231233
pub fn into_builder(self) -> anyhow::Result<Builder> {
234+
if self.report().is_problematic() {
235+
anyhow::bail!("Invalid Document");
236+
}
232237
Ok(Builder::new()
233238
.with_metadata(self.inner.metadata.clone())
234239
.with_decoded_content(self.doc_content().decoded_bytes()?.to_vec())
@@ -327,14 +332,16 @@ mod tests {
327332
use super::*;
328333

329334
fn test_metadata() -> anyhow::Result<(UuidV7, UuidV4, Metadata)> {
335+
let alg = Algorithm::EdDSA;
330336
let uuid_v7 = UuidV7::new();
331337
let uuid_v4 = UuidV4::new();
332-
let section = "some section".to_string();
338+
let section = "$".to_string();
333339
let collabs = vec!["Alex1".to_string(), "Alex2".to_string()];
334340
let content_type = ContentType::Json;
335341
let content_encoding = ContentEncoding::Brotli;
336342

337343
let metadata: Metadata = serde_json::from_value(serde_json::json!({
344+
"alg": alg.to_string(),
338345
"content-type": content_type.to_string(),
339346
"content-encoding": content_encoding.to_string(),
340347
"type": uuid_v4.to_string(),
@@ -350,7 +357,7 @@ mod tests {
350357
"brand_id": {"id": uuid_v7.to_string()},
351358
"category_id": {"id": uuid_v7.to_string()},
352359
}))
353-
.map_err(|_| anyhow::anyhow!("Invalid example metadata. This should not happen."))?;
360+
.map_err(|e| anyhow::anyhow!("Invalid example metadata. This should not happen. Err: {e}"))?;
354361
Ok((uuid_v7, uuid_v4, metadata))
355362
}
356363

rust/signed_doc/src/metadata/algorithm.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Cryptographic Algorithm in COSE SIGN protected header.
22
3+
use std::fmt::{Display, Formatter};
4+
35
use strum::VariantArray;
46

57
/// Cryptography Algorithm.
@@ -9,6 +11,20 @@ pub enum Algorithm {
911
EdDSA,
1012
}
1113

14+
impl Default for Algorithm {
15+
fn default() -> Self {
16+
Self::EdDSA
17+
}
18+
}
19+
20+
impl Display for Algorithm {
21+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
22+
match self {
23+
Self::EdDSA => write!(f, "EdDSA"),
24+
}
25+
}
26+
}
27+
1228
impl From<Algorithm> for coset::iana::Algorithm {
1329
fn from(_: Algorithm) -> Self {
1430
coset::iana::Algorithm::EdDSA

rust/signed_doc/src/metadata/mod.rs

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod extra_fields;
99
mod section;
1010
pub(crate) mod utils;
1111

12-
use algorithm::Algorithm;
12+
pub use algorithm::Algorithm;
1313
use catalyst_types::{
1414
problem_report::ProblemReport,
1515
uuid::{UuidV4, UuidV7},
@@ -20,7 +20,7 @@ use coset::iana::CoapContentFormat;
2020
pub use document_ref::DocumentRef;
2121
pub use extra_fields::ExtraFields;
2222
pub use section::Section;
23-
use utils::{cose_protected_header_find, decode_cbor_uuid, encode_cbor_uuid};
23+
use utils::{cose_protected_header_find, decode_cbor_uuid, encode_cbor_uuid, validate_option};
2424

2525
/// `content_encoding` field COSE key value
2626
const CONTENT_ENCODING_KEY: &str = "Content-Encoding";
@@ -37,22 +37,22 @@ const VER_KEY: &str = "ver";
3737
#[derive(Clone, Debug, PartialEq, serde::Deserialize, Default)]
3838
pub struct Metadata {
3939
/// Cryptographic Algorithm
40-
#[serde(skip_serializing_if = "Option::is_none")]
40+
#[serde(deserialize_with = "validate_option")]
4141
alg: Option<Algorithm>,
4242
/// Document Type `UUIDv4`.
43-
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
43+
#[serde(rename = "type", deserialize_with = "validate_option")]
4444
doc_type: Option<UuidV4>,
4545
/// Document ID `UUIDv7`.
46-
#[serde(skip_serializing_if = "Option::is_none")]
46+
#[serde(deserialize_with = "validate_option")]
4747
id: Option<UuidV7>,
4848
/// Document Version `UUIDv7`.
49-
#[serde(skip_serializing_if = "Option::is_none")]
49+
#[serde(deserialize_with = "validate_option")]
5050
ver: Option<UuidV7>,
5151
/// Document Payload Content Type.
52-
#[serde(rename = "content-type")]
52+
#[serde(rename = "content-type", deserialize_with = "validate_option")]
5353
content_type: Option<ContentType>,
5454
/// Document Payload Content Encoding.
55-
#[serde(rename = "content-encoding", skip_serializing_if = "Option::is_none")]
55+
#[serde(rename = "content-encoding")]
5656
content_encoding: Option<ContentEncoding>,
5757
/// Additional Metadata Fields.
5858
#[serde(flatten)]
@@ -300,3 +300,69 @@ impl TryFrom<&Metadata> for coset::Header {
300300
Ok(builder.build())
301301
}
302302
}
303+
304+
#[cfg(test)]
305+
mod tests {
306+
use super::*;
307+
308+
#[test]
309+
fn metadata_serde_test() {
310+
let alg = Algorithm::EdDSA;
311+
let uuid_v7 = UuidV7::new();
312+
let uuid_v4 = UuidV4::new();
313+
let content_type = ContentType::Json;
314+
315+
let valid = serde_json::json!({
316+
"alg": alg.to_string(),
317+
"content-type": content_type.to_string(),
318+
"type": uuid_v4.to_string(),
319+
"id": uuid_v7.to_string(),
320+
"ver": uuid_v7.to_string(),
321+
322+
});
323+
assert!(serde_json::from_value::<Metadata>(valid).is_ok());
324+
325+
let missing_alg = serde_json::json!({
326+
"content-type": content_type.to_string(),
327+
"type": uuid_v4.to_string(),
328+
"id": uuid_v7.to_string(),
329+
"ver": uuid_v7.to_string(),
330+
331+
});
332+
assert!(serde_json::from_value::<Metadata>(missing_alg).is_err());
333+
334+
let missing_content_type = serde_json::json!({
335+
"alg": alg.to_string(),
336+
"type": uuid_v4.to_string(),
337+
"id": uuid_v7.to_string(),
338+
"ver": uuid_v7.to_string(),
339+
});
340+
assert!(serde_json::from_value::<Metadata>(missing_content_type).is_err());
341+
342+
let missing_type = serde_json::json!({
343+
"alg": alg.to_string(),
344+
"content-type": content_type.to_string(),
345+
"id": uuid_v7.to_string(),
346+
"ver": uuid_v7.to_string(),
347+
348+
});
349+
assert!(serde_json::from_value::<Metadata>(missing_type).is_err());
350+
351+
let missing_id = serde_json::json!({
352+
"alg": alg.to_string(),
353+
"content-type": content_type.to_string(),
354+
"type": uuid_v4.to_string(),
355+
"ver": uuid_v7.to_string(),
356+
357+
});
358+
assert!(serde_json::from_value::<Metadata>(missing_id).is_err());
359+
360+
let missing_ver = serde_json::json!({
361+
"alg": alg.to_string(),
362+
"content-type": content_type.to_string(),
363+
"type": uuid_v4.to_string(),
364+
"id": uuid_v7.to_string(),
365+
});
366+
assert!(serde_json::from_value::<Metadata>(missing_ver).is_err());
367+
}
368+
}

rust/signed_doc/src/metadata/utils.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22
33
use catalyst_types::uuid::CborContext;
44
use coset::CborSerializable;
5+
use serde::{Deserialize, Deserializer};
6+
7+
/// Custom serde deserialization function that fails if the field is `None`
8+
pub(crate) fn validate_option<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
9+
where
10+
T: Deserialize<'de>,
11+
D: Deserializer<'de>,
12+
{
13+
let value = Option::deserialize(deserializer)?;
14+
if value.is_none() {
15+
return Err(serde::de::Error::custom(
16+
"Field is required but was missing or null",
17+
));
18+
}
19+
Ok(value)
20+
}
521

622
/// Find a value for a predicate in the protected header.
723
pub(crate) fn cose_protected_header_find(

0 commit comments

Comments
 (0)