2020from datetime import datetime , timedelta
2121from typing import Any , ClassVar , Dict , List , Mapping , Optional , Tuple , Type
2222
23- from securesystemslib . keys import verify_signature
23+ from securesystemslib import keys as sslib_keys
2424from securesystemslib .signer import Signature , Signer
2525from securesystemslib .storage import FilesystemBackend , StorageBackendInterface
2626from securesystemslib .util import persist_temp_file
@@ -251,59 +251,6 @@ def sign(
251251
252252 return signature
253253
254- def verify (
255- self ,
256- key : Mapping [str , Any ],
257- signed_serializer : Optional [SignedSerializer ] = None ,
258- ) -> bool :
259- """Verifies 'signatures' over 'signed' that match the passed key by id.
260-
261- Arguments:
262- key: A securesystemslib-style public key object.
263- signed_serializer: A SignedSerializer subclass instance that
264- implements the desired canonicalization format. Per default a
265- CanonicalJSONSerializer is used.
266-
267- Raises:
268- # TODO: Revise exception taxonomy
269- tuf.exceptions.Error: None or multiple signatures found for key.
270- securesystemslib.exceptions.FormatError: Key argument is malformed.
271- tuf.api.serialization.SerializationError:
272- 'signed' cannot be serialized.
273- securesystemslib.exceptions.CryptoError, \
274- securesystemslib.exceptions.UnsupportedAlgorithmError:
275- Signing errors.
276-
277- Returns:
278- A boolean indicating if the signature is valid for the passed key.
279-
280- """
281- signatures_for_keyid = list (
282- filter (lambda sig : sig .keyid == key ["keyid" ], self .signatures )
283- )
284-
285- if not signatures_for_keyid :
286- raise exceptions .Error (f"no signature for key { key ['keyid' ]} ." )
287-
288- if len (signatures_for_keyid ) > 1 :
289- raise exceptions .Error (
290- f"{ len (signatures_for_keyid )} signatures for key "
291- f"{ key ['keyid' ]} , not sure which one to verify."
292- )
293-
294- if signed_serializer is None :
295- # Use local scope import to avoid circular import errors
296- # pylint: disable=import-outside-toplevel
297- from tuf .api .serialization .json import CanonicalJSONSerializer
298-
299- signed_serializer = CanonicalJSONSerializer ()
300-
301- return verify_signature (
302- key ,
303- signatures_for_keyid [0 ].to_dict (),
304- signed_serializer .serialize (self .signed ),
305- )
306-
307254
308255class Signed (metaclass = abc .ABCMeta ):
309256 """A base class for the signed part of TUF metadata.
@@ -431,6 +378,9 @@ class Key:
431378 """A container class representing the public portion of a Key.
432379
433380 Attributes:
381+ keyid: An identifier string that must uniquely identify a key within
382+ the metadata it is used in. This implementation does not verify
383+ that keyid is the hash of a specific representation of the key.
434384 keytype: A string denoting a public key signature system,
435385 such as "rsa", "ed25519", and "ecdsa-sha2-nistp256".
436386 scheme: A string denoting a corresponding signature scheme. For example:
@@ -442,26 +392,28 @@ class Key:
442392
443393 def __init__ (
444394 self ,
395+ keyid : str ,
445396 keytype : str ,
446397 scheme : str ,
447398 keyval : Dict [str , str ],
448399 unrecognized_fields : Optional [Mapping [str , Any ]] = None ,
449400 ) -> None :
450401 if not keyval .get ("public" ):
451402 raise ValueError ("keyval doesn't follow the specification format!" )
403+ self .keyid = keyid
452404 self .keytype = keytype
453405 self .scheme = scheme
454406 self .keyval = keyval
455407 self .unrecognized_fields : Mapping [str , Any ] = unrecognized_fields or {}
456408
457409 @classmethod
458- def from_dict (cls , key_dict : Dict [str , Any ]) -> "Key" :
410+ def from_dict (cls , keyid : str , key_dict : Dict [str , Any ]) -> "Key" :
459411 """Creates Key object from its dict representation."""
460412 keytype = key_dict .pop ("keytype" )
461413 scheme = key_dict .pop ("scheme" )
462414 keyval = key_dict .pop ("keyval" )
463415 # All fields left in the key_dict are unrecognized.
464- return cls (keytype , scheme , keyval , key_dict )
416+ return cls (keyid , keytype , scheme , keyval , key_dict )
465417
466418 def to_dict (self ) -> Dict [str , Any ]:
467419 """Returns the dictionary representation of self."""
@@ -472,6 +424,59 @@ def to_dict(self) -> Dict[str, Any]:
472424 ** self .unrecognized_fields ,
473425 }
474426
427+ def to_securesystemslib_key (self ) -> Dict [str , Any ]:
428+ """Returns a Securesystemslib compatible representation of self."""
429+ return {
430+ "keyid" : self .keyid ,
431+ "keytype" : self .keytype ,
432+ "scheme" : self .scheme ,
433+ "keyval" : self .keyval ,
434+ }
435+
436+ def verify_signature (
437+ self ,
438+ metadata : Metadata ,
439+ signed_serializer : Optional [SignedSerializer ] = None ,
440+ ):
441+ """Verifies that the 'metadata.signatures' contains a signature made
442+ with this key, correctly signing 'metadata.signed'.
443+
444+ Arguments:
445+ metadata: Metadata to verify
446+ signed_serializer: Optional; SignedSerializer to serialize
447+ 'metadata.signed' with. Default is CanonicalJSONSerializer.
448+
449+ Raises:
450+ UnsignedMetadataError: The signature could not be verified for a
451+ variety of possible reasons: see error message.
452+ TODO: Various other errors currently bleed through from lower
453+ level components: Issue #1351
454+ """
455+ try :
456+ sigs = metadata .signatures
457+ signature = next (sig for sig in sigs if sig .keyid == self .keyid )
458+ except StopIteration :
459+ raise exceptions .UnsignedMetadataError (
460+ f"no signature for key { self .keyid } found in metadata" ,
461+ metadata .signed ,
462+ ) from None
463+
464+ if signed_serializer is None :
465+ # pylint: disable=import-outside-toplevel
466+ from tuf .api .serialization .json import CanonicalJSONSerializer
467+
468+ signed_serializer = CanonicalJSONSerializer ()
469+
470+ if not sslib_keys .verify_signature (
471+ self .to_securesystemslib_key (),
472+ signature .to_dict (),
473+ signed_serializer .serialize (metadata .signed ),
474+ ):
475+ raise exceptions .UnsignedMetadataError (
476+ f"Failed to verify { self .keyid } signature for metadata" ,
477+ metadata .signed ,
478+ )
479+
475480
476481class Role :
477482 """A container class containing the set of keyids and threshold associated
@@ -572,7 +577,7 @@ def from_dict(cls, signed_dict: Dict[str, Any]) -> "Root":
572577 roles = signed_dict .pop ("roles" )
573578
574579 for keyid , key_dict in keys .items ():
575- keys [keyid ] = Key .from_dict (key_dict )
580+ keys [keyid ] = Key .from_dict (keyid , key_dict )
576581 for role_name , role_dict in roles .items ():
577582 roles [role_name ] = Role .from_dict (role_dict )
578583
@@ -598,10 +603,10 @@ def to_dict(self) -> Dict[str, Any]:
598603 return root_dict
599604
600605 # Update key for a role.
601- def add_key (self , role : str , keyid : str , key_metadata : Key ) -> None :
602- """Adds new key for 'role' and updates the key store ."""
603- self .roles [role ].keyids .add (keyid )
604- self .keys [keyid ] = key_metadata
606+ def add_key (self , role : str , key : Key ) -> None :
607+ """Adds new signing key for delegated role 'role'."""
608+ self .roles [role ].keyids .add (key . keyid )
609+ self .keys [key . keyid ] = key
605610
606611 def remove_key (self , role : str , keyid : str ) -> None :
607612 """Removes key from 'role' and updates the key store.
@@ -880,7 +885,7 @@ def from_dict(cls, delegations_dict: Dict[str, Any]) -> "Delegations":
880885 keys = delegations_dict .pop ("keys" )
881886 keys_res = {}
882887 for keyid , key_dict in keys .items ():
883- keys_res [keyid ] = Key .from_dict (key_dict )
888+ keys_res [keyid ] = Key .from_dict (keyid , key_dict )
884889 roles = delegations_dict .pop ("roles" )
885890 roles_res = []
886891 for role_dict in roles :
0 commit comments