Skip to content

Commit ef39122

Browse files
committed
wip(rust/signed_doc): print cose sign example
1 parent 96846f0 commit ef39122

File tree

1 file changed

+110
-38
lines changed

1 file changed

+110
-38
lines changed

rust/signed_doc/src/lib.rs

Lines changed: 110 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use std::{
88

99
use coset::CborSerializable;
1010

11+
/// Collection of Content Errors.
12+
pub struct ContentErrors(Vec<String>);
13+
1114
/// Keep all the contents private.
1215
/// Better even to use a structure like this. Wrapping in an Arc means we don't have to
1316
/// manage the Arc anywhere else. These are likely to be large, best to have the Arc be
@@ -23,8 +26,12 @@ impl Display for CatalystSignedDocument {
2326
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
2427
writeln!(f, "Metadata: {:?}", self.inner.metadata)?;
2528
writeln!(f, "JSON Payload: {}", self.inner.payload)?;
26-
writeln!(f, "Signatures: {:?}", self.inner.signatures)?;
27-
write!(f, "Content Errors: {:?}", self.content_errors)
29+
writeln!(f, "Signatures:")?;
30+
for signature in &self.inner.signatures {
31+
writeln!(f, "\t{}", hex::encode(signature.signature.as_slice()))?;
32+
}
33+
writeln!(f, "Content Errors: {:#?}", self.content_errors)?;
34+
write!(f, "COSE Sign: {:?}", self.inner.cose_sign)
2835
}
2936
}
3037

@@ -103,6 +110,8 @@ impl TryFrom<Vec<u8>> for CatalystSignedDocument {
103110
fn try_from(cose_bytes: Vec<u8>) -> Result<Self, Self::Error> {
104111
let cose = coset::CoseSign::from_slice(&cose_bytes)
105112
.map_err(|e| anyhow::anyhow!("Invalid COSE Sign document: {e}"))?;
113+
114+
let (metadata, content_errors) = metadata_from_cose_protected_header(&cose)?;
106115
let payload = match &cose.payload {
107116
Some(payload) => {
108117
let mut buf = Vec::new();
@@ -115,14 +124,16 @@ impl TryFrom<Vec<u8>> for CatalystSignedDocument {
115124
serde_json::Value::Object(serde_json::Map::new())
116125
},
117126
};
127+
let signatures = cose.signatures.clone();
118128
let inner = InnerCatalystSignedDocument {
119-
cose_sign: cose,
129+
metadata,
120130
payload,
121-
..Default::default()
131+
signatures,
132+
cose_sign: cose,
122133
};
123134
Ok(CatalystSignedDocument {
124135
inner: Arc::new(inner),
125-
content_errors: Vec::new(),
136+
content_errors: content_errors.0,
126137
})
127138
}
128139
}
@@ -202,60 +213,121 @@ fn decode_cbor_document_ref(val: &coset::cbor::Value) -> anyhow::Result<Document
202213
}
203214

