Skip to content

Commit

Permalink
Metadata API: snapshot_meta property in Timestamp
Browse files Browse the repository at this point in the history
In Timestamp, the only valid "meta" value is the dictionary representing
meta information for the snapshot file. This makes the API unnecessarily
complicated and requires validation that only information about snapshot
is available inside "meta".
Together with the python-tuf maintainers we decided that snapshot meta
information will not be represented by a "meta" dictionary but instead
by a MetaFile instance and with this it will diverge from the
specification.
This decision is coherent with ADR9 and the rationale
behind it is to provide easier, safer, and direct access to the
snapshot meta information.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
  • Loading branch information
MVrachev committed Aug 31, 2021
1 parent e1ec782 commit 7bb05bd
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 30 deletions.
6 changes: 3 additions & 3 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,11 @@ def test_metadata_timestamp(self):
fileinfo = MetaFile(2, 520, hashes)

self.assertNotEqual(
timestamp.signed.meta['snapshot.json'].to_dict(), fileinfo.to_dict()
timestamp.signed.meta.to_dict(), fileinfo.to_dict()
)
timestamp.signed.update(fileinfo)
self.assertEqual(
timestamp.signed.meta['snapshot.json'].to_dict(), fileinfo.to_dict()
timestamp.signed.meta.to_dict(), fileinfo.to_dict()
)


Expand Down Expand Up @@ -558,7 +558,7 @@ def test_length_and_hash_validation(self):
timestamp_path = os.path.join(
self.repo_dir, 'metadata', 'timestamp.json')
timestamp = Metadata[Timestamp].from_file(timestamp_path)
snapshot_metafile = timestamp.signed.meta["snapshot.json"]
snapshot_metafile = timestamp.signed.meta

snapshot_path = os.path.join(
self.repo_dir, 'metadata', 'snapshot.json')
Expand Down
14 changes: 7 additions & 7 deletions tests/test_trusted_metadata_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ def setUpClass(cls):
cls.keystore[role] = SSlibSigner(key_dict)

def hashes_length_modifier(timestamp: Timestamp) -> None:
timestamp.meta["snapshot.json"].hashes = None
timestamp.meta["snapshot.json"].length = None
timestamp.meta.hashes = None
timestamp.meta.length = None

