diff options
-rw-r--r-- | CHANGELOG.rst | 2 | ||||
-rw-r--r-- | cryptography/hazmat/backends/openssl/backend.py | 79 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/rsa.rst | 61 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 11 | ||||
-rw-r--r-- | 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): |