Skip to content

Commit 7f0913c

Browse files
author
Jussi Kukkonen
committed
ngclient: Allow limited use of expired timestamp/snapshot
While this is not explicitly said in the spec, the intention is that expired timestamp and snapshot should be used for rollback protection checks on newer timestamp/snapshot (but not for anything else). Move the expiry checks to the "next" metadata update: timestamp expiry is checked when snapshot is loaded, and snapshot expiry is checked when targets is loaded. Signed-off-by: Jussi Kukkonen <[email protected]>
1 parent 5643b0c commit 7f0913c

File tree

2 files changed

+24
-7
lines changed

2 files changed

+24
-7
lines changed

tests/test_trusted_metadata_set.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,13 @@ def test_update_timestamp_expired(self):
268268
def timestamp_expired_modifier(timestamp: Timestamp) -> None:
269269
timestamp.expires = datetime(1970, 1, 1)
270270

271+
# intermediate timestamp is allowed to be expired
271272
timestamp = self.modify_metadata("timestamp", timestamp_expired_modifier)
273+
self.trusted_set.update_timestamp(timestamp)
274+
275+
# update snapshot to trigger final timestamp expiry check
272276
with self.assertRaises(exceptions.ExpiredMetadataError):
273-
self.trusted_set.update_timestamp(timestamp)
277+
self.trusted_set.update_snapshot(self.metadata["snapshot"])
274278

275279
def test_update_snapshot_length_or_hash_mismatch(self):
276280
def modify_snapshot_length(timestamp: Timestamp) -> None:
@@ -328,13 +332,16 @@ def version_meta_modifier(snapshot: Snapshot) -> None:
328332

329333
def test_update_snapshot_expired_new_snapshot(self):
330334
self._root_updated_and_update_timestamp(self.metadata["timestamp"])
331-
# new_snapshot has expired
332335
def snapshot_expired_modifier(snapshot: Snapshot) -> None:
333336
snapshot.expires = datetime(1970, 1, 1)
334337

338+
# intermediate snapshot is allowed to be expired
335339
snapshot = self.modify_metadata("snapshot", snapshot_expired_modifier)
340+
self.trusted_set.update_snapshot(snapshot)
341+
342+
# update targets to trigger final snapshot expiry check
336343
with self.assertRaises(exceptions.ExpiredMetadataError):
337-
self.trusted_set.update_snapshot(snapshot)
344+
self.trusted_set.update_targets(self.metadata["targets"])
338345

339346
def test_update_targets_no_meta_in_snapshot(self):
340347
def no_meta_modifier(snapshot: Snapshot) -> None:

tuf/ngclient/_internal/trusted_metadata_set.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,8 @@ def update_timestamp(self, data: bytes):
227227
self.timestamp.signed.meta["snapshot.json"].version,
228228
)
229229

230-
if new_timestamp.signed.is_expired(self.reference_time):
231-
raise exceptions.ExpiredMetadataError("New timestamp is expired")
230+
# expiry not checked to allow old timestamp to be used for rollback
231+
# protection of new timestamp: expiry is checked in update_snapshot()
232232

233233
self._trusted_set["timestamp"] = new_timestamp
234234
logger.debug("Updated timestamp")
@@ -250,6 +250,11 @@ def update_snapshot(self, data: bytes):
250250
raise RuntimeError("Cannot update snapshot after targets")
251251
logger.debug("Updating snapshot")
252252

253+
# Local timestamp was allowed to be expired to allow for rollback
254+
# checks on new timestamp but now timestamp must not be expired
255+
if self.timestamp.signed.is_expired(self.reference_time):
256+
raise exceptions.ExpiredMetadataError("timestamp.json is expired")
257+
253258
meta = self.timestamp.signed.meta["snapshot.json"]
254259

255260
# Verify against the hashes in timestamp, if any
@@ -301,8 +306,8 @@ def update_snapshot(self, data: bytes):
301306
f"{new_fileinfo.version}, got {fileinfo.version}."
302307
)
303308

304-
if new_snapshot.signed.is_expired(self.reference_time):
305-
raise exceptions.ExpiredMetadataError("New snapshot is expired")
309+
# expiry not checked to allow old snapshot to be used for rollback
310+
# protection of new snapshot: expiry is checked in update_targets()
306311

307312
self._trusted_set["snapshot"] = new_snapshot
308313
logger.debug("Updated snapshot")
@@ -336,6 +341,11 @@ def update_delegated_targets(
336341
if self.snapshot is None:
337342
raise RuntimeError("Cannot load targets before snapshot")
338343

344+
# Local snapshot was allowed to be expired to allow for rollback
345+
# checks on new snapshot but now snapshot must not be expired
346+
if self.snapshot.signed.is_expired(self.reference_time):
347+
raise exceptions.ExpiredMetadataError("snapshot.json is expired")
348+
339349
delegator: Optional[Metadata] = self.get(delegator_name)
340350
if delegator is None:
341351
raise RuntimeError("Cannot load targets before delegator")

0 commit comments

Comments
 (0)