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 Jun 27, 2022
1 parent 4f4136f commit 0b64bd5
Showing 1 changed file with 118 additions and 0 deletions.
118 changes: 118 additions & 0 deletions securesystemslib/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -88,6 +89,64 @@ 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: 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: dict = 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.
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."""

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 @@ -160,3 +219,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)

0 comments on commit 0b64bd5

Please sign in to comment.