From 06aa7961d9a922a931d25a99c6a69eb9f35c71d5 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 17 Mar 2014 20:05:27 -0400 Subject: RSA PSS signature support --- CHANGELOG.rst | 2 + cryptography/hazmat/backends/openssl/backend.py | 79 ++++++++++++++++++++++- docs/hazmat/primitives/asymmetric/rsa.rst | 61 +++++++++++++++++- tests/hazmat/backends/test_openssl.py | 11 ++++ tests/hazmat/primitives/test_rsa.py | 85 +++++++++++++++++++++++++ 5 files changed, 233 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index abbea9fa..ef2f7c1e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ Changelog * Added :class:`~cryptography.hazmat.primitives.twofactor.hotp.HOTP`. * Added :class:`~cryptography.hazmat.primitives.twofactor.totp.TOTP`. * Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.IDEA` support. +* Added signature support to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` + and verification support to :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. 0.2.2 - 2014-03-03 ~~~~~~~~~~~~~~~~~~ diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 7c058f58..1495e75c 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -704,12 +704,29 @@ class _RSASignatureContext(object): raise TypeError( "Expected provider of interfaces.AsymmetricPadding") - if padding.name == "EMSA-PKCS1-v1_5": + if isinstance(padding, PKCS1v15): if self._backend._lib.Cryptography_HAS_PKEY_CTX: self._finalize_method = self._finalize_pkey_ctx self._padding_enum = self._backend._lib.RSA_PKCS1_PADDING else: self._finalize_method = self._finalize_pkcs1 + elif isinstance(padding, PSS): + if not isinstance(padding._mgf, MGF1): + raise UnsupportedAlgorithm( + "Only MGF1 is supported by this backend" + ) + + if not self._backend.mgf1_hash_supported(padding._mgf._algorithm): + raise UnsupportedHash( + "When OpenSSL is older than 1.0.1 then only SHA1 is " + "supported with MGF1." + ) + + if self._backend._lib.Cryptography_HAS_PKEY_CTX: + self._finalize_method = self._finalize_pkey_ctx + self._padding_enum = self._backend._lib.RSA_PKCS1_PSS_PADDING + else: + self._finalize_method = self._finalize_pss else: raise UnsupportedPadding( "{0} is not supported by this backend".format(padding.name) @@ -754,6 +771,19 @@ class _RSASignatureContext(object): res = self._backend._lib.EVP_PKEY_CTX_set_rsa_padding( pkey_ctx, self._padding_enum) assert res > 0 + if isinstance(self._padding, PSS): + res = self._backend._lib.EVP_PKEY_CTX_set_rsa_pss_saltlen( + pkey_ctx, self._get_salt_length()) + assert res > 0 + if self._backend._lib.Cryptography_HAS_MGF1_MD: + # MGF1 MD is configurable in OpenSSL 1.0.1+ + mgf1_md = self._backend._lib.EVP_get_digestbyname( + self._padding._mgf._algorithm.name.encode("ascii")) + assert mgf1_md != self._backend._ffi.NULL + res = self._backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md( + pkey_ctx, mgf1_md + ) + assert res > 0 data_to_sign = self._hash_ctx.finalize() self._hash_ctx = None buflen = self._backend._ffi.new("size_t *") @@ -768,7 +798,13 @@ class _RSASignatureContext(object): buf = self._backend._ffi.new("unsigned char[]", buflen[0]) res = self._backend._lib.EVP_PKEY_sign( pkey_ctx, buf, buflen, data_to_sign, len(data_to_sign)) - assert res == 1 + if res != 1: + errors = self._backend._consume_errors() + assert errors[0].lib == self._backend._lib.ERR_LIB_RSA + assert (errors[0].reason == + self._backend._lib.RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE) + raise ValueError("Salt length too long for key size") + return self._backend._ffi.buffer(buf)[:] def _finalize_pkcs1(self, evp_pkey, pkey_size, evp_md): @@ -785,6 +821,45 @@ class _RSASignatureContext(object): assert res == 1 return self._backend._ffi.buffer(sig_buf)[:sig_len[0]] + def _finalize_pss(self, evp_pkey, pkey_size, evp_md): + data_to_sign = self._hash_ctx.finalize() + self._hash_ctx = None + padded = self._backend._ffi.new("unsigned char[]", pkey_size) + rsa_cdata = self._backend._lib.EVP_PKEY_get1_RSA(evp_pkey) + assert rsa_cdata != self._backend._ffi.NULL + rsa_cdata = self._backend._ffi.gc(rsa_cdata, + self._backend._lib.RSA_free) + res = self._backend._lib.RSA_padding_add_PKCS1_PSS( + rsa_cdata, + padded, + data_to_sign, + evp_md, + self._get_salt_length() + ) + if res != 1: + errors = self._backend._consume_errors() + assert errors[0].lib == self._backend._lib.ERR_LIB_RSA + assert (errors[0].reason == + self._backend._lib.RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE) + raise ValueError("Salt length too long for key size") + + sig_buf = self._backend._ffi.new("char[]", pkey_size) + sig_len = self._backend._lib.RSA_private_encrypt( + pkey_size, + padded, + sig_buf, + rsa_cdata, + self._backend._lib.RSA_NO_PADDING + ) + assert sig_len != -1 + return self._backend._ffi.buffer(sig_buf)[:sig_len] + + def _get_salt_length(self): + if self._padding._mgf._salt_length is MGF1.MAX_LENGTH: + return -2 + else: + return self._padding._mgf._salt_length + @utils.register_interface(interfaces.AsymmetricVerificationContext) class _RSAVerificationContext(object): diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index 03a7caed..dbb0da4f 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -72,7 +72,12 @@ RSA ... backend=default_backend() ... ) >>> signer = private_key.signer( - ... padding.PKCS1v15(), + ... padding.PSS( + ... mgf=padding.MGF1( + ... algorithm=hashes.SHA256(), + ... salt_length=padding.MGF1.MAX_LENGTH + ... ) + ... ), ... hashes.SHA256(), ... default_backend() ... ) @@ -99,6 +104,22 @@ RSA the provided ``backend`` does not implement :class:`~cryptography.hazmat.backends.interfaces.RSABackend` + :raises TypeError: This is raised when the padding is not an + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` + provider. + + :raises UnsupportedHash: This is raised when the backend does not + support the chosen hash algorithm. If the padding is + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` + with the + :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF1` + mask generation function it may also refer to the `MGF1` hash + algorithm. + + :raises UnsupportedPadding: This is raised when the backend does not + support the chosen padding. + + .. class:: RSAPublicKey(public_exponent, modulus) .. versionadded:: 0.2 @@ -136,12 +157,31 @@ RSA ... key_size=2048, ... backend=default_backend() ... ) - >>> signer = private_key.signer(padding.PKCS1v15(), hashes.SHA256(), default_backend()) + >>> signer = private_key.signer( + ... padding.PSS( + ... mgf=padding.MGF1( + ... algorithm=hashes.SHA256(), + ... salt_length=padding.MGF1.MAX_LENGTH + ... ) + ... ), + ... hashes.SHA256(), + ... default_backend() + ... ) >>> data= b"this is some data I'd like to sign" >>> signer.update(data) >>> signature = signer.finalize() >>> public_key = private_key.public_key() - >>> verifier = public_key.verifier(signature, padding.PKCS1v15(), hashes.SHA256(), default_backend()) + >>> verifier = public_key.verifier( + ... signature, + ... padding.PSS( + ... mgf=padding.MGF1( + ... algorithm=hashes.SHA256(), + ... salt_length=padding.MGF1.MAX_LENGTH + ... ) + ... ), + ... hashes.SHA256(), + ... default_backend() + ... ) >>> verifier.update(data) >>> verifier.verify() @@ -166,6 +206,21 @@ RSA the provided ``backend`` does not implement :class:`~cryptography.hazmat.backends.interfaces.RSABackend` + :raises TypeError: This is raised when the padding is not an + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` + provider. + + :raises UnsupportedHash: This is raised when the backend does not + support the chosen hash algorithm. If the padding is + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` + with the + :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF1` + mask generation function it may also refer to the `MGF1` hash + algorithm. + + :raises UnsupportedPadding: This is raised when the backend does not + support the chosen padding. + .. _`RSA`: https://en.wikipedia.org/wiki/RSA_(cryptosystem) .. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography .. _`use 65537`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index c5d0a013..ebabd5f1 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -148,6 +148,17 @@ class TestOpenSSL(object): key_size=512, backend=backend ) + with pytest.raises(UnsupportedHash): + private_key.signer( + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA256(), + salt_length=padding.MGF1.MAX_LENGTH + ) + ), + hashes.SHA1(), + backend + ) public_key = private_key.public_key() with pytest.raises(UnsupportedHash): public_key.verifier( diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 955e69c9..a1ed8959 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -16,6 +16,7 @@ from __future__ import absolute_import, division, print_function import binascii import itertools +import math import os import pytest @@ -428,6 +429,80 @@ class TestRSASignature(object): signature = signer.finalize() assert binascii.hexlify(signature) == example["signature"] + @pytest.mark.parametrize( + "pkcs1_example", + _flatten_pkcs1_examples(load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt"), + load_pkcs1_vectors + )) + ) + def test_pss_signing(self, pkcs1_example, backend): + private, public, example = pkcs1_example + private_key = rsa.RSAPrivateKey( + p=private["p"], + q=private["q"], + private_exponent=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_exponent=private["public_exponent"], + modulus=private["modulus"] + ) + public_key = rsa.RSAPublicKey( + public_exponent=public["public_exponent"], + modulus=public["modulus"] + ) + signer = private_key.signer( + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA1(), + salt_length=padding.MGF1.MAX_LENGTH + ) + ), + hashes.SHA1(), + backend + ) + signer.update(binascii.unhexlify(example["message"])) + signature = signer.finalize() + assert len(signature) == math.ceil(private_key.key_size / 8.0) + # PSS signatures contain randomness so we can't do an exact + # signature check. Instead we'll verify that the signature created + # successfully verifies. + verifier = public_key.verifier( + signature, + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA1(), + salt_length=padding.MGF1.MAX_LENGTH + ) + ), + hashes.SHA1(), + backend + ) + verifier.update(binascii.unhexlify(example["message"])) + verifier.verify() + + def test_pss_signing_salt_length_too_long(self, backend): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + signer = private_key.signer( + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA1(), + salt_length=1000000 + ) + ), + hashes.SHA1(), + backend + ) + signer.update(b"failure coming") + with pytest.raises(ValueError): + signer.finalize() + def test_use_after_finalize(self, backend): private_key = rsa.RSAPrivateKey.generate( public_exponent=65537, @@ -468,6 +543,16 @@ class TestRSASignature(object): private_key.signer( padding.PKCS1v15(), hashes.SHA256, pretend_backend) + def test_unsupported_pss_mgf(self, backend): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + with pytest.raises(UnsupportedAlgorithm): + private_key.signer(padding.PSS(mgf=DummyMGF()), hashes.SHA1(), + backend) + @pytest.mark.rsa class TestRSAVerification(object): -- cgit v1.2.3 From a3bb335b2bfec37b0a37be1f5525d70945d4d815 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 19 Mar 2014 13:23:33 -0400 Subject: never trust openssl Turns out you can't trust it to safely compute the max salt length allowed for PSS, so now we get to do it ourselves. We also check for whether the key size is large enough for the selected hash function (PSS only for now, PKCS1 coming in another PR) --- cryptography/hazmat/backends/openssl/backend.py | 67 +++++++++++---- tests/hazmat/primitives/test_rsa.py | 105 +++++++++++++++++++++++- tests/hazmat/primitives/utils.py | 30 +++++++ 3 files changed, 184 insertions(+), 18 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 1495e75c..d7c0f882 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -15,6 +15,7 @@ from __future__ import absolute_import, division, print_function import collections import itertools +import math import six @@ -695,11 +696,24 @@ class _HMACContext(object): return self._backend._ffi.buffer(buf)[:outlen[0]] +def _get_rsa_pss_salt_length(mgf, key_size, digest_size): + if mgf._salt_length is MGF1.MAX_LENGTH: + # bit length - 1 per RFC 3447 + emlen = int(math.ceil((key_size - 1) / 8.0)) + salt_length = emlen - digest_size - 2 + assert salt_length >= 0 + return salt_length + else: + return mgf._salt_length + + @utils.register_interface(interfaces.AsymmetricSignatureContext) class _RSASignatureContext(object): def __init__(self, backend, private_key, padding, algorithm): self._backend = backend self._private_key = private_key + key_size_bytes = int(math.ceil(private_key.key_size / 8.0)) + if not isinstance(padding, interfaces.AsymmetricPadding): raise TypeError( "Expected provider of interfaces.AsymmetricPadding") @@ -716,6 +730,11 @@ class _RSASignatureContext(object): "Only MGF1 is supported by this backend" ) + # Size of key in bytes - 2 is the maximum + # PSS signature length (salt length is checked later) + if key_size_bytes - algorithm.digest_size - 2 < 0: + raise ValueError("Digest too large for key size.") + if not self._backend.mgf1_hash_supported(padding._mgf._algorithm): raise UnsupportedHash( "When OpenSSL is older than 1.0.1 then only SHA1 is " @@ -773,8 +792,15 @@ class _RSASignatureContext(object): assert res > 0 if isinstance(self._padding, PSS): res = self._backend._lib.EVP_PKEY_CTX_set_rsa_pss_saltlen( - pkey_ctx, self._get_salt_length()) + pkey_ctx, + _get_rsa_pss_salt_length( + self._padding._mgf, + self._private_key.key_size, + self._hash_ctx.algorithm.digest_size + ) + ) assert res > 0 + if self._backend._lib.Cryptography_HAS_MGF1_MD: # MGF1 MD is configurable in OpenSSL 1.0.1+ mgf1_md = self._backend._lib.EVP_get_digestbyname( @@ -834,7 +860,11 @@ class _RSASignatureContext(object): padded, data_to_sign, evp_md, - self._get_salt_length() + _get_rsa_pss_salt_length( + self._padding._mgf, + self._private_key.key_size, + len(data_to_sign) + ) ) if res != 1: errors = self._backend._consume_errors() @@ -854,12 +884,6 @@ class _RSASignatureContext(object): assert sig_len != -1 return self._backend._ffi.buffer(sig_buf)[:sig_len] - def _get_salt_length(self): - if self._padding._mgf._salt_length is MGF1.MAX_LENGTH: - return -2 - else: - return self._padding._mgf._salt_length - @utils.register_interface(interfaces.AsymmetricVerificationContext) class _RSAVerificationContext(object): @@ -867,6 +891,8 @@ class _RSAVerificationContext(object): self._backend = backend self._public_key = public_key self._signature = signature + key_size_bytes = int(math.ceil(public_key.key_size / 8.0)) + if not isinstance(padding, interfaces.AsymmetricPadding): raise TypeError( "Expected provider of interfaces.AsymmetricPadding") @@ -883,6 +909,11 @@ class _RSAVerificationContext(object): "Only MGF1 is supported by this backend" ) + # Size of key in bytes - 2 is the maximum + # PSS signature length (salt length is checked later) + if key_size_bytes - algorithm.digest_size - 2 < 0: + raise ValueError("Digest too large for key size.") + if not self._backend.mgf1_hash_supported(padding._mgf._algorithm): raise UnsupportedHash( "When OpenSSL is older than 1.0.1 then only SHA1 is " @@ -936,7 +967,13 @@ class _RSAVerificationContext(object): assert res > 0 if isinstance(self._padding, PSS): res = self._backend._lib.EVP_PKEY_CTX_set_rsa_pss_saltlen( - pkey_ctx, self._get_salt_length()) + pkey_ctx, + _get_rsa_pss_salt_length( + self._padding._mgf, + self._public_key.key_size, + self._hash_ctx.algorithm.digest_size + ) + ) assert res > 0 if self._backend._lib.Cryptography_HAS_MGF1_MD: # MGF1 MD is configurable in OpenSSL 1.0.1+ @@ -1011,18 +1048,16 @@ class _RSAVerificationContext(object): data_to_verify, evp_md, buf, - self._get_salt_length() + _get_rsa_pss_salt_length( + self._padding._mgf, + self._public_key.key_size, + len(data_to_verify) + ) ) if res != 1: errors = self._backend._consume_errors() assert errors raise InvalidSignature - def _get_salt_length(self): - if self._padding._mgf._salt_length is MGF1.MAX_LENGTH: - return -2 - else: - return self._padding._mgf._salt_length - backend = Backend() diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index a1ed8959..34f49f94 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -28,7 +28,7 @@ from cryptography.exceptions import ( from cryptography.hazmat.primitives import hashes, interfaces from cryptography.hazmat.primitives.asymmetric import rsa, padding -from .utils import generate_rsa_pss_test +from .utils import generate_rsa_pss_test, rsa_pss_signing_test from ...utils import ( load_pkcs1_vectors, load_vectors_from_file, load_rsa_nist_vectors ) @@ -483,6 +483,79 @@ class TestRSASignature(object): verifier.update(binascii.unhexlify(example["message"])) verifier.verify() + @pytest.mark.supported( + only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA224()), + skip_message="Does not support SHA224 with MGF1." + ) + def test_pss_signing_sha224(self, backend): + rsa_pss_signing_test(backend, hashes.SHA224()) + + @pytest.mark.supported( + only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA256()), + skip_message="Does not support SHA256 with MGF1." + ) + def test_pss_signing_sha256(self, backend): + rsa_pss_signing_test(backend, hashes.SHA256()) + + @pytest.mark.supported( + only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA384()), + skip_message="Does not support SHA384 with MGF1." + ) + def test_pss_signing_sha384(self, backend): + rsa_pss_signing_test(backend, hashes.SHA384()) + + @pytest.mark.supported( + only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA512()), + skip_message="Does not support SHA512 with MGF1." + ) + def test_pss_signing_sha512(self, backend): + rsa_pss_signing_test(backend, hashes.SHA512()) + + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), + skip_message="Does not support SHA512." + ) + def test_pss_minimum_key_size_for_digest(self, backend): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=522, + backend=backend + ) + signer = private_key.signer( + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA1(), + salt_length=padding.MGF1.MAX_LENGTH + ) + ), + hashes.SHA512(), + backend + ) + signer.update(b"no failure") + signer.finalize() + + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), + skip_message="Does not support SHA512." + ) + def test_pss_signing_digest_too_large_for_key_size(self, backend): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + with pytest.raises(ValueError): + private_key.signer( + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA1(), + salt_length=padding.MGF1.MAX_LENGTH + ) + ), + hashes.SHA512(), + backend + ) + def test_pss_signing_salt_length_too_long(self, backend): private_key = rsa.RSAPrivateKey.generate( public_exponent=65537, @@ -643,7 +716,7 @@ class TestRSAVerification(object): padding.PSS( mgf=padding.MGF1( algorithm=hashes.SHA1(), - salt_length=padding.MGF1.MAX_LENGTH + salt_length=20 ) ), hashes.SHA1(), @@ -804,6 +877,34 @@ class TestRSAVerification(object): public_key.verifier(b"sig", padding.PSS(mgf=DummyMGF()), hashes.SHA1(), backend) + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), + skip_message="Does not support SHA512." + ) + def test_pss_verify_digest_too_large_for_key_size(self, backend): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + signature = binascii.unhexlify( + b"8b9a3ae9fb3b64158f3476dd8d8a1f1425444e98940e0926378baa9944d219d8" + b"534c050ef6b19b1bdc6eb4da422e89161106a6f5b5cc16135b11eb6439b646bd" + ) + public_key = private_key.public_key() + with pytest.raises(ValueError): + public_key.verifier( + signature, + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA1(), + salt_length=padding.MGF1.MAX_LENGTH + ) + ), + hashes.SHA512(), + backend + ) + def test_pss_verify_salt_length_too_long(self, backend): signature = binascii.unhexlify( b"8b9a3ae9fb3b64158f3476dd8d8a1f1425444e98940e0926378baa9944d219d8" diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 31491023..5d3b4d15 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -406,3 +406,33 @@ def rsa_pss_test(backend, params, hash_alg): ) verifier.update(binascii.unhexlify(params["msg"])) verifier.verify() + + +def rsa_pss_signing_test(backend, hash_alg): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=768, + backend=backend + ) + public_key = private_key.public_key() + pss = padding.PSS( + mgf=padding.MGF1( + algorithm=hash_alg, + salt_length=padding.MGF1.MAX_LENGTH + ) + ) + signer = private_key.signer( + pss, + hash_alg, + backend + ) + signer.update(b"testing signature") + signature = signer.finalize() + verifier = public_key.verifier( + signature, + pss, + hash_alg, + backend + ) + verifier.update(b"testing signature") + verifier.verify() -- cgit v1.2.3 From 798c03456d6f1fa8f27433a7e3928d583e1e120f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 19 Mar 2014 18:41:35 -0400 Subject: document the ValueError --- cryptography/hazmat/backends/openssl/backend.py | 4 ++-- docs/hazmat/primitives/asymmetric/rsa.rst | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index d7c0f882..fa50fcab 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -712,7 +712,6 @@ class _RSASignatureContext(object): def __init__(self, backend, private_key, padding, algorithm): self._backend = backend self._private_key = private_key - key_size_bytes = int(math.ceil(private_key.key_size / 8.0)) if not isinstance(padding, interfaces.AsymmetricPadding): raise TypeError( @@ -732,6 +731,7 @@ class _RSASignatureContext(object): # Size of key in bytes - 2 is the maximum # PSS signature length (salt length is checked later) + key_size_bytes = int(math.ceil(private_key.key_size / 8.0)) if key_size_bytes - algorithm.digest_size - 2 < 0: raise ValueError("Digest too large for key size.") @@ -891,7 +891,6 @@ class _RSAVerificationContext(object): self._backend = backend self._public_key = public_key self._signature = signature - key_size_bytes = int(math.ceil(public_key.key_size / 8.0)) if not isinstance(padding, interfaces.AsymmetricPadding): raise TypeError( @@ -911,6 +910,7 @@ class _RSAVerificationContext(object): # Size of key in bytes - 2 is the maximum # PSS signature length (salt length is checked later) + key_size_bytes = int(math.ceil(public_key.key_size / 8.0)) if key_size_bytes - algorithm.digest_size - 2 < 0: raise ValueError("Digest too large for key size.") diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index dbb0da4f..57c8eec2 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -108,6 +108,9 @@ RSA :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` provider. + :raises ValueError: This is raised when the chosen hash algorithm is + too large for the key size. + :raises UnsupportedHash: This is raised when the backend does not support the chosen hash algorithm. If the padding is :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` @@ -210,6 +213,9 @@ RSA :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` provider. + :raises ValueError: This is raised when the chosen hash algorithm is + too large for the key size. + :raises UnsupportedHash: This is raised when the backend does not support the chosen hash algorithm. If the padding is :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` -- cgit v1.2.3 From b8666f76d91b42cd88eb5601ce40482c72d036e4 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 20 Mar 2014 19:07:45 -0400 Subject: improve exception msgs, change how test_pss_signing_sha2 works --- cryptography/hazmat/backends/openssl/backend.py | 14 ++++-- tests/hazmat/primitives/test_rsa.py | 63 ++++++++++++++----------- tests/hazmat/primitives/utils.py | 32 +------------ 3 files changed, 47 insertions(+), 62 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index e3f421a5..d2744cf3 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -734,7 +734,8 @@ class _RSASignatureContext(object): # PSS signature length (salt length is checked later) key_size_bytes = int(math.ceil(private_key.key_size / 8.0)) if key_size_bytes - algorithm.digest_size - 2 < 0: - raise ValueError("Digest too large for key size.") + raise ValueError("Digest too large for key size. Use a larger " + "key.") if not self._backend.mgf1_hash_supported(padding._mgf._algorithm): raise UnsupportedHash( @@ -830,7 +831,8 @@ class _RSASignatureContext(object): assert errors[0].lib == self._backend._lib.ERR_LIB_RSA assert (errors[0].reason == self._backend._lib.RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE) - raise ValueError("Salt length too long for key size") + raise ValueError("Salt length too long for key size. Try using " + "MAX_LENGTH instead.") return self._backend._ffi.buffer(buf)[:] @@ -872,7 +874,8 @@ class _RSASignatureContext(object): assert errors[0].lib == self._backend._lib.ERR_LIB_RSA assert (errors[0].reason == self._backend._lib.RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE) - raise ValueError("Salt length too long for key size") + raise ValueError("Salt length too long for key size. Try using " + "MAX_LENGTH instead.") sig_buf = self._backend._ffi.new("char[]", pkey_size) sig_len = self._backend._lib.RSA_private_encrypt( @@ -913,7 +916,10 @@ class _RSAVerificationContext(object): # PSS signature length (salt length is checked later) key_size_bytes = int(math.ceil(public_key.key_size / 8.0)) if key_size_bytes - algorithm.digest_size - 2 < 0: - raise ValueError("Digest too large for key size.") + raise ValueError( + "Digest too large for key size. Check that you have the " + "correct key and digest algorithm." + ) if not self._backend.mgf1_hash_supported(padding._mgf._algorithm): raise UnsupportedHash( diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 957e70a3..eb7e1e60 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -28,7 +28,7 @@ from cryptography.exceptions import ( from cryptography.hazmat.primitives import hashes, interfaces from cryptography.hazmat.primitives.asymmetric import padding, rsa -from .utils import generate_rsa_verification_test, rsa_pss_signing_test +from .utils import generate_rsa_verification_test from ...utils import ( load_pkcs1_vectors, load_rsa_nist_vectors, load_vectors_from_file ) @@ -483,33 +483,42 @@ class TestRSASignature(object): verifier.update(binascii.unhexlify(example["message"])) verifier.verify() - @pytest.mark.supported( - only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA224()), - skip_message="Does not support SHA224 with MGF1." - ) - def test_pss_signing_sha224(self, backend): - rsa_pss_signing_test(backend, hashes.SHA224()) - - @pytest.mark.supported( - only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA256()), - skip_message="Does not support SHA256 with MGF1." - ) - def test_pss_signing_sha256(self, backend): - rsa_pss_signing_test(backend, hashes.SHA256()) - - @pytest.mark.supported( - only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA384()), - skip_message="Does not support SHA384 with MGF1." - ) - def test_pss_signing_sha384(self, backend): - rsa_pss_signing_test(backend, hashes.SHA384()) - - @pytest.mark.supported( - only_if=lambda backend: backend.mgf1_hash_supported(hashes.SHA512()), - skip_message="Does not support SHA512 with MGF1." + @pytest.mark.parametrize( + "hash_alg", + [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512()] ) - def test_pss_signing_sha512(self, backend): - rsa_pss_signing_test(backend, hashes.SHA512()) + def test_pss_signing_sha2(self, hash_alg, backend): + if not backend.mgf1_hash_supported(hash_alg): + pytest.skip( + "Does not support {0} with MGF1.".format(hash_alg.name) + ) + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=768, + backend=backend + ) + public_key = private_key.public_key() + pss = padding.PSS( + mgf=padding.MGF1( + algorithm=hash_alg, + salt_length=padding.MGF1.MAX_LENGTH + ) + ) + signer = private_key.signer( + pss, + hash_alg, + backend + ) + signer.update(b"testing signature") + signature = signer.finalize() + verifier = public_key.verifier( + signature, + pss, + hash_alg, + backend + ) + verifier.update(b"testing signature") + verifier.verify() @pytest.mark.supported( only_if=lambda backend: backend.hash_supported(hashes.SHA512()), diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py index 76212daa..2e838474 100644 --- a/tests/hazmat/primitives/utils.py +++ b/tests/hazmat/primitives/utils.py @@ -24,7 +24,7 @@ from cryptography.exceptions import ( NotYetFinalized ) from cryptography.hazmat.primitives import hashes, hmac -from cryptography.hazmat.primitives.asymmetric import padding, rsa +from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC @@ -406,33 +406,3 @@ def rsa_verification_test(backend, params, hash_alg, pad_factory): verifier.verify() else: verifier.verify() - - -def rsa_pss_signing_test(backend, hash_alg): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=768, - backend=backend - ) - public_key = private_key.public_key() - pss = padding.PSS( - mgf=padding.MGF1( - algorithm=hash_alg, - salt_length=padding.MGF1.MAX_LENGTH - ) - ) - signer = private_key.signer( - pss, - hash_alg, - backend - ) - signer.update(b"testing signature") - signature = signer.finalize() - verifier = public_key.verifier( - signature, - pss, - hash_alg, - backend - ) - verifier.update(b"testing signature") - verifier.verify() -- cgit v1.2.3