@@ -8,9 +8,9 @@ use std::{
8
8
path:: PathBuf ,
9
9
} ;
10
10
11
- use catalyst_signed_doc:: { CatalystSignedDocument , Decode , Decoder , KidUri , Metadata } ;
11
+ use catalyst_signed_doc:: { Builder , CatalystSignedDocument , Decode , Decoder , KidUri , Metadata } ;
12
12
use clap:: Parser ;
13
- use coset:: { iana :: CoapContentFormat , CborSerializable } ;
13
+ use coset:: CborSerializable ;
14
14
use ed25519_dalek:: { ed25519:: signature:: Signer , pkcs8:: DecodePrivateKey } ;
15
15
16
16
fn main ( ) {
@@ -19,27 +19,26 @@ fn main() {
19
19
}
20
20
}
21
21
22
- /// Hermes cli commands
22
+ /// Catalyst Sign Document CLI Commands
23
23
#[ derive( clap:: Parser ) ]
24
+ #[ allow( clippy:: large_enum_variant) ]
24
25
enum Cli {
25
26
/// Builds a COSE document without signatures
26
27
Build {
27
28
/// Path to the document in the json format
28
29
doc : PathBuf ,
29
- /// Path to the json schema (Draft 7) to validate document against it
30
- schema : PathBuf ,
31
30
/// Path to the output COSE file to store.
32
31
output : PathBuf ,
33
32
/// Document metadata, must be in JSON format
34
33
meta : PathBuf ,
35
34
} ,
36
35
/// Adds a signature to already formed COSE document
37
36
Sign {
38
- /// Path to the secret key in PEM format
39
- sk : PathBuf ,
40
37
/// Path to the formed (could be empty, without any signatures) COSE document
41
38
/// This exact file would be modified and new signature would be added
42
39
doc : PathBuf ,
40
+ /// Path to the secret key in PEM format
41
+ sk : PathBuf ,
43
42
/// Signer kid
44
43
kid : KidUri ,
45
44
} ,
@@ -55,34 +54,28 @@ enum Cli {
55
54
} ,
56
55
}
57
56
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
-
68
57
impl Cli {
69
58
fn exec ( self ) -> anyhow:: Result < ( ) > {
70
59
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)
80
63
. 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) ?;
86
79
} ,
87
80
Self :: Sign { sk, doc, kid } => {
88
81
let sk = load_secret_key_from_file ( & sk)
@@ -116,99 +109,43 @@ fn decode_signed_doc(cose_bytes: &[u8]) {
116
109
) ;
117
110
match CatalystSignedDocument :: decode ( & mut Decoder :: new ( cose_bytes) , & mut ( ) ) {
118
111
Ok ( cat_signed_doc) => {
119
- println ! ( "This is a valid Catalyst Signed Document." ) ;
112
+ println ! ( "This is a valid Catalyst Document." ) ;
120
113
println ! ( "{cat_signed_doc}" ) ;
121
114
} ,
122
- Err ( e) => eprintln ! ( "Invalid Catalyst Signed Document, err: {e}" ) ,
115
+ Err ( e) => eprintln ! ( "Invalid Catalyst Document, err: {e}" ) ,
123
116
}
124
117
}
125
118
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
-
136
119
fn load_json_from_file < T > ( path : & PathBuf ) -> anyhow:: Result < T >
137
120
where T : for < ' de > serde:: Deserialize < ' de > {
138
121
let file = File :: open ( path) ?;
139
122
let json = serde_json:: from_reader ( file) ?;
140
123
Ok ( json)
141
124
}
142
125
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)
160
130
}
161
131
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)
195
136
}
196
137
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}" ) )
203
142
}
204
143
205
144
fn store_cose_file ( cose : coset:: CoseSign , output : & PathBuf ) -> anyhow:: Result < ( ) > {
206
- let mut cose_file = File :: create ( output) ?;
207
145
let cose_bytes = cose
208
146
. to_vec ( )
209
147
. 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)
212
149
}
213
150
214
151
fn load_secret_key_from_file ( sk_path : & PathBuf ) -> anyhow:: Result < ed25519_dalek:: SigningKey > {
0 commit comments