Skip to content

Commit 7b15487

Browse files
committed
Verify validation is performed from local metadata
This change verifies that when local metadata has expired, it is still used to verify new metadata that's pulled from remote Signed-off-by: Ivana Atanasova <[email protected]>
1 parent c86ad65 commit 7b15487

File tree

2 files changed

+58
-60
lines changed

2 files changed

+58
-60
lines changed

tests/test_updater_top_level_update.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import unittest
1212
from datetime import datetime, timedelta
1313
from typing import Iterable, Optional
14+
from unittest import mock
1415

1516
from tests import utils
1617
from tests.repository_simulator import RepositorySimulator
@@ -288,6 +289,46 @@ def test_new_timestamp_unsigned(self) -> None:
288289

289290
self._assert_files_exist([Root.type])
290291

292+
def test_expired_timestamp_version_rollback(self) -> None:
293+
self._run_refresh()
294+
295+
mock_time = mock.Mock()
296+
mock_time.return_value = (
297+
int(self.sim.timestamp.expires.strftime("%Y%m%d%H%M%S")) + 1
298+
)
299+
with mock.patch("time.time", mock_time):
300+
# Check for a rollback attack
301+
self.sim.timestamp.version = 2
302+
self._run_refresh()
303+
304+
self.sim.timestamp.version = 1
305+
with self.assertRaises(ReplayedMetadataError):
306+
self._run_refresh()
307+
308+
self._assert_version_equals(Timestamp.type, 2)
309+
310+
def test_expired_timestamp_snapshot_rollback(self) -> None:
311+
self._run_refresh()
312+
313+
mock_time = mock.Mock()
314+
mock_time.return_value = (
315+
int(self.sim.timestamp.expires.strftime("%Y%m%d%H%M%S")) + 1
316+
)
317+
with mock.patch("time.time", mock_time):
318+
# Check for a rollback attack.
319+
self.sim.snapshot.version = 2
320+
self.sim.update_timestamp() # timestamp v2
321+
self._run_refresh()
322+
323+
# Snapshot meta version is smaller than previous
324+
self.sim.timestamp.snapshot_meta.version = 1
325+
self.sim.timestamp.version += 1 # timestamp v3
326+
327+
with self.assertRaises(ReplayedMetadataError):
328+
self._run_refresh()
329+
330+
self._assert_version_equals(Timestamp.type, 2)
331+
291332
def test_new_timestamp_version_rollback(self) -> None:
292333
# Check for a rollback attack
293334
self.sim.timestamp.version = 2

tests/test_updater_with_simulator.py

Lines changed: 17 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import unittest
1414
from datetime import datetime, timedelta
1515
from typing import Optional, Tuple
16-
from unittest.mock import MagicMock, call, patch
16+
from unittest.mock import MagicMock, Mock, patch
1717

1818
from tests import utils
1919
from tests.repository_simulator import RepositorySimulator
@@ -257,74 +257,31 @@ def test_not_loading_targets_twice(self, wrapped_open: MagicMock) -> None:
257257
updater.get_targetinfo("somepath")
258258
wrapped_open.assert_not_called()
259259

260-
@patch.object(builtins, "open", wraps=builtins.open)
261-
def test_expired_metadata(self, wrapped_open: MagicMock) -> None:
262-
# Test that expired timestamp/snapshot can be used to verify the next
263-
# version of timestamp/snapshot respectively.
264-
# If there is an expired local targets it won't be verified and the
265-
# updater will try to fetch and verify the next version without using
266-
# any information from the old expired targets file.
260+
def test_expired_metadata(self) -> None:
261+
# Test that expired local timestamp/snapshot can be used for updating
262+
# from remote
267263

268264
# Make a successful update of valid metadata which stores it in cache
269265
self._run_refresh()
270266

271-
past_datetime = datetime.utcnow().replace(microsecond=0) - timedelta(
272-
days=5
273-
)
274-
275-
# Store the future_datetime for future reference
276-
future_datetime = self.sim.timestamp.expires
277-
278-
# Make version 1 stored metadata in the simulator expired.
279-
past_datetime = datetime.utcnow().replace(microsecond=0) - timedelta(
280-
weeks=52
281-
)
282-
self.sim.timestamp.expires = past_datetime
283-
self.sim.snapshot.expires = past_datetime
284-
self.sim.targets.expires = past_datetime
285-
286-
# Serializer is used to serialize JSON in a human readable format.
287-
seriazer = JSONSerializer()
288-
289-
# Replace current version 1 roles with expired ones.
290-
for role in ["timestamp", "snapshot"]:
291-
md = Metadata.from_bytes(self.sim.fetch_metadata(role))
292-
md.to_file(f"{self.metadata_dir}/{role}.json", seriazer)
293-
294-
# Make version 2 of the roles valid by using a future expiry date
295-
self.sim.timestamp.expires = future_datetime
296-
self.sim.snapshot.expires = future_datetime
297-
self.sim.targets.expires = future_datetime
298-
299-
self.sim.targets.version += 1
300-
self.sim.update_snapshot()
301-
302-
# Clean up calls to open during refresh()
303-
wrapped_open.reset_mock()
304-
305-
# Create a new updater and perform a second update while
306-
# the metadata is already stored in cache (metadata dir)
307-
self._run_refresh()
308-
309-
# Test that an expired timestamp/snapshot when loaded from cache is not
310-
# stored as final but is used to verify the new timestamp
311-
wrapped_open.assert_has_calls(
312-
[
313-
call(os.path.join(self.metadata_dir, "root.json"), "rb"),
314-
call(os.path.join(self.metadata_dir, "timestamp.json"), "rb"),
315-
call(os.path.join(self.metadata_dir, "snapshot.json"), "rb"),
316-
call(os.path.join(self.metadata_dir, "targets.json"), "rb"),
317-
]
267+
# Simulate expired local metadata by mocking system time one second ahead
268+
mock_time = Mock()
269+
mock_time.return_value = (
270+
int(self.sim.timestamp.expires.strftime("%Y%m%d%H%M%S")) + 1
318271
)
272+
with patch("time.time", mock_time):
273+
self.sim.targets.version += 1
274+
self.sim.update_snapshot()
275+
# Create a new updater and perform a second update while
276+
# the metadata is already stored in cache (metadata dir)
277+
self._run_refresh()
319278

320-
# Assert that the final version of timestamp/snapshot is version 2 with
321-
# a future expiry date.
279+
# Assert that the final version of timestamp/snapshot is version 2
280+
# which means a successful refresh is performed
281+
# with expired local metadata
322282
for role in ["timestamp", "snapshot", "targets"]:
323283
md = Metadata.from_file(f"{self.metadata_dir}/{role}.json")
324284
self.assertEqual(md.signed.version, 2)
325-
self.assertEqual(md.signed.expires, future_datetime)
326-
327-
wrapped_open.reset_mock()
328285

329286

330287
if __name__ == "__main__":

0 commit comments

Comments
 (0)