Skip to content

Commit

Permalink
Metadata API: validate root role names
Browse files Browse the repository at this point in the history
Validate that root role names are 4 and that they are exactly
"root", "snapshot", "targets" and "timestamp" as described in
the spec:
https://theupdateframework.github.io/specification/latest/#root-role

Additionally, fix the valid_roots dataset, so each of the cases contains
the top metadata role names inside the roles dictionary.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
  • Loading branch information
MVrachev committed Oct 23, 2021
1 parent e86a615 commit aafa75a
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 7 deletions.
8 changes: 5 additions & 3 deletions tests/repository_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,12 @@ def _initialize(self):
timestamp = Timestamp(1, SPEC_VER, self.safe_expiry, snapshot_meta)
self.md_timestamp = Metadata(timestamp, OrderedDict())

root = Root(1, SPEC_VER, self.safe_expiry, {}, {}, True)
for role in ["root", "timestamp", "snapshot", "targets"]:
top_level_md = ["root", "timestamp", "snapshot", "targets"]
roles = {role_name: Role([], 1) for role_name in top_level_md}
root = Root(1, SPEC_VER, self.safe_expiry, {}, roles, True)

for role in top_level_md:
key, signer = self.create_key()
root.roles[role] = Role([], 1)
root.add_key(role, key)
# store the private key
if role not in self.signers:
Expand Down
66 changes: 62 additions & 4 deletions tests/test_metadata_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}',
}

Expand All @@ -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"}}',
Expand Down
4 changes: 4 additions & 0 deletions tuf/api/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit aafa75a

Please sign in to comment.