Skip to content

Commit

Permalink
Allow signing and verification of prehashed messages. Test signatures.
Browse files Browse the repository at this point in the history
  • Loading branch information
cygnusv committed Feb 15, 2019
1 parent 5a5ae65 commit 71b1e54
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 10 deletions.
64 changes: 64 additions & 0 deletions tests/unit/test_signing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
Copyright (C) 2018 NuCypher
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""

import pytest
from cryptography.hazmat.backends.openssl import backend
from cryptography.hazmat.primitives import hashes
from umbral.keys import UmbralPrivateKey
from umbral.signing import Signer, Signature, DEFAULT_HASH_ALGORITHM


@pytest.mark.parametrize('execution_number', range(20)) # Run this test 20 times.
def test_sign_and_verify(execution_number):
privkey = UmbralPrivateKey.gen_key()
pubkey = privkey.get_pubkey()
signer = Signer(private_key=privkey)
message = b"peace at dawn"
signature = signer(message=message)

# Basic signature verification
assert signature.verify(message, pubkey)
assert not signature.verify(b"another message", pubkey)
another_pubkey = UmbralPrivateKey.gen_key().pubkey
assert not signature.verify(message, another_pubkey)

# Signature serialization
sig_bytes = bytes(signature)
assert len(sig_bytes) == Signature.expected_bytes_length()
restored_signature = Signature.from_bytes(sig_bytes)
assert restored_signature == signature
assert restored_signature.verify(message, pubkey)


@pytest.mark.parametrize('execution_number', range(20)) # Run this test 20 times.
def test_prehashed_message(execution_number):
privkey = UmbralPrivateKey.gen_key()
pubkey = privkey.get_pubkey()
signer = Signer(private_key=privkey)

message = b"peace at dawn"
hash_function = hashes.Hash(DEFAULT_HASH_ALGORITHM(), backend=backend)
hash_function.update(message)
prehashed_message = hash_function.finalize()

signature = signer(message=prehashed_message, is_prehashed=True)

assert signature.verify(message=prehashed_message,
verifying_key=pubkey,
is_prehashed=True)
32 changes: 22 additions & 10 deletions umbral/signing.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@

from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.hashes import HashAlgorithm, SHA256
from cryptography.hazmat.primitives.asymmetric import utils
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature


from umbral.config import default_curve
from umbral.curve import Curve
Expand Down Expand Up @@ -57,23 +58,29 @@ def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
curve = curve if curve is not None else default_curve()
return 2 * curve.group_order_size_in_bytes

def verify(self, message: bytes, verifying_key: UmbralPublicKey) -> bool:
def verify(self, message: bytes,
verifying_key: UmbralPublicKey,
is_prehashed: bool = False) -> bool:
"""
Verifies that a message's signature was valid.
:param message: The message to verify
:param verifying_key: UmbralPublicKey of the signer
:param is_prehashed: True if the message has been prehashed previously
:return: True if valid, False if invalid
"""
cryptography_pub_key = verifying_key.to_cryptography_pubkey()
if is_prehashed:
signature_algorithm = ECDSA(utils.Prehashed(self.hash_algorithm()))
else:
signature_algorithm = ECDSA(self.hash_algorithm())

# TODO: Raise error instead of returning boolean
try:
cryptography_pub_key.verify(
self._der_encoded_bytes(),
message,
ECDSA(self.hash_algorithm())
signature=self._der_encoded_bytes(),
data=message,
signature_algorithm=signature_algorithm
)
except InvalidSignature:
return False
Expand All @@ -86,7 +93,7 @@ def from_bytes(cls,
curve: Optional[Curve] = None) -> 'Signature':
curve = curve if curve is not None else default_curve()
if der_encoded:
r, s = decode_dss_signature(signature_as_bytes)
r, s = utils.decode_dss_signature(signature_as_bytes)
else:
expected_len = cls.expected_bytes_length(curve)
if not len(signature_as_bytes) == expected_len:
Expand All @@ -99,7 +106,7 @@ def from_bytes(cls,
return cls(CurveBN.from_int(r, curve), CurveBN.from_int(s, curve))

def _der_encoded_bytes(self) -> bytes:
return encode_dss_signature(int(self.r), int(self.s))
return utils.encode_dss_signature(int(self.r), int(self.s))

def __bytes__(self) -> bytes:
return self.r.to_bytes() + self.s.to_bytes()
Expand Down Expand Up @@ -129,13 +136,18 @@ def __init__(self,
self.curve = private_key.params.curve
self.hash_algorithm = hash_algorithm

def __call__(self, message: bytes) -> Signature:
def __call__(self, message: bytes, is_prehashed: bool = False) -> Signature:
"""
Signs the message with this instance's private key.
:param message: Message to hash and sign
:param is_prehashed: True if the message has been prehashed previously
:return: signature
"""
signature_algorithm = ECDSA(self.hash_algorithm())
if is_prehashed:
signature_algorithm = ECDSA(utils.Prehashed(self.hash_algorithm()))
else:
signature_algorithm = ECDSA(self.hash_algorithm())

signature_der_bytes = self.__cryptography_private_key.sign(message, signature_algorithm)
return Signature.from_bytes(signature_der_bytes, der_encoded=True, curve=self.curve)

0 comments on commit 71b1e54

Please sign in to comment.