204215
/// Extract `Metadata` from `coset::CoseSign`.
205-
fn validate_cose_protected_header(cose: &coset::CoseSign) -> anyhow::Result<Metadata> {
216+
#[allow(clippy::too_many_lines)]
217+
fn metadata_from_cose_protected_header(
218+
cose: &coset::CoseSign,
219+
) -> anyhow::Result<(Metadata, ContentErrors)> {
206220
let expected_header = cose_protected_header();
207-
anyhow::ensure!(
208-
cose.protected.header.alg == expected_header.alg,
209-
"Invalid COSE document protected header `algorithm` field"
210-
);
211-
anyhow::ensure!(
212-
cose.protected.header.content_type == expected_header.content_type,
213-
"Invalid COSE document protected header `content-type` field"
214-
);
215-
anyhow::ensure!(
216-
cose.protected.header.rest.iter().any(|(key, value)| {
217-
key == &coset::Label::Text(CONTENT_ENCODING_KEY.to_string())
218-
&& value == &coset::cbor::Value::Text(CONTENT_ENCODING_VALUE.to_string())
219-
}),
220-
"Invalid COSE document protected header {CONTENT_ENCODING_KEY} field"
221-
);
221+
let mut errors = Vec::new();
222+
223+
if cose.protected.header.alg != expected_header.alg {
224+
errors.push("Invalid COSE document protected header `algorithm` field".to_string());
225+
}
226+
227+
if cose.protected.header.content_type != expected_header.content_type {
228+
errors.push("Invalid COSE document protected header `content-type` field".to_string());
229+
}
230+
231+
if !cose.protected.header.rest.iter().any(|(key, value)| {
232+
key == &coset::Label::Text(CONTENT_ENCODING_KEY.to_string())
233+
&& value == &coset::cbor::Value::Text(CONTENT_ENCODING_VALUE.to_string())
234+
}) {
235+
errors.push(
236+
"Invalid COSE document protected header {CONTENT_ENCODING_KEY} field".to_string(),
237+
);
238+
}
222239
let mut metadata = Metadata::default();
223240

224-
let Some((_, value)) = cose
241+
match cose
225242
.protected
226243
.header
227244
.rest
228245
.iter()
229246
.find(|(key, _)| key == &coset::Label::Text("type".to_string()))
230-
else {
231-
anyhow::bail!("Invalid COSE protected header, missing `type` field");
247+
{
248+
Some((_, doc_type)) => {
249+
match decode_cbor_uuid(doc_type) {
250+
Ok(doc_type_uuid) => {
251+
if doc_type_uuid.get_version_num() == 4 {
252+
metadata.r#type = doc_type_uuid;
253+
} else {
254+
errors.push(format!(
255+
"Document type is not a valid UUIDv4: {doc_type_uuid}"
256+
));
257+
}
258+
},
259+
Err(e) => {
260+
errors.push(format!(
261+
"Invalid COSE protected header `type` field, err: {e}"
262+
));
263+
},
264+
}
265+
},
266+
None => errors.push("Invalid COSE protected header, missing `type` field".to_string()),
232267
};
233-
metadata.r#type = decode_cbor_uuid(value)
234-
.map_err(|e| anyhow::anyhow!("Invalid COSE protected header `type` field, err: {e}"))?;
235268

236-
let Some((_, value)) = cose
269+
match cose
237270
.protected
238271
.header
239272
.rest
240273
.iter()
241274
.find(|(key, _)| key == &coset::Label::Text("id".to_string()))
242-
else {
243-
anyhow::bail!("Invalid COSE protected header, missing `id` field");
275+
{
276+
Some((_, doc_id)) => {
277+
match decode_cbor_uuid(doc_id) {
278+
Ok(doc_id_uuid) => {
279+
if doc_id_uuid.get_version_num() == 7 {
280+
metadata.id = doc_id_uuid;
281+
} else {
282+
errors.push(format!("Document ID is not a valid UUIDv7: {doc_id_uuid}"));
283+
}
284+
},
285+
Err(e) => {
286+
errors.push(format!(
287+
"Invalid COSE protected header `id` field, err: {e}"
288+
));
289+
},
290+
}
291+
},
292+
None => errors.push("Invalid COSE protected header, missing `id` field".to_string()),
244293
};
245-
decode_cbor_uuid(value)
246-
.map_err(|e| anyhow::anyhow!("Invalid COSE protected header `id` field, err: {e}"))?;
247294

248-
let Some((_, value)) = cose
295+
match cose
249296
.protected
250297
.header
251298
.rest
252299
.iter()
253300
.find(|(key, _)| key == &coset::Label::Text("ver".to_string()))
254-
else {
255-
anyhow::bail!("Invalid COSE protected header, missing `ver` field");
256-
};
257-
decode_cbor_uuid(value)
258-
.map_err(|e| anyhow::anyhow!("Invalid COSE protected header `ver` field, err: {e}"))?;
301+
{
302+
Some((_, doc_ver)) => {
303+
match decode_cbor_uuid(doc_ver) {
304+
Ok(doc_ver_uuid) => {
305+
let mut is_valid = true;
306+
if doc_ver_uuid.get_version_num() != 7 {
307+
errors.push(format!(
308+
"Document Version is not a valid UUIDv7: {doc_ver_uuid}"
309+
));
310+
is_valid = false;
311+
}
312+
if doc_ver_uuid < metadata.id {
313+
errors.push(format!(
314+
"Document Version {doc_ver_uuid} cannot be smaller than Document ID {0}", metadata.id
315+
));
316+
is_valid = false;
317+
}
318+
if is_valid {
319+
metadata.ver = doc_ver_uuid;
320+
}
321+
},
322+
Err(e) => {
323+
errors.push(format!(
324+
"Invalid COSE protected header `ver` field, err: {e}"
325+
));
326+
},
327+
}
328+
},
329+
None => errors.push("Invalid COSE protected header, missing `ver` field".to_string()),
330+
}
259331

260332
if let Some((_, value)) = cose
261333
.protected
@@ -305,5 +377,5 @@ fn validate_cose_protected_header(cose: &coset::CoseSign) -> anyhow::Result<Meta
305377
);
306378
}
307379

308-
Ok(metadata)
380+
Ok((metadata, ContentErrors(errors)))
309381
}

0 commit comments

Comments
 (0)