Move SecretKey signing capabilities to a Signer class

pull/267/head
Bogdan Opanchuk 2021-04-18 21:34:26 -07:00
parent 768ac3ae9e
commit 6545eacca2
7 changed files with 101 additions and 44 deletions

View File

@ -19,6 +19,9 @@ Keys
:members:
:show-inheritance:
.. autoclass:: Signer
:members:
Intermediate objects
--------------------

View File

@ -1,7 +1,7 @@
import pytest
from umbral.keys import PublicKey, SecretKey
from umbral.signing import Signature
from umbral.signing import Signature, Signer
from umbral.hashing import Hash
@ -9,13 +9,14 @@ from umbral.hashing import Hash
def test_sign_and_verify(execution_number):
sk = SecretKey.random()
pk = PublicKey.from_secret_key(sk)
signer = Signer(sk)
message = b"peace at dawn"
message = b"peace at dawn" + str(execution_number).encode()
dst = b"dst"
digest = Hash(dst)
digest.update(message)
signature = sk.sign_digest(digest)
signature = signer.sign_digest(digest)
digest = Hash(dst)
digest.update(message)
@ -26,13 +27,14 @@ def test_sign_and_verify(execution_number):
def test_sign_serialize_and_verify(execution_number):
sk = SecretKey.random()
pk = PublicKey.from_secret_key(sk)
signer = Signer(sk)
message = b"peace at dawn"
message = b"peace at dawn" + str(execution_number).encode()
dst = b"dst"
digest = Hash(dst)
digest.update(message)
signature = sk.sign_digest(digest)
signature = signer.sign_digest(digest)
signature_bytes = bytes(signature)
signature_restored = Signature.from_bytes(signature_bytes)
@ -45,13 +47,14 @@ def test_sign_serialize_and_verify(execution_number):
def test_verification_fail():
sk = SecretKey.random()
pk = PublicKey.from_secret_key(sk)
signer = Signer(sk)
message = b"peace at dawn"
dst = b"dst"
digest = Hash(dst)
digest.update(message)
signature = sk.sign_digest(digest)
signature = signer.sign_digest(digest)
# wrong DST
digest = Hash(b"other dst")
@ -77,13 +80,41 @@ def test_signature_repr():
sk = SecretKey.random()
pk = PublicKey.from_secret_key(sk)
signer = Signer(sk)
message = b"peace at dawn"
dst = b"dst"
digest = Hash(dst)
digest.update(message)
signature = sk.sign_digest(digest)
signature = signer.sign_digest(digest)
s = repr(signature)
assert 'Signature' in s
def test_signer_str():
signer = Signer(SecretKey.random())
s = str(signer)
assert s == "Signer:..."
def test_signer_hash():
signer = Signer(SecretKey.random())
# Insecure Python hash, shouldn't be available.
with pytest.raises(RuntimeError):
hash(signer)
def test_signer_bytes():
signer = Signer(SecretKey.random())
# Shouldn't be able to serialize.
with pytest.raises(RuntimeError):
bytes(signer)
def test_signer_pubkey():
sk = SecretKey.random()
pk = PublicKey.from_secret_key(sk)
signer = Signer(sk)
assert signer.verifying_key() == pk

View File

