From 8d26a4441c5ae16f6d9b3cff2c6684a3d7e6c759 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 easily 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 easily 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. Unfortunately, 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 | 111 +++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/securesystemslib/signer.py b/securesystemslib/signer.py index bfe87d773..cb952d4c6 100644 --- a/securesystemslib/signer.py +++ b/securesystemslib/signer.py @@ -7,6 +7,7 @@ import abc import securesystemslib.keys as sslib_keys +import securesystemslib.gpg.functions as gpg from typing import Any, Dict, Optional, Mapping @@ -88,6 +89,57 @@ 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. + """ + def __init__( + self, + keyid: str, + signature: str, + other_headers: str, + ): + super().__init__(keyid, signature) + self.other_headers = other_headers + + + @classmethod + def from_dict(cls, signature_dict: Dict) -> "Signature": + """Creates a GPGSignature object from its JSON/dict representation. + + Args: + signature_dict: Dict containing valid "keyid", "signature" and + "other_fields" fields. + + Raises: + KeyError: If any of the "keyid", "sig" or "other_headers" fields + are missing from the signature_dict. + + Returns: + 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.""" + return { + "keyid": self.keyid, + "signature": self.signature, + "other_headers": self.other_headers + } + + + class Signer: """Signer interface created to support multiple signing implementations.""" @@ -160,3 +212,62 @@ 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 signature with gpg, using + an RSA, DSA or EdDSA private key identified by the keyid on the instance. + + Args: + keyid: The keyid of the gpg signing keyid. If not passed the default + key in the keyring is used. + + homedir: 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: bytes) -> "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.gpg.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: the gpg command failed to create a valid signature. + OSError: the gpg command is not present or non-executable. + securesystemslib.exceptions.UnsupportedLibraryError: thehe gpg + command is not available, or the cryptography library is + not installed. + securesystemslib.gpg.exceptions.CommandError: the gpg command + returned a non-zero exit code. + securesystemslib.gpg.exceptions.KeyNotFoundError: 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)