Skip to content

Commit ef136d4

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 944f63b commit ef136d4

File tree

2 files changed

+59
-62
lines changed

2 files changed

+59
-62
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
@@ -303,6 +304,46 @@ def test_new_timestamp_unsigned(self) -> None:
303304

304305
self._assert_files_exist([Root.type])
305306

307+
def test_expired_timestamp_version_rollback(self) -> None:
308+
self._run_refresh()
309+
310+
mock_time = mock.Mock()
311+
mock_time.return_value = (
312+
int(self.sim.timestamp.expires.strftime("%Y%m%d%H%M%S")) + 1
313+
)
314+
with mock.patch("time.time", mock_time):
315+
# Check for a rollback attack
316+
self.sim.timestamp.version = 2
317+
self._run_refresh()
318+
319+
self.sim.timestamp.version = 1
320+
with self.assertRaises(ReplayedMetadataError):
321+
self._run_refresh()
322+
323+
self._assert_version_equals(Timestamp.type, 2)
324+
325+
def test_expired_timestamp_snapshot_rollback(self) -> None:
326+
self._run_refresh()
327+
328+
mock_time = mock.Mock()
329+
mock_time.return_value = (
330+
int(self.sim.timestamp.expires.strftime("%Y%m%d%H%M%S")) + 1
331+
)
332+
with mock.patch("time.time", mock_time):
333+
# Check for a rollback attack.
334+
self.sim.snapshot.version = 2
335+
self.sim.update_timestamp() # timestamp v2
336+
self._run_refresh()
337+
338+
# Snapshot meta version is smaller than previous
339+
self.sim.timestamp.snapshot_meta.version = 1
340+
self.sim.timestamp.version += 1 # timestamp v3
341+
342+
with self.assertRaises(ReplayedMetadataError):
343+
self._run_refresh()
344+
345+
self._assert_version_equals(Timestamp.type, 2)
346+
306347
def test_new_timestamp_version_rollback(self) -> None:
307348
# Check for a rollback attack
308349
self.sim.timestamp.version = 2

tests/test_updater_with_simulator.py

Lines changed: 18 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,17 @@
1111
import sys
1212
import tempfile
1313
import unittest
14-
from datetime import datetime, timedelta
1514
from typing import Optional
16-
from unittest.mock import MagicMock, call, patch
15+
from unittest.mock import MagicMock, Mock, patch
1716

1817
from tests import utils
1918
from tests.repository_simulator import RepositorySimulator
2019
from tuf.api.metadata import (
2120
SPECIFICATION_VERSION,
21+
DelegatedRole,
2222
Metadata,
2323
Targets,
2424
)
25-
from tuf.api.serialization.json import JSONSerializer
2625
from tuf.exceptions import BadVersionNumberError, UnsignedMetadataError
2726
from tuf.ngclient import Updater
2827

@@ -213,74 +212,31 @@ def test_not_loading_targets_twice(self, wrapped_open: MagicMock) -> None:
213212
updater.get_targetinfo("somepath")
214213
wrapped_open.assert_not_called()
215214

216-
@patch.object(builtins, "open", wraps=builtins.open)
217-
def test_expired_metadata(self, wrapped_open: MagicMock) -> None:
218-
# Test that expired timestamp/snapshot can be used to verify the next
219-
# version of timestamp/snapshot respectively.
220-
# If there is an expired local targets it won't be verified and the
221-
# updater will try to fetch and verify the next version without using
222-
# any information from the old expired targets file.
215+
def test_expired_metadata(self) -> None:
216+
# Test that expired local timestamp/snapshot can be used for updating
217+
# from remote
223218

224219
# Make a successful update of valid metadata which stores it in cache
225220
self._run_refresh()
226221

227-
past_datetime = datetime.utcnow().replace(microsecond=0) - timedelta(
228-
days=5
229-
)
230-
231-
# Store the future_datetime for future reference
232-
future_datetime = self.sim.timestamp.expires
233-
234-
# Make version 1 stored metadata in the simulator expired.
235-
past_datetime = datetime.utcnow().replace(microsecond=0) - timedelta(
236-
weeks=52
237-
)
238-
self.sim.timestamp.expires = past_datetime
239-
self.sim.snapshot.expires = past_datetime
240-
self.sim.targets.expires = past_datetime
241-
242-
# Serializer is used to serialize JSON in a human readable format.
243-
seriazer = JSONSerializer()
244-
245-
# Replace current version 1 roles with expired ones.
246-
for role in ["timestamp", "snapshot"]:
247-
md = Metadata.from_bytes(self.sim.fetch_metadata(role))
248-
md.to_file(f"{self.metadata_dir}/{role}.json", seriazer)
249-
250-
# Make version 2 of the roles valid by using a future expiry date
251-
self.sim.timestamp.expires = future_datetime
252-
self.sim.snapshot.expires = future_datetime
253-
self.sim.targets.expires = future_datetime
254-
255-
self.sim.targets.version += 1
256-
self.sim.update_snapshot()
257-
258-
# Clean up calls to open during refresh()
259-
wrapped_open.reset_mock()
260-
261-
# Create a new updater and perform a second update while
262-
# the metadata is already stored in cache (metadata dir)
263-
self._run_refresh()
264-
265-
# Test that an expired timestamp/snapshot when loaded from cache is not
266-
# stored as final but is used to verify the new timestamp
267-
wrapped_open.assert_has_calls(
268-
[
269-
call(os.path.join(self.metadata_dir, "root.json"), "rb"),
270-
call(os.path.join(self.metadata_dir, "timestamp.json"), "rb"),
271-
call(os.path.join(self.metadata_dir, "snapshot.json"), "rb"),
272-
call(os.path.join(self.metadata_dir, "targets.json"), "rb"),
273-
]
222+
# Simulate expired local metadata by mocking system time one second ahead
223+
mock_time = Mock()
224+
mock_time.return_value = (
225+
int(self.sim.timestamp.expires.strftime("%Y%m%d%H%M%S")) + 1
274226
)
227+
with patch("time.time", mock_time):
228+
self.sim.targets.version += 1
229+
self.sim.update_snapshot()
230+
# Create a new updater and perform a second update while
231+
# the metadata is already stored in cache (metadata dir)
232+
self._run_refresh()
275233

276-
# Assert that the final version of timestamp/snapshot is version 2 with
277-
# a future expiry date.
234+
# Assert that the final version of timestamp/snapshot is version 2
235+
# which means a successful refresh is performed
236+
# with expired local metadata
278237
for role in ["timestamp", "snapshot", "targets"]:
279238
md = Metadata.from_file(f"{self.metadata_dir}/{role}.json")
280239
self.assertEqual(md.signed.version, 2)
281-
self.assertEqual(md.signed.expires, future_datetime)
282-
283-
wrapped_open.reset_mock()
284240

285241

286242
if __name__ == "__main__":

0 commit comments

Comments
 (0)