18
18
19
19
* Add Root metadata class
20
20
21
+ * Add classes for other complex metadata attributes, see 'signatures' (in
22
+ Metadata) 'meta'/'targets' (in Timestamp, Snapshot, Targets), 'delegations'
23
+ (in Targets), 'keys'/'roles' (in not yet existent 'Delegation'), ...
24
+
21
25
"""
22
26
# Imports
23
-
24
27
from datetime import datetime , timedelta
25
28
from typing import Any , Dict , Optional
26
29
45
48
46
49
47
50
# Types
48
-
49
51
JsonDict = Dict [str , Any ]
50
52
51
53
52
54
# Classes.
53
-
54
55
class Metadata ():
55
56
"""A container for signed TUF metadata.
56
57
@@ -77,12 +78,50 @@ def __init__(self, signed: 'Signed', signatures: list) -> None:
77
78
self .signed = signed
78
79
self .signatures = signatures
79
80
80
- def to_dict (self ) -> JsonDict :
81
- """Returns the JSON-serializable dictionary representation of self. """
82
- return {
83
- 'signatures' : self .signatures ,
84
- 'signed' : self .signed .to_dict ()
85
- }
81
+
82
+ # Deserialization (factories).
83
+ @classmethod
84
+ def from_dict (cls , metadata : JsonDict ) -> 'Metadata' :
85
+ """Creates Metadata object from its JSON/dict representation.
86
+
87
+ Calls 'from_dict' for any complex metadata attribute represented by a
88
+ class also that has a 'from_dict' factory method. (Currently this is
89
+ only the signed attribute.)
90
+
91
+ Arguments:
92
+ metadata: TUF metadata in JSON/dict representation, as e.g.
93
+ returned by 'json.loads'.
94
+
95
+ Raises:
96
+ KeyError: The metadata dict format is invalid.
97
+ ValueError: The metadata has an unrecognized signed._type field.
98
+
99
+ Returns:
100
+ A TUF Metadata object.
101
+
102
+ """
103
+ # Dispatch to contained metadata class on metadata _type field.
104
+ _type = metadata ['signed' ]['_type' ]
105
+
106
+ if _type == 'targets' :
107
+ inner_cls = Targets
108
+ elif _type == 'snapshot' :
109
+ inner_cls = Snapshot
110
+ elif _type == 'timestamp' :
111
+ inner_cls = Timestamp
112
+ elif _type == 'root' :
113
+ # TODO: implement Root class
114
+ raise NotImplementedError ('Root not yet implemented' )
115
+ else :
116
+ raise ValueError (f'unrecognized metadata type "{ _type } "' )
117
+
118
+ # NOTE: If Signature becomes a class, we should iterate over
119
+ # metadata['signatures'], call Signature.from_dict for each item, and
120
+ # pass a list of Signature objects to the Metadata constructor intead.
121
+ return cls (
122
+ signed = inner_cls .from_dict (metadata ['signed' ]),
123
+ signatures = metadata ['signatures' ])
124
+
86
125
87
126
@classmethod
88
127
def from_json (cls , metadata_json : str ) -> 'Metadata' :
@@ -102,6 +141,40 @@ def from_json(cls, metadata_json: str) -> 'Metadata':
102
141
return cls .from_dict (load_json_string (metadata_json ))
103
142
104
143
144
+ @classmethod
145
+ def from_json_file (
146
+ cls , filename : str ,
147
+ storage_backend : Optional [StorageBackendInterface ] = None
148
+ ) -> 'Metadata' :
149
+ """Loads JSON-formatted TUF metadata from file storage.
150
+
151
+ Arguments:
152
+ filename: The path to read the file from.
153
+ storage_backend: An object that implements
154
+ securesystemslib.storage.StorageBackendInterface. Per default
155
+ a (local) FilesystemBackend is used.
156
+
157
+ Raises:
158
+ securesystemslib.exceptions.StorageError: The file cannot be read.
159
+ securesystemslib.exceptions.Error, ValueError, KeyError: The
160
+ metadata cannot be parsed.
161
+
162
+ Returns:
163
+ A TUF Metadata object.
164
+
165
+ """
166
+ return cls .from_dict (load_json_file (filename , storage_backend ))
167
+
168
+
169
+ # Serialization.
170
+ def to_dict (self ) -> JsonDict :
171
+ """Returns the JSON-serializable dictionary representation of self. """
172
+ return {
173
+ 'signatures' : self .signatures ,
174
+ 'signed' : self .signed .to_dict ()
175
+ }
176
+
177
+
105
178
def to_json (self , compact : bool = False ) -> None :
106
179
"""Returns the optionally compacted JSON representation of self. """
107
180
return json .dumps (
@@ -110,6 +183,30 @@ def to_json(self, compact: bool = False) -> None:
110
183
separators = ((',' , ':' ) if compact else (',' , ': ' )),
111
184
sort_keys = True )
112
185
186
+
187
+ def to_json_file (
188
+ self , filename : str , compact : bool = False ,
189
+ storage_backend : StorageBackendInterface = None ) -> None :
190
+ """Writes the JSON representation of self to file storage.
191
+
192
+ Arguments:
193
+ filename: The path to write the file to.
194
+ compact: A boolean indicating if the JSON string should be compact
195
+ by excluding whitespace.
196
+ storage_backend: An object that implements
197
+ securesystemslib.storage.StorageBackendInterface. Per default
198
+ a (local) FilesystemBackend is used.
199
+ Raises:
200
+ securesystemslib.exceptions.StorageError:
201
+ The file cannot be written.
202
+
203
+ """
204
+ with tempfile .TemporaryFile () as f :
205
+ f .write (self .to_json (compact ).encode ('utf-8' ))
206
+ persist_temp_file (f , filename , storage_backend )
207
+
208
+
209
+ # Signatures.
113
210
def sign (self , key : JsonDict , append : bool = False ) -> JsonDict :
114
211
"""Creates signature over 'signed' and assigns it to 'signatures'.
115
212
@@ -137,6 +234,7 @@ def sign(self, key: JsonDict, append: bool = False) -> JsonDict:
137
234
138
235
return signature
139
236
237
+
140
238
def verify (self , key : JsonDict ) -> bool :
141
239
"""Verifies 'signatures' over 'signed' that match the passed key by id.
142
240
@@ -172,93 +270,6 @@ def verify(self, key: JsonDict) -> bool:
172
270
173
271
return True
174
272
175
- @classmethod
176
- def from_json_file (
177
- cls , filename : str ,
178
- storage_backend : Optional [StorageBackendInterface ] = None
179
- ) -> 'Metadata' :
180
- """Loads JSON-formatted TUF metadata from file storage.
181
-
182
- Arguments:
183
- filename: The path to read the file from.
184
- storage_backend: An object that implements
185
- securesystemslib.storage.StorageBackendInterface. Per default
186
- a (local) FilesystemBackend is used.
187
-
188
- Raises:
189
- securesystemslib.exceptions.StorageError: The file cannot be read.
190
- securesystemslib.exceptions.Error, ValueError, KeyError: The
191
- metadata cannot be parsed.
192
-
193
- Returns:
194
- A TUF Metadata object.
195
-
196
- """
197
- return cls .from_dict (load_json_file (filename , storage_backend ))
198
-
199
- @classmethod
200
- def from_dict (cls , metadata : JsonDict ) -> 'Metadata' :
201
- """Creates Metadata object from its JSON/dict representation.
202
-
203
- Calls 'from_dict' for any complex metadata attribute represented by a
204
- class also that has a 'from_dict' factory method. (Currently this is
205
- only the signed attribute.)
206
-
207
- Arguments:
208
- metadata: TUF metadata in JSON/dict representation, as e.g.
209
- returned by 'json.loads'.
210
-
211
- Raises:
212
- KeyError: The metadata dict format is invalid.
213
- ValueError: The metadata has an unrecognized signed._type field.
214
-
215
- Returns:
216
- A TUF Metadata object.
217
-
218
- """
219
- # Dispatch to contained metadata class on metadata _type field.
220
- _type = metadata ['signed' ]['_type' ]
221
-
222
- if _type == 'targets' :
223
- inner_cls = Targets
224
- elif _type == 'snapshot' :
225
- inner_cls = Snapshot
226
- elif _type == 'timestamp' :
227
- inner_cls = Timestamp
228
- elif _type == 'root' :
229
- # TODO: implement Root class
230
- raise NotImplementedError ('Root not yet implemented' )
231
- else :
232
- raise ValueError (f'unrecognized metadata type "{ _type } "' )
233
-
234
- # NOTE: If Signature becomes a class, we should iterate over
235
- # metadata['signatures'], call Signature.from_dict for each item, and
236
- # pass a list of Signature objects to the Metadata constructor intead.
237
- return cls (
238
- signed = inner_cls .from_dict (metadata ['signed' ]),
239
- signatures = metadata ['signatures' ])
240
-
241
-
242
- def to_json_file (
243
- self , filename : str , compact : bool = False ,
244
- storage_backend : StorageBackendInterface = None ) -> None :
245
- """Writes the JSON representation of self to file storage.
246
-
247
- Arguments:
248
- filename: The path to write the file to.
249
- compact: A boolean indicating if the JSON string should be compact
250
- by excluding whitespace.
251
- storage_backend: An object that implements
252
- securesystemslib.storage.StorageBackendInterface. Per default
253
- a (local) FilesystemBackend is used.
254
- Raises:
255
- securesystemslib.exceptions.StorageError:
256
- The file cannot be written.
257
-
258
- """
259
- with tempfile .TemporaryFile () as f :
260
- f .write (self .to_json (compact ).encode ('utf-8' ))
261
- persist_temp_file (f , filename , storage_backend )
262
273
263
274
class Signed :
264
275
"""A base class for the signed part of TUF metadata.
@@ -293,6 +304,8 @@ def __init__(
293
304
raise ValueError (f'version must be < 0, got { version } ' )
294
305
self .version = version
295
306
307
+
308
+ # Deserialization (factories).
296
309
@classmethod
297
310
def from_dict (cls , signed_dict ) -> 'Signed' :
298
311
"""Creates Signed object from its JSON/dict representation. """
@@ -314,17 +327,12 @@ def from_dict(cls, signed_dict) -> 'Signed':
314
327
# that subclass (see e.g. Metadata.from_dict).
315
328
return cls (** signed_dict )
316
329
330
+
331
+ # Serialization.
317
332
def to_canonical_bytes (self ) -> bytes :
318
333
"""Returns the UTF-8 encoded canonical JSON representation of self. """
319
334
return encode_canonical (self .to_dict ()).encode ('UTF-8' )
320
335
321
- def bump_expiration (self , delta : timedelta = timedelta (days = 1 )) -> None :
322
- """Increments the expires attribute by the passed timedelta. """
323
- self .expires += delta
324
-
325
- def bump_version (self ) -> None :
326
- """Increments the metadata version number by 1."""
327
- self .version += 1
328
336
329
337
def to_dict (self ) -> JsonDict :
330
338
"""Returns the JSON-serializable dictionary representation of self. """
@@ -335,6 +343,18 @@ def to_dict(self) -> JsonDict:
335
343
'expires' : self .expires .isoformat () + 'Z'
336
344
}
337
345
346
+
347
+ # Modification.
348
+ def bump_expiration (self , delta : timedelta = timedelta (days = 1 )) -> None :
349
+ """Increments the expires attribute by the passed timedelta. """
350
+ self .expires += delta
351
+
352
+
353
+ def bump_version (self ) -> None :
354
+ """Increments the metadata version number by 1."""
355
+ self .version += 1
356
+
357
+
338
358
class Timestamp (Signed ):
339
359
"""A container for the signed part of timestamp metadata.
340
360
@@ -361,6 +381,8 @@ def __init__(
361
381
# TODO: Add class for meta
362
382
self .meta = meta
363
383
384
+
385
+ # Serialization.
364
386
def to_dict (self ) -> JsonDict :
365
387
"""Returns the JSON-serializable dictionary representation of self. """
366
388
json_dict = super ().to_dict ()
@@ -369,6 +391,8 @@ def to_dict(self) -> JsonDict:
369
391
})
370
392
return json_dict
371
393
394
+
395
+ # Modification.
372
396
def update (self , version : int , length : int , hashes : JsonDict ) -> None :
373
397
"""Assigns passed info about snapshot metadata to meta dict. """
374
398
self .meta ['snapshot.json' ] = {
@@ -411,6 +435,7 @@ def __init__(
411
435
# TODO: Add class for meta
412
436
self .meta = meta
413
437
438
+ # Serialization.
414
439
def to_dict (self ) -> JsonDict :
415
440
"""Returns the JSON-serializable dictionary representation of self. """
416
441
json_dict = super ().to_dict ()
@@ -419,7 +444,8 @@ def to_dict(self) -> JsonDict:
419
444
})
420
445
return json_dict
421
446
422
- # Add or update metadata about the targets metadata.
447
+
448
+ # Modification.
423
449
def update (
424
450
self , rolename : str , version : int , length : Optional [int ] = None ,
425
451
hashes : Optional [JsonDict ] = None ) -> None :
@@ -497,6 +523,7 @@ def __init__(
497
523
self .delegations = delegations
498
524
499
525
526
+ # Serialization.
500
527
def to_dict (self ) -> JsonDict :
501
528
"""Returns the JSON-serializable dictionary representation of self. """
502
529
json_dict = super ().to_dict ()
@@ -506,7 +533,7 @@ def to_dict(self) -> JsonDict:
506
533
})
507
534
return json_dict
508
535
509
- # Add or update metadata about the target .
536
+ # Modification .
510
537
def update (self , filename : str , fileinfo : JsonDict ) -> None :
511
538
"""Assigns passed target file info to meta dict. """
512
539
self .targets [filename ] = fileinfo
0 commit comments