@@ -8,6 +8,9 @@ use std::{
88
99use 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