Skip to content

Commit 0911972

Browse files
author
Jussi Kukkonen
committed
ngclient: Remove root_update_finished()
The usefulness was debatable to begin with, and now that it has become clear that rollback protection requires a second "final verification" step for all three root, timestamp and snapshot it is clear that root_update_finished() is not good design. update_root() still accepts expired root metadata but now the final root expiry is checked when the "next" metadata (timestamp) is loaded. Signed-off-by: Jussi Kukkonen <[email protected]>
1 parent e9106b5 commit 0911972

File tree

3 files changed

+17
-56
lines changed

3 files changed

+17
-56
lines changed

tests/test_trusted_metadata_set.py

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ def _root_updated_and_update_timestamp(
9292
9393
"""
9494
timestamp_bytes = timestamp_bytes or self.metadata["timestamp"]
95-
self.trusted_set.root_update_finished()
9695
self.trusted_set.update_timestamp(timestamp_bytes)
9796

9897

@@ -118,7 +117,6 @@ def _update_all_besides_targets(
118117

119118

120119
def test_update(self):
121-
self.trusted_set.root_update_finished()
122120
self.trusted_set.update_timestamp(self.metadata["timestamp"])
123121
self.trusted_set.update_snapshot(self.metadata["snapshot"])
124122
self.trusted_set.update_targets(self.metadata["targets"])
@@ -139,24 +137,16 @@ def test_update(self):
139137
self.assertTrue(count, 6)
140138

141139
def test_out_of_order_ops(self):
142-
# Update timestamp before root is finished
143-
with self.assertRaises(RuntimeError):
144-
self.trusted_set.update_timestamp(self.metadata["timestamp"])
145-
146-
self.trusted_set.root_update_finished()
147-
with self.assertRaises(RuntimeError):
148-
self.trusted_set.root_update_finished()
149-
150-
# Update root after a previous successful root update
151-
with self.assertRaises(RuntimeError):
152-
self.trusted_set.update_root(self.metadata["root"])
153-
154140
# Update snapshot before timestamp
155141
with self.assertRaises(RuntimeError):
156142
self.trusted_set.update_snapshot(self.metadata["snapshot"])
157143

158144
self.trusted_set.update_timestamp(self.metadata["timestamp"])
159145

146+
# Update root after timestamp
147+
with self.assertRaises(RuntimeError):
148+
self.trusted_set.update_root(self.metadata["root"])
149+
160150
# Update targets before snapshot
161151
with self.assertRaises(RuntimeError):
162152
self.trusted_set.update_targets(self.metadata["targets"])
@@ -198,8 +188,6 @@ def test_update_with_invalid_json(self):
198188
with self.assertRaises(exceptions.RepositoryError):
199189
self.trusted_set.update_root(self.metadata["snapshot"])
200190

201-
self.trusted_set.root_update_finished()
202-
203191
top_level_md = [
204192
(self.metadata["timestamp"], self.trusted_set.update_timestamp),
205193
(self.metadata["snapshot"], self.trusted_set.update_snapshot),
@@ -241,15 +229,16 @@ def test_update_root_new_root_ver_same_as_trusted_root_ver(self):
241229
with self.assertRaises(exceptions.ReplayedMetadataError):
242230
self.trusted_set.update_root(self.metadata["root"])
243231

244-
def test_root_update_finished_expired(self):
232+
def test_root_expired_final_root(self):
245233
def root_expired_modifier(root: Root) -> None:
246234
root.expires = datetime(1970, 1, 1)
247235

236+
# intermediate root can be expired
248237
root = self.modify_metadata("root", root_expired_modifier)
249238
tmp_trusted_set = TrustedMetadataSet(root)
250-
# call root_update_finished when trusted root has expired
239+
# update timestamp to trigger final root expiry check
251240
with self.assertRaises(exceptions.ExpiredMetadataError):
252-
tmp_trusted_set.root_update_finished()
241+
tmp_trusted_set.update_timestamp(self.metadata["timestamp"])
253242

254243

255244
def test_update_timestamp_new_timestamp_ver_below_trusted_ver(self):
@@ -275,7 +264,6 @@ def bump_snapshot_version(timestamp: Timestamp) -> None:
275264
self.trusted_set.update_timestamp(self.metadata["timestamp"])
276265

277266
def test_update_timestamp_expired(self):
278-
self.trusted_set.root_update_finished()
279267
# new_timestamp has expired
280268
def timestamp_expired_modifier(timestamp: Timestamp) -> None:
281269
timestamp.expires = datetime(1970, 1, 1)

tuf/ngclient/_internal/trusted_metadata_set.py

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030
>>> # update root from remote until no more are available
3131
>>> with download("root", trusted_set.root.signed.version + 1) as f:
3232
>>> trusted_set.update_root(f.read())
33-
>>> # ...
34-
>>> trusted_set.root_update_finished()
3533
>>>
3634
>>> # load local timestamp, then update from remote
3735
>>> try:
@@ -58,9 +56,6 @@
5856
a generic RepositoryError that covers every issue that server provided
5957
metadata could inflict (other errors would be user errors), but this is not
6058
yet the case
61-
* usefulness of root_update_finished() can be debated: it could be done
62-
in the beginning of load_timestamp()...
63-
* some metadata interactions might work better in Metadata itself
6459
* Progress through Specification update process should be documented
6560
(not sure yet how: maybe a spec_logger that logs specification events?)
6661
"""
@@ -99,7 +94,6 @@ def __init__(self, root_data: bytes):
9994
"""
10095
self._trusted_set = {} # type: Dict[str: Metadata]
10196
self.reference_time = datetime.utcnow()
102-
self._root_update_finished = False
10397

10498
# Load and validate the local root metadata. Valid initial trusted root
10599
# metadata is required
@@ -144,7 +138,7 @@ def update_root(self, data: bytes):
144138
"""Verifies and loads 'data' as new root metadata.
145139
146140
Note that an expired intermediate root is considered valid: expiry is
147-
only checked for the final root in root_update_finished().
141+
only checked for the final root in update_timestamp().
148142
149143
Args:
150144
data: unverified new root metadata as bytes
@@ -153,10 +147,8 @@ def update_root(self, data: bytes):
153147
RepositoryError: Metadata failed to load or verify. The actual
154148
error type and content will contain more details.
155149
"""
156-
if self._root_update_finished:
157-
raise RuntimeError(
158-
"Cannot update root after root update is finished"
159-
)
150+
if self.timestamp is not None:
151+
raise RuntimeError("Cannot update root after timestamp")
160152
logger.debug("Updating root")
161153

162154
try:
@@ -183,26 +175,6 @@ def update_root(self, data: bytes):
183175
self._trusted_set["root"] = new_root
184176
logger.debug("Updated root")
185177

186-
def root_update_finished(self):
187-
"""Marks root metadata as final and verifies it is not expired
188-
189-
Raises:
190-
ExpiredMetadataError: The final root metadata is expired.
191-
"""
192-
if self._root_update_finished:
193-
raise RuntimeError("Root update is already finished")
194-
195-
if self.root.signed.is_expired(self.reference_time):
196-
raise exceptions.ExpiredMetadataError("New root.json is expired")
197-
198-
# No need to delete timestamp/snapshot here as specification instructs
199-
# for fast-forward attack recovery: timestamp/snapshot can not be
200-
# loaded at this point and when loaded later they will be verified
201-
# with current root keys.
202-
203-
self._root_update_finished = True
204-
logger.debug("Verified final root.json")
205-
206178
def update_timestamp(self, data: bytes):
207179
"""Verifies and loads 'data' as new timestamp metadata.
208180
@@ -213,11 +185,15 @@ def update_timestamp(self, data: bytes):
213185
RepositoryError: Metadata failed to load or verify. The actual
214186
error type and content will contain more details.
215187
"""
216-
if not self._root_update_finished:
217-
raise RuntimeError("Cannot update timestamp before root")
218188
if self.snapshot is not None:
219189
raise RuntimeError("Cannot update timestamp after snapshot")
220190

191+
# client workflow 5.3.10: Make sure final root is not expired.
192+
if self.root.signed.is_expired(self.reference_time):
193+
raise exceptions.ExpiredMetadataError("Final root.json is expired")
194+
# No need to check for 5.3.11 (fast forward attack recovery):
195+
# timestamp/snapshot can not yet be loaded at this point
196+
221197
try:
222198
new_timestamp = Metadata.from_bytes(data)
223199
except DeserializationError as e:

tuf/ngclient/updater.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,6 @@ def _load_root(self) -> None:
320320
# 404/403 means current root is newest available
321321
break
322322

323-
# Verify final root
324-
self._trusted_set.root_update_finished()
325-
326323
def _load_timestamp(self) -> None:
327324
"""Load local and remote timestamp metadata"""
328325
try:

0 commit comments

Comments
 (0)