Skip to content

Commit

Permalink
Add a GPGSigner
Browse files Browse the repository at this point in the history
The GPGSigner is an implementation of the new "Signer" interface
that was merged in secure-systems-lab#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 <[email protected]>
  • Loading branch information
MVrachev committed Feb 25, 2021
1 parent 760523c commit 66fb993
Showing 1 changed file with 123 additions and 2 deletions.
125 changes: 123 additions & 2 deletions securesystemslib/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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."""

Expand Down Expand Up @@ -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)

0 comments on commit 66fb993

Please sign in to comment.