Metadata API: Fix verify_delegate for new Key API

verify_delegate() unfortunately needs an almost complete rewrite
as the Key.verify_signature() API change affects it quite a bit.

Refactoring the role and key lookup into a separate method makes the
code readable again.

Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
This commit is contained in:
Jussi Kukkonen 2022-12-01 11:38:19 +02:00
parent b55ac25cf5
commit ed0ec03399
2 changed files with 58 additions and 29 deletions

View file

@ -353,6 +353,15 @@ def test_metadata_verify_delegate(self) -> None:
root.verify_delegate(Snapshot.type, snapshot)
snapshot.signed.expires = expires
# verify fails if sslib verify fails with VerificationError
# (in this case signature is malformed)
keyid = next(iter(root.signed.roles[Snapshot.type].keyids))
good_sig = snapshot.signatures[keyid].signature
snapshot.signatures[keyid].signature = "foo"
with self.assertRaises(exceptions.UnsignedMetadataError):
root.verify_delegate(Snapshot.type, snapshot)
snapshot.signatures[keyid].signature = good_sig
# verify fails if roles keys do not sign the metadata
with self.assertRaises(exceptions.UnsignedMetadataError):
root.verify_delegate(Timestamp.type, snapshot)

View file

@ -386,29 +386,11 @@ def sign(
return signature
def verify_delegate(
self,
delegated_role: str,
delegated_metadata: "Metadata",
signed_serializer: Optional[SignedSerializer] = None,
) -> None:
"""Verify that ``delegated_metadata`` is signed with the required
threshold of keys for the delegated role ``delegated_role``.
def _get_role_and_keys(
self, delegated_role: str
) -> Tuple["Role", Dict[str, Key]]:
"""Return the keys and role for delegated_role"""
Args:
delegated_role: Name of the delegated role to verify
delegated_metadata: ``Metadata`` object for the delegated role
signed_serializer: Serializer used for delegate
serialization. Default is ``CanonicalJSONSerializer``.
Raises:
UnsignedMetadataError: ``delegated_role`` was not signed with
required threshold of keys for ``role_name``.
ValueError: no delegation was found for ``delegated_role``.
TypeError: called this function on non-delegating metadata class.
"""
# Find the keys and role in delegator metadata
role: Optional[Role] = None
if isinstance(self.signed, Root):
keys = self.signed.keys
@ -431,17 +413,55 @@ def verify_delegate(
if role is None:
raise ValueError(f"No delegation found for {delegated_role}")
return (role, keys)
def verify_delegate(
self,
delegated_role: str,
delegated_metadata: "Metadata",
signed_serializer: Optional[SignedSerializer] = None,
) -> None:
"""Verify that ``delegated_metadata`` is signed with the required
threshold of keys for the delegated role ``delegated_role``.
Args:
delegated_role: Name of the delegated role to verify
delegated_metadata: ``Metadata`` object for the delegated role
signed_serializer: Serializer used for delegate
serialization. Default is ``CanonicalJSONSerializer``.
Raises:
UnsignedMetadataError: ``delegated_role`` was not signed with
required threshold of keys for ``role_name``.
ValueError: no delegation was found for ``delegated_role``.
TypeError: called this function on non-delegating metadata class.
"""
if signed_serializer is None:
# pylint: disable=import-outside-toplevel
from tuf.api.serialization.json import CanonicalJSONSerializer
signed_serializer = CanonicalJSONSerializer()
data = signed_serializer.serialize(delegated_metadata.signed)
role, keys = self._get_role_and_keys(delegated_role)
# verify that delegated_metadata is signed by threshold of unique keys
signing_keys = set()
for keyid in role.keyids:
key = keys[keyid]
if keyid not in keys:
logger.info("No key for keyid %s", keyid)
continue
if keyid not in delegated_metadata.signatures:
logger.info("No signature for keyid %s", keyid)
continue
sig = delegated_metadata.signatures[keyid]
try:
key.verify_signature(delegated_metadata, signed_serializer)
signing_keys.add(key.keyid)
except UnsignedMetadataError:
logger.debug(
"Key %s failed to verify %s", keyid, delegated_role
)
keys[keyid].verify_signature(sig, data)
signing_keys.add(keyid)
except sslib_exceptions.UnverifiedSignatureError:
logger.info("Key %s failed to verify %s", keyid, delegated_role)
if len(signing_keys) < role.threshold:
raise UnsignedMetadataError(