@@ -240,9 +240,11 @@ def update_timestamp(self, data: bytes):
240240 def update_snapshot (self , data : bytes ):
241241 """Verifies and loads 'data' as new snapshot metadata.
242242
243- Note that an expired intermediate snapshot is considered valid so it
244- can be used for rollback checks on newer, final snapshot. Expiry is
245- only checked for the final snapshot in update_delegated_targets().
243+ Note that intermediate snapshot is considered valid even if it is
244+ expired or the version does not match the timestamp meta version. This
245+ means the intermediate snapshot can be used for rollback checks on
246+ newer, final snapshot. Expiry and meta version are only checked for
247+ the final snapshot in update_delegated_targets().
246248
247249 Args:
248250 data: unverified new snapshot metadata as bytes
@@ -285,18 +287,10 @@ def update_snapshot(self, data: bytes):
285287
286288 self .root .verify_delegate ("snapshot" , new_snapshot )
287289
288- if (
289- new_snapshot .signed .version
290- != self .timestamp .signed .meta ["snapshot.json" ].version
291- ):
292- raise exceptions .BadVersionNumberError (
293- f"Expected snapshot version "
294- f"{ self .timestamp .signed .meta ['snapshot.json' ].version } , "
295- f"got { new_snapshot .signed .version } "
296- )
290+ # version not checked against meta version to allow old snapshot to be
291+ # used in rollback protection: it is checked when targets is updated
297292
298- # If an existing trusted snapshot is updated,
299- # check for a rollback attack
293+ # If an existing trusted snapshot is updated, check for rollback attack
300294 if self .snapshot is not None :
301295 for filename , fileinfo in self .snapshot .signed .meta .items ():
302296 new_fileinfo = new_snapshot .signed .meta .get (filename )
@@ -315,11 +309,25 @@ def update_snapshot(self, data: bytes):
315309 )
316310
317311 # expiry not checked to allow old snapshot to be used for rollback
318- # protection of new snapshot: expiry is checked in update_targets()
312+ # protection of new snapshot: it is checked when targets is updated
319313
320314 self ._trusted_set ["snapshot" ] = new_snapshot
321315 logger .debug ("Updated snapshot" )
322316
317+ def _check_final_snapshot (self ):
318+ if self .snapshot .signed .is_expired (self .reference_time ):
319+ raise exceptions .ExpiredMetadataError ("snapshot.json is expired" )
320+
321+ if (
322+ self .snapshot .signed .version
323+ != self .timestamp .signed .meta ["snapshot.json" ].version
324+ ):
325+ raise exceptions .BadVersionNumberError (
326+ f"Expected snapshot version "
327+ f"{ self .timestamp .signed .meta ['snapshot.json' ].version } , "
328+ f"got { self .snapshot .signed .version } "
329+ )
330+
323331 def update_targets (self , data : bytes ):
324332 """Verifies and loads 'data' as new top-level targets metadata.
325333
@@ -349,10 +357,10 @@ def update_delegated_targets(
349357 if self .snapshot is None :
350358 raise RuntimeError ("Cannot load targets before snapshot" )
351359
352- # Local snapshot was allowed to be expired to allow for rollback
353- # checks on new snapshot but now snapshot must not be expired
354- if self . snapshot . signed . is_expired ( self . reference_time ):
355- raise exceptions . ExpiredMetadataError ( "snapshot.json is expired" )
360+ # Local snapshot was allowed to be expired and to not match meta
361+ # version to allow for rollback checks on new snapshot but now
362+ # snapshot must not be expired and must match meta version
363+ self . _check_final_snapshot ( )
356364
357365 delegator : Optional [Metadata ] = self .get (delegator_name )
358366 if delegator is None :
0 commit comments