From df6a399b0869c4b530d9088bf555032bea4f7a16 Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Tue, 27 Jul 2021 17:00:25 +0300 Subject: [PATCH 1/2] new API: test containers for zero or more elements Test metadata (de)serialization with input data containing containers with zero or more elements. Here is the status for the different use cases: Root keys: - many keys: added Root roles: - many roles: added Root role keyids: - many keids: already added in https://github.com/theupdateframework/tuf/pull/1481 MetaFile hashes: - many hashes: already tested - zero hashes: added. Testing as invalid test case. Timestamp meta: - zero elements: already tested - many elements: added Snapshot meta: - zero items: added - many items: added Delegation keys: - many keys: added Delegation role keyids: - many keyids: added Delegation role paths: - many paths: already tested Delegation role path_hash_prefixes: - many path_hash_path_prefixes: already tested Delegation roles: - zero roles: added - multiple roles: added Targets targets: - zero items: already tested - multiple items: added Signed-off-by: Martin Vrachev --- tests/test_metadata_serialization.py | 62 ++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/tests/test_metadata_serialization.py b/tests/test_metadata_serialization.py index c5eafbd296..1ca22f4274 100644 --- a/tests/test_metadata_serialization.py +++ b/tests/test_metadata_serialization.py @@ -155,8 +155,12 @@ def test_role_serialization(self, test_case_data: str): valid_roots: DataSet = { "all": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \ "expires": "2030-01-01T00:00:00Z", "consistent_snapshot": false, \ - "keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \ - "roles": { "targets": {"keyids": ["keyid"], "threshold": 3}} \ + "keys": { \ + "keyid1" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}, \ + "keyid2" : {"keytype": "ed25519", "scheme": "ed25519", "keyval": {"public": "bar"}}}, \ + "roles": { \ + "targets": {"keyids": ["keyid1"], "threshold": 1}, \ + "snapshot": {"keyids": ["keyid2"], "threshold": 1}} \ }', "no consistent_snapshot": '{ "_type": "root", "spec_version": "1.0.0", "version": 1, \ "expires": "2030-01-01T00:00:00Z", \ @@ -198,6 +202,7 @@ def test_invalid_metafile_serialization(self, test_case_data: Dict[str, str]): "no length": '{"hashes": {"sha256" : "abc"}, "version": 1 }', "no hashes": '{"length": 12, "version": 1}', "unrecognized field": '{"hashes": {"sha256" : "abc"}, "length": 12, "version": 1, "foo": "bar"}', + "many hashes": '{"hashes": {"sha256" : "abc", "sha512": "cde"}, "length": 12, "version": 1}', } @run_sub_tests_with_dataset(valid_metafiles) @@ -206,6 +211,16 @@ def test_metafile_serialization(self, test_case_data: str): metafile = MetaFile.from_dict(copy.copy(case_dict)) self.assertDictEqual(case_dict, metafile.to_dict()) + invalid_timestamps: DataSet = { + "no metafile": '{ "_type": "timestamp", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z"}', + } + + @run_sub_tests_with_dataset(invalid_timestamps) + def test_invalid_timestamp_serialization(self, test_case_data: Dict[str, str]): + case_dict = json.loads(test_case_data) + with self.assertRaises((ValueError, KeyError)): + Timestamp.from_dict(copy.deepcopy(case_dict)) + valid_timestamps: DataSet = { "all": '{ "_type": "timestamp", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \ @@ -223,7 +238,13 @@ def test_timestamp_serialization(self, test_case_data: str): valid_snapshots: DataSet = { "all": '{ "_type": "snapshot", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \ - "meta": { "file.txt": { "hashes": {"sha256" : "abc"}, "version": 1 }}}', + "meta": { \ + "file1.txt": {"hashes": {"sha256" : "abc"}, "version": 1}, \ + "file2.txt": {"hashes": {"sha256" : "cde"}, "version": 1} \ + }}', + "empty meta": '{ "_type": "snapshot", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \ + "meta": {} \ + }', "unrecognized field": '{ "_type": "snapshot", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \ "meta": { "file.txt": { "hashes": {"sha256" : "abc"}, "version": 1 }}, "foo": "bar"}', } @@ -243,8 +264,10 @@ def test_snapshot_serialization(self, test_case_data: str): '{"keyids": ["keyid"], "name": "a", "terminating": false, \ "path_hash_prefixes": ["h1", "h2"], "threshold": 99}', "unrecognized field": - '{"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], \ - "terminating": true, "threshold": 3, "foo": "bar"}', + '{"keyids": ["keyid"], "name": "a", "terminating": true, "paths": ["fn1"], "threshold": 3, "foo": "bar"}', + "many keyids": + '{"keyids": ["keyid1", "keyid2"], "name": "a", "paths": ["fn1", "fn2"], \ + "terminating": false, "threshold": 1}', } @run_sub_tests_with_dataset(valid_delegated_roles) @@ -270,12 +293,21 @@ def test_invalid_delegated_role_serialization(self, test_case_data: str): valid_delegations: DataSet = { - "all": '{"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \ - "roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ]}', + "all": + '{"keys": { \ + "keyid1" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}, \ + "keyid2" : {"keytype": "ed25519", "scheme": "ed25519", "keyval": {"public": "bar"}}}, \ + "roles": [ \ + {"keyids": ["keyid"], "name": "a", "terminating": true, "paths": ["fn1"], "threshold": 3}, \ + {"keyids": ["keyid2"], "name": "b", "terminating": true, "paths": ["fn2"], "threshold": 4} ] \ + }', "unrecognized field": '{"keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \ "roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ], \ "foo": "bar"}', + "empty keys and roles": '{"keys": {}, \ + "roles": [] \ + }', } @run_sub_tests_with_dataset(valid_delegations) @@ -316,11 +348,17 @@ def test_targetfile_serialization(self, test_case_data: str): valid_targets: DataSet = { "all attributes": '{"_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \ - "targets": { "file.txt": {"length": 12, "hashes": {"sha256" : "abc"} } }, \ - "delegations": {"keys": {"keyid" : {"keytype": "rsa", \ - "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"} }}, \ - "roles": [ {"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], "terminating": true, "threshold": 3} ]} \ - }', + "targets": { \ + "file.txt": {"length": 12, "hashes": {"sha256" : "abc"} }, \ + "file2.txt": {"length": 50, "hashes": {"sha256" : "cde"} } }, \ + "delegations": { \ + "keys": { \ + "keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}, \ + "keyid2": {"keytype": "ed25519", "scheme": "ed25519", "keyval": {"public": "bar"}}}, \ + "roles": [ \ + {"keyids": ["keyid"], "name": "a", "terminating": true, "paths": ["fn1"], "threshold": 3}, \ + {"keyids": ["keyid2"], "name": "b", "terminating": true, "paths": ["fn2"], "threshold": 4} ] \ + }}', "empty targets": '{"_type": "targets", "spec_version": "1.0.0", "version": 1, "expires": "2030-01-01T00:00:00Z", \ "targets": {}, \ "delegations": {"keys": {"keyid" : {"keytype": "rsa", \ From 4c3fd95cb11ff4688bc045d75544752b4d81cc51 Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Thu, 19 Aug 2021 16:01:42 +0300 Subject: [PATCH 2/2] Additional tests for containers with 0 or >1 items Those tests are needed to cover use cases when syntatcticly as standalone objects the metadata classes and their helper classes defined in tuf/api/metadata.py are valid even if they cannot be verified. An example where an object is valid, but cannot be verified is if we have a Role instance with an empty list of "keyids". This instance is valid and can be created, but cannot be verified because there is a requirement that the threshold should be above 1, meaning that there should be at least 1 element inside the "keyids" list to complete successful threshold verification. The situation is the same for the rest of the tests I am adding to this commit: - Root object without keys - Root object without roles - DelegationRole object with empty "keyids" - DelegationRole object with an empty list of "paths" - DelegationRole object with an empty list of "path_hash_prefixes" all of these objects can be instantiated, but cannot complete successfully threshold verification. Signed-off-by: Martin Vrachev --- tests/test_metadata_serialization.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_metadata_serialization.py b/tests/test_metadata_serialization.py index 1ca22f4274..b8a2d818ab 100644 --- a/tests/test_metadata_serialization.py +++ b/tests/test_metadata_serialization.py @@ -142,6 +142,7 @@ def test_invalid_role_serialization(self, test_case_data: Dict[str, str]): valid_roles: DataSet = { "all": '{"keyids": ["keyid"], "threshold": 3}', "many keyids": '{"keyids": ["a", "b", "c", "d", "e"], "threshold": 1}', + "empty keyids": '{"keyids": [], "threshold": 1}', "unrecognized field": '{"keyids": ["keyid"], "threshold": 3, "foo": "bar"}', } @@ -167,6 +168,11 @@ def test_role_serialization(self, test_case_data: str): "keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"} }}, \ "roles": { "targets": {"keyids": ["keyid"], "threshold": 3} } \ }', + "empty keys and roles": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \ + "expires": "2030-01-01T00:00:00Z", "consistent_snapshot": false, \ + "keys": {}, \ + "roles": {} \ + }', "unrecognized field": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \ "expires": "2030-01-01T00:00:00Z", "consistent_snapshot": false, \ "keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}}, \ @@ -257,12 +263,17 @@ def test_snapshot_serialization(self, test_case_data: str): valid_delegated_roles: DataSet = { + # DelegatedRole inherits Role and some use cases can be found in the valid_roles. "no hash prefix attribute": '{"keyids": ["keyid"], "name": "a", "paths": ["fn1", "fn2"], \ "terminating": false, "threshold": 1}', "no path attribute": '{"keyids": ["keyid"], "name": "a", "terminating": false, \ "path_hash_prefixes": ["h1", "h2"], "threshold": 99}', + "empty paths": '{"keyids": ["keyid"], "name": "a", "paths": [], \ + "terminating": false, "threshold": 1}', + "empty path_hash_prefixes": '{"keyids": ["keyid"], "name": "a", "terminating": false, \ + "path_hash_prefixes": [], "threshold": 99}', "unrecognized field": '{"keyids": ["keyid"], "name": "a", "terminating": true, "paths": ["fn1"], "threshold": 3, "foo": "bar"}', "many keyids": @@ -278,6 +289,7 @@ def test_delegated_role_serialization(self, test_case_data: str): invalid_delegated_roles: DataSet = { + # DelegatedRole inherits Role and some use cases can be found in the invalid_roles. "missing hash prefixes and paths": '{"name": "a", "keyids": ["keyid"], "threshold": 1, "terminating": false}', "both hash prefixes and paths":