@@ -8,9 +8,9 @@ use std::{
88 path:: PathBuf ,
99} ;
1010
11- use catalyst_signed_doc:: { CatalystSignedDocument , Decode , Decoder , KidUri , Metadata } ;
11+ use catalyst_signed_doc:: { Builder , CatalystSignedDocument , Decode , Decoder , KidUri , Metadata } ;
1212use clap:: Parser ;
13- use coset:: { iana :: CoapContentFormat , CborSerializable } ;
13+ use coset:: CborSerializable ;
1414use ed25519_dalek:: { ed25519:: signature:: Signer , pkcs8:: DecodePrivateKey } ;
1515
1616fn main ( ) {
@@ -19,27 +19,26 @@ fn main() {
1919 }
2020}
2121
22- /// Hermes cli commands
22+ /// Catalyst Sign Document CLI Commands
2323#[ derive( clap:: Parser ) ]
24+ #[ allow( clippy:: large_enum_variant) ]
2425enum Cli {
2526 /// Builds a COSE document without signatures
2627 Build {
2728 /// Path to the document in the json format
2829 doc : PathBuf ,
29- /// Path to the json schema (Draft 7) to validate document against it
30- schema : PathBuf ,
3130 /// Path to the output COSE file to store.
3231 output : PathBuf ,
3332 /// Document metadata, must be in JSON format
3433 meta : PathBuf ,
3534 } ,
3635 /// Adds a signature to already formed COSE document
3736 Sign {
38- /// Path to the secret key in PEM format
39- sk : PathBuf ,
4037 /// Path to the formed (could be empty, without any signatures) COSE document
4138 /// This exact file would be modified and new signature would be added
4239 doc : PathBuf ,
40+ /// Path to the secret key in PEM format
41+ sk : PathBuf ,
4342 /// Signer kid
4443 kid : KidUri ,
4544 } ,
@@ -55,34 +54,28 @@ enum Cli {
5554 } ,
5655}
5756
58- const CONTENT_ENCODING_KEY : & str = "Content-Encoding" ;
59- const UUID_CBOR_TAG : u64 = 37 ;
60-
61- fn encode_cbor_uuid ( uuid : & uuid:: Uuid ) -> coset:: cbor:: Value {
62- coset:: cbor:: Value :: Tag (
63- UUID_CBOR_TAG ,
64- coset:: cbor:: Value :: Bytes ( uuid. as_bytes ( ) . to_vec ( ) ) . into ( ) ,
65- )
66- }
67-
6857impl Cli {
6958 fn exec ( self ) -> anyhow:: Result < ( ) > {
7059 match self {
71- Self :: Build {
72- doc,
73- schema,
74- output,
75- meta,
76- } => {
77- let doc_schema = load_schema_from_file ( & schema) ?;
78- let json_doc = load_json_from_file ( & doc) ?;
79- let json_meta = load_json_from_file ( & meta)
60+ Self :: Build { doc, output, meta } => {
61+ // Load Metadata from JSON file
62+ let metadata: Metadata = load_json_from_file ( & meta)
8063 . map_err ( |e| anyhow:: anyhow!( "Failed to load metadata from file: {e}" ) ) ?;
81- println ! ( "{json_meta}" ) ;
82- validate_json ( & json_doc, & doc_schema) ?;
83- let compressed_doc = brotli_compress_json ( & json_doc) ?;
84- let empty_cose_sign = build_empty_cose_doc ( compressed_doc, & json_meta) ;
85- store_cose_file ( empty_cose_sign, & output) ?;
64+ println ! ( "{metadata}" ) ;
65+ // Load Document from JSON file
66+ let json_doc: serde_json:: Value = load_json_from_file ( & doc) ?;
67+ // Possibly encode if Metadata has an encoding set.
68+ let payload = serde_json:: to_vec ( & json_doc) ?;
69+ // Start with no signatures.
70+ let signed_doc = Builder :: new ( )
71+ . with_content ( payload)
72+ . with_metadata ( metadata)
73+ . 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) ?;
8679 } ,
8780 Self :: Sign { sk, doc, kid } => {
8881 let sk = load_secret_key_from_file ( & sk)
@@ -116,99 +109,43 @@ fn decode_signed_doc(cose_bytes: &[u8]) {
116109 ) ;
117110 match CatalystSignedDocument :: decode ( & mut Decoder :: new ( cose_bytes) , & mut ( ) ) {
118111 Ok ( cat_signed_doc) => {
119- println ! ( "This is a valid Catalyst Signed Document." ) ;
112+ println ! ( "This is a valid Catalyst Document." ) ;
120113 println ! ( "{cat_signed_doc}" ) ;
121114 } ,
122- Err ( e) => eprintln ! ( "Invalid Catalyst Signed Document, err: {e}" ) ,
115+ Err ( e) => eprintln ! ( "Invalid Catalyst Document, err: {e}" ) ,
123116 }
124117}
125118
126- fn load_schema_from_file ( schema_path : & PathBuf ) -> anyhow:: Result < jsonschema:: JSONSchema > {
127- let schema_file = File :: open ( schema_path) ?;
128- let schema_json = serde_json:: from_reader ( schema_file) ?;
129- let schema = jsonschema:: JSONSchema :: options ( )
130- . with_draft ( jsonschema:: Draft :: Draft7 )
131- . compile ( & schema_json)
132- . map_err ( |e| anyhow:: anyhow!( "{e}" ) ) ?;
133- Ok ( schema)
134- }
135-
136119fn load_json_from_file < T > ( path : & PathBuf ) -> anyhow:: Result < T >
137120where T : for < ' de > serde:: Deserialize < ' de > {
138121 let file = File :: open ( path) ?;
139122 let json = serde_json:: from_reader ( file) ?;
140123 Ok ( json)
141124}
142125
143- fn validate_json ( doc : & serde_json:: Value , schema : & jsonschema:: JSONSchema ) -> anyhow:: Result < ( ) > {
144- schema. validate ( doc) . map_err ( |err| {
145- let mut validation_error = String :: new ( ) ;
146- for e in err {
147- validation_error. push_str ( & format ! ( "\n - {e}" ) ) ;
148- }
149- anyhow:: anyhow!( "{validation_error}" )
150- } ) ?;
151- Ok ( ( ) )
152- }
153-
154- fn brotli_compress_json ( doc : & serde_json:: Value ) -> anyhow:: Result < Vec < u8 > > {
155- let brotli_params = brotli:: enc:: BrotliEncoderParams :: default ( ) ;
156- let doc_bytes = serde_json:: to_vec ( & doc) ?;
157- let mut buf = Vec :: new ( ) ;
158- brotli:: BrotliCompress ( & mut doc_bytes. as_slice ( ) , & mut buf, & brotli_params) ?;
159- Ok ( buf)
126+ fn load_cose_from_file ( cose_path : & PathBuf ) -> anyhow:: Result < coset:: CoseSign > {
127+ let cose_file_bytes = read_bytes_from_file ( cose_path) ?;
128+ let cose = coset:: CoseSign :: from_slice ( & cose_file_bytes) . map_err ( |e| anyhow:: anyhow!( "{e}" ) ) ?;
129+ Ok ( cose)
160130}
161131
162- fn build_empty_cose_doc ( doc_bytes : Vec < u8 > , meta : & Metadata ) -> coset:: CoseSign {
163- let mut builder =
164- coset:: HeaderBuilder :: new ( ) . content_format ( CoapContentFormat :: from ( meta. content_type ( ) ) ) ;
165-
166- if let Some ( content_encoding) = meta. content_encoding ( ) {
167- builder = builder. text_value (
168- CONTENT_ENCODING_KEY . to_string ( ) ,
169- format ! ( "{content_encoding}" ) . into ( ) ,
170- ) ;
171- }
172- let mut protected_header = builder. build ( ) ;
173-
174- protected_header. rest . push ( (
175- coset:: Label :: Text ( "type" . to_string ( ) ) ,
176- encode_cbor_uuid ( & meta. doc_type ( ) ) ,
177- ) ) ;
178- protected_header. rest . push ( (
179- coset:: Label :: Text ( "id" . to_string ( ) ) ,
180- encode_cbor_uuid ( & meta. doc_id ( ) ) ,
181- ) ) ;
182- protected_header. rest . push ( (
183- coset:: Label :: Text ( "ver" . to_string ( ) ) ,
184- encode_cbor_uuid ( & meta. doc_ver ( ) ) ,
185- ) ) ;
186- let meta_rest = meta. extra ( ) . header_rest ( ) . unwrap_or_default ( ) ;
187-
188- if !meta_rest. is_empty ( ) {
189- protected_header. rest . extend ( meta_rest) ;
190- }
191- coset:: CoseSignBuilder :: new ( )
192- . protected ( protected_header)
193- . payload ( doc_bytes)
194- . build ( )
132+ fn read_bytes_from_file ( path : & PathBuf ) -> anyhow:: Result < Vec < u8 > > {
133+ let mut file_bytes = Vec :: new ( ) ;
134+ File :: open ( path) ?. read_to_end ( & mut file_bytes) ?;
135+ Ok ( file_bytes)
195136}
196137
197- fn load_cose_from_file ( cose_path : & PathBuf ) -> anyhow:: Result < coset:: CoseSign > {
198- let mut cose_file = File :: open ( cose_path) ?;
199- let mut cose_file_bytes = Vec :: new ( ) ;
200- cose_file. read_to_end ( & mut cose_file_bytes) ?;
201- let cose = coset:: CoseSign :: from_slice ( & cose_file_bytes) . map_err ( |e| anyhow:: anyhow!( "{e}" ) ) ?;
202- Ok ( cose)
138+ fn write_bytes_to_file ( bytes : & [ u8 ] , output : & PathBuf ) -> anyhow:: Result < ( ) > {
139+ File :: create ( output) ?
140+ . write_all ( bytes)
141+ . map_err ( |e| anyhow:: anyhow!( "Failed to write to file {output:?}: {e}" ) )
203142}
204143
205144fn store_cose_file ( cose : coset:: CoseSign , output : & PathBuf ) -> anyhow:: Result < ( ) > {
206- let mut cose_file = File :: create ( output) ?;
207145 let cose_bytes = cose
208146 . to_vec ( )
209147 . map_err ( |e| anyhow:: anyhow!( "Failed to Store COSE SIGN: {e}" ) ) ?;
210- cose_file. write_all ( & cose_bytes) ?;
211- Ok ( ( ) )
148+ write_bytes_to_file ( & cose_bytes, output)
212149}
213150
214151fn load_secret_key_from_file ( sk_path : & PathBuf ) -> anyhow:: Result < ed25519_dalek:: SigningKey > {
0 commit comments