diff options
-rw-r--r-- | docs/hazmat/primitives/asymmetric/rsa.rst | 7 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/utils.rst | 34 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/rsa.py | 16 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/asymmetric/utils.py | 12 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_asym_utils.py | 9 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_rsa.py | 41 |
6 files changed, 112 insertions, 7 deletions
diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index d37b40f8..b6acab6b 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -564,6 +564,9 @@ Key interfaces .. method:: sign(data, padding, algorithm) .. versionadded:: 1.4 + .. versionchanged:: 1.6 + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + can now be used as an ``algorithm``. Sign one block of data which can be verified later by others using the public key. @@ -574,7 +577,9 @@ Key interfaces :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. :param algorithm: An instance of - :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + if the ``data`` you want to sign has already been hashed. :return bytes: Signature. diff --git a/docs/hazmat/primitives/asymmetric/utils.rst b/docs/hazmat/primitives/asymmetric/utils.rst index 07883598..f29b3e99 100644 --- a/docs/hazmat/primitives/asymmetric/utils.rst +++ b/docs/hazmat/primitives/asymmetric/utils.rst @@ -28,3 +28,37 @@ Asymmetric Utilities :param int s: The raw signature value ``s``. :return bytes: The encoded signature. + +.. class:: Prehashed(algorithm) + + .. versionadded:: 1.6 + + ``Prehashed`` can be passed as the ``algorithm`` in + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign` + if the data to be signed has been hashed beforehand. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + .. doctest:: + + >>> import hashlib + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import ( + ... padding, rsa, utils + ... ) + >>> private_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> prehashed_msg = hashlib.sha256(b"A message I want to sign").digest() + >>> signature = private_key.sign( + ... prehashed_msg, + ... padding.PSS( + ... mgf=padding.MGF1(hashes.SHA256()), + ... salt_length=padding.PSS.MAX_LENGTH + ... ), + ... utils.Prehashed(hashes.SHA256()) + ... ) diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py index 8bb85783..85d06525 100644 --- a/src/cryptography/hazmat/backends/openssl/rsa.py +++ b/src/cryptography/hazmat/backends/openssl/rsa.py @@ -13,6 +13,7 @@ from cryptography.exceptions import ( from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ( AsymmetricSignatureContext, AsymmetricVerificationContext, rsa, + utils as asym_utils ) from cryptography.hazmat.primitives.asymmetric.padding import ( AsymmetricPadding, MGF1, OAEP, PKCS1v15, PSS, calculate_max_pss_salt_length @@ -452,9 +453,18 @@ class _RSAPrivateKey(object): padding_enum = _rsa_sig_determine_padding( self._backend, self, padding, algorithm ) - hash_ctx = hashes.Hash(algorithm, self._backend) - hash_ctx.update(data) - data = hash_ctx.finalize() + if not isinstance(algorithm, asym_utils.Prehashed): + hash_ctx = hashes.Hash(algorithm, self._backend) + hash_ctx.update(data) + data = hash_ctx.finalize() + else: + algorithm = algorithm._algorithm + + if len(data) != algorithm.digest_size: + raise ValueError( + "The provided data must be the same length as the hash " + "algorithm's digest size." + ) return _rsa_sig_sign( self._backend, padding, padding_enum, diff --git a/src/cryptography/hazmat/primitives/asymmetric/utils.py b/src/cryptography/hazmat/primitives/asymmetric/utils.py index 5b27654f..44bf59d1 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/utils.py +++ b/src/cryptography/hazmat/primitives/asymmetric/utils.py @@ -13,6 +13,7 @@ from pyasn1.type import namedtype, univ import six from cryptography import utils +from cryptography.hazmat.primitives import hashes class _DSSSigValue(univ.Sequence): @@ -69,3 +70,14 @@ def encode_dss_signature(r, s): sig.setComponentByName('r', r) sig.setComponentByName('s', s) return encoder.encode(sig) + + +class Prehashed(object): + def __init__(self, algorithm): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of HashAlgorithm.") + + self._algorithm = algorithm + self._digest_size = algorithm.digest_size + + digest_size = utils.read_only_property("_digest_size") diff --git a/tests/hazmat/primitives/test_asym_utils.py b/tests/hazmat/primitives/test_asym_utils.py index b9971137..bd1fa35e 100644 --- a/tests/hazmat/primitives/test_asym_utils.py +++ b/tests/hazmat/primitives/test_asym_utils.py @@ -7,8 +7,8 @@ from __future__ import absolute_import, division, print_function import pytest from cryptography.hazmat.primitives.asymmetric.utils import ( - decode_dss_signature, decode_rfc6979_signature, - encode_dss_signature, encode_rfc6979_signature + Prehashed, decode_dss_signature, decode_rfc6979_signature, + encode_dss_signature, encode_rfc6979_signature, ) @@ -76,3 +76,8 @@ def test_decode_dss_invalid_asn1(): # This is the BER "end-of-contents octets," which older versions of # pyasn1 are wrongly willing to return from top-level DER decoding. decode_dss_signature(b"\x00\x00") + + +def test_pass_invalid_prehashed_arg(): + with pytest.raises(TypeError): + Prehashed(object()) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 81e3f946..6ec17993 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -18,7 +18,9 @@ from cryptography.hazmat.backends.interfaces import ( PEMSerializationBackend, RSABackend ) from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding, rsa +from cryptography.hazmat.primitives.asymmetric import ( + padding, rsa, utils as asym_utils +) from cryptography.hazmat.primitives.asymmetric.rsa import ( RSAPrivateNumbers, RSAPublicNumbers ) @@ -492,6 +494,43 @@ class TestRSASignature(object): verifier.update(message) verifier.verify() + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_prehashed_sign(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + signature = private_key.sign(digest, pss, prehashed_alg) + public_key = private_key.public_key() + verifier = public_key.verifier(signature, pss, hashes.SHA1()) + verifier.update(message) + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_prehashed_digest_mismatch(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA512(), backend) + h.update(message) + digest = h.finalize() + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + with pytest.raises(ValueError): + private_key.sign(digest, pss, prehashed_alg) + @pytest.mark.requires_backend_interface(interface=RSABackend) class TestRSAVerification(object): |