diff --git a/securesystemslib/signer.py b/securesystemslib/signer.py index ed8e994c6..8159a4d4e 100644 --- a/securesystemslib/signer.py +++ b/securesystemslib/signer.py @@ -6,9 +6,9 @@ """ import abc +from typing import Dict, Optional import securesystemslib.keys as sslib_keys -from typing import Dict - +import securesystemslib.gpg.functions as gpg class Signature: """A container class containing information about a signature. @@ -60,6 +60,61 @@ def to_dict(self) -> Dict: +class GPGSignature(Signature): + """A container class containing information about a gpg signature. + + Besides the signature, it also contains other meta information + needed to uniquely identify the key used to generate the signature. + + Attributes: + keyid: HEX string used as a unique identifier of the key. + signature: HEX string representing the signature. + other_headers: HEX representation of additional GPG headers. + info: An optional dict containing information about the GPG key + used to generate the signature and the signed data. + """ + def __init__(self, keyid: str, signature: str, other_headers: str, info=None): + super().__init__(keyid, signature) + self.other_headers = other_headers + self.info = info + + + @classmethod + def from_dict(cls, signature_dict: Dict) -> "Signature": + """Creates a GPGSignature object from its JSON/dict representation. + + Arguments: + signature_dict: + A dict containing a valid "keyid", "signature" and "other_fields" + fields. + Note that the fields in it should be named "keyid", "signature" + and "other_fields" respectively. + + Raises: + KeyError: If any of the "keyid", "sig" or "other_headers" fields are + missing from the signature_dict. + + Returns: + A "GPGSignature" instance. + """ + + return cls(signature_dict["keyid"], signature_dict["sig"], + signature_dict["other_headers"], signature_dict.get("info")) + + + def to_dict(self) -> Dict: + """Returns the JSON-serializable dictionary representation of self.""" + + res = {"keyid": self.keyid, "signature": self.signature, + "other_headers": self.other_headers} + + if self.info: + res["info"] = self.info + + return res + + + class Signer: """Signer interface created to support multiple signing implementations.""" @@ -132,3 +187,69 @@ def sign(self, payload: bytes) -> "Signature": sig_dict = sslib_keys.create_signature(self.key_dict, payload) return Signature(**sig_dict) + + + +class GPGSigner(Signer): + """A securesystemslib gpg implementation of the "Signer" interface. + + Provides a sign method to generate a cryptographic gpg signature with a + securesystemslib-style RSA, DSA or EdDSA private key on the instance. + + Attributes: + keyid: (optional) + The keyid of the gpg signing keyid. If not passed the default + key in the keyring is used. + + homedir: (optional) + Path to the gpg keyring. If not passed the default keyring is used. + + """ + def __init__( + self, keyid: Optional[str] = None, + homedir: Optional[str] = None): + self.keyid = keyid + self.homedir = homedir + + + def sign(self, payload) -> 'GPGSignature': + """Signs a given payload by the key assigned to the GPGSigner instance. + + Calls the gpg command line utility to sign the passed content with the + key identified by the passed keyid from the gpg keyring at the passed + homedir. + + The executed base command is defined in + securesystemslib.gpgp.constants.GPG_SIGN_COMMAND. + + Arguments: + payload: The bytes to be signed. + + Raises: + securesystemslib.exceptions.FormatError: + If the keyid was passed and does not match + securesystemslib.formats.KEYID_SCHEMA. + + ValueError: + If the gpg command failed to create a valid signature. + + OSError: + If the gpg command is not present or non-executable. + + securesystemslib.exceptions.UnsupportedLibraryError: + If the gpg command is not available, or + the cryptography library is not installed. + + securesystemslib.gpg.exceptions.CommandError: + If the gpg command returned a non-zero exit code. + + securesystemslib.gpg.exceptions.KeyNotFoundError: + If the used gpg version is not fully supported + and no public key can be found for short keyid. + + Returns: + Returns a "GPGSignature" class instance. + """ + + sig_dict = gpg.create_signature(payload, self.keyid, self.homedir) + return GPGSignature(**sig_dict)