@ -8,7 +8,7 @@ from .errors import GenericError
from .key_frag import KeyFrag, generate_kfrags
from .keys import SecretKey, PublicKey, SecretKeyFactory
from .pre import encrypt, decrypt_original, decrypt_reencrypted, reencrypt
from .signing import Signature
from .signing import Signature, Signer
__all__ = [
"__title__",
@ -23,6 +23,7 @@ __all__ = [
"PublicKey",
"SecretKeyFactory",
"Signature",
"Signer",
"Capsule",
"KeyFrag",
"CapsuleFrag",

View File

@ -8,7 +8,7 @@ from .curve_scalar import CurveScalar
from .curve_point import CurvePoint
from .keys import PublicKey, SecretKey
from .serializable import Serializable, serialize_bool
from .signing import Signature
from .signing import Signature, Signer
if TYPE_CHECKING: # pragma: no cover
from .key_frag import KeyFragID
@ -108,8 +108,8 @@ class SignatureDigest:
def update(self, value):
self._digest.update(value)
def sign(self, sk: SecretKey) -> Signature:
return sk.sign_digest(self._digest)
def sign(self, signer: Signer) -> Signature:
return signer.sign_digest(self._digest)
def verify(self, pk: PublicKey, sig: Signature):
return sig.verify_digest(pk, self._digest)

View File

@ -7,7 +7,7 @@ from .hashing import hash_to_shared_secret, hash_to_cfrag_signature, hash_to_pol
from .keys import PublicKey, SecretKey
from .params import PARAMETERS
from .serializable import Serializable, serialize_bool, take_bool
from .signing import Signature
from .signing import Signature, Signer
class KeyFragID(Serializable):
@ -58,7 +58,7 @@ class KeyFragProof(Serializable):
kfrag_precursor,
delegating_pk,
receiving_pk,
).sign(signing_sk)
).sign(Signer(signing_sk))
maybe_delegating_pk = delegating_pk if sign_delegating_key else None
maybe_receiving_pk = receiving_pk if sign_receiving_key else None
@ -67,7 +67,7 @@ class KeyFragProof(Serializable):
kfrag_precursor,
maybe_delegating_pk,
maybe_receiving_pk
).sign(signing_sk)
).sign(Signer(signing_sk))
return cls(commitment,
signature_for_proxy,

View File

@ -1,19 +1,11 @@
import os
from typing import TYPE_CHECKING, Tuple
from typing import Tuple
from cryptography.hazmat.primitives.asymmetric import utils
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
from . import openssl
from .curve import CURVE
from .curve_scalar import CurveScalar
from .curve_point import CurvePoint
from .dem import kdf
from .serializable import Serializable
if TYPE_CHECKING: # pragma: no cover
from .hashing import Hash
class SecretKey(Serializable):
"""
@ -57,27 +49,6 @@ class SecretKey(Serializable):
def __bytes__(self) -> bytes:
return bytes(self._scalar_key)
def sign_digest(self, digest: 'Hash') -> 'Signature':
signature_algorithm = ECDSA(utils.Prehashed(digest._backend_hash_algorithm))
message = digest.finalize()
backend_sk = openssl.bn_to_privkey(CURVE, self._scalar_key._backend_bignum)
signature_der_bytes = backend_sk.sign(message, signature_algorithm)
r_int, s_int = utils.decode_dss_signature(signature_der_bytes)
# Normalize s
# s is public, so no constant-timeness required here
if s_int > (CURVE.order >> 1):
s_int = CURVE.order - s_int
# Already normalized, don't waste time
r = CurveScalar.from_int(r_int, check_normalization=False)
s = CurveScalar.from_int(s_int, check_normalization=False)
from .signing import Signature
return Signature(r, s)
class PublicKey(Serializable):
"""

View File

@ -1,3 +1,5 @@
from typing import TYPE_CHECKING
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric import utils
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
@ -5,8 +7,57 @@ from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
from . import openssl
from .curve import CURVE
from .curve_scalar import CurveScalar
from .keys import SecretKey, PublicKey
from .serializable import Serializable
if TYPE_CHECKING: # pragma: no cover
from .hashing import Hash
class Signer:
"""
An object possessing the capability to create signatures.
For safety reasons serialization is prohibited.
"""
def __init__(self, secret_key: SecretKey):
self.__secret_key = secret_key
def sign_digest(self, digest: 'Hash') -> 'Signature':
signature_algorithm = ECDSA(utils.Prehashed(digest._backend_hash_algorithm))
message = digest.finalize()
backend_sk = openssl.bn_to_privkey(CURVE, self.__secret_key.secret_scalar()._backend_bignum)
signature_der_bytes = backend_sk.sign(message, signature_algorithm)
r_int, s_int = utils.decode_dss_signature(signature_der_bytes)
# Normalize s
# s is public, so no constant-timeness required here
if s_int > (CURVE.order >> 1):
s_int = CURVE.order - s_int
# Already normalized, don't waste time
r = CurveScalar.from_int(r_int, check_normalization=False)
s = CurveScalar.from_int(s_int, check_normalization=False)
return Signature(r, s)
def verifying_key(self) -> PublicKey:
"""
Returns the public verification key corresponding to the secret key used for signing.
"""
return PublicKey.from_secret_key(self.__secret_key)
def __str__(self):
return f"{self.__class__.__name__}:..."
def __hash__(self):
raise RuntimeError(f"{self.__class__.__name__} objects do not support hashing")
def __bytes__(self):
raise RuntimeError(f"{self.__class__.__name__} objects do not support serialization")
class Signature(Serializable):
"""