cls.metadata["timestamp"] = cls.modify_metadata(
cls, "timestamp", hashes_length_modifier
Expand Down Expand Up @@ -241,13 +241,13 @@ def version_modifier(timestamp: Timestamp) -> None:

def test_update_timestamp_snapshot_ver_below_current(self):
def bump_snapshot_version(timestamp: Timestamp) -> None:
timestamp.meta["snapshot.json"].version = 2
timestamp.meta.version = 2

# set current known snapshot.json version to 2
timestamp = self.modify_metadata("timestamp", bump_snapshot_version)
self.trusted_set.update_timestamp(timestamp)

# newtimestamp.meta["snapshot.json"].version < trusted_timestamp.meta["snapshot.json"].version
# newtimestamp.meta.version < trusted_timestamp.meta.version
with self.assertRaises(exceptions.ReplayedMetadataError):
self.trusted_set.update_timestamp(self.metadata["timestamp"])

Expand All @@ -266,7 +266,7 @@ def timestamp_expired_modifier(timestamp: Timestamp) -> None:

def test_update_snapshot_length_or_hash_mismatch(self):
def modify_snapshot_length(timestamp: Timestamp) -> None:
timestamp.meta["snapshot.json"].length = 1
timestamp.meta.length = 1

# set known snapshot.json length to 1
timestamp = self.modify_metadata("timestamp", modify_snapshot_length)
Expand All @@ -284,7 +284,7 @@ def test_update_snapshot_cannot_verify_snapshot_with_threshold(self):

def test_update_snapshot_version_different_timestamp_snapshot_version(self):
def timestamp_version_modifier(timestamp: Timestamp) -> None:
timestamp.meta["snapshot.json"].version = 2
timestamp.meta.version = 2

timestamp = self.modify_metadata("timestamp", timestamp_version_modifier)
self.trusted_set.update_timestamp(timestamp)
Expand Down Expand Up @@ -334,7 +334,7 @@ def snapshot_expired_modifier(snapshot: Snapshot) -> None:

def test_update_snapshot_successful_rollback_checks(self):
def meta_version_bump(timestamp: Timestamp) -> None:
timestamp.meta["snapshot.json"].version += 1
timestamp.meta.version += 1

def version_bump(snapshot: Snapshot) -> None:
snapshot.version += 1
Expand Down
19 changes: 10 additions & 9 deletions tuf/api/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -905,10 +905,13 @@ class Timestamp(Signed):
"""A container for the signed part of timestamp metadata.
Timestamp contains information about the snapshot Metadata file.
Snapshot meta information is not represented by a "meta" a dictionary but by
a MetaFile instance and with this it diverges from the specification.
This decision is coherent with ADR9 and the rationale behind it is to
provide easier, safer, and direct access to the snapshot meta information.
Attributes:
meta: A dictionary of filenames to MetaFiles. The only valid key value
is the snapshot filename, as defined by the specification.
meta: MetaFile instance with the snapshot meta information.
"""

_signed_type = "timestamp"
Expand All @@ -918,7 +921,7 @@ def __init__(
version: int,
spec_version: str,
expires: datetime,
meta: Dict[str, MetaFile],
meta: MetaFile,
unrecognized_fields: Optional[Mapping[str, Any]] = None,
) -> None:
super().__init__(version, spec_version, expires, unrecognized_fields)
Expand All @@ -929,22 +932,20 @@ def from_dict(cls, signed_dict: Dict[str, Any]) -> "Timestamp":
"""Creates Timestamp object from its dict representation."""
common_args = cls._common_fields_from_dict(signed_dict)
meta_dict = signed_dict.pop("meta")
meta = {"snapshot.json": MetaFile.from_dict(meta_dict["snapshot.json"])}
meta = MetaFile.from_dict(meta_dict["snapshot.json"])
# All fields left in the timestamp_dict are unrecognized.
return cls(*common_args, meta, signed_dict)

def to_dict(self) -> Dict[str, Any]:
"""Returns the dict representation of self."""
res_dict = self._common_fields_to_dict()
res_dict["meta"] = {
"snapshot.json": self.meta["snapshot.json"].to_dict()
}
res_dict["meta"] = {"snapshot.json": self.meta.to_dict()}
return res_dict

# Modification.
def update(self, snapshot_meta: MetaFile) -> None:
"""Assigns passed info about snapshot metadata to meta dict."""
self.meta["snapshot.json"] = snapshot_meta
"""Assigns passed info about snapshot metadata."""
self.meta = snapshot_meta


class Snapshot(Signed):
Expand Down
17 changes: 7 additions & 10 deletions tuf/ngclient/_internal/trusted_metadata_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,13 @@ def update_timestamp(self, data: bytes) -> None:
)
# Prevent rolling back snapshot version
if (
new_timestamp.signed.meta["snapshot.json"].version
< self.timestamp.signed.meta["snapshot.json"].version
new_timestamp.signed.meta.version
< self.timestamp.signed.meta.version
):
raise exceptions.ReplayedMetadataError(
"snapshot",
new_timestamp.signed.meta["snapshot.json"].version,
self.timestamp.signed.meta["snapshot.json"].version,
new_timestamp.signed.meta.version,
self.timestamp.signed.meta.version,
)

# expiry not checked to allow old timestamp to be used for rollback
Expand Down Expand Up @@ -265,7 +265,7 @@ def update_snapshot(self, data: bytes) -> None:
if self.timestamp.signed.is_expired(self.reference_time):
raise exceptions.ExpiredMetadataError("timestamp.json is expired")

meta = self.timestamp.signed.meta["snapshot.json"]
meta = self.timestamp.signed.meta

# Verify against the hashes in timestamp, if any
try:
Expand Down Expand Up @@ -322,13 +322,10 @@ def _check_final_snapshot(self) -> None:
if self.snapshot.signed.is_expired(self.reference_time):
raise exceptions.ExpiredMetadataError("snapshot.json is expired")

if (
self.snapshot.signed.version
!= self.timestamp.signed.meta["snapshot.json"].version
):
if self.snapshot.signed.version != self.timestamp.signed.meta.version:
raise exceptions.BadVersionNumberError(
f"Expected snapshot version "
f"{self.timestamp.signed.meta['snapshot.json'].version}, "
f"{self.timestamp.signed.meta.version}, "
f"got {self.snapshot.signed.version}"
)

Expand Down
2 changes: 1 addition & 1 deletion tuf/ngclient/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ def _load_snapshot(self) -> None:
logger.debug("Failed to load local snapshot %s", e)

assert self._trusted_set.timestamp is not None # nosec
metainfo = self._trusted_set.timestamp.signed.meta["snapshot.json"]
metainfo = self._trusted_set.timestamp.signed.meta
length = metainfo.length or self.config.snapshot_max_length
version = None
if self._trusted_set.root.signed.consistent_snapshot:
Expand Down

0 comments on commit 7bb05bd

Please sign in to comment.