From 5bd5925a7a1bbab0cbcd650499b6320bc90abff9 Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Thu, 25 Feb 2021 19:42:21 +0200 Subject: [PATCH] Add a GPGSigner The GPGSigner is an implementation of the new "Signer" interface that was merged in https://github.com/secure-systems-lab/securesystemslib/pull/319 and could be found in securesystemslib.signer.py This is the next logical step given that securesystemslib supports GPG and we want this interface to have implementations for all signature types which are already supported by securesystemslib. While implementing the GPGSigner, I wanted to make sure that one can easly sign a portion of data, receive a Signature object and use the information stored in that object to verify the signature. To verifty a GPG signature, one have to use securesystemslib/gpg/functions.verifty_signature(). There, the signature_object argument should be in the securesystemslib.formats.GPG_SIGNATURE_SCHEMA format. I searched for a way to easly retrieve the additional fields in the GPG_SIGNATURE_SCHEMA -"other_headers" and "info" from the keyid stored in the "Signature" object returned by the "sign" operation. Unfortunatly, right now there is no function that I can use for that purpose. The only option I was left with, was to create a new class: "GPGSignature" where we can store those additional fields returned from securesystemslib.gpg.functions.create_signature() which we call during the "sign" process. Signed-off-by: Martin Vrachev --- securesystemslib/signer.py | 125 ++++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 2 deletions(-) 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)