Skip to content

Commit 15dd931

Browse files
author
Lukas Puehringer
committed
Metadata API: make new verify_delegate unaware of Metadata
Change new _Delegator.verify_delegate to take payload bytes and signatures instead of a Metadata object and a payload serializer. This allows using verify_delegate for payloads that do not come in a Metadata container, but e.g. in a DSSE envelope (see #2385). Usage becomes a bit more cumbersome, but still feels reasonable with the recently added shortcut for default canonical bytes representation of Metadata.signed. Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
1 parent fc6c91a commit 15dd931

4 files changed

Lines changed: 83 additions & 41 deletions

File tree

examples/repository/_simplerepo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def submit_role(self, role: str, data: bytes) -> bool:
177177
if not targetpath.startswith(f"{role}/"):
178178
raise ValueError(f"targets allowed under {role}/ only")
179179

180-
self.targets().verify_delegate(role, md)
180+
self.targets().verify_delegate(role, md.signed_bytes, md.signatures)
181181

182182
if md.signed.version != self.targets(role).version + 1:
183183
raise ValueError("Invalid version {md.signed.version}")

tests/test_api.py

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -364,29 +364,49 @@ def test_signed_verify_delegate(self) -> None:
364364
role2 = role2_md.signed
365365

366366
# test the expected delegation tree
367-
root.verify_delegate(Root.type, root_md)
368-
root.verify_delegate(Snapshot.type, snapshot_md)
369-
root.verify_delegate(Targets.type, targets_md)
370-
targets.verify_delegate("role1", role1_md)
371-
role1.verify_delegate("role2", role2_md)
367+
root.verify_delegate(
368+
Root.type, root_md.signed_bytes, root_md.signatures
369+
)
370+
root.verify_delegate(
371+
Snapshot.type, snapshot_md.signed_bytes, snapshot_md.signatures
372+
)
373+
root.verify_delegate(
374+
Targets.type, targets_md.signed_bytes, targets_md.signatures
375+
)
376+
targets.verify_delegate(
377+
"role1", role1_md.signed_bytes, role1_md.signatures
378+
)
379+
role1.verify_delegate(
380+
"role2", role2_md.signed_bytes, role2_md.signatures
381+
)
372382

373383
# only root and targets can verify delegates
374384
with self.assertRaises(AttributeError):
375-
snapshot.verify_delegate(Snapshot.type, snapshot_md)
385+
snapshot.verify_delegate(
386+
Snapshot.type, snapshot_md.signed_bytes, snapshot_md.signatures
387+
)
376388
# verify fails for roles that are not delegated by delegator
377389
with self.assertRaises(ValueError):
378-
root.verify_delegate("role1", role1_md)
390+
root.verify_delegate(
391+
"role1", role1_md.signed_bytes, role1_md.signatures
392+
)
379393
with self.assertRaises(ValueError):
380-
targets.verify_delegate(Targets.type, targets_md)
394+
targets.verify_delegate(
395+
Targets.type, targets_md.signed_bytes, targets_md.signatures
396+
)
381397
# verify fails when delegator has no delegations
382398
with self.assertRaises(ValueError):
383-
role2.verify_delegate("role1", role1_md)
399+
role2.verify_delegate(
400+
"role1", role1_md.signed_bytes, role1_md.signatures
401+
)
384402

385403
# verify fails when delegate content is modified
386404
expires = snapshot.expires
387405
snapshot.expires = expires + timedelta(days=1)
388406
with self.assertRaises(exceptions.UnsignedMetadataError):
389-
root.verify_delegate(Snapshot.type, snapshot_md)
407+
root.verify_delegate(
408+
Snapshot.type, snapshot_md.signed_bytes, snapshot_md.signatures
409+
)
390410
snapshot.expires = expires
391411

392412
# verify fails if sslib verify fails with VerificationError
@@ -395,12 +415,16 @@ def test_signed_verify_delegate(self) -> None:
395415
good_sig = snapshot_md.signatures[keyid].signature
396416
snapshot_md.signatures[keyid].signature = "foo"
397417
with self.assertRaises(exceptions.UnsignedMetadataError):
398-
root.verify_delegate(Snapshot.type, snapshot_md)
418+
root.verify_delegate(
419+
Snapshot.type, snapshot_md.signed_bytes, snapshot_md.signatures
420+
)
399421
snapshot_md.signatures[keyid].signature = good_sig
400422

401423
# verify fails if roles keys do not sign the metadata
402424
with self.assertRaises(exceptions.UnsignedMetadataError):
403-
root.verify_delegate(Timestamp.type, snapshot_md)
425+
root.verify_delegate(
426+
Timestamp.type, snapshot_md.signed_bytes, snapshot_md.signatures
427+
)
404428

405429
# Add a key to snapshot role, make sure the new sig fails to verify
406430
ts_keyid = next(iter(root.roles[Timestamp.type].keyids))
@@ -409,19 +433,25 @@ def test_signed_verify_delegate(self) -> None:
409433

410434
# verify succeeds if threshold is reached even if some signatures
411435
# fail to verify
412-
root.verify_delegate(Snapshot.type, snapshot_md)
436+
root.verify_delegate(
437+
Snapshot.type, snapshot_md.signed_bytes, snapshot_md.signatures
438+
)
413439

414440
# verify fails if threshold of signatures is not reached
415441
root.roles[Snapshot.type].threshold = 2
416442
with self.assertRaises(exceptions.UnsignedMetadataError):
417-
root.verify_delegate(Snapshot.type, snapshot_md)
443+
root.verify_delegate(
444+
Snapshot.type, snapshot_md.signed_bytes, snapshot_md.signatures
445+
)
418446

419447
# verify succeeds when we correct the new signature and reach the
420448
# threshold of 2 keys
421449
snapshot_md.sign(
422450
SSlibSigner(self.keystore[Timestamp.type]), append=True
423451
)
424-
root.verify_delegate(Snapshot.type, snapshot_md)
452+
root.verify_delegate(
453+
Snapshot.type, snapshot_md.signed_bytes, snapshot_md.signatures
454+
)
425455

426456
def test_key_class(self) -> None:
427457
# Test if from_securesystemslib_key removes the private key from keyval

tuf/api/metadata.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -408,8 +408,14 @@ def verify_delegate(
408408
if self.signed.type not in ["root", "targets"]:
409409
raise TypeError("Call is valid only on delegator metadata")
410410

411+
if signed_serializer is None:
412+
payload = delegated_metadata.signed_bytes
413+
414+
else:
415+
payload = signed_serializer.serialize(delegated_metadata.signed)
416+
411417
self.signed.verify_delegate(
412-
delegated_role, delegated_metadata, signed_serializer
418+
delegated_role, payload, delegated_metadata.signatures
413419
)
414420

415421

@@ -662,31 +668,25 @@ def get_key(self, keyid: str) -> Key:
662668
def verify_delegate(
663669
self,
664670
delegated_role: str,
665-
delegated_metadata: Metadata,
666-
signed_serializer: Optional[SignedSerializer] = None,
671+
payload: bytes,
672+
signatures: Dict[str, Signature],
667673
) -> None:
668-
"""Verify that ``delegated_metadata`` is signed with the required
669-
threshold of keys for ``delegated_role``.
674+
"""Verify signature threshold for delegated role.
675+
676+
Verify that there are enough valid ``signatures`` over ``payload``, to
677+
meet the threshold of keys for ``delegated_role``, as defined by the
678+
delegator (``self``).
670679
671680
Args:
672681
delegated_role: Name of the delegated role to verify
673-
delegated_metadata: ``Metadata`` object for the delegated role
674-
signed_serializer: Serializer used for delegate
675-
serialization. Default is ``CanonicalJSONSerializer``.
682+
payload: Signed payload bytes for the delegated role
683+
signatures: Signatures over payload bytes
676684
677685
Raises:
678686
UnsignedMetadataError: ``delegated_role`` was not signed with
679687
required threshold of keys for ``role_name``.
680688
ValueError: no delegation was found for ``delegated_role``.
681689
"""
682-
683-
if signed_serializer is None:
684-
# pylint: disable=import-outside-toplevel
685-
from tuf.api.serialization.json import CanonicalJSONSerializer
686-
687-
signed_serializer = CanonicalJSONSerializer()
688-
689-
data = signed_serializer.serialize(delegated_metadata.signed)
690690
role = self.get_delegated_role(delegated_role)
691691

692692
# verify that delegated_metadata is signed by threshold of unique keys
@@ -698,13 +698,13 @@ def verify_delegate(
698698
logger.info("No key for keyid %s", keyid)
699699
continue
700700

701-
if keyid not in delegated_metadata.signatures:
701+
if keyid not in signatures:
702702
logger.info("No signature for keyid %s", keyid)
703703
continue
704704

705-
sig = delegated_metadata.signatures[keyid]
705+
sig = signatures[keyid]
706706
try:
707-
key.verify_signature(sig, data)
707+
key.verify_signature(sig, payload)
708708
signing_keys.add(keyid)
709709
except sslib_exceptions.UnverifiedSignatureError:
710710
logger.info("Key %s failed to verify %s", keyid, delegated_role)

tuf/ngclient/_internal/trusted_metadata_set.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ def update_root(self, data: bytes) -> Metadata[Root]:
161161
)
162162

163163
# Verify that new root is signed by trusted root
164-
self.root.signed.verify_delegate(Root.type, new_root)
164+
self.root.signed.verify_delegate(
165+
Root.type, new_root.signed_bytes, new_root.signatures
166+
)
165167

166168
if new_root.signed.version != self.root.signed.version + 1:
167169
raise exceptions.BadVersionNumberError(
@@ -170,7 +172,9 @@ def update_root(self, data: bytes) -> Metadata[Root]:
170172
)
171173

172174
# Verify that new root is signed by itself
173-
new_root.signed.verify_delegate(Root.type, new_root)
175+
new_root.signed.verify_delegate(
176+
Root.type, new_root.signed_bytes, new_root.signatures
177+
)
174178

175179
self._trusted_set[Root.type] = new_root
176180
logger.debug("Updated root v%d", new_root.signed.version)
@@ -215,7 +219,9 @@ def update_timestamp(self, data: bytes) -> Metadata[Timestamp]:
215219
f"Expected 'timestamp', got '{new_timestamp.signed.type}'"
216220
)
217221

218-
self.root.signed.verify_delegate(Timestamp.type, new_timestamp)
222+
self.root.signed.verify_delegate(
223+
Timestamp.type, new_timestamp.signed_bytes, new_timestamp.signatures
224+
)
219225

220226
# If an existing trusted timestamp is updated,
221227
# check for a rollback attack
@@ -310,7 +316,9 @@ def update_snapshot(
310316
f"Expected 'snapshot', got '{new_snapshot.signed.type}'"
311317
)
312318

313-
self.root.signed.verify_delegate(Snapshot.type, new_snapshot)
319+
self.root.signed.verify_delegate(
320+
Snapshot.type, new_snapshot.signed_bytes, new_snapshot.signatures
321+
)
314322

315323
# version not checked against meta version to allow old snapshot to be
316324
# used in rollback protection: it is checked when targets is updated
@@ -418,7 +426,9 @@ def update_delegated_targets(
418426
f"Expected 'targets', got '{new_delegate.signed.type}'"
419427
)
420428

421-
delegator.signed.verify_delegate(role_name, new_delegate)
429+
delegator.signed.verify_delegate(
430+
role_name, new_delegate.signed_bytes, new_delegate.signatures
431+
)
422432

423433
version = new_delegate.signed.version
424434
if version != meta.version:
@@ -447,7 +457,9 @@ def _load_trusted_root(self, data: bytes) -> None:
447457
f"Expected 'root', got '{new_root.signed.type}'"
448458
)
449459

450-
new_root.signed.verify_delegate(Root.type, new_root)
460+
new_root.signed.verify_delegate(
461+
Root.type, new_root.signed_bytes, new_root.signatures
462+
)
451463

452464
self._trusted_set[Root.type] = new_root
453465
logger.debug("Loaded trusted root v%d", new_root.signed.version)

0 commit comments

Comments
 (0)