Skip to content

Commit

Permalink
Merge pull request #319 from MVrachev/signer-interface
Browse files Browse the repository at this point in the history
Add the Signer interface
  • Loading branch information
lukpueh authored Feb 23, 2021
2 parents b5023f3 + 2bc36c9 commit 760523c
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 0 deletions.
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.
"""

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

# 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)):
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)):
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()

0 comments on commit 760523c

Please sign in to comment.