From b315aba01874264059673b89868734c76bbad183 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 15 Mar 2019 15:35:14 -0400 Subject: [PATCH 01/15] Replace six classes with one function in formats.py Kills classes MetaFile, RootFile, TargetsFile, MirrorsFile, SnapshotFile, and TimestampFile. They each had an unused from_ method and a used make_ method. They were all additional, unnecessary representations of the same metadata, and it is very important that metadata formats be defined once in the reference implementation, in the schemas that are already used more broadly, in foramts.py. Replaces the classes, their methods, and some associated variables with a single short function called build_dict_conforming_to_schema that takes keyword arguments and builds a dictionary, then checks to make sure that the result conforms to the given schema. This commit shifts repository_lib from use of the old classes to the new function. In later commits, we should use this function more broadly, since it can be of use in all schema construction. There are several TODOs added to the code, mostly for post-#660 tasks. Signed-off-by: Sebastien Awwad --- tuf/formats.py | 259 +++++------------------------------------- tuf/repository_lib.py | 89 ++++++++++++--- 2 files changed, 101 insertions(+), 247 deletions(-) diff --git a/tuf/formats.py b/tuf/formats.py index 78a03a20c2..e6d2cc7834 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -24,8 +24,7 @@ module should be read and understood before tackling this module. 'formats.py' can be broken down into three sections. (1) Schemas and object - matching. (2) Classes that represent Role Metadata and help produce correctly - formatted files. (3) Functions that help produce or verify TUF objects. + matching. (2) Functions that help produce or verify TUF objects. The first section deals with schemas and object matching based on format. There are two ways of checking the format of objects. The first method @@ -49,17 +48,7 @@ the match fails. There are numerous variations of object checking provided by 'formats.py' and 'schema.py'. - The second section deals with the role metadata classes. There are - multiple top-level roles, each with differing metadata formats. - Example: - - root_object = tuf.formats.RootFile.from_metadata(root_metadata_file) - targets_metadata = tuf.formats.TargetsFile.make_metadata(...) - - The input and output of these classes are checked against their respective - schema to ensure correctly formatted metadata. - - The last section contains miscellaneous functions related to the format of + The second section contains miscellaneous functions related to the format of TUF objects. Example: @@ -474,227 +463,46 @@ def make_signable(role_schema): -class MetaFile(object): - """ - - Base class for all metadata file classes. - Classes representing metadata files such as RootFile - and SnapshotFile all inherit from MetaFile. The - __eq__, __ne__, perform 'equal' and 'not equal' comparisons - between Metadata File objects. - """ - - info = {} - - def __eq__(self, other): - return isinstance(other, MetaFile) and self.info == other.info - - __hash__ = None - - def __ne__(self, other): - return not self.__eq__(other) - - - def __getattr__(self, name): - """ - Allow all metafile objects to have their interesting attributes - referred to directly without the info dict. The info dict is just - to be able to do the __eq__ comparison generically. - """ - - if name in self.info: - return self.info[name] - - else: - raise AttributeError(name) - - - -class TimestampFile(MetaFile): - def __init__(self, version, expires, filedict): - self.info = {} - self.info['version'] = version - self.info['expires'] = expires - self.info['meta'] = filedict - - - @staticmethod - def from_metadata(timestamp_metadata): - # Is 'timestamp_metadata' a Timestamp metadata file? - # Raise securesystemslib.exceptions.FormatError if not. - TIMESTAMP_SCHEMA.check_match(timestamp_metadata) - - version = timestamp_metadata['version'] - expires = timestamp_metadata['expires'] - filedict = timestamp_metadata['meta'] - - return TimestampFile(version, expires, filedict) - - - @staticmethod - def make_metadata(version, expiration_date, filedict): - result = {'_type' : 'timestamp'} - result['spec_version'] = tuf.SPECIFICATION_VERSION - result['version'] = version - result['expires'] = expiration_date - result['meta'] = filedict - - # Is 'result' a Timestamp metadata file? - # Raise 'securesystemslib.exceptions.FormatError' if not. - TIMESTAMP_SCHEMA.check_match(result) - - return result - - - -class RootFile(MetaFile): - def __init__(self, version, expires, keys, roles, consistent_snapshot): - self.info = {} - self.info['version'] = version - self.info['expires'] = expires - self.info['keys'] = keys - self.info['roles'] = roles - self.info['consistent_snapshot'] = consistent_snapshot - - @staticmethod - def from_metadata(root_metadata): - # Is 'root_metadata' a Root metadata file? - # Raise 'securesystemslib.exceptions.FormatError' if not. - tuf.formats.ROOT_SCHEMA.check_match(root_metadata) - version = root_metadata['version'] - expires = root_metadata['expires'] - keys = root_metadata['keys'] - roles = root_metadata['roles'] - consistent_snapshot = root_metadata['consistent_snapshot'] - return RootFile(version, expires, keys, roles, consistent_snapshot) +def build_dict_conforming_to_schema(schema, **kwargs): + """ + Given a schema object (for example, TIMESTAMP_SCHEMA from this module) and + a set of keyword arguments, create a dictionary that conforms to the given + schema, using the keyword arguments to define the elements of the new dict. - @staticmethod - def make_metadata(version, expiration_date, keydict, roledict, consistent_snapshot): - result = {'_type' : 'root'} - result['spec_version'] = tuf.SPECIFICATION_VERSION - result['version'] = version - result['expires'] = expiration_date - result['keys'] = keydict - result['roles'] = roledict - result['consistent_snapshot'] = consistent_snapshot - - # Is 'result' a Root metadata file? - # Raise 'securesystemslib.exceptions.FormatError' if not. - ROOT_SCHEMA.check_match(result) - - return result - - - - -class SnapshotFile(MetaFile): - def __init__(self, version, expires, versiondict): - self.info = {} - self.info['version'] = version - self.info['expires'] = expires - self.info['meta'] = versiondict - - - @staticmethod - def from_metadata(snapshot_metadata): - # Is 'snapshot_metadata' a Snapshot metadata file? - # Raise 'securesystemslib.exceptions.FormatError' if not. - SNAPSHOT_SCHEMA.check_match(snapshot_metadata) - - version = snapshot_metadata['version'] - expires = snapshot_metadata['expires'] - versiondict = snapshot_metadata['meta'] - - return SnapshotFile(version, expires, versiondict) - - - @staticmethod - def make_metadata(version, expiration_date, versiondict): - result = {'_type' : 'snapshot'} - result['spec_version'] = tuf.SPECIFICATION_VERSION - result['version'] = version - result['expires'] = expiration_date - result['meta'] = versiondict - - # Is 'result' a Snapshot metadata file? - # Raise 'securesystemslib.exceptions.FormatError' if not. - SNAPSHOT_SCHEMA.check_match(result) - - return result - - - - -class TargetsFile(MetaFile): - def __init__(self, version, expires, filedict=None, delegations=None): - if filedict is None: - filedict = {} - if delegations is None: - delegations = {} - self.info = {} - self.info['version'] = version - self.info['expires'] = expires - self.info['targets'] = filedict - self.info['delegations'] = delegations - - - @staticmethod - def from_metadata(targets_metadata): - # Is 'targets_metadata' a Targets metadata file? - # Raise securesystemslib.exceptions.FormatError if not. - tuf.formats.TARGETS_SCHEMA.check_match(targets_metadata) - - version = targets_metadata['version'] - expires = targets_metadata['expires'] - filedict = targets_metadata.get('targets') - delegations = targets_metadata.get('delegations') - - return TargetsFile(version, expires, filedict, delegations) - - - @staticmethod - def make_metadata(version, expiration_date, filedict=None, delegations=None): - if filedict is None and delegations is None: - raise securesystemslib.exceptions.Error('We don\'t allow completely' - ' empty targets metadata.') - - result = {'_type' : 'targets'} - result['spec_version'] = tuf.SPECIFICATION_VERSION - result['version'] = version - result['expires'] = expiration_date - result['targets'] = {} - - if filedict is not None: - result['targets'] = filedict - if delegations is not None: - result['delegations'] = delegations + Checks the result to make sure that it conforms to the given schema, raising + an error if not. - # Is 'result' a Targets metadata file? - # Raise 'securesystemslib.exceptions.FormatError' if not. - tuf.formats.TARGETS_SCHEMA.check_match(result) + Returns the new dict conforming to the schema if there are no problems. + """ - return result + # Check that schema supports a check_match call. + # Duck typing version of this check: + if not hasattr(schema, 'check_match'): + raise ValueError( + 'The given "schema" does not seem to be a schema. It has no ' + '"check_match" method. Given schema: ' + repr(schema)) + # # Strict typing version of this check: + # # Check that schema_name is a SCHEMA.Object. + # if not isinstance(schema, schema.Schema): + # raise ValueError( + # 'The first argument must be a schema.Schema object, but is not. ' + # 'Given schema: ' + repr(schema)) + # The return value. + d = {} -class MirrorsFile(MetaFile): - def __init__(self, version, expires): - self.info = {} - self.info['version'] = version - self.info['expires'] = expires + for key, value in kwargs.items(): + d[key] = value + schema.check_match(d) - @staticmethod - def from_metadata(mirrors_metadata): - raise NotImplementedError + return d - @staticmethod - def make_metadata(): - raise NotImplementedError @@ -706,15 +514,6 @@ def make_metadata(): 'timestamp' : TIMESTAMP_SCHEMA, 'mirrors' : MIRRORLIST_SCHEMA} -# A dict holding the recognized class names for the top-level roles. -# That is, the role classes listed in this module (e.g., class TargetsFile()). -ROLE_CLASSES_BY_TYPE = { - 'Root' : RootFile, - 'Targets' : TargetsFile, - 'Snapshot' : SnapshotFile, - 'Timestamp' : TimestampFile, - 'Mirrors' : MirrorsFile} - def datetime_to_unix_timestamp(datetime_object): diff --git a/tuf/repository_lib.py b/tuf/repository_lib.py index a2b56567fe..5ff0a60f00 100755 --- a/tuf/repository_lib.py +++ b/tuf/repository_lib.py @@ -1316,11 +1316,22 @@ def generate_root_metadata(version, expiration_date, consistent_snapshot, role_metadata = tuf.formats.make_role_metadata(keyids, role_threshold) roledict[rolename] = role_metadata - # Generate the root metadata object. - root_metadata = tuf.formats.RootFile.make_metadata(version, expiration_date, - keydict, roledict, consistent_snapshot) - - return root_metadata + # Use generalized build_dict_conforming_to_schema func to produce a dict that + # contains all the appropriate information for this type of metadata, + # checking that the result conforms to the appropriate schema. + # TODO: Later, probably after the rewrite for TUF Issue #660, generalize + # further, upward, by replacing generate_targets_metadata, + # generate_root_metadata, etc. with one function that generates + # metadata, possibly rolling that upwards into the calling function. + # There are very few things that really need to be done differently. + return tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + version=version, + expires=expiration_date, + keys=keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot) @@ -1442,10 +1453,34 @@ def generate_targets_metadata(targets_directory, target_files, version, shutil.copyfile(target_path, digest_target) # Generate the targets metadata object. - targets_metadata = tuf.formats.TargetsFile.make_metadata(version, - expiration_date, filedict, delegations) - - return targets_metadata + # Use generalized build_dict_conforming_to_schema func to produce a dict that + # contains all the appropriate information for targets metadata, + # checking that the result conforms to the appropriate schema. + # TODO: Later, probably after the rewrite for TUF Issue #660, generalize + # further, upward, by replacing generate_targets_metadata, + # generate_root_metadata, etc. with one function that generates + # metadata, possibly rolling that upwards into the calling function. + # There are very few things that really need to be done differently. + if delegations is not None: + return tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TARGETS_SCHEMA, + _type='targets', + version=version, + expires=expiration_date, + targets=filedict, + delegations=delegations) + else: + return tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TARGETS_SCHEMA, + _type='targets', + version=version, + expires=expiration_date, + targets=filedict) + # TODO: As an alternative to the odd if/else above where we decide whether or + # not to include the delegations argument based on whether or not it is + # None, consider instead adding a check in + # build_dict_conforming_to_schema that skips a keyword if that keyword + # is optional in the schema and the value passed in is set to None.... @@ -1561,10 +1596,20 @@ def generate_snapshot_metadata(metadata_directory, version, expiration_date, ' extension: ' + metadata_filename) # Generate the Snapshot metadata object. - snapshot_metadata = tuf.formats.SnapshotFile.make_metadata(version, - expiration_date, fileinfodict) - - return snapshot_metadata + # Use generalized build_dict_conforming_to_schema func to produce a dict that + # contains all the appropriate information for snapshot metadata, + # checking that the result conforms to the appropriate schema. + # TODO: Later, probably after the rewrite for TUF Issue #660, generalize + # further, upward, by replacing generate_targets_metadata, + # generate_root_metadata, etc. with one function that generates + # metadata, possibly rolling that upwards into the calling function. + # There are very few things that really need to be done differently. + return tuf.formats.build_dict_conforming_to_schema( + tuf.formats.SNAPSHOT_SCHEMA, + _type='snapshot', + version=version, + expires=expiration_date, + meta=fileinfodict) @@ -1629,10 +1674,20 @@ def generate_timestamp_metadata(snapshot_filename, version, expiration_date, # excluded. # Generate the timestamp metadata object. - timestamp_metadata = tuf.formats.TimestampFile.make_metadata(version, - expiration_date, snapshot_fileinfo) - - return timestamp_metadata + # Use generalized build_dict_conforming_to_schema func to produce a dict that + # contains all the appropriate information for timestamp metadata, + # checking that the result conforms to the appropriate schema. + # TODO: Later, probably after the rewrite for TUF Issue #660, generalize + # further, upward, by replacing generate_targets_metadata, + # generate_root_metadata, etc. with one function that generates + # metadata, possibly rolling that upwards into the calling function. + # There are very few things that really need to be done differently. + return tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + _type='timestamp', + version=version, + expires=expiration_date, + meta=snapshot_fileinfo) From b4d274f527e1f636b9fc1cf62697ce5c4db3b9fa Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 15 Mar 2019 17:19:14 -0400 Subject: [PATCH 02/15] Indicate the TUF spec version we conform to, in a tuf variable Add a tuf-level variable in tuf/__init__.py indicating the version of the TUF specification that the code in this repository is intended and expected to conform to. This will be used when writing metadata. Signed-off-by: Sebastien Awwad --- tuf/repository_lib.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tuf/repository_lib.py b/tuf/repository_lib.py index 5ff0a60f00..1ed1ac8f8a 100755 --- a/tuf/repository_lib.py +++ b/tuf/repository_lib.py @@ -1327,6 +1327,7 @@ def generate_root_metadata(version, expiration_date, consistent_snapshot, return tuf.formats.build_dict_conforming_to_schema( tuf.formats.ROOT_SCHEMA, _type='root', + spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, keys=keydict, @@ -1465,6 +1466,7 @@ def generate_targets_metadata(targets_directory, target_files, version, return tuf.formats.build_dict_conforming_to_schema( tuf.formats.TARGETS_SCHEMA, _type='targets', + spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, targets=filedict, @@ -1473,6 +1475,7 @@ def generate_targets_metadata(targets_directory, target_files, version, return tuf.formats.build_dict_conforming_to_schema( tuf.formats.TARGETS_SCHEMA, _type='targets', + spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, targets=filedict) @@ -1607,6 +1610,7 @@ def generate_snapshot_metadata(metadata_directory, version, expiration_date, return tuf.formats.build_dict_conforming_to_schema( tuf.formats.SNAPSHOT_SCHEMA, _type='snapshot', + spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, meta=fileinfodict) @@ -1684,6 +1688,7 @@ def generate_timestamp_metadata(snapshot_filename, version, expiration_date, # There are very few things that really need to be done differently. return tuf.formats.build_dict_conforming_to_schema( tuf.formats.TIMESTAMP_SCHEMA, + spec_version=tuf.SPECIFICATION_VERSION, _type='timestamp', version=version, expires=expiration_date, From fca27122ef86715d14eb6b79b43f2ad72a330c1d Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 15 Mar 2019 17:16:55 -0400 Subject: [PATCH 03/15] Add specification version to TIMESTAMP_SCHEMA It was excluded from the Timestamp schema definition in error. In the process of making metadata writing use the Timestamp schema strictly, this bug was discovered. Metadata previously written included specification version, but the schema check did not. Signed-off-by: Sebastien Awwad --- tuf/formats.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tuf/formats.py b/tuf/formats.py index e6d2cc7834..aeeef65a6b 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -345,6 +345,7 @@ TIMESTAMP_SCHEMA = SCHEMA.Object( object_name = 'TIMESTAMP_SCHEMA', _type = SCHEMA.String('timestamp'), + spec_version = SPECIFICATION_VERSION_SCHEMA, version = securesystemslib.formats.METADATAVERSION_SCHEMA, expires = securesystemslib.formats.ISO8601_DATETIME_SCHEMA, meta = securesystemslib.formats.FILEDICT_SCHEMA) From ce21d8d0a0c4b74be7082e23bee5715b9d2393a7 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 15 Mar 2019 17:22:44 -0400 Subject: [PATCH 04/15] Destroy get_role_class in formats.py (not needed) It pertains to now-deleted metadata classes. Signed-off-by: Sebastien Awwad --- tuf/formats.py | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/tuf/formats.py b/tuf/formats.py index aeeef65a6b..7a9a8e5638 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -828,52 +828,6 @@ def make_role_metadata(keyids, threshold, name=None, paths=None, -def get_role_class(expected_rolename): - """ - - Return the role class corresponding to - 'expected_rolename'. The role name returned - by expected_meta_rolename() should be the name - passed as an argument to this function. If - 'expected_rolename' is 'Root', the class - RootFile is returned. - - - expected_rolename: - The role name used to determine which role class - to return. - - - securesystemslib.exceptions.FormatError, if 'expected_rolename' is not a - supported role. - - - None. - - - The class corresponding to 'expected_rolename'. - E.g., 'Snapshot' as an argument to this function causes - SnapshotFile' to be returned. - """ - - # Does 'expected_rolename' have the correct type? - # This check ensures 'expected_rolename' conforms to - # 'securesystemslib.formats.NAME_SCHEMA'. - # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. - securesystemslib.formats.NAME_SCHEMA.check_match(expected_rolename) - - try: - role_class = ROLE_CLASSES_BY_TYPE[expected_rolename] - - except KeyError: - raise securesystemslib.exceptions.FormatError(repr(expected_rolename) + ' ' - 'not supported.') - - else: - return role_class - - - def expected_meta_rolename(meta_rolename): """ From e7c822954bed7e9b01aa4a88aa46dbe13659618c Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 15 Mar 2019 17:23:42 -0400 Subject: [PATCH 05/15] Update testing following MetaFile(etc) class removals Testing will now use (and test) build_dict_conforming_to_schema. Signed-off-by: Sebastien Awwad --- tests/test_formats.py | 429 +++++++++++++++++++++++++----------------- tests/test_keydb.py | 24 ++- tests/test_roledb.py | 25 ++- 3 files changed, 299 insertions(+), 179 deletions(-) diff --git a/tests/test_formats.py b/tests/test_formats.py index af97af353d..f7caa9ffd4 100755 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -286,69 +286,76 @@ def test_schemas(self): - def test_MetaFile(self): - # Test conditions for instantiations of a class that inherits from - # 'tuf.formats.MetaFile'. - class NewMetadataFile(tuf.formats.MetaFile): - def __init__(self, version, expires): - self.info = {} - self.info['version'] = version - self.info['expires'] = expires - metadata = NewMetadataFile(123, 456) - metadata2 = NewMetadataFile(123, 456) - metadata3 = NewMetadataFile(333, 333) - # Test the comparison operators. - self.assertTrue(metadata == metadata2) - self.assertFalse(metadata != metadata2) - self.assertFalse(metadata == metadata3) + def test_build_dict_conforming_to_schema(self): + # Test construction of a few metadata formats using + # build_dict_conforming_to_schema(). - # Test the 'getattr' method. - self.assertEqual(123, getattr(metadata, 'version')) - self.assertRaises(AttributeError, getattr, metadata, 'bad') - - - - def test_TimestampFile(self): - # Test conditions for valid instances of 'tuf.formats.TimestampFile'. + # Try building Timestamp metadata. + spec_version = tuf.SPECIFICATION_VERSION version = 8 length = 88 hashes = {'sha256': '3c7fe3eeded4a34'} expires = '1985-10-21T13:20:00Z' filedict = {'snapshot.json': {'length': length, 'hashes': hashes}} - make_metadata = tuf.formats.TimestampFile.make_metadata - from_metadata = tuf.formats.TimestampFile.from_metadata - TIMESTAMP_SCHEMA = tuf.formats.TIMESTAMP_SCHEMA - - self.assertTrue(TIMESTAMP_SCHEMA.matches(make_metadata(version, expires, - filedict))) - metadata = make_metadata(version, expires, filedict) - self.assertTrue(isinstance(from_metadata(metadata), tuf.formats.TimestampFile)) - - # Test conditions for invalid arguments. + self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + _type='timestamp', + spec_version=spec_version, + version=version, + expires=expires, + meta=filedict))) + + # Try test arguments for invalid Timestamp creation. + bad_spec_version = 123 bad_version = 'eight' bad_expires = '2000' bad_filedict = 123 - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, bad_version, - expires, filedict) - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, version, - bad_expires, filedict) - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, version, - expires, bad_filedict) - - self.assertRaises(securesystemslib.exceptions.FormatError, from_metadata, 123) - - - - - - def test_RootFile(self): - # Test conditions for valid instances of 'tuf.formats.RootFile'. - version = 8 + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + _type='timestamp', + spec_version=bad_spec_version, + version=version, + expires=expires, + meta=filedict) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + _type='timestamp', + spec_version=spec_version, + version=bad_version, + expires=expires, + meta=filedict) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + _type='timestamp', + spec_version=spec_version, + version=version, + expires=bad_expires, + meta=filedict) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + _type='timestamp', + spec_version=spec_version, + version=version, + expires=expires, + meta=bad_filedict) + + with self.assertRaises(ValueError): + tuf.formats.build_dict_conforming_to_schema(123) + + + # Try building Root metadata. consistent_snapshot = False - expires = '1985-10-21T13:20:00Z' keydict = {'123abc': {'keytype': 'rsa', 'scheme': 'rsassa-pss-sha256', @@ -359,70 +366,153 @@ def test_RootFile(self): 'threshold': 1, 'paths': ['path1/', 'path2']}} - make_metadata = tuf.formats.RootFile.make_metadata - from_metadata = tuf.formats.RootFile.from_metadata - ROOT_SCHEMA = tuf.formats.ROOT_SCHEMA - self.assertTrue(ROOT_SCHEMA.matches(make_metadata(version, expires, - keydict, roledict, consistent_snapshot))) - metadata = make_metadata(version, expires, keydict, roledict, - consistent_snapshot) - self.assertTrue(isinstance(from_metadata(metadata), tuf.formats.RootFile)) + self.assertTrue(tuf.formats.ROOT_SCHEMA.matches( + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + spec_version=spec_version, + version=version, + expires=expires, + keys=keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot))) - # Test conditions for invalid arguments. - bad_version = '8' - bad_expires = 'eight' + + # Additional test arguments for invalid Root creation. bad_keydict = 123 bad_roledict = 123 - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, - bad_version, expires, keydict, roledict, consistent_snapshot) - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, - version, bad_expires, keydict, roledict, consistent_snapshot) - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, - version, expires, bad_keydict, roledict, consistent_snapshot) - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, - version, expires, keydict, bad_roledict, consistent_snapshot) - - self.assertRaises(securesystemslib.exceptions.FormatError, from_metadata, 'bad') - - - - def test_SnapshotFile(self): - # Test conditions for valid instances of 'tuf.formats.SnapshotFile'. - version = 8 - expires = '1985-10-21T13:20:00Z' + # TODO: Later on, write a test looper that takes pairs of key-value args + # to substitute in on each run to shorten this.... There's a lot of + # test code that looks like this, and it'd be easier to use a looper. + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + spec_version=bad_spec_version, + version=version, + expires=expires, + keys=keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + spec_version=spec_version, + version=bad_version, + expires=expires, + keys=keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + spec_version=spec_version, + version=version, + expires=bad_expires, + keys=keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + spec_version=spec_version, + version=version, + expires=expires, + keys=bad_keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + spec_version=spec_version, + version=version, + expires=expires, + keys=keydict, + roles=bad_roledict, + consistent_snapshot=consistent_snapshot) + + with self.assertRaises(TypeError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, 'bad') + + with self.assertRaises(ValueError): + tuf.formats.build_dict_conforming_to_schema( + 'bad', + _type='root', + spec_version=spec_version, + version=version, + expires=expires, + keys=keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot) + + + + # Try building Snapshot metadata. versiondict = {'targets.json' : {'version': version}} - make_metadata = tuf.formats.SnapshotFile.make_metadata - from_metadata = tuf.formats.SnapshotFile.from_metadata - SNAPSHOT_SCHEMA = tuf.formats.SNAPSHOT_SCHEMA - - self.assertTrue(SNAPSHOT_SCHEMA.matches(make_metadata(version, expires, - versiondict))) - metadata = make_metadata(version, expires, versiondict) - self.assertTrue(isinstance(from_metadata(metadata), tuf.formats.SnapshotFile)) + self.assertTrue(tuf.formats.SNAPSHOT_SCHEMA.matches( + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.SNAPSHOT_SCHEMA, + _type='snapshot', + spec_version=spec_version, + version=version, + expires=expires, + meta=versiondict))) - # Test conditions for invalid arguments. - bad_version = '8' - bad_expires = '2000' + # Additional test arguments for invalid Snapshot creation. bad_versiondict = 123 - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, version, - expires, bad_versiondict) - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, bad_version, expires, - versiondict) - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, version, bad_expires, - bad_versiondict) - - self.assertRaises(securesystemslib.exceptions.FormatError, from_metadata, 123) - - - - def test_TargetsFile(self): - # Test conditions for valid instances of 'tuf.formats.TargetsFile'. - version = 8 - expires = '1985-10-21T13:20:00Z' + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.SNAPSHOT_SCHEMA, + _type='snapshot', + spec_version=bad_spec_version, + version=version, + expires=expires, + meta=versiondict) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.SNAPSHOT_SCHEMA, + _type='snapshot', + spec_version=spec_version, + version=bad_version, + expires=expires, + meta=versiondict) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.SNAPSHOT_SCHEMA, + _type='snapshot', + spec_version=spec_version, + version=version, + expires=bad_expires, + meta=versiondict) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.SNAPSHOT_SCHEMA, + _type='snapshot', + spec_version=spec_version, + version=version, + expires=expires, + meta=bad_versiondict) + + + + # Try building Targets metadata. filedict = {'metadata/targets.json': {'length': 1024, 'hashes': {'sha256': 'ABCD123'}, 'custom': {'type': 'metadata'}}} @@ -434,61 +524,73 @@ def test_TargetsFile(self): 'roles': [{'name': 'root', 'keyids': ['123abc'], 'threshold': 1, 'paths': ['path1/', 'path2']}]} - make_metadata = tuf.formats.TargetsFile.make_metadata - from_metadata = tuf.formats.TargetsFile.from_metadata - TARGETS_SCHEMA = tuf.formats.TARGETS_SCHEMA - - self.assertTrue(TARGETS_SCHEMA.matches(make_metadata(version, expires, - filedict, delegations))) - self.assertTrue(TARGETS_SCHEMA.matches(make_metadata(version, expires, filedict))) - - metadata = make_metadata(version, expires, filedict, delegations) - self.assertTrue(isinstance(from_metadata(metadata), tuf.formats.TargetsFile)) - - # Test conditions for different combination of required arguments (i.e., - # a filedict or delegations argument is required.) - metadata = make_metadata(version, expires, filedict) - self.assertTrue(isinstance(from_metadata(metadata), tuf.formats.TargetsFile)) - - metadata = make_metadata(version, expires, delegations=delegations) - self.assertTrue(isinstance(from_metadata(metadata), tuf.formats.TargetsFile)) - - # Directly instantiating a TargetsFile object. - tuf.formats.TargetsFile(version, expires) - tuf.formats.TargetsFile(version, expires, filedict) - tuf.formats.TargetsFile(version, expires, delegations=delegations) - # Test conditions for invalid arguments. - bad_version = 'eight' - bad_expires = '2000' + self.assertTrue(tuf.formats.TARGETS_SCHEMA.matches( + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TARGETS_SCHEMA, + _type='targets', + spec_version=spec_version, + version=version, + expires=expires, + targets=filedict, + delegations=delegations))) + + # Try with no delegations included (should work, since they're optional). + self.assertTrue(tuf.formats.TARGETS_SCHEMA.matches( + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TARGETS_SCHEMA, + _type='targets', + spec_version=spec_version, + version=version, + expires=expires, + targets=filedict))) + + + # Additional test arguments for invalid Targets creation. bad_filedict = 123 bad_delegations = 123 - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, bad_version, expires, - filedict, delegations) - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, version, bad_expires, - filedict, delegations) - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, version, expires, - bad_filedict, delegations) - self.assertRaises(securesystemslib.exceptions.FormatError, make_metadata, version, expires, - filedict, bad_delegations) - self.assertRaises(securesystemslib.exceptions.Error, make_metadata, version, expires) - - self.assertRaises(securesystemslib.exceptions.FormatError, from_metadata, 123) - - - def test_MirrorsFile(self): - # Test normal case. - version = 8 - expires = '1985-10-21T13:20:00Z' - - mirrors_file = tuf.formats.MirrorsFile(version, expires) + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TARGETS_SCHEMA, + _type='targets', + spec_version=spec_version, + version=bad_version, + expires=expires, + targets=filedict, + delegations=delegations) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TARGETS_SCHEMA, + _type='targets', + spec_version=spec_version, + version=version, + expires=bad_expires, + targets=filedict, + delegations=delegations) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TARGETS_SCHEMA, + _type='targets', + spec_version=spec_version, + version=version, + expires=expires, + targets=bad_filedict, + delegations=delegations) + + with self.assertRaises(securesystemslib.exceptions.FormatError): + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TARGETS_SCHEMA, + _type='targets', + spec_version=spec_version, + version=version, + expires=expires, + targets=filedict, + delegations=bad_delegations) - make_metadata = tuf.formats.MirrorsFile.make_metadata - from_metadata = tuf.formats.MirrorsFile.from_metadata - self.assertRaises(NotImplementedError, make_metadata) - self.assertRaises(NotImplementedError, from_metadata, mirrors_file) @@ -659,22 +761,7 @@ def test_make_role_metadata(self): # 'paths' and 'path_hash_prefixes' cannot both be specified. self.assertRaises(securesystemslib.exceptions.FormatError, make_role, keyids, threshold, name, paths, path_hash_prefixes) - def test_get_role_class(self): - # Test conditions for valid arguments. - get_role_class = tuf.formats.get_role_class - - self.assertEqual(tuf.formats.RootFile, get_role_class('Root')) - self.assertEqual(tuf.formats.TargetsFile, get_role_class('Targets')) - self.assertEqual(tuf.formats.SnapshotFile, get_role_class('Snapshot')) - self.assertEqual(tuf.formats.TimestampFile, get_role_class('Timestamp')) - self.assertEqual(tuf.formats.MirrorsFile, get_role_class('Mirrors')) - # Test conditions for invalid arguments. - self.assertRaises(securesystemslib.exceptions.FormatError, get_role_class, 'role') - self.assertRaises(securesystemslib.exceptions.FormatError, get_role_class, 'ROLE') - self.assertRaises(securesystemslib.exceptions.FormatError, get_role_class, 'abcd') - self.assertRaises(securesystemslib.exceptions.FormatError, get_role_class, 123) - self.assertRaises(securesystemslib.exceptions.FormatError, get_role_class, tuf.formats.RootFile) @@ -692,7 +779,7 @@ def test_expected_meta_rolename(self): # Test conditions for invalid arguments. self.assertRaises(securesystemslib.exceptions.FormatError, expected_rolename, 123) - self.assertRaises(securesystemslib.exceptions.FormatError, expected_rolename, tuf.formats.RootFile) + self.assertRaises(securesystemslib.exceptions.FormatError, expected_rolename, tuf.formats.ROOT_SCHEMA) self.assertRaises(securesystemslib.exceptions.FormatError, expected_rolename, True) @@ -719,7 +806,7 @@ def test_check_signable_object_format(self): check_signable = tuf.formats.check_signable_object_format self.assertRaises(securesystemslib.exceptions.FormatError, check_signable, 'root') self.assertRaises(securesystemslib.exceptions.FormatError, check_signable, 123) - self.assertRaises(securesystemslib.exceptions.FormatError, check_signable, tuf.formats.RootFile) + self.assertRaises(securesystemslib.exceptions.FormatError, check_signable, tuf.formats.ROOT_SCHEMA) self.assertRaises(securesystemslib.exceptions.FormatError, check_signable, True) saved_type = root['signed']['_type'] @@ -758,7 +845,7 @@ def test_encode_canonical(self): self.assertEqual('[1,2,3]', ''.join(result)) # Test conditions for invalid arguments. - self.assertRaises(securesystemslib.exceptions.FormatError, encode, tuf.formats.RootFile) + self.assertRaises(securesystemslib.exceptions.FormatError, encode, tuf.formats.ROOT_SCHEMA) self.assertRaises(securesystemslib.exceptions.FormatError, encode, 8.0) self.assertRaises(securesystemslib.exceptions.FormatError, encode, {"x": 8.0}) self.assertRaises(securesystemslib.exceptions.FormatError, encode, 8.0, output) diff --git a/tests/test_keydb.py b/tests/test_keydb.py index 51969a8741..0630442508 100755 --- a/tests/test_keydb.py +++ b/tests/test_keydb.py @@ -315,8 +315,16 @@ def test_create_keydb_from_root_metadata(self): consistent_snapshot = False expires = '1985-10-21T01:21:00Z' - root_metadata = tuf.formats.RootFile.make_metadata(version, expires, - keydict, roledict, consistent_snapshot) + root_metadata = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + spec_version='1.0', + version=version, + expires=expires, + keys=keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot) + self.assertEqual(None, tuf.keydb.create_keydb_from_root_metadata(root_metadata)) tuf.keydb.create_keydb_from_root_metadata(root_metadata) @@ -367,8 +375,16 @@ def test_create_keydb_from_root_metadata(self): version = 8 expires = '1985-10-21T01:21:00Z' - root_metadata = tuf.formats.RootFile.make_metadata(version, expires, - keydict, roledict, consistent_snapshot) + root_metadata = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + spec_version='1.0', + version=version, + expires=expires, + keys=keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot) + self.assertEqual(None, tuf.keydb.create_keydb_from_root_metadata(root_metadata)) # Ensure only 'keyid2' was added to the keydb database. 'keyid' and diff --git a/tests/test_roledb.py b/tests/test_roledb.py index 9b296e77fc..ebb33a20fe 100755 --- a/tests/test_roledb.py +++ b/tests/test_roledb.py @@ -546,8 +546,16 @@ def test_create_roledb_from_root_metadata(self): consistent_snapshot = False expires = '1985-10-21T01:21:00Z' - root_metadata = tuf.formats.RootFile.make_metadata(version, - expires, keydict, roledict, consistent_snapshot) + root_metadata = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + spec_version='1.0', + version=version, + expires=expires, + keys=keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot) + self.assertEqual(None, tuf.roledb.create_roledb_from_root_metadata(root_metadata)) @@ -592,8 +600,17 @@ def test_create_roledb_from_root_metadata(self): # Generate 'root_metadata' to verify that 'release' and 'root' are added # to the role database. - root_metadata = tuf.formats.RootFile.make_metadata(version, - expires, keydict, roledict, consistent_snapshot) + + root_metadata = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROOT_SCHEMA, + _type='root', + spec_version='1.0', + version=version, + expires=expires, + keys=keydict, + roles=roledict, + consistent_snapshot=consistent_snapshot) + self.assertEqual(None, tuf.roledb.create_roledb_from_root_metadata(root_metadata)) From 47e05f8b960b04502dea1277f1e86c15db29c9ed Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 29 Mar 2019 15:13:58 -0400 Subject: [PATCH 06/15] Kill another partially-used metadata writing func tuf.formats.make_role_metadata concerned itself with exclusivity checks for paths and path_hash_prefixes, but no code actually used it for relevant data. It's yet another custom metadata writer replaced by build_dict_conforming_to_schema. Signed-off-by: Sebastien Awwad --- tests/test_formats.py | 45 --------------------------- tuf/formats.py | 72 ------------------------------------------- tuf/repository_lib.py | 8 +++-- 3 files changed, 6 insertions(+), 119 deletions(-) diff --git a/tests/test_formats.py b/tests/test_formats.py index f7caa9ffd4..a36868ee65 100755 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -718,51 +718,6 @@ def test_make_versioninfo(self): - def test_make_role_metadata(self): - # Test conditions for valid arguments. - keyids = ['123abc', 'abc123'] - threshold = 2 - paths = ['path1/', 'path2'] - path_hash_prefixes = ['000', '003'] - name = '123' - - ROLE_SCHEMA = tuf.formats.ROLE_SCHEMA - make_role = tuf.formats.make_role_metadata - - self.assertTrue(ROLE_SCHEMA.matches(make_role(keyids, threshold))) - self.assertTrue(ROLE_SCHEMA.matches(make_role(keyids, threshold, name=name))) - self.assertTrue(ROLE_SCHEMA.matches(make_role(keyids, threshold, paths=paths))) - self.assertTrue(ROLE_SCHEMA.matches(make_role(keyids, threshold, name=name, paths=paths))) - self.assertTrue(ROLE_SCHEMA.matches(make_role(keyids, threshold, name=name, - path_hash_prefixes=path_hash_prefixes))) - - # Test conditions for invalid arguments. - bad_keyids = 'bad' - bad_threshold = 'bad' - bad_paths = 'bad' - bad_name = 123 - - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, bad_keyids, threshold) - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, keyids, bad_threshold) - - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, bad_keyids, threshold, paths=paths) - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, keyids, bad_threshold, paths=paths) - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, keyids, threshold, paths=bad_paths) - - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, bad_keyids, threshold, name=name) - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, keyids, bad_threshold, name=name) - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, keyids, threshold, name=bad_name) - - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, bad_keyids, threshold, name=name, paths=paths) - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, keyids, bad_threshold, name=name, paths=paths) - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, keyids, threshold, name=bad_name, paths=paths) - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, keyids, threshold, name=name, paths=bad_paths) - - # 'paths' and 'path_hash_prefixes' cannot both be specified. - self.assertRaises(securesystemslib.exceptions.FormatError, make_role, keyids, threshold, name, paths, path_hash_prefixes) - - - def test_expected_meta_rolename(self): diff --git a/tuf/formats.py b/tuf/formats.py index 7a9a8e5638..32444f0caa 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -756,78 +756,6 @@ def make_versioninfo(version_number): -def make_role_metadata(keyids, threshold, name=None, paths=None, - path_hash_prefixes=None): - """ - - Create a dictionary conforming to 'tuf.formats.ROLE_SCHEMA', - representing the role with 'keyids', 'threshold', and 'paths' - as field values. 'paths' is optional (i.e., used only by the - 'Target' role). - - - keyids: a list of key ids. - - threshold: - An integer denoting the number of required keys - for the signing role. - - name: - A string that is the name of this role. - - paths: - The 'Target' role stores the paths of target files - in its metadata file. 'paths' is a list of - file paths. - - path_hash_prefixes: - The 'Target' role stores the paths of target files in its metadata file. - 'path_hash_prefixes' is a succint way to describe a set of paths to - target files. - - - securesystemslib.exceptions.FormatError, if the returned role meta is - formatted incorrectly. - - - If any of the arguments do not have a proper format, a - securesystemslib.exceptions.FormatError exception is raised when the - 'ROLE_SCHEMA' dict is created. - - - A properly formatted role meta dict, conforming to - 'ROLE_SCHEMA'. - """ - - role_meta = {} - role_meta['keyids'] = keyids - role_meta['threshold'] = threshold - - if name is not None: - role_meta['name'] = name - - # According to the specification, the 'paths' and 'path_hash_prefixes' must - # be mutually exclusive. However, at the time of writing we do not always - # ensure that this is the case with the schema checks (see #83). Therefore, - # we must do it for ourselves. - - if paths is not None and path_hash_prefixes is not None: - raise securesystemslib.exceptions.FormatError('Both "paths" and' - ' "path_hash_prefixes" are specified.') - - if path_hash_prefixes is not None: - role_meta['path_hash_prefixes'] = path_hash_prefixes - elif paths is not None: - role_meta['paths'] = paths - - # Does 'role_meta' have the correct type? - # This check ensures 'role_meta' conforms to tuf.formats.ROLE_SCHEMA. - ROLE_SCHEMA.check_match(role_meta) - - return role_meta - - - def expected_meta_rolename(meta_rolename): """ diff --git a/tuf/repository_lib.py b/tuf/repository_lib.py index 1ed1ac8f8a..2e3346b8d9 100755 --- a/tuf/repository_lib.py +++ b/tuf/repository_lib.py @@ -1311,9 +1311,13 @@ def generate_root_metadata(version, expiration_date, consistent_snapshot, # Add the loaded keyid for the role being processed. keyids.append(keyid) - # Generate and store the role data belonging to the processed role. + # Generate the authentication information Root establishes for each + # top-level role. role_threshold = tuf.roledb.get_role_threshold(rolename, repository_name) - role_metadata = tuf.formats.make_role_metadata(keyids, role_threshold) + role_metadata = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, + keyids=keyids, + threshold=role_threshold) roledict[rolename] = role_metadata # Use generalized build_dict_conforming_to_schema func to produce a dict that From 491577c2828e0f52f7fa78a6539dbd953582a2ad Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 29 Mar 2019 15:18:07 -0400 Subject: [PATCH 07/15] Perform deep copies in build_dict_conforming_to_schema Make a copy of the provided fields so that the caller's provided values do not change when the returned values are changed. Signed-off-by: Sebastien Awwad --- tuf/formats.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tuf/formats.py b/tuf/formats.py index 32444f0caa..9890ec0cca 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -67,6 +67,7 @@ import calendar import datetime import time +import copy import tuf @@ -493,15 +494,17 @@ def build_dict_conforming_to_schema(schema, **kwargs): # 'The first argument must be a schema.Schema object, but is not. ' # 'Given schema: ' + repr(schema)) - # The return value. - d = {} + # Make a copy of the provided fields so that the caller's provided values + # do not change when the returned values are changed. + dictionary = copy.deepcopy(kwargs) - for key, value in kwargs.items(): - d[key] = value - schema.check_match(d) - return d + + # If what we produce does not match the provided schema, raise a FormatError. + schema.check_match(dictionary) + + return dictionary From 7ecf522698ae38e1eb26ba33de945981c2248665 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 29 Mar 2019 15:19:23 -0400 Subject: [PATCH 08/15] Automatically fill _type and spec_version in build_dict_c... in tuf.formats.build_dict_conforming_to_schema Populate _type with the expected value for the given schema, and populate spec_version with tuf.SPECIFICATION_VERSION. Do this only when the values are not provided, and support overriding them. Also adds testing for the above and takes advantage of the above in repository_lib's _generate metadata functions. Signed-off-by: Sebastien Awwad --- tests/test_formats.py | 26 +++++++++++++++- tuf/formats.py | 72 ++++++++++++++++++++++++++++++++++++++----- tuf/repository_lib.py | 10 ------ 3 files changed, 89 insertions(+), 19 deletions(-) diff --git a/tests/test_formats.py b/tests/test_formats.py index a36868ee65..42a354c71b 100755 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -300,7 +300,10 @@ def test_build_dict_conforming_to_schema(self): expires = '1985-10-21T13:20:00Z' filedict = {'snapshot.json': {'length': length, 'hashes': hashes}} - self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( + + # Try with and without _type and spec_version, both of which are + # automatically populated if they are not included. + self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( # both tuf.formats.build_dict_conforming_to_schema( tuf.formats.TIMESTAMP_SCHEMA, _type='timestamp', @@ -308,6 +311,27 @@ def test_build_dict_conforming_to_schema(self): version=version, expires=expires, meta=filedict))) + self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( # neither + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + version=version, + expires=expires, + meta=filedict))) + self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( # one + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + spec_version=spec_version, + version=version, + expires=expires, + meta=filedict))) + self.assertTrue(tuf.formats.TIMESTAMP_SCHEMA.matches( # the other + tuf.formats.build_dict_conforming_to_schema( + tuf.formats.TIMESTAMP_SCHEMA, + _type='timestamp', + version=version, + expires=expires, + meta=filedict))) + # Try test arguments for invalid Timestamp creation. bad_spec_version = 123 diff --git a/tuf/formats.py b/tuf/formats.py index 9890ec0cca..bc6fa0d3f5 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -69,11 +69,11 @@ import time import copy -import tuf - import securesystemslib.formats import securesystemslib.schema as SCHEMA +import tuf + import six @@ -98,6 +98,8 @@ # Role object in {'keyids': [keydids..], 'name': 'ABC', 'threshold': 1, # 'paths':[filepaths..]} format. +# TODO: This is not a role. In further #660-related PRs, fix it, similar to +# the way I did in Uptane's TUF fork. ROLE_SCHEMA = SCHEMA.Object( object_name = 'ROLE_SCHEMA', name = SCHEMA.Optional(securesystemslib.formats.ROLENAME_SCHEMA), @@ -470,14 +472,27 @@ def make_signable(role_schema): def build_dict_conforming_to_schema(schema, **kwargs): """ - Given a schema object (for example, TIMESTAMP_SCHEMA from this module) and - a set of keyword arguments, create a dictionary that conforms to the given - schema, using the keyword arguments to define the elements of the new dict. + + Given a schema object (for example, TIMESTAMP_SCHEMA from this module) and + a set of keyword arguments, create a dictionary that conforms to the given + schema, using the keyword arguments to define the elements of the new dict. + + Checks the result to make sure that it conforms to the given schema, raising + an error if not. - Checks the result to make sure that it conforms to the given schema, raising - an error if not. + + A dictionary conforming to the given schema. Adds certain required fields + if they are missing and can be deduced from the schema. The data returned + is a deep copy. + + + securesystemslib.exceptions.FormatError + if the provided data does not match the schema when assembled. + + + None. In particular, the provided values are not modified, and the + returned dictionary does not include references to them. - Returns the new dict conforming to the schema if there are no problems. """ # Check that schema supports a check_match call. @@ -501,6 +516,47 @@ def build_dict_conforming_to_schema(schema, **kwargs): + + + + + # Automatically provide certain schema properties if they are not already + # provided and are required in objects of class . + # This includes: + # _type: + # spec_version: SPECIFICATION_VERSION_SCHEMA + # + # (Please note that _required is slightly misleading, as it includes both + # required and optional elements. It should probably be called _components.) + # + for schema_element in schema._required: + key = schema_element[0] + element_type = schema_element[1] + + if key in dictionary: + # If the field has been provided, proceed normally. + continue + + elif isinstance(element_type, SCHEMA.Optional): + # If the field has NOT been provided but IS optional, proceed without it. + continue + + else: + # If the field has not been provided and is required, check to see if + # the field is one of the one of the fields we automatically fill. + + # Currently, the list is limited to ['_type', 'spec_version']. + + if key == '_type' and isinstance(element_type, SCHEMA.String): + # A SCHEMA.String stores its expected value in _string, so use that. + dictionary[key] = element_type._string + + elif (key == 'spec_version' and + element_type == SPECIFICATION_VERSION_SCHEMA): + # If not provided, use the specification version in tuf/__init__.py + dictionary[key] = tuf.SPECIFICATION_VERSION + + # If what we produce does not match the provided schema, raise a FormatError. schema.check_match(dictionary) diff --git a/tuf/repository_lib.py b/tuf/repository_lib.py index 2e3346b8d9..10a1444b98 100755 --- a/tuf/repository_lib.py +++ b/tuf/repository_lib.py @@ -1330,8 +1330,6 @@ def generate_root_metadata(version, expiration_date, consistent_snapshot, # There are very few things that really need to be done differently. return tuf.formats.build_dict_conforming_to_schema( tuf.formats.ROOT_SCHEMA, - _type='root', - spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, keys=keydict, @@ -1469,8 +1467,6 @@ def generate_targets_metadata(targets_directory, target_files, version, if delegations is not None: return tuf.formats.build_dict_conforming_to_schema( tuf.formats.TARGETS_SCHEMA, - _type='targets', - spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, targets=filedict, @@ -1478,8 +1474,6 @@ def generate_targets_metadata(targets_directory, target_files, version, else: return tuf.formats.build_dict_conforming_to_schema( tuf.formats.TARGETS_SCHEMA, - _type='targets', - spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, targets=filedict) @@ -1613,8 +1607,6 @@ def generate_snapshot_metadata(metadata_directory, version, expiration_date, # There are very few things that really need to be done differently. return tuf.formats.build_dict_conforming_to_schema( tuf.formats.SNAPSHOT_SCHEMA, - _type='snapshot', - spec_version=tuf.SPECIFICATION_VERSION, version=version, expires=expiration_date, meta=fileinfodict) @@ -1692,8 +1684,6 @@ def generate_timestamp_metadata(snapshot_filename, version, expiration_date, # There are very few things that really need to be done differently. return tuf.formats.build_dict_conforming_to_schema( tuf.formats.TIMESTAMP_SCHEMA, - spec_version=tuf.SPECIFICATION_VERSION, - _type='timestamp', version=version, expires=expiration_date, meta=snapshot_fileinfo) From 3ac174f597e6c0981d7a641ad2cfd0790858a664 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 29 Mar 2019 15:38:08 -0400 Subject: [PATCH 09/15] Use build_dict... instead of make_role_... in test_sig.py since make_role_metadata is being replaced by build_dict_conforming_to_schema Signed-off-by: Sebastien Awwad --- tests/test_sig.py | 71 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/tests/test_sig.py b/tests/test_sig.py index f8730b86d3..de671a6468 100755 --- a/tests/test_sig.py +++ b/tests/test_sig.py @@ -108,8 +108,10 @@ def test_get_signature_status_bad_sig(self): tuf.keydb.add_key(KEYS[0]) threshold = 1 - roleinfo = tuf.formats.make_role_metadata( - [KEYS[0]['keyid']], threshold) + + roleinfo = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, keyids=[KEYS[0]['keyid']], threshold=threshold) + tuf.roledb.add_role('Root', roleinfo) sig_status = tuf.sig.get_signature_status(signable, 'Root') @@ -139,8 +141,10 @@ def test_get_signature_status_unknown_signing_scheme(self): KEYS[0]['scheme'] = 'unknown_signing_scheme' tuf.keydb.add_key(KEYS[0]) threshold = 1 - roleinfo = tuf.formats.make_role_metadata( - [KEYS[0]['keyid']], threshold) + + roleinfo = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, keyids=[KEYS[0]['keyid']], threshold=threshold) + tuf.roledb.add_role('root', roleinfo) sig_status = tuf.sig.get_signature_status(signable, 'root') @@ -169,8 +173,9 @@ def test_get_signature_status_single_key(self): KEYS[0], signable['signed'])) threshold = 1 - roleinfo = tuf.formats.make_role_metadata( - [KEYS[0]['keyid']], threshold) + + roleinfo = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, keyids=[KEYS[0]['keyid']], threshold=threshold) tuf.roledb.add_role('Root', roleinfo) tuf.keydb.add_key(KEYS[0]) @@ -210,9 +215,12 @@ def test_get_signature_status_below_threshold(self): tuf.keydb.add_key(KEYS[0]) threshold = 2 - roleinfo = tuf.formats.make_role_metadata( - [KEYS[0]['keyid'], - KEYS[2]['keyid']], threshold) + + roleinfo = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, + keyids=[KEYS[0]['keyid'], KEYS[2]['keyid']], + threshold=threshold) + tuf.roledb.add_role('Root', roleinfo) sig_status = tuf.sig.get_signature_status(signable, 'Root') @@ -245,9 +253,12 @@ def test_get_signature_status_below_threshold_unrecognized_sigs(self): tuf.keydb.add_key(KEYS[0]) tuf.keydb.add_key(KEYS[1]) threshold = 2 - roleinfo = tuf.formats.make_role_metadata( - [KEYS[0]['keyid'], - KEYS[1]['keyid']], threshold) + + roleinfo = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, + keyids=[KEYS[0]['keyid'], KEYS[1]['keyid']], + threshold=threshold) + tuf.roledb.add_role('Root', roleinfo) sig_status = tuf.sig.get_signature_status(signable, 'Root') @@ -282,11 +293,19 @@ def test_get_signature_status_below_threshold_unauthorized_sigs(self): tuf.keydb.add_key(KEYS[0]) tuf.keydb.add_key(KEYS[1]) threshold = 2 - roleinfo = tuf.formats.make_role_metadata( - [KEYS[0]['keyid'], KEYS[2]['keyid']], threshold) + + roleinfo = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, + keyids=[KEYS[0]['keyid'], KEYS[2]['keyid']], + threshold=threshold) + tuf.roledb.add_role('Root', roleinfo) - roleinfo = tuf.formats.make_role_metadata( - [KEYS[1]['keyid'], KEYS[2]['keyid']], threshold) + + roleinfo = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, + keyids=[KEYS[1]['keyid'], KEYS[2]['keyid']], + threshold=threshold) + tuf.roledb.add_role('Release', roleinfo) sig_status = tuf.sig.get_signature_status(signable, 'Root') @@ -339,8 +358,10 @@ def test_verify_single_key(self): tuf.keydb.add_key(KEYS[0]) threshold = 1 - roleinfo = tuf.formats.make_role_metadata( - [KEYS[0]['keyid']], threshold) + + roleinfo = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, keyids=[KEYS[0]['keyid']], threshold=threshold) + tuf.roledb.add_role('Root', roleinfo) # This will call verify() and return True if 'signable' is valid, @@ -366,8 +387,12 @@ def test_verify_unrecognized_sig(self): tuf.keydb.add_key(KEYS[0]) tuf.keydb.add_key(KEYS[1]) threshold = 2 - roleinfo = tuf.formats.make_role_metadata( - [KEYS[0]['keyid'], KEYS[1]['keyid']], threshold) + + roleinfo = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, + keyids=[KEYS[0]['keyid'], KEYS[1]['keyid']], + threshold=threshold) + tuf.roledb.add_role('Root', roleinfo) self.assertFalse(tuf.sig.verify(signable, 'Root')) @@ -412,8 +437,10 @@ def test_may_need_new_keys(self): tuf.keydb.add_key(KEYS[1]) threshold = 1 - roleinfo = tuf.formats.make_role_metadata( - [KEYS[1]['keyid']], threshold) + + roleinfo = tuf.formats.build_dict_conforming_to_schema( + tuf.formats.ROLE_SCHEMA, keyids=[KEYS[1]['keyid']], threshold=threshold) + tuf.roledb.add_role('Root', roleinfo) sig_status = tuf.sig.get_signature_status(signable, 'Root') From b52ff2d744c5fb1189a2e5edc08e3e7b0ee33045 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Fri, 29 Mar 2019 15:47:19 -0400 Subject: [PATCH 10/15] Beg pylint for forgiveness re. protected access of SCHEMAs Signed-off-by: Sebastien Awwad --- tuf/formats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tuf/formats.py b/tuf/formats.py index bc6fa0d3f5..e7d49698b5 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -529,7 +529,7 @@ def build_dict_conforming_to_schema(schema, **kwargs): # (Please note that _required is slightly misleading, as it includes both # required and optional elements. It should probably be called _components.) # - for schema_element in schema._required: + for schema_element in schema._required: #pylint: disable=protected-access key = schema_element[0] element_type = schema_element[1] @@ -549,7 +549,7 @@ def build_dict_conforming_to_schema(schema, **kwargs): if key == '_type' and isinstance(element_type, SCHEMA.String): # A SCHEMA.String stores its expected value in _string, so use that. - dictionary[key] = element_type._string + dictionary[key] = element_type._string #pylint: disable=protected-access elif (key == 'spec_version' and element_type == SPECIFICATION_VERSION_SCHEMA): From 40f4202b6f325e17813bfe339ec0a5a997919267 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Tue, 2 Apr 2019 12:46:37 -0400 Subject: [PATCH 11/15] minor DOC: typo fixes Signed-off-by: Sebastien Awwad --- tuf/formats.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tuf/formats.py b/tuf/formats.py index e7d49698b5..07ffb68f20 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -23,7 +23,7 @@ Note: 'formats.py' depends heavily on 'schema.py', so the 'schema.py' module should be read and understood before tackling this module. - 'formats.py' can be broken down into three sections. (1) Schemas and object + 'formats.py' can be broken down into two sections. (1) Schemas and object matching. (2) Functions that help produce or verify TUF objects. The first section deals with schemas and object matching based on format. @@ -514,12 +514,6 @@ def build_dict_conforming_to_schema(schema, **kwargs): dictionary = copy.deepcopy(kwargs) - - - - - - # Automatically provide certain schema properties if they are not already # provided and are required in objects of class . # This includes: @@ -543,7 +537,7 @@ def build_dict_conforming_to_schema(schema, **kwargs): else: # If the field has not been provided and is required, check to see if - # the field is one of the one of the fields we automatically fill. + # the field is one of the fields we automatically fill. # Currently, the list is limited to ['_type', 'spec_version']. From 915291ea8591317fd68897f44dfe941a82bfa5c1 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Tue, 2 Apr 2019 12:47:16 -0400 Subject: [PATCH 12/15] PR revision: use type checking, minor readability edit Edits prompted by Lukas's review. Signed-off-by: Sebastien Awwad --- tuf/formats.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/tuf/formats.py b/tuf/formats.py index 07ffb68f20..6e6fc4544e 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -495,19 +495,11 @@ def build_dict_conforming_to_schema(schema, **kwargs): """ - # Check that schema supports a check_match call. - # Duck typing version of this check: - if not hasattr(schema, 'check_match'): + # Check the schema argument type (must provide check_match and _required). + if not isinstance(schema, schema.Schema): raise ValueError( - 'The given "schema" does not seem to be a schema. It has no ' - '"check_match" method. Given schema: ' + repr(schema)) - - # # Strict typing version of this check: - # # Check that schema_name is a SCHEMA.Object. - # if not isinstance(schema, schema.Schema): - # raise ValueError( - # 'The first argument must be a schema.Schema object, but is not. ' - # 'Given schema: ' + repr(schema)) + 'The first argument must be a schema.Schema object, but is not. ' + 'Given schema: ' + repr(schema)) # Make a copy of the provided fields so that the caller's provided values # do not change when the returned values are changed. @@ -523,9 +515,7 @@ def build_dict_conforming_to_schema(schema, **kwargs): # (Please note that _required is slightly misleading, as it includes both # required and optional elements. It should probably be called _components.) # - for schema_element in schema._required: #pylint: disable=protected-access - key = schema_element[0] - element_type = schema_element[1] + for key, element_type in schema._required: #pylint: disable=protected-access if key in dictionary: # If the field has been provided, proceed normally. From 188bc84f906f3898805efbfb78ecfaf9e7db92e0 Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Tue, 2 Apr 2019 12:48:13 -0400 Subject: [PATCH 13/15] DOC: improve build_dict_conforming_to_schema docstring - Expand on the arguments/kwargs - Note that the function is particular to schema.Object objects. Signed-off-by: Sebastien Awwad --- tuf/formats.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/tuf/formats.py b/tuf/formats.py index 6e6fc4544e..8a4c2952e4 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -473,13 +473,33 @@ def make_signable(role_schema): def build_dict_conforming_to_schema(schema, **kwargs): """ - Given a schema object (for example, TIMESTAMP_SCHEMA from this module) and - a set of keyword arguments, create a dictionary that conforms to the given - schema, using the keyword arguments to define the elements of the new dict. + Given a schema.Object object (for example, TIMESTAMP_SCHEMA from this + module) and a set of keyword arguments, create a dictionary that conforms + to the given schema, using the keyword arguments to define the elements of + the new dict. Checks the result to make sure that it conforms to the given schema, raising an error if not. + + schema + A schema.Object, like TIMESTAMP_SCHEMA, FILEINFO_SCHEMA, + securesystemslib.formats.SIGNATURE_SCHEMA, etc. + + **kwargs + A keyword argument for each element of the schema. Optional arguments + may be included or skipped, but all required arguments must be included. + + For example, for TIMESTAMP_SCHEMA, a call might look like: + build_dict_conforming_to_schema( + TIMESTAMP_SCHEMA, + _type='timestamp', + spec_version='1.0', + version=1, + expires='2020-01-01T00:00:00Z', + meta={...}) + Some arguments will be filled in if excluded: _type, spec_version + A dictionary conforming to the given schema. Adds certain required fields if they are missing and can be deduced from the schema. The data returned From 9aa22466c09f0a1835e8c970f4749ad7fe8bf6ea Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Tue, 2 Apr 2019 12:50:20 -0400 Subject: [PATCH 14/15] PR revision: minor bugfix in casing May the Python gods forgive me for having three different names with the same names and different casing on one line.... Perhaps I should address the import module as.... Signed-off-by: Sebastien Awwad --- tuf/formats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tuf/formats.py b/tuf/formats.py index 8a4c2952e4..9846fa4f42 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -516,7 +516,7 @@ def build_dict_conforming_to_schema(schema, **kwargs): """ # Check the schema argument type (must provide check_match and _required). - if not isinstance(schema, schema.Schema): + if not isinstance(schema, SCHEMA.Schema): raise ValueError( 'The first argument must be a schema.Schema object, but is not. ' 'Given schema: ' + repr(schema)) From 25aa71d4c60280653c745850215e7ac9dd2abaef Mon Sep 17 00:00:00 2001 From: Sebastien Awwad Date: Wed, 3 Apr 2019 10:30:42 -0400 Subject: [PATCH 15/15] PR revision: test build_dict_conforming... arg for schema type Raise an error if it's not a schema.Object instance (not just if it's not a schema.Schema instance). Also adds a test for this. Signed-off-by: Sebastien Awwad --- tests/test_formats.py | 7 +++++++ tuf/formats.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_formats.py b/tests/test_formats.py index 42a354c71b..25c27f289d 100755 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -292,6 +292,13 @@ def test_build_dict_conforming_to_schema(self): # Test construction of a few metadata formats using # build_dict_conforming_to_schema(). + # Try the wrong type of schema object. + STRING_SCHEMA = securesystemslib.schema.AnyString() + + with self.assertRaises(ValueError): + tuf.formats.build_dict_conforming_to_schema( + STRING_SCHEMA, string='some string') + # Try building Timestamp metadata. spec_version = tuf.SPECIFICATION_VERSION version = 8 diff --git a/tuf/formats.py b/tuf/formats.py index 9846fa4f42..2c5128db57 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -516,9 +516,9 @@ def build_dict_conforming_to_schema(schema, **kwargs): """ # Check the schema argument type (must provide check_match and _required). - if not isinstance(schema, SCHEMA.Schema): + if not isinstance(schema, SCHEMA.Object): raise ValueError( - 'The first argument must be a schema.Schema object, but is not. ' + 'The first argument must be a schema.Object instance, but is not. ' 'Given schema: ' + repr(schema)) # Make a copy of the provided fields so that the caller's provided values