@@ -150,6 +150,16 @@ def __eq__(self, other: Any) -> bool:
150150 and self .unrecognized_fields == other .unrecognized_fields
151151 )
152152
153+ @property
154+ def signed_bytes (self ) -> bytes :
155+ """Default canonical json byte representation of ``self.signed``."""
156+
157+ # Use local scope import to avoid circular import errors
158+ # pylint: disable=import-outside-toplevel
159+ from tuf .api .serialization .json import CanonicalJSONSerializer
160+
161+ return CanonicalJSONSerializer ().serialize (self .signed )
162+
153163 @classmethod
154164 def from_dict (cls , metadata : Dict [str , Any ]) -> "Metadata[T]" :
155165 """Create ``Metadata`` object from its json/dict representation.
@@ -366,13 +376,9 @@ def sign(
366376 """
367377
368378 if signed_serializer is None :
369- # Use local scope import to avoid circular import errors
370- # pylint: disable=import-outside-toplevel
371- from tuf .api .serialization .json import CanonicalJSONSerializer
372-
373- signed_serializer = CanonicalJSONSerializer ()
374-
375- bytes_data = signed_serializer .serialize (self .signed )
379+ bytes_data = self .signed_bytes
380+ else :
381+ bytes_data = signed_serializer .serialize (self .signed )
376382
377383 try :
378384 signature = signer .sign (bytes_data )
@@ -393,58 +399,24 @@ def verify_delegate(
393399 signed_serializer : Optional [SignedSerializer ] = None ,
394400 ) -> None :
395401 """Verify that ``delegated_metadata`` is signed with the required
396- threshold of keys for the delegated role ``delegated_role``.
402+ threshold of keys for ``delegated_role``.
397403
398- Args:
399- delegated_role: Name of the delegated role to verify
400- delegated_metadata: ``Metadata`` object for the delegated role
401- signed_serializer: Serializer used for delegate
402- serialization. Default is ``CanonicalJSONSerializer``.
403-
404- Raises:
405- UnsignedMetadataError: ``delegated_role`` was not signed with
406- required threshold of keys for ``role_name``.
407- ValueError: no delegation was found for ``delegated_role``.
408- TypeError: called this function on non-delegating metadata class.
404+ .. deprecated:: 3.1.0
405+ Please use ``Root.verify_delegate()`` or ``Targets.verify_delegate()``.
409406 """
410407
411408 if self .signed .type not in ["root" , "targets" ]:
412409 raise TypeError ("Call is valid only on delegator metadata" )
413410
414411 if signed_serializer is None :
415- # pylint: disable=import-outside-toplevel
416- from tuf .api .serialization .json import CanonicalJSONSerializer
417-
418- signed_serializer = CanonicalJSONSerializer ()
419-
420- data = signed_serializer .serialize (delegated_metadata .signed )
421- role = self .signed .get_delegated_role (delegated_role )
422-
423- # verify that delegated_metadata is signed by threshold of unique keys
424- signing_keys = set ()
425- for keyid in role .keyids :
426- try :
427- key = self .signed .get_key (keyid )
428- except ValueError :
429- logger .info ("No key for keyid %s" , keyid )
430- continue
412+ payload = delegated_metadata .signed_bytes
431413
432- if keyid not in delegated_metadata .signatures :
433- logger .info ("No signature for keyid %s" , keyid )
434- continue
435-
436- sig = delegated_metadata .signatures [keyid ]
437- try :
438- key .verify_signature (sig , data )
439- signing_keys .add (keyid )
440- except sslib_exceptions .UnverifiedSignatureError :
441- logger .info ("Key %s failed to verify %s" , keyid , delegated_role )
414+ else :
415+ payload = signed_serializer .serialize (delegated_metadata .signed )
442416
443- if len (signing_keys ) < role .threshold :
444- raise UnsignedMetadataError (
445- f"{ delegated_role } was signed by { len (signing_keys )} /"
446- f"{ role .threshold } keys" ,
447- )
417+ self .signed .verify_delegate (
418+ delegated_role , payload , delegated_metadata .signatures
419+ )
448420
449421
450422class Signed (metaclass = abc .ABCMeta ):
@@ -674,7 +646,77 @@ def to_dict(self) -> Dict[str, Any]:
674646 }
675647
676648
677- class Root (Signed ):
649+ class _DelegatorMixin (metaclass = abc .ABCMeta ):
650+ """Class that implements verify_delegate() for Root and Targets"""
651+
652+ @abc .abstractmethod
653+ def get_delegated_role (self , delegated_role : str ) -> Role :
654+ """Return the role object for the given delegated role.
655+
656+ Raises ValueError if delegated_role is not actually delegated.
657+ """
658+ raise NotImplementedError
659+
660+ @abc .abstractmethod
661+ def get_key (self , keyid : str ) -> Key :
662+ """Return the key object for the given keyid.
663+
664+ Raises ValueError if key is not found.
665+ """
666+ raise NotImplementedError
667+
668+ def verify_delegate (
669+ self ,
670+ delegated_role : str ,
671+ payload : bytes ,
672+ signatures : Dict [str , Signature ],
673+ ) -> None :
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``).
679+
680+ Args:
681+ delegated_role: Name of the delegated role to verify
682+ payload: Signed payload bytes for the delegated role
683+ signatures: Signatures over payload bytes
684+
685+ Raises:
686+ UnsignedMetadataError: ``delegated_role`` was not signed with
687+ required threshold of keys for ``role_name``.
688+ ValueError: no delegation was found for ``delegated_role``.
689+ """
690+ role = self .get_delegated_role (delegated_role )
691+
692+ # verify that delegated_metadata is signed by threshold of unique keys
693+ signing_keys = set ()
694+ for keyid in role .keyids :
695+ try :
696+ key = self .get_key (keyid )
697+ except ValueError :
698+ logger .info ("No key for keyid %s" , keyid )
699+ continue
700+
701+ if keyid not in signatures :
702+ logger .info ("No signature for keyid %s" , keyid )
703+ continue
704+
705+ sig = signatures [keyid ]
706+ try :
707+ key .verify_signature (sig , payload )
708+ signing_keys .add (keyid )
709+ except sslib_exceptions .UnverifiedSignatureError :
710+ logger .info ("Key %s failed to verify %s" , keyid , delegated_role )
711+
712+ if len (signing_keys ) < role .threshold :
713+ raise UnsignedMetadataError (
714+ f"{ delegated_role } was signed by { len (signing_keys )} /"
715+ f"{ role .threshold } keys" ,
716+ )
717+
718+
719+ class Root (Signed , _DelegatorMixin ):
678720 """A container for the signed part of root metadata.
679721
680722 Parameters listed below are also instance attributes.
@@ -823,11 +865,7 @@ def get_delegated_role(self, delegated_role: str) -> Role:
823865
824866 return self .roles [delegated_role ]
825867
826- def get_key (self , keyid : str ) -> Key :
827- """Return the key object for the given keyid.
828-
829- Raises ValueError if key is not found.
830- """
868+ def get_key (self , keyid : str ) -> Key : # noqa: D102
831869 if keyid not in self .keys :
832870 raise ValueError (f"Key { keyid } not found" )
833871
@@ -1778,7 +1816,7 @@ def get_prefixed_paths(self) -> List[str]:
17781816 return paths
17791817
17801818
1781- class Targets (Signed ):
1819+ class Targets (Signed , _DelegatorMixin ):
17821820 """A container for the signed part of targets metadata.
17831821
17841822 Targets contains verifying information about target files and also
@@ -1952,11 +1990,7 @@ def get_delegated_role(self, delegated_role: str) -> Role:
19521990
19531991 return role
19541992
1955- def get_key (self , keyid : str ) -> Key :
1956- """Return the key object for the given keyid.
1957-
1958- Raises ValueError if keyid is not found.
1959- """
1993+ def get_key (self , keyid : str ) -> Key : # noqa: D102
19601994 if self .delegations is None :
19611995 raise ValueError ("No delegations found" )
19621996 if keyid not in self .delegations .keys :
0 commit comments