diff --git a/tests/test_metadata_serialization.py b/tests/test_metadata_serialization.py index 13bb55003a..3d1cafcc6e 100644 --- a/tests/test_metadata_serialization.py +++ b/tests/test_metadata_serialization.py @@ -142,23 +142,38 @@ def test_role_serialization(self, test_case_data: str): "keyid1" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}, \ "keyid2" : {"keytype": "ed25519", "scheme": "ed25519", "keyval": {"public": "bar"}}}, \ "roles": { \ + "root": {"keyids": ["keyid3"], "threshold": 1}, \ + "timestamp": {"keyids": ["keyid4"], "threshold": 1}, \ "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", \ "keys": {"keyid" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"} }}, \ - "roles": { "targets": {"keyids": ["keyid"], "threshold": 3} } \ + "roles": { \ + "root": {"keyids": ["keyid1"], "threshold": 1}, \ + "timestamp": {"keyids": ["keyid2"], "threshold": 1}, \ + "targets": {"keyids": ["keyid3"], "threshold": 1}, \ + "snapshot": {"keyids": ["keyid4"], "threshold": 1}} \ }', - "empty keys and roles": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \ + "empty keys": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \ "expires": "2030-01-01T00:00:00Z", "consistent_snapshot": false, \ "keys": {}, \ - "roles": {} \ + "roles": { \ + "root": {"keyids": ["keyid3"], "threshold": 1}, \ + "timestamp": {"keyids": ["keyid4"], "threshold": 1}, \ + "targets": {"keyids": ["keyid1"], "threshold": 1}, \ + "snapshot": {"keyids": ["keyid2"], "threshold": 1}} \ }', "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"}}}, \ - "roles": { "targets": {"keyids": ["keyid"], "threshold": 3}}, \ + "roles": { \ + "root": {"keyids": ["keyid1"], "threshold": 1}, \ + "timestamp": {"keyids": ["keyid2"], "threshold": 1}, \ + "targets": {"keyids": ["keyid3"], "threshold": 1}, \ + "snapshot": {"keyids": ["keyid4"], "threshold": 1} \ + }, \ "foo": "bar"}', } @@ -169,6 +184,49 @@ def test_root_serialization(self, test_case_data: str): self.assertDictEqual(case_dict, root.to_dict()) + invalid_roots: utils.DataSet = { + "invalid role name": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \ + "expires": "2030-01-01T00:00:00Z", "consistent_snapshot": false, \ + "keys": { \ + "keyid1" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}, \ + "keyid2" : {"keytype": "ed25519", "scheme": "ed25519", "keyval": {"public": "bar"}}}, \ + "roles": { \ + "bar": {"keyids": ["keyid3"], "threshold": 1}, \ + "timestamp": {"keyids": ["keyid4"], "threshold": 1}, \ + "targets": {"keyids": ["keyid1"], "threshold": 1}, \ + "snapshot": {"keyids": ["keyid2"], "threshold": 1}, \ + "foo": {"keyids": ["keyid2"], "threshold": 1}} \ + }', + "missing root role": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \ + "expires": "2030-01-01T00:00:00Z", "consistent_snapshot": false, \ + "keys": { \ + "keyid1" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}, \ + "keyid2" : {"keytype": "ed25519", "scheme": "ed25519", "keyval": {"public": "bar"}}}, \ + "roles": { \ + "timestamp": {"keyids": ["keyid4"], "threshold": 1}, \ + "targets": {"keyids": ["keyid1"], "threshold": 1}, \ + "snapshot": {"keyids": ["keyid2"], "threshold": 1}} \ + }', + "one additional role": '{"_type": "root", "spec_version": "1.0.0", "version": 1, \ + "expires": "2030-01-01T00:00:00Z", "consistent_snapshot": false, \ + "keys": { \ + "keyid1" : {"keytype": "rsa", "scheme": "rsassa-pss-sha256", "keyval": {"public": "foo"}}, \ + "keyid2" : {"keytype": "ed25519", "scheme": "ed25519", "keyval": {"public": "bar"}}}, \ + "roles": { \ + "root": {"keyids": ["keyid3"], "threshold": 1}, \ + "timestamp": {"keyids": ["keyid4"], "threshold": 1}, \ + "targets": {"keyids": ["keyid1"], "threshold": 1}, \ + "snapshot": {"keyids": ["keyid2"], "threshold": 1}, \ + "foo": {"keyids": ["keyid2"], "threshold": 1}} \ + }', + } + + @utils.run_sub_tests_with_dataset(invalid_roots) + def test_invalid_root_serialization(self, test_case_data: Dict[str, str]): + case_dict = json.loads(test_case_data) + with self.assertRaises(ValueError): + Root.from_dict(copy.deepcopy(case_dict)) + invalid_metafiles: utils.DataSet = { "wrong length type": '{"version": 1, "length": "a", "hashes": {"sha256" : "abc"}}', "length 0": '{"version": 1, "length": 0, "hashes": {"sha256" : "abc"}}', diff --git a/tuf/api/metadata.py b/tuf/api/metadata.py index 0b88f4c0d2..b3a687a74f 100644 --- a/tuf/api/metadata.py +++ b/tuf/api/metadata.py @@ -726,6 +726,10 @@ def __init__( super().__init__(version, spec_version, expires, unrecognized_fields) self.consistent_snapshot = consistent_snapshot self.keys = keys + mandatory_roles = ["root", "timestamp", "snapshot", "timestamp"] + if not (len(roles) == 4 and all(r in roles for r in mandatory_roles)): + raise ValueError("Role names must be the top-level metadata roles") + self.roles = roles @classmethod