Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix serialization in GPGSignature and formatting #419

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 17 additions & 27 deletions securesystemslib/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
"""

import abc
from typing import Any, Dict, Optional, Mapping

import securesystemslib.keys as sslib_keys
import securesystemslib.gpg.functions as gpg
from typing import Any, Dict, Optional, Mapping


class Signature:
Expand All @@ -27,17 +28,17 @@ class Signature:
by securesystemslib.

"""

def __init__(
self,
keyid: str,
sig: str,
unrecognized_fields: Optional[Mapping[str, Any]] = None
unrecognized_fields: Optional[Mapping[str, Any]] = None,
):
self.keyid = keyid
self.signature = sig
self.unrecognized_fields: Mapping[str, Any] = unrecognized_fields or {}


def __eq__(self, other: Any) -> bool:
if not isinstance(other, Signature):
return False
Expand All @@ -48,7 +49,6 @@ def __eq__(self, other: Any) -> bool:
and self.unrecognized_fields == other.unrecognized_fields
)


@classmethod
def from_dict(cls, signature_dict: Dict) -> "Signature":
"""Creates a Signature object from its JSON/dict representation.
Expand All @@ -73,10 +73,7 @@ def from_dict(cls, signature_dict: Dict) -> "Signature":
keyid = signature_dict.pop("keyid")
sig = signature_dict.pop("sig")
# All fields left in the signature_dict are unrecognized.
return cls(
keyid, sig, signature_dict
)

return cls(keyid, sig, signature_dict)

def to_dict(self) -> Dict:
"""Returns the JSON-serializable dictionary representation of self."""
Expand All @@ -88,7 +85,6 @@ def to_dict(self) -> Dict:
}



class GPGSignature(Signature):
"""A container class containing information about a gpg signature.

Expand All @@ -100,6 +96,7 @@ class GPGSignature(Signature):
signature: HEX string representing the signature.
other_headers: HEX representation of additional GPG headers.
"""

def __init__(
self,
keyid: str,
Expand All @@ -109,9 +106,8 @@ def __init__(
super().__init__(keyid, signature)
self.other_headers = other_headers


@classmethod
def from_dict(cls, signature_dict: Dict) -> "Signature":
def from_dict(cls, signature_dict: Dict) -> "GPGSignature":
"""Creates a GPGSignature object from its JSON/dict representation.

Args:
Expand All @@ -128,28 +124,26 @@ def from_dict(cls, signature_dict: Dict) -> "Signature":

return cls(
signature_dict["keyid"],
signature_dict["sig"],
signature_dict["other_headers"]
signature_dict["signature"],
signature_dict["other_headers"],
)


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
"other_headers": self.other_headers,
}



class Signer:
"""Signer interface created to support multiple signing implementations."""

__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def sign(self, payload: bytes) -> "Signature":
def sign(self, payload: bytes) -> Signature:
"""Signs a given payload by the key assigned to the Signer instance.

Arguments:
Expand All @@ -158,8 +152,7 @@ def sign(self, payload: bytes) -> "Signature":
Returns:
Returns a "Signature" class instance.
"""
raise NotImplementedError # pragma: no cover

raise NotImplementedError # pragma: no cover


class SSlibSigner(Signer):
Expand Down Expand Up @@ -193,11 +186,11 @@ class SSlibSigner(Signer):

The public and private keys are strings in PEM format.
"""

def __init__(self, key_dict: Dict):
self.key_dict = key_dict


def sign(self, payload: bytes) -> "Signature":
def sign(self, payload: bytes) -> Signature:
"""Signs a given payload by the key assigned to the SSlibSigner instance.

Arguments:
Expand All @@ -217,7 +210,6 @@ def sign(self, payload: bytes) -> "Signature":
return Signature(**sig_dict)



class GPGSigner(Signer):
"""A securesystemslib gpg implementation of the "Signer" interface.

Expand All @@ -232,14 +224,12 @@ class GPGSigner(Signer):
is used.

"""
def __init__(
self, keyid: Optional[str] = None, homedir: Optional[str] = None
):

def __init__(self, keyid: Optional[str] = None, homedir: Optional[str] = None):
self.keyid = keyid
self.homedir = homedir


def sign(self, payload: bytes) -> "GPGSignature":
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
Expand Down
50 changes: 27 additions & 23 deletions tests/test_signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,21 @@
import securesystemslib.formats
import securesystemslib.keys as KEYS
from securesystemslib.exceptions import FormatError, UnsupportedAlgorithmError
from securesystemslib.signer import Signature, SSlibSigner, GPGSigner
from securesystemslib.signer import GPGSignature, Signature, SSlibSigner, GPGSigner
from securesystemslib.gpg.constants import HAVE_GPG
from securesystemslib.gpg.functions import (
export_pubkey,
verify_signature as verify_sig
)
from securesystemslib.gpg.functions import export_pubkey, verify_signature as verify_sig


class TestSSlibSigner(unittest.TestCase):

@classmethod
def setUpClass(cls):
cls.rsakey_dict = KEYS.generate_rsa_key()
cls.ed25519key_dict = KEYS.generate_ed25519_key()
cls.ecdsakey_dict = KEYS.generate_ecdsa_key()
cls.DATA_STR = "SOME DATA REQUIRING AUTHENTICITY."
cls.DATA = securesystemslib.formats.encode_canonical(
cls.DATA_STR).encode("utf-8")

cls.DATA = securesystemslib.formats.encode_canonical(cls.DATA_STR).encode(
"utf-8"
)

def test_sslib_sign(self):
dicts = [self.rsakey_dict, self.ecdsakey_dict, self.ed25519key_dict]
Expand All @@ -39,8 +35,7 @@ def test_sslib_sign(self):
sig_obj = sslib_signer.sign(self.DATA)

# Verify signature
verified = KEYS.verify_signature(scheme_dict, sig_obj.to_dict(),
self.DATA)
verified = KEYS.verify_signature(scheme_dict, sig_obj.to_dict(), self.DATA)
self.assertTrue(verified, "Incorrect signature.")

# Removing private key from "scheme_dict".
Expand All @@ -63,12 +58,11 @@ def test_sslib_sign(self):

scheme_dict["scheme"] = valid_scheme


def test_signature_from_to_dict(self):
signature_dict = {
"sig": "30460221009342e4566528fcecf6a7a5d53ebacdb1df151e242f55f8775883469cb01dbc6602210086b426cc826709acfa2c3f9214610cb0a832db94bbd266fd7c5939a48064a851",
"keyid": "11fa391a0ed7a447cbfeb4b2667e286fc248f64d5e6d0eeed2e5e23f97f9f714",
"foo": "bar" # unrecognized_field
"foo": "bar", # unrecognized_field
}
sig_obj = Signature.from_dict(copy.copy(signature_dict))

Expand All @@ -77,11 +71,10 @@ def test_signature_from_to_dict(self):

self.assertDictEqual(signature_dict, sig_obj.to_dict())


def test_signature_eq_(self):
signature_dict = {
"sig": "30460221009342e4566528fcecf6a7a5d53ebacdb1df151e242f55f8775883469cb01dbc6602210086b426cc826709acfa2c3f9214610cb0a832db94bbd266fd7c5939a48064a851",
"keyid": "11fa391a0ed7a447cbfeb4b2667e286fc248f64d5e6d0eeed2e5e23f97f9f714"
"keyid": "11fa391a0ed7a447cbfeb4b2667e286fc248f64d5e6d0eeed2e5e23f97f9f714",
}
sig_obj = Signature.from_dict(signature_dict)
sig_obj_2 = copy.deepcopy(sig_obj)
Expand All @@ -101,6 +94,7 @@ def test_signature_eq_(self):
sig_obj_2 = None
self.assertNotEqual(sig_obj, sig_obj_2)


@unittest.skipIf(not HAVE_GPG, "gpg not found")
class TestGPGRSA(unittest.TestCase):
"""Test RSA gpg signature creation and verification."""
Expand All @@ -112,29 +106,28 @@ def setUpClass(cls):

# Create directory to run the tests without having everything blow up.
cls.working_dir = os.getcwd()
cls.test_data = b'test_data'
cls.wrong_data = b'something malicious'
cls.test_data = b"test_data"
cls.wrong_data = b"something malicious"

# Find demo files.
gpg_keyring_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "gpg_keyrings", "rsa")
os.path.dirname(os.path.realpath(__file__)), "gpg_keyrings", "rsa"
)

cls.test_dir = os.path.realpath(tempfile.mkdtemp())
cls.gnupg_home = os.path.join(cls.test_dir, "rsa")
shutil.copytree(gpg_keyring_path, cls.gnupg_home)
os.chdir(cls.test_dir)


@classmethod
def tearDownClass(cls):
"""Change back to initial working dir and remove temp test directory."""

os.chdir(cls.working_dir)
shutil.rmtree(cls.test_dir)


def test_gpg_sign_and_verify_object_with_default_key(self):
"""Create a signature using the default key on the keyring. """
"""Create a signature using the default key on the keyring."""

signer = GPGSigner(homedir=self.gnupg_home)
signature = signer.sign(self.test_data)
Expand All @@ -145,9 +138,8 @@ def test_gpg_sign_and_verify_object_with_default_key(self):
self.assertTrue(verify_sig(signature_dict, key_data, self.test_data))
self.assertFalse(verify_sig(signature_dict, key_data, self.wrong_data))


def test_gpg_sign_and_verify_object(self):
"""Create a signature using a specific key on the keyring. """
"""Create a signature using a specific key on the keyring."""

signer = GPGSigner(self.signing_subkey_keyid, self.gnupg_home)
signature = signer.sign(self.test_data)
Expand All @@ -158,6 +150,18 @@ def test_gpg_sign_and_verify_object(self):
self.assertTrue(verify_sig(signature_dict, key_data, self.test_data))
self.assertFalse(verify_sig(signature_dict, key_data, self.wrong_data))

def test_gpg_serialization(self):
"""Tests from_dict and to_dict methods of GPGSignature."""

sig_dict = {
"keyid": "f4f90403af58eef6",
"signature": "c39f86e70e12e70e11d87eb7e3ab7d3b",
"other_headers": "d8f8a89b5d71f07b842a",
}

signature = GPGSignature.from_dict(sig_dict)
self.assertEqual(sig_dict, signature.to_dict())


# Run the unit tests.
if __name__ == "__main__":
Expand Down