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

Add the Signer interface #319

Merged
merged 3 commits into from
Feb 23, 2021
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
134 changes: 134 additions & 0 deletions securesystemslib/signer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""Signer interface and example interface implementations.

The goal of this module is to provide a signing interface supporting multiple
signing implementations and a couple of example implementations.

"""
MVrachev marked this conversation as resolved.
Show resolved Hide resolved

import abc
import securesystemslib.keys as sslib_keys
from typing import Dict


class Signature:
"""A container class containing information about a signature.

Contains a signature and the keyid uniquely identifying the key used
to generate the signature.

Provides utility methods to easily create an object from a dictionary
and return the dictionary representation of the object.

Attributes:
keyid: HEX string used as a unique identifier of the key.
signature: HEX string representing the signature.

"""
def __init__(self, keyid: str, sig: str):
self.keyid = keyid
self.signature = sig


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

Arguments:
signature_dict:
A dict containing a valid keyid and a signature.
Note that the fields in it should be named "keyid" and "sig"
respectively.

Raises:
KeyError: If any of the "keyid" and "sig" fields are missing from
the signature_dict.

Returns:
A "Signature" instance.
"""

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


def to_dict(self) -> Dict:
"""Returns the JSON-serializable dictionary representation of self."""

return {
"keyid": self.keyid,
"sig": self.signature
}



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

__metaclass__ = abc.ABCMeta

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

Arguments:
payload: The bytes to be signed.

Returns:
Returns a "Signature" class instance.
"""
raise NotImplementedError # pragma: no cover



class SSlibSigner(Signer):
"""A securesystemslib signer implementation.

Provides a sign method to generate a cryptographic signature with a
securesystemslib-style rsa, ed25519 or ecdsa private key on the instance.
The signature scheme is determined by the key and must be one of:

- rsa(ssa-pss|pkcs1v15)-(md5|sha1|sha224|sha256|sha384|sha512) (12 schemes)
- ed25519
- ecdsa-sha2-nistp256

See "securesystemslib.interface" for functions to generate and load keys.

Attributes:
key_dict:
A securesystemslib-style key dictionary, which includes a keyid,
key type, signature scheme, and the public and private key values,
e.g.::

{
"keytype": "rsa",
"scheme": "rsassa-pss-sha256",
"keyid": "f30a0870d026980100c0573bd557394f8c1bbd6...",
"keyval": {
"public": "-----BEGIN RSA PUBLIC KEY----- ...",
"private": "-----BEGIN RSA PRIVATE KEY----- ..."
}
}

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":
"""Signs a given payload by the key assigned to the SSlibSigner instance.

Arguments:
payload: The bytes to be signed.

Raises:
securesystemslib.exceptions.FormatError: Key argument is malformed.
securesystemslib.exceptions.CryptoError, \
securesystemslib.exceptions.UnsupportedAlgorithmError:
Signing errors.

MVrachev marked this conversation as resolved.
Show resolved Hide resolved
Returns:
Returns a "Signature" class instance.
"""

sig_dict = sslib_keys.create_signature(self.key_dict, payload)
return Signature(**sig_dict)
83 changes: 83 additions & 0 deletions tests/test_signer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python

"""Test cases for "signer.py". """

import sys
import unittest

import unittest
import securesystemslib.formats
import securesystemslib.keys as KEYS
from securesystemslib.exceptions import FormatError, UnsupportedAlgorithmError

# TODO: Remove case handling when fully dropping support for versions < 3.6
IS_PY_VERSION_SUPPORTED = sys.version_info >= (3, 6)

# Use setUpModule to tell unittest runner to skip this test module gracefully.
def setUpModule():
if not IS_PY_VERSION_SUPPORTED:
raise unittest.SkipTest("requires Python 3.6 or higher")

# Since setUpModule is called after imports we need to import conditionally.
if IS_PY_VERSION_SUPPORTED:
from securesystemslib.signer import Signature, SSlibSigner


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")


def test_sslib_sign(self):
dicts = [self.rsakey_dict, self.ecdsakey_dict, self.ed25519key_dict]
for scheme_dict in dicts:
# Test generation of signatures.
sslib_signer = SSlibSigner(scheme_dict)
sig_obj = sslib_signer.sign(self.DATA)

# Verify signature
verified = KEYS.verify_signature(scheme_dict, sig_obj.to_dict(),
self.DATA)
self.assertTrue(verified, "Incorrect signature.")
MVrachev marked this conversation as resolved.
Show resolved Hide resolved

# Removing private key from "scheme_dict".
private = scheme_dict["keyval"]["private"]
scheme_dict["keyval"]["private"] = ""
sslib_signer.key_dict = scheme_dict

with self.assertRaises((ValueError, FormatError)):
MVrachev marked this conversation as resolved.
Show resolved Hide resolved
sslib_signer.sign(self.DATA)

scheme_dict["keyval"]["private"] = private

# Test for invalid signature scheme.
valid_scheme = scheme_dict["scheme"]
scheme_dict["scheme"] = "invalid_scheme"
sslib_signer = SSlibSigner(scheme_dict)

with self.assertRaises((UnsupportedAlgorithmError, FormatError)):
MVrachev marked this conversation as resolved.
Show resolved Hide resolved
sslib_signer.sign(self.DATA)

scheme_dict["scheme"] = valid_scheme


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

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


# Run the unit tests.
if __name__ == "__main__":
unittest.main()