66
77TODO:
88
9- * Add docstrings
10-
119 * Finalize/Document Verify/Sign functions (I am not fully sure about expected
1210 behavior). See
1311 https://github.com/theupdateframework/tuf/pull/1060#issuecomment-660056376
5452# Classes.
5553
5654class Metadata ():
55+ """A container for signed TUF metadata.
56+
57+ Provides methods to (de-)serialize JSON metadata from and to file
58+ storage, and to create and verify signatures.
59+
60+ Attributes:
61+ signed: A subclass of Signed, which has the actual metadata payload,
62+ i.e. one of Targets, Snapshot, Timestamp or Root.
63+
64+ signatures: A list of signatures over the canonical JSON representation
65+ of the value of the signed attribute::
66+
67+ [
68+ {
69+ 'keyid': '<SIGNING KEY KEYID>',
70+ 'sig':' '<SIGNATURE HEX REPRESENTATION>'
71+ },
72+ ...
73+ ]
74+
75+ """
5776 def __init__ (
5877 self , signed : 'Signed' = None , signatures : list = None ) -> None :
5978 # TODO: How much init magic do we want?
6079 self .signed = signed
6180 self .signatures = signatures
6281
6382 def as_dict (self ) -> JsonDict :
83+ """Returns the JSON-serializable dictionary representation of self. """
6484 return {
6585 'signatures' : self .signatures ,
6686 'signed' : self .signed .as_dict ()
6787 }
6888
6989 def as_json (self , compact : bool = False ) -> None :
70- """Returns the optionally compacted JSON representation. """
90+ """Returns the optionally compacted JSON representation of self . """
7191 return json .dumps (
7292 self .as_dict (),
7393 indent = (None if compact else 1 ),
@@ -124,7 +144,7 @@ def read_from_json(
124144 cls , filename : str ,
125145 storage_backend : Optional [StorageBackendInterface ] = None
126146 ) -> 'Metadata' :
127- """Loads JSON-formatted TUF metadata from a file storage.
147+ """Loads JSON-formatted TUF metadata from file storage.
128148
129149 Arguments:
130150 filename: The path to read the file from.
@@ -166,7 +186,7 @@ def read_from_json(
166186 def write_to_json (
167187 self , filename : str , compact : bool = False ,
168188 storage_backend : StorageBackendInterface = None ) -> None :
169- """Writes the JSON representation of the instance to file storage.
189+ """Writes the JSON representation of self to file storage.
170190
171191 Arguments:
172192 filename: The path to write the file to.
@@ -186,6 +206,21 @@ def write_to_json(
186206
187207
188208class Signed :
209+ """A base class for the signed part of TUF metadata.
210+
211+ Objects with base class Signed are usually included in a Metablock object
212+ on the signed attribute. This class provides attributes and methods that
213+ are common for all TUF metadata types (roles).
214+
215+ Attributes:
216+ _type: The metadata type string.
217+ version: The metadata version number.
218+ spec_version: The TUF specification version number (semver) the
219+ metadata format adheres to.
220+ expires: The metadata expiration date in 'YYYY-MM-DDTHH:MM:SSZ' format.
221+ signed_bytes: The UTF-8 encoded canonical JSON representation of self.
222+
223+ """
189224 # NOTE: Signed is a stupid name, because this might not be signed yet, but
190225 # we keep it to match spec terminology (I often refer to this as "payload",
191226 # or "inner metadata")
@@ -220,19 +255,18 @@ def signed_bytes(self) -> bytes:
220255
221256 @property
222257 def expires (self ) -> str :
223- """The expiration property in TUF metadata format."""
224258 return self .__expiration .isoformat () + 'Z'
225259
226260 def bump_expiration (self , delta : timedelta = timedelta (days = 1 )) -> None :
261+ """Increments the expires attribute by the passed timedelta. """
227262 self .__expiration = self .__expiration + delta
228263
229264 def bump_version (self ) -> None :
265+ """Increments the metadata version number by 1."""
230266 self .version += 1
231267
232268 def as_dict (self ) -> JsonDict :
233- # NOTE: The classes should be the single source of truth about metadata
234- # let's define the dict representation here and not in some dubious
235- # build_dict_conforming_to_schema
269+ """Returns the JSON-serializable dictionary representation of self. """
236270 return {
237271 '_type' : self ._type ,
238272 'version' : self .version ,
@@ -246,7 +280,24 @@ def read_from_json(
246280 storage_backend : Optional [StorageBackendInterface ] = None
247281 ) -> Metadata :
248282 signable = load_json_file (filename , storage_backend )
283+ """Loads corresponding JSON-formatted metadata from file storage.
249284
285+ Arguments:
286+ filename: The path to read the file from.
287+ storage_backend: An object that implements
288+ securesystemslib.storage.StorageBackendInterface. Per default
289+ a (local) FilesystemBackend is used.
290+
291+ Raises:
292+ securesystemslib.exceptions.StorageError: The file cannot be read.
293+ securesystemslib.exceptions.Error, ValueError: The metadata cannot
294+ be parsed.
295+
296+ Returns:
297+ A TUF Metadata object whose signed attribute contains an object
298+ of this class.
299+
300+ """
250301 # FIXME: It feels dirty to access signable["signed"]["version"] here in
251302 # order to do this check, and also a bit random (there are likely other
252303 # things to check), but later we don't have the filename anymore. If we
@@ -264,21 +315,40 @@ def read_from_json(
264315
265316
266317class Timestamp (Signed ):
318+ """A container for the signed part of timestamp metadata.
319+
320+ Attributes:
321+ meta: A dictionary that contains information about snapshot metadata::
322+
323+ {
324+ 'snapshot.json': {
325+ 'version': <SNAPSHOT METADATA VERSION NUMBER>,
326+ 'length': <SNAPSHOT METADATA FILE SIZE>, // optional
327+ 'hashes': {
328+ '<HASH ALGO 1>': '<SNAPSHOT METADATA FILE HASH 1>',
329+ '<HASH ALGO 2>': '<SNAPSHOT METADATA FILE HASH 2>',
330+ ...
331+ }
332+ }
333+ }
334+
335+ """
267336 def __init__ (self , meta : JsonDict = None , ** kwargs ) -> None :
268337 super ().__init__ (** kwargs )
269338 # TODO: How much init magic do we want?
270339 # TODO: Is there merit in creating classes for dict fields?
271340 self .meta = meta
272341
273342 def as_dict (self ) -> JsonDict :
343+ """Returns the JSON-serializable dictionary representation of self. """
274344 json_dict = super ().as_dict ()
275345 json_dict .update ({
276346 'meta' : self .meta
277347 })
278348 return json_dict
279349
280- # Update metadata about the snapshot metadata.
281350 def update (self , version : int , length : int , hashes : JsonDict ) -> None :
351+ """Assigns passed info about snapshot metadata to meta dict. """
282352 self .meta ['snapshot.json' ] = {
283353 'version' : version ,
284354 'length' : length ,
@@ -287,13 +357,39 @@ def update(self, version: int, length: int, hashes: JsonDict) -> None:
287357
288358
289359class Snapshot (Signed ):
360+ """A container for the signed part of snapshot metadata.
361+
362+ Attributes:
363+ meta: A dictionary that contains information about targets metadata::
364+
365+ {
366+ 'targets.json': {
367+ 'version': <TARGETS METADATA VERSION NUMBER>,
368+ 'length': <TARGETS METADATA FILE SIZE>, // optional
369+ 'hashes': {
370+ '<HASH ALGO 1>': '<TARGETS METADATA FILE HASH 1>',
371+ '<HASH ALGO 2>': '<TARGETS METADATA FILE HASH 2>',
372+ ...
373+ } // optional
374+ },
375+ '<DELEGATED TARGETS ROLE 1>.json': {
376+ ...
377+ },
378+ '<DELEGATED TARGETS ROLE 2>.json': {
379+ ...
380+ },
381+ ...
382+ }
383+
384+ """
290385 def __init__ (self , meta : JsonDict = None , ** kwargs ) -> None :
291386 # TODO: How much init magic do we want?
292387 # TODO: Is there merit in creating classes for dict fields?
293388 super ().__init__ (** kwargs )
294389 self .meta = meta
295390
296391 def as_dict (self ) -> JsonDict :
392+ """Returns the JSON-serializable dictionary representation of self. """
297393 json_dict = super ().as_dict ()
298394 json_dict .update ({
299395 'meta' : self .meta
@@ -304,6 +400,7 @@ def as_dict(self) -> JsonDict:
304400 def update (
305401 self , rolename : str , version : int , length : Optional [int ] = None ,
306402 hashes : Optional [JsonDict ] = None ) -> None :
403+ """Assigns passed (delegated) targets role info to meta dict. """
307404 metadata_fn = f'{ rolename } .json'
308405
309406 self .meta [metadata_fn ] = {'version' : version }
@@ -315,6 +412,59 @@ def update(
315412
316413
317414class Targets (Signed ):
415+ """A container for the signed part of targets metadata.
416+
417+ Attributes:
418+ targets: A dictionary that contains information about target files::
419+
420+ {
421+ '<TARGET FILE NAME>': {
422+ 'length': <TARGET FILE SIZE>,
423+ 'hashes': {
424+ '<HASH ALGO 1>': '<TARGET FILE HASH 1>',
425+ '<HASH ALGO 2>': '<TARGETS FILE HASH 2>',
426+ ...
427+ },
428+ 'custom': <CUSTOM OPAQUE DICT> // optional
429+ },
430+ ...
431+ }
432+
433+ delegations: A dictionary that contains a list of delegated target
434+ roles and public key store used to verify their metadata
435+ signatures::
436+
437+ {
438+ 'keys' : {
439+ '<KEYID>': {
440+ 'keytype': '<KEY TYPE>',
441+ 'scheme': '<KEY SCHEME>',
442+ 'keyid_hash_algorithms': [
443+ '<HASH ALGO 1>',
444+ '<HASH ALGO 2>'
445+ ...
446+ ],
447+ 'keyval': {
448+ 'public': '<PUBLIC KEY HEX REPRESENTATION>'
449+ }
450+ },
451+ ...
452+ },
453+ 'roles': [
454+ {
455+ 'name': '<ROLENAME>',
456+ 'keyids': ['<SIGNING KEY KEYID>', ...],
457+ 'threshold': <SIGNATURE THRESHOLD>,
458+ 'terminating': <TERMINATING BOOLEAN>,
459+ 'path_hash_prefixes': ['<HEX DIGEST>', ... ], // or
460+ 'paths' : ['PATHPATTERN', ... ],
461+ },
462+ ...
463+ ]
464+ }
465+
466+
467+ """
318468 def __init__ (
319469 self , targets : JsonDict = None , delegations : JsonDict = None ,
320470 ** kwargs ) -> None :
@@ -325,6 +475,7 @@ def __init__(
325475 self .delegations = delegations
326476
327477 def as_dict (self ) -> JsonDict :
478+ """Returns the JSON-serializable dictionary representation of self. """
328479 json_dict = super ().as_dict ()
329480 json_dict .update ({
330481 'targets' : self .targets ,
@@ -334,4 +485,5 @@ def as_dict(self) -> JsonDict:
334485
335486 # Add or update metadata about the target.
336487 def update (self , filename : str , fileinfo : JsonDict ) -> None :
488+ """Assigns passed target file info to meta dict. """
337489 self .targets [filename ] = fileinfo
0 commit comments