From 16b953a22abf2092f6d428f04141f3e5c9513ce9 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 29 Mar 2014 12:27:46 -0500 Subject: prelim OAEP and PKCS1v15 for openssl 1.0.0+ and 0.9.8. decryption only --- cryptography/hazmat/backends/interfaces.py | 6 ++ cryptography/hazmat/backends/openssl/backend.py | 75 +++++++++++++++++++++- .../hazmat/primitives/asymmetric/padding.py | 8 +++ tests/hazmat/primitives/test_rsa.py | 63 ++++++++++++++++++ 4 files changed, 151 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index 92413d8c..c5c5a16e 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -117,6 +117,12 @@ class RSABackend(object): Return True if the hash algorithm is supported for MGF1 in PSS. """ + @abc.abstractmethod + def rsa_decrypt(self, private_key, ciphertext, padding): + """ + Returns decrypted bytes. + """ + @six.add_metaclass(abc.ABCMeta) class DSABackend(object): diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 86fa704b..2965c781 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -32,7 +32,7 @@ from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives import hashes, interfaces from cryptography.hazmat.primitives.asymmetric import dsa, rsa from cryptography.hazmat.primitives.asymmetric.padding import ( - MGF1, PKCS1v15, PSS + MGF1, OAEP, PKCS1v15, PSS ) from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, Blowfish, CAST5, Camellia, IDEA, SEED, TripleDES @@ -473,6 +473,79 @@ class Backend(object): y=self._bn_to_int(ctx.pub_key) ) + def rsa_decrypt(self, private_key, ciphertext, padding): + if isinstance(padding, PKCS1v15): + padding_enum = self._lib.RSA_PKCS1_PADDING + elif isinstance(padding, OAEP): + padding_enum = self._lib.RSA_PKCS1_OAEP_PADDING + if not isinstance(padding._mgf, MGF1): + raise UnsupportedAlgorithm( + "Only MGF1 is supported by this backend" + ) + + if not isinstance(padding._mgf._algorithm, hashes.SHA1): + raise UnsupportedAlgorithm( + "This backend only supports SHA1 inside MGF1 when " + "using OAEP", + _Reasons.UNSUPPORTED_HASH + ) + else: + raise UnsupportedAlgorithm( + "{0} is not supported by this backend".format( + padding.name + ), + _Reasons.UNSUPPORTED_PADDING + ) + + if self._lib.Cryptography_HAS_PKEY_CTX: + evp_pkey = self._rsa_private_key_to_evp_pkey(private_key) + pkey_ctx = self._lib.EVP_PKEY_CTX_new( + evp_pkey, self._ffi.NULL + ) + assert pkey_ctx != self._ffi.NULL + res = self._lib.EVP_PKEY_decrypt_init(pkey_ctx) + assert res == 1 + res = self._lib.EVP_PKEY_CTX_set_rsa_padding( + pkey_ctx, padding_enum) + assert res > 0 + buf_size = self._lib.EVP_PKEY_size(evp_pkey) + assert buf_size > 0 + outlen = self._ffi.new("size_t *", buf_size) + buf = self._ffi.new("char[]", buf_size) + res = self._lib.Cryptography_EVP_PKEY_decrypt( + pkey_ctx, + buf, + outlen, + ciphertext, + len(ciphertext) + ) + assert res >= 0 + if res == 0: + errors = self._consume_errors() + assert errors + raise SystemError # TODO + + return self._ffi.buffer(buf)[:outlen[0]] + else: + rsa_cdata = self._rsa_cdata_from_private_key(private_key) + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + key_size = self._lib.RSA_size(rsa_cdata) + assert key_size > 0 + buf = self._ffi.new("unsigned char[]", key_size) + res = self._lib.RSA_private_decrypt( + len(ciphertext), + ciphertext, + buf, + rsa_cdata, + padding_enum + ) + if res < 0: + errors = self._consume_errors() + assert errors + raise SystemError # TODO + + return self._ffi.buffer(buf)[:res] + class GetCipherByName(object): def __init__(self, fmt): diff --git a/cryptography/hazmat/primitives/asymmetric/padding.py b/cryptography/hazmat/primitives/asymmetric/padding.py index 72806a61..899fed17 100644 --- a/cryptography/hazmat/primitives/asymmetric/padding.py +++ b/cryptography/hazmat/primitives/asymmetric/padding.py @@ -54,6 +54,14 @@ class PSS(object): self._salt_length = salt_length +@utils.register_interface(interfaces.AsymmetricPadding) +class OAEP(object): + name = "EME-OAEP" + + def __init__(self, mgf): + self._mgf = mgf + + class MGF1(object): MAX_LENGTH = object() diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 84d0f805..70ae20dc 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -1225,3 +1225,66 @@ class TestMGF1(object): mgf = padding.MGF1(algorithm, padding.MGF1.MAX_LENGTH) assert mgf._algorithm == algorithm assert mgf._salt_length == padding.MGF1.MAX_LENGTH + + +@pytest.mark.rsa +class TestRSADecryption(object): + @pytest.mark.parametrize( + "vector", + _flatten_pkcs1_examples(load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "oaep-vect.txt"), + load_pkcs1_vectors + )) + ) + def test_decrypt_oaep_vectors(self, vector, backend): + private, public, example = vector + skey = 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"] + ) + message = backend.rsa_decrypt( + skey, + binascii.unhexlify(example["encryption"]), + # TODO: handle MGF1 here + padding.OAEP( + padding.MGF1( + algorithm=hashes.SHA1(), + salt_length=padding.MGF1.MAX_LENGTH + ) + ) + ) + assert message == binascii.unhexlify(example["message"]) + + @pytest.mark.parametrize( + "vector", + _flatten_pkcs1_examples(load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"), + load_pkcs1_vectors + )) + ) + def test_decrypt_pkcs1v15_vectors(self, vector, backend): + private, public, example = vector + skey = 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"] + ) + message = backend.rsa_decrypt( + skey, + binascii.unhexlify(example["encryption"]), + padding.PKCS1v15() + ) + assert message == binascii.unhexlify(example["message"]) -- cgit v1.2.3 From 4c0a374dd90cd48c21267e4d8be1ddef8288b29c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 5 Apr 2014 19:51:00 -0500 Subject: docs, tests, general huge improvements to RSA decryption --- cryptography/hazmat/backends/interfaces.py | 2 +- cryptography/hazmat/backends/openssl/backend.py | 24 ++++++-- .../hazmat/primitives/asymmetric/padding.py | 7 ++- cryptography/hazmat/primitives/asymmetric/rsa.py | 9 +++ docs/hazmat/backends/interfaces.rst | 12 ++++ docs/hazmat/primitives/asymmetric/padding.rst | 22 ++++++- tests/hazmat/backends/test_openssl.py | 55 ++++++++++++++++- tests/hazmat/primitives/test_rsa.py | 70 ++++++++++++++++++---- 8 files changed, 178 insertions(+), 23 deletions(-) diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index c5c5a16e..677f4c67 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -118,7 +118,7 @@ class RSABackend(object): """ @abc.abstractmethod - def rsa_decrypt(self, private_key, ciphertext, padding): + def decrypt_rsa(self, private_key, ciphertext, padding): """ Returns decrypted bytes. """ diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 2965c781..ca898dfd 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -473,14 +473,15 @@ class Backend(object): y=self._bn_to_int(ctx.pub_key) ) - def rsa_decrypt(self, private_key, ciphertext, padding): + def decrypt_rsa(self, private_key, ciphertext, padding): if isinstance(padding, PKCS1v15): padding_enum = self._lib.RSA_PKCS1_PADDING elif isinstance(padding, OAEP): padding_enum = self._lib.RSA_PKCS1_OAEP_PADDING if not isinstance(padding._mgf, MGF1): raise UnsupportedAlgorithm( - "Only MGF1 is supported by this backend" + "Only MGF1 is supported by this backend", + _Reasons.UNSUPPORTED_MGF ) if not isinstance(padding._mgf._algorithm, hashes.SHA1): @@ -489,6 +490,16 @@ class Backend(object): "using OAEP", _Reasons.UNSUPPORTED_HASH ) + + if padding._label is not None and padding._label != b"": + raise ValueError("This backend does not support OAEP labels") + + if not isinstance(padding._algorithm, hashes.SHA1): + raise UnsupportedAlgorithm( + "This backend only supports SHA1 when using OAEP", + _Reasons.UNSUPPORTED_HASH + ) + else: raise UnsupportedAlgorithm( "{0} is not supported by this backend".format( @@ -519,16 +530,17 @@ class Backend(object): ciphertext, len(ciphertext) ) - assert res >= 0 - if res == 0: + if res <= 0: errors = self._consume_errors() assert errors - raise SystemError # TODO + raise self._unknown_error(errors[0]) # TODO return self._ffi.buffer(buf)[:outlen[0]] else: rsa_cdata = self._rsa_cdata_from_private_key(private_key) rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + res = self._lib.RSA_blinding_on(rsa_cdata, self._ffi.NULL) + assert res == 1 key_size = self._lib.RSA_size(rsa_cdata) assert key_size > 0 buf = self._ffi.new("unsigned char[]", key_size) @@ -542,7 +554,7 @@ class Backend(object): if res < 0: errors = self._consume_errors() assert errors - raise SystemError # TODO + raise self._unknown_error(errors[0]) # TODO return self._ffi.buffer(buf)[:res] diff --git a/cryptography/hazmat/primitives/asymmetric/padding.py b/cryptography/hazmat/primitives/asymmetric/padding.py index 899fed17..9755dbcf 100644 --- a/cryptography/hazmat/primitives/asymmetric/padding.py +++ b/cryptography/hazmat/primitives/asymmetric/padding.py @@ -58,8 +58,13 @@ class PSS(object): class OAEP(object): name = "EME-OAEP" - def __init__(self, mgf): + def __init__(self, mgf, algorithm, label): self._mgf = mgf + if not isinstance(algorithm, interfaces.HashAlgorithm): + raise TypeError("Expected instance of interfaces.HashAlgorithm.") + + self._algorithm = algorithm + self._label = label class MGF1(object): diff --git a/cryptography/hazmat/primitives/asymmetric/rsa.py b/cryptography/hazmat/primitives/asymmetric/rsa.py index 5b15350a..cffd4e98 100644 --- a/cryptography/hazmat/primitives/asymmetric/rsa.py +++ b/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -189,6 +189,15 @@ class RSAPrivateKey(object): return backend.create_rsa_signature_ctx(self, padding, algorithm) + def decrypt(self, ciphertext, padding, backend): + if not isinstance(backend, RSABackend): + raise UnsupportedAlgorithm( + "Backend object does not implement RSABackend", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + return backend.decrypt_rsa(self, ciphertext, padding) + @property def key_size(self): return utils.bit_length(self.modulus) diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 394d060b..71cd4564 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -263,6 +263,18 @@ A specific ``backend`` may provide one or more of these interfaces. :returns: ``True`` if the specified ``algorithm`` is supported by this backend, otherwise ``False``. + .. method:: decrypt_rsa(private_key, ciphertext, padding) + + :param private_key: An instance of an + :class:`~cryptography.hazmat.primitives.interfaces.RSAPrivateKey` + provider. + + :param bytes ciphertext: The ciphertext to decrypt. + + :param padding: An instance of an + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` + provider. + .. class:: OpenSSLSerializationBackend diff --git a/docs/hazmat/primitives/asymmetric/padding.rst b/docs/hazmat/primitives/asymmetric/padding.rst index 89af7eaa..0c2f7ce7 100644 --- a/docs/hazmat/primitives/asymmetric/padding.rst +++ b/docs/hazmat/primitives/asymmetric/padding.rst @@ -10,6 +10,21 @@ Padding correct padding signatures can be forged, messages decrypted, and private keys compromised. +.. class:: OAEP(mgf, label) + + .. versionadded:: 0.4 + + OAEP (Optimal Asymmetric Encryption Padding) is a padding scheme defined in + :rfc:`3447`. It provides probabilistic encryption and is `proven secure`_ + against several attack types. This is the `recommended padding algorithm`_ + for RSA encryption. It cannot be used with RSA signing. + + :param mgf: A mask generation function object. At this time the only + supported MGF is :class:`MGF1`. + + :param bytes label: A label to apply. This is a rarely used field and many + backends do not support it. + .. class:: PSS(mgf, salt_length) .. versionadded:: 0.3 @@ -19,7 +34,8 @@ Padding PSS (Probabilistic Signature Scheme) is a signature scheme defined in :rfc:`3447`. It is more complex than PKCS1 but possesses a `security proof`_. - This is the `recommended padding algorithm`_ for RSA signatures. + This is the `recommended padding algorithm`_ for RSA signatures. It cannot + be used with RSA encryption. :param mgf: A mask generation function object. At this time the only supported MGF is :class:`MGF1`. @@ -37,7 +53,8 @@ Padding .. versionadded:: 0.3 PKCS1 v1.5 (also known as simply PKCS1) is a simple padding scheme - developed for use with RSA keys. It is defined in :rfc:`3447`. + developed for use with RSA keys. It is defined in :rfc:`3447`. This padding + can be used for signing and encryption. Mask generation functions ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -58,5 +75,6 @@ Mask generation functions .. _`Padding is critical`: http://rdist.root.org/2009/10/06/why-rsa-encryption-padding-is-critical/ +.. _`proven secure`: http://cseweb.ucsd.edu/users/mihir/papers/oae.pdf .. _`security proof`: http://eprint.iacr.org/2001/062.pdf .. _`recommended padding algorithm`: 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 43d28c33..46feae46 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -143,8 +143,8 @@ class TestOpenSSL(object): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): backend.derive_pbkdf2_hmac(hashes.SHA256(), 10, b"", 1000, b"") - # This test is not in the next class because to check if it's really - # default we don't want to run the setup_method before it + # This test is not in the TestOpenSSLRandomEngine class because to check + # if it's really default we don't want to run the setup_method before it def test_osrandom_engine_is_default(self): e = backend._lib.ENGINE_get_default_RAND() name = backend._lib.ENGINE_get_name(e) @@ -291,3 +291,54 @@ class TestOpenSSLRSA(object): def test_unsupported_mgf1_hash_algorithm(self): assert backend.mgf1_hash_supported(DummyHash()) is False + + def test_unsupported_mgf1_hash_algorithm_decrypt(self): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + private_key.decrypt( + b"ciphertext", + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA1(), + label=None + ), + backend + ) + + def test_unsupported_oaep_hash_algorithm_decrypt(self): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + private_key.decrypt( + b"ciphertext", + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA256(), + label=None + ), + backend + ) + + def test_unsupported_oaep_label_decrypt(self): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + with pytest.raises(ValueError): + private_key.decrypt( + b"ciphertext", + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=b"label" + ), + backend + ) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 70ae20dc..7b658b69 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -1227,6 +1227,17 @@ class TestMGF1(object): assert mgf._salt_length == padding.MGF1.MAX_LENGTH +class TestOAEP(object): + def test_invalid_algorithm(self): + mgf = padding.MGF1(hashes.SHA1()) + with pytest.raises(TypeError): + padding.OAEP( + mgf=mgf, + algorithm=b"", + label=None + ) + + @pytest.mark.rsa class TestRSADecryption(object): @pytest.mark.parametrize( @@ -1249,16 +1260,14 @@ class TestRSADecryption(object): public_exponent=private["public_exponent"], modulus=private["modulus"] ) - message = backend.rsa_decrypt( - skey, + message = skey.decrypt( binascii.unhexlify(example["encryption"]), - # TODO: handle MGF1 here padding.OAEP( - padding.MGF1( - algorithm=hashes.SHA1(), - salt_length=padding.MGF1.MAX_LENGTH - ) - ) + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ), + backend ) assert message == binascii.unhexlify(example["message"]) @@ -1282,9 +1291,48 @@ class TestRSADecryption(object): public_exponent=private["public_exponent"], modulus=private["modulus"] ) - message = backend.rsa_decrypt( - skey, + message = skey.decrypt( binascii.unhexlify(example["encryption"]), - padding.PKCS1v15() + padding.PKCS1v15(), + backend ) assert message == binascii.unhexlify(example["message"]) + + def test_unsupported_padding(self, backend): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + private_key.decrypt(b"somedata", DummyPadding(), backend) + + def test_unsupported_oaep_mgf(self, backend): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): + private_key.decrypt( + b"ciphertext", + padding.OAEP( + mgf=DummyMGF(), + algorithm=hashes.SHA1(), + label=None + ), + backend + ) + + def test_decrypt_invalid_decrypt(self, backend): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + with pytest.raises(exceptions.InternalError): + private_key.decrypt( + b"\x00" * 64, + padding.PKCS1v15(), + backend + ) -- cgit v1.2.3 From 55c4afc2b9d5178293d8fec4a08ff08db5bd43e1 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 5 Apr 2014 19:57:48 -0500 Subject: split 0.9.8 and evp_pkey_ctx branches into separate methods --- cryptography/hazmat/backends/openssl/backend.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index ca898dfd..5dc8b389 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -509,6 +509,12 @@ class Backend(object): ) if self._lib.Cryptography_HAS_PKEY_CTX: + return self._decrypt_rsa_pkey_ctx(private_key, ciphertext, + padding_enum) + else: + return self._decrypt_rsa_098(private_key, ciphertext, padding_enum) + + def _decrypt_rsa_pkey_ctx(self, private_key, ciphertext, padding_enum): evp_pkey = self._rsa_private_key_to_evp_pkey(private_key) pkey_ctx = self._lib.EVP_PKEY_CTX_new( evp_pkey, self._ffi.NULL @@ -536,7 +542,8 @@ class Backend(object): raise self._unknown_error(errors[0]) # TODO return self._ffi.buffer(buf)[:outlen[0]] - else: + + def _decrypt_rsa_098(self, private_key, ciphertext, padding_enum): rsa_cdata = self._rsa_cdata_from_private_key(private_key) rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) res = self._lib.RSA_blinding_on(rsa_cdata, self._ffi.NULL) -- cgit v1.2.3 From 52fb0a2b1d1a13a4b11d191be3c72dc86ac34575 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 6 Apr 2014 18:12:28 -0500 Subject: fix indentation --- cryptography/hazmat/backends/openssl/backend.py | 90 ++++++++++++------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 5dc8b389..193fe925 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -515,55 +515,55 @@ class Backend(object): return self._decrypt_rsa_098(private_key, ciphertext, padding_enum) def _decrypt_rsa_pkey_ctx(self, private_key, ciphertext, padding_enum): - evp_pkey = self._rsa_private_key_to_evp_pkey(private_key) - pkey_ctx = self._lib.EVP_PKEY_CTX_new( - evp_pkey, self._ffi.NULL - ) - assert pkey_ctx != self._ffi.NULL - res = self._lib.EVP_PKEY_decrypt_init(pkey_ctx) - assert res == 1 - res = self._lib.EVP_PKEY_CTX_set_rsa_padding( - pkey_ctx, padding_enum) - assert res > 0 - buf_size = self._lib.EVP_PKEY_size(evp_pkey) - assert buf_size > 0 - outlen = self._ffi.new("size_t *", buf_size) - buf = self._ffi.new("char[]", buf_size) - res = self._lib.Cryptography_EVP_PKEY_decrypt( - pkey_ctx, - buf, - outlen, - ciphertext, - len(ciphertext) - ) - if res <= 0: - errors = self._consume_errors() - assert errors - raise self._unknown_error(errors[0]) # TODO + evp_pkey = self._rsa_private_key_to_evp_pkey(private_key) + pkey_ctx = self._lib.EVP_PKEY_CTX_new( + evp_pkey, self._ffi.NULL + ) + assert pkey_ctx != self._ffi.NULL + res = self._lib.EVP_PKEY_decrypt_init(pkey_ctx) + assert res == 1 + res = self._lib.EVP_PKEY_CTX_set_rsa_padding( + pkey_ctx, padding_enum) + assert res > 0 + buf_size = self._lib.EVP_PKEY_size(evp_pkey) + assert buf_size > 0 + outlen = self._ffi.new("size_t *", buf_size) + buf = self._ffi.new("char[]", buf_size) + res = self._lib.Cryptography_EVP_PKEY_decrypt( + pkey_ctx, + buf, + outlen, + ciphertext, + len(ciphertext) + ) + if res <= 0: + errors = self._consume_errors() + assert errors + raise self._unknown_error(errors[0]) # TODO - return self._ffi.buffer(buf)[:outlen[0]] + return self._ffi.buffer(buf)[:outlen[0]] def _decrypt_rsa_098(self, private_key, ciphertext, padding_enum): - rsa_cdata = self._rsa_cdata_from_private_key(private_key) - rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - res = self._lib.RSA_blinding_on(rsa_cdata, self._ffi.NULL) - assert res == 1 - key_size = self._lib.RSA_size(rsa_cdata) - assert key_size > 0 - buf = self._ffi.new("unsigned char[]", key_size) - res = self._lib.RSA_private_decrypt( - len(ciphertext), - ciphertext, - buf, - rsa_cdata, - padding_enum - ) - if res < 0: - errors = self._consume_errors() - assert errors - raise self._unknown_error(errors[0]) # TODO + rsa_cdata = self._rsa_cdata_from_private_key(private_key) + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + res = self._lib.RSA_blinding_on(rsa_cdata, self._ffi.NULL) + assert res == 1 + key_size = self._lib.RSA_size(rsa_cdata) + assert key_size > 0 + buf = self._ffi.new("unsigned char[]", key_size) + res = self._lib.RSA_private_decrypt( + len(ciphertext), + ciphertext, + buf, + rsa_cdata, + padding_enum + ) + if res < 0: + errors = self._consume_errors() + assert errors + raise self._unknown_error(errors[0]) # TODO - return self._ffi.buffer(buf)[:res] + return self._ffi.buffer(buf)[:res] class GetCipherByName(object): -- cgit v1.2.3 From e1c89f3d25c381f945db9de45c4782b123b7fe49 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 7 Apr 2014 21:44:57 -0500 Subject: update docs for OAEP --- docs/hazmat/primitives/asymmetric/padding.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/primitives/asymmetric/padding.rst b/docs/hazmat/primitives/asymmetric/padding.rst index 0c2f7ce7..06bd2c42 100644 --- a/docs/hazmat/primitives/asymmetric/padding.rst +++ b/docs/hazmat/primitives/asymmetric/padding.rst @@ -22,8 +22,8 @@ Padding :param mgf: A mask generation function object. At this time the only supported MGF is :class:`MGF1`. - :param bytes label: A label to apply. This is a rarely used field and many - backends do not support it. + :param bytes label: A label to apply. This is a rarely used field and + should typically be set to ``None`` or ``b""`` (equivalent values). .. class:: PSS(mgf, salt_length) -- cgit v1.2.3 From af9a2cc7bc73129fcd807ac890be59dcc9672a4c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 7 Apr 2014 22:15:38 -0500 Subject: add InvalidDecryption exception, check for ct > key size --- cryptography/exceptions.py | 4 ++++ cryptography/hazmat/backends/openssl/backend.py | 12 ++++++++---- docs/exceptions.rst | 4 ++++ tests/hazmat/primitives/test_rsa.py | 13 +++++++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py index b4ee8feb..fe9bf840 100644 --- a/cryptography/exceptions.py +++ b/cryptography/exceptions.py @@ -59,3 +59,7 @@ class InvalidKey(Exception): class InvalidToken(Exception): pass + + +class InvalidDecryption(Exception): + pass diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 193fe925..31f6a344 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -21,8 +21,8 @@ import six from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, InternalError, InvalidSignature, InvalidTag, - UnsupportedAlgorithm, _Reasons + AlreadyFinalized, InternalError, InvalidDecryption, InvalidSignature, + InvalidTag, UnsupportedAlgorithm, _Reasons ) from cryptography.hazmat.backends.interfaces import ( CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend, @@ -508,6 +508,10 @@ class Backend(object): _Reasons.UNSUPPORTED_PADDING ) + key_size_bytes = int(math.ceil(private_key.key_size / 8.0)) + if key_size_bytes < len(ciphertext): + raise ValueError("Ciphertext too large for key size") + if self._lib.Cryptography_HAS_PKEY_CTX: return self._decrypt_rsa_pkey_ctx(private_key, ciphertext, padding_enum) @@ -539,7 +543,7 @@ class Backend(object): if res <= 0: errors = self._consume_errors() assert errors - raise self._unknown_error(errors[0]) # TODO + raise InvalidDecryption return self._ffi.buffer(buf)[:outlen[0]] @@ -561,7 +565,7 @@ class Backend(object): if res < 0: errors = self._consume_errors() assert errors - raise self._unknown_error(errors[0]) # TODO + raise InvalidDecryption return self._ffi.buffer(buf)[:res] diff --git a/docs/exceptions.rst b/docs/exceptions.rst index 28da8ecc..23e0df0a 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -43,3 +43,7 @@ Exceptions This is raised when the verify method of a one time password function's computed token does not match the expected token. + +.. class:: InvalidDecryption + + This is raised when RSA decryption does not succeed. diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 7b658b69..9c6d6f87 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -1336,3 +1336,16 @@ class TestRSADecryption(object): padding.PKCS1v15(), backend ) + + def test_decrypt_ciphertext_too_large(self, backend): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + with pytest.raises(ValueError): + private_key.decrypt( + b"\x00" * 65, + padding.PKCS1v15(), + backend + ) -- cgit v1.2.3 From 9a32ad6eed73674fecdd9c757a9842dc45c78fe4 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 7 Apr 2014 23:39:43 -0500 Subject: test should check for the right exception --- tests/hazmat/primitives/test_rsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 9c6d6f87..d8b25cbb 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -1330,7 +1330,7 @@ class TestRSADecryption(object): key_size=512, backend=backend ) - with pytest.raises(exceptions.InternalError): + with pytest.raises(exceptions.InvalidDecryption): private_key.decrypt( b"\x00" * 64, padding.PKCS1v15(), -- cgit v1.2.3 From a84de770889dec70d9976981445e68f9c4d658bf Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 8 Apr 2014 13:35:51 -0500 Subject: register the EVP_PKEY_CTX for gc --- cryptography/hazmat/backends/openssl/backend.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 31f6a344..0d27bc7f 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -524,6 +524,7 @@ class Backend(object): evp_pkey, self._ffi.NULL ) assert pkey_ctx != self._ffi.NULL + pkey_ctx = self._ffi.gc(pkey_ctx, self._lib.EVP_PKEY_CTX_free) res = self._lib.EVP_PKEY_decrypt_init(pkey_ctx) assert res == 1 res = self._lib.EVP_PKEY_CTX_set_rsa_padding( -- cgit v1.2.3 From 67feca0acd64a5c19fa56efd754430d4213e9f8b Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 9 Apr 2014 14:11:42 -0500 Subject: more testing for rsa decrypt --- cryptography/hazmat/backends/openssl/backend.py | 10 ++++++++++ cryptography/hazmat/bindings/openssl/err.py | 2 ++ tests/hazmat/primitives/test_rsa.py | 17 +++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 0d27bc7f..1586bf71 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -544,6 +544,11 @@ class Backend(object): if res <= 0: errors = self._consume_errors() assert errors + assert errors[0].lib == self._lib.ERR_LIB_RSA + assert (errors[0].reason == + self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or + errors[0].reason == + self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02) raise InvalidDecryption return self._ffi.buffer(buf)[:outlen[0]] @@ -566,6 +571,11 @@ class Backend(object): if res < 0: errors = self._consume_errors() assert errors + assert errors[0].lib == self._lib.ERR_LIB_RSA + assert (errors[0].reason == + self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or + errors[0].reason == + self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02) raise InvalidDecryption return self._ffi.buffer(buf)[:res] diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py index f51393aa..c08c880c 100644 --- a/cryptography/hazmat/bindings/openssl/err.py +++ b/cryptography/hazmat/bindings/openssl/err.py @@ -216,6 +216,8 @@ static const int PEM_R_UNSUPPORTED_ENCRYPTION; static const int RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE; static const int RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY; +static const int RSA_R_BLOCK_TYPE_IS_NOT_01; +static const int RSA_R_BLOCK_TYPE_IS_NOT_02; """ FUNCTIONS = """ diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index d8b25cbb..38fab6ec 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -1349,3 +1349,20 @@ class TestRSADecryption(object): padding.PKCS1v15(), backend ) + + def test_decrypt_ciphertext_too_small(self, backend): + private_key = rsa.RSAPrivateKey.generate( + public_exponent=65537, + key_size=512, + backend=backend + ) + ct = binascii.unhexlify( + b"50b4c14136bd198c2f3c3ed243fce036e168d56517984a263cd66492b80804f1" + b"69d210f2b9bdfb48b12f9ea05009c77da257cc600ccefe3a6283789d8ea0" + ) + with pytest.raises(exceptions.InvalidDecryption): + private_key.decrypt( + ct, + padding.PKCS1v15(), + backend + ) -- cgit v1.2.3 From db35b21a3eddda869d1e77bc9b8cb7f43370704d Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 14 Apr 2014 14:35:58 -0400 Subject: improve comment based on review --- cryptography/hazmat/backends/openssl/backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 1586bf71..3ecb9c19 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -486,7 +486,7 @@ class Backend(object): if not isinstance(padding._mgf._algorithm, hashes.SHA1): raise UnsupportedAlgorithm( - "This backend only supports SHA1 inside MGF1 when " + "This backend supports only SHA1 inside MGF1 when " "using OAEP", _Reasons.UNSUPPORTED_HASH ) -- cgit v1.2.3 From 34ce33859b87df620e1edf27ba88db5b7e151a25 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 14 Apr 2014 16:04:44 -0400 Subject: cover a missing line --- tests/hazmat/primitives/test_rsa.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 38fab6ec..c43fd0b5 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -1366,3 +1366,14 @@ class TestRSADecryption(object): padding.PKCS1v15(), backend ) + + def test_rsa_decrypt_invalid_backend(self, backend): + pretend_backend = object() + private_key = rsa.RSAPrivateKey.generate(65537, 2048, backend) + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + private_key.decrypt( + b"irrelevant", + padding.PKCS1v15(), + pretend_backend + ) -- cgit v1.2.3 From 755c8ba8b3cfc9e310d9f8ec6135961a2d933282 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 15 Apr 2014 11:01:01 -0400 Subject: change some alignment --- cryptography/hazmat/backends/openssl/backend.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 3ecb9c19..27141eca 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -545,10 +545,10 @@ class Backend(object): errors = self._consume_errors() assert errors assert errors[0].lib == self._lib.ERR_LIB_RSA - assert (errors[0].reason == - self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or - errors[0].reason == - self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02) + assert ( + errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or + errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02 + ) raise InvalidDecryption return self._ffi.buffer(buf)[:outlen[0]] @@ -572,10 +572,10 @@ class Backend(object): errors = self._consume_errors() assert errors assert errors[0].lib == self._lib.ERR_LIB_RSA - assert (errors[0].reason == - self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or - errors[0].reason == - self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02) + assert ( + errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or + errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02 + ) raise InvalidDecryption return self._ffi.buffer(buf)[:res] -- cgit v1.2.3 From 4d7d44e1be9f4bfe0aae9e395b231f34d431aaaf Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 15 Apr 2014 11:01:46 -0400 Subject: move an assignment for clarity --- cryptography/hazmat/primitives/asymmetric/padding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/primitives/asymmetric/padding.py b/cryptography/hazmat/primitives/asymmetric/padding.py index 9755dbcf..dcc6fe06 100644 --- a/cryptography/hazmat/primitives/asymmetric/padding.py +++ b/cryptography/hazmat/primitives/asymmetric/padding.py @@ -59,10 +59,10 @@ class OAEP(object): name = "EME-OAEP" def __init__(self, mgf, algorithm, label): - self._mgf = mgf if not isinstance(algorithm, interfaces.HashAlgorithm): raise TypeError("Expected instance of interfaces.HashAlgorithm.") + self._mgf = mgf self._algorithm = algorithm self._label = label -- cgit v1.2.3 From 7bdcdc175675bc78edaa7e0f931676652ab7a427 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 15 Apr 2014 14:16:35 -0400 Subject: remove OAEP decryption to simplify review --- cryptography/hazmat/backends/openssl/backend.py | 26 +--------- .../hazmat/primitives/asymmetric/padding.py | 13 ----- docs/hazmat/primitives/asymmetric/padding.rst | 16 ------ tests/hazmat/backends/test_openssl.py | 51 ------------------- tests/hazmat/primitives/test_rsa.py | 59 ---------------------- 5 files changed, 1 insertion(+), 164 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 27141eca..8b721e84 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -32,7 +32,7 @@ from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives import hashes, interfaces from cryptography.hazmat.primitives.asymmetric import dsa, rsa from cryptography.hazmat.primitives.asymmetric.padding import ( - MGF1, OAEP, PKCS1v15, PSS + MGF1, PKCS1v15, PSS ) from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, Blowfish, CAST5, Camellia, IDEA, SEED, TripleDES @@ -476,30 +476,6 @@ class Backend(object): def decrypt_rsa(self, private_key, ciphertext, padding): if isinstance(padding, PKCS1v15): padding_enum = self._lib.RSA_PKCS1_PADDING - elif isinstance(padding, OAEP): - padding_enum = self._lib.RSA_PKCS1_OAEP_PADDING - if not isinstance(padding._mgf, MGF1): - raise UnsupportedAlgorithm( - "Only MGF1 is supported by this backend", - _Reasons.UNSUPPORTED_MGF - ) - - if not isinstance(padding._mgf._algorithm, hashes.SHA1): - raise UnsupportedAlgorithm( - "This backend supports only SHA1 inside MGF1 when " - "using OAEP", - _Reasons.UNSUPPORTED_HASH - ) - - if padding._label is not None and padding._label != b"": - raise ValueError("This backend does not support OAEP labels") - - if not isinstance(padding._algorithm, hashes.SHA1): - raise UnsupportedAlgorithm( - "This backend only supports SHA1 when using OAEP", - _Reasons.UNSUPPORTED_HASH - ) - else: raise UnsupportedAlgorithm( "{0} is not supported by this backend".format( diff --git a/cryptography/hazmat/primitives/asymmetric/padding.py b/cryptography/hazmat/primitives/asymmetric/padding.py index dcc6fe06..72806a61 100644 --- a/cryptography/hazmat/primitives/asymmetric/padding.py +++ b/cryptography/hazmat/primitives/asymmetric/padding.py @@ -54,19 +54,6 @@ class PSS(object): self._salt_length = salt_length -@utils.register_interface(interfaces.AsymmetricPadding) -class OAEP(object): - name = "EME-OAEP" - - def __init__(self, mgf, algorithm, label): - if not isinstance(algorithm, interfaces.HashAlgorithm): - raise TypeError("Expected instance of interfaces.HashAlgorithm.") - - self._mgf = mgf - self._algorithm = algorithm - self._label = label - - class MGF1(object): MAX_LENGTH = object() diff --git a/docs/hazmat/primitives/asymmetric/padding.rst b/docs/hazmat/primitives/asymmetric/padding.rst index 06bd2c42..f33ca4e2 100644 --- a/docs/hazmat/primitives/asymmetric/padding.rst +++ b/docs/hazmat/primitives/asymmetric/padding.rst @@ -10,21 +10,6 @@ Padding correct padding signatures can be forged, messages decrypted, and private keys compromised. -.. class:: OAEP(mgf, label) - - .. versionadded:: 0.4 - - OAEP (Optimal Asymmetric Encryption Padding) is a padding scheme defined in - :rfc:`3447`. It provides probabilistic encryption and is `proven secure`_ - against several attack types. This is the `recommended padding algorithm`_ - for RSA encryption. It cannot be used with RSA signing. - - :param mgf: A mask generation function object. At this time the only - supported MGF is :class:`MGF1`. - - :param bytes label: A label to apply. This is a rarely used field and - should typically be set to ``None`` or ``b""`` (equivalent values). - .. class:: PSS(mgf, salt_length) .. versionadded:: 0.3 @@ -75,6 +60,5 @@ Mask generation functions .. _`Padding is critical`: http://rdist.root.org/2009/10/06/why-rsa-encryption-padding-is-critical/ -.. _`proven secure`: http://cseweb.ucsd.edu/users/mihir/papers/oae.pdf .. _`security proof`: http://eprint.iacr.org/2001/062.pdf .. _`recommended padding algorithm`: 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 46feae46..c589506f 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -291,54 +291,3 @@ class TestOpenSSLRSA(object): def test_unsupported_mgf1_hash_algorithm(self): assert backend.mgf1_hash_supported(DummyHash()) is False - - def test_unsupported_mgf1_hash_algorithm_decrypt(self): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - private_key.decrypt( - b"ciphertext", - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA256()), - algorithm=hashes.SHA1(), - label=None - ), - backend - ) - - def test_unsupported_oaep_hash_algorithm_decrypt(self): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): - private_key.decrypt( - b"ciphertext", - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA256(), - label=None - ), - backend - ) - - def test_unsupported_oaep_label_decrypt(self): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) - with pytest.raises(ValueError): - private_key.decrypt( - b"ciphertext", - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), - label=b"label" - ), - backend - ) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index c43fd0b5..74a0c111 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -1227,50 +1227,8 @@ class TestMGF1(object): assert mgf._salt_length == padding.MGF1.MAX_LENGTH -class TestOAEP(object): - def test_invalid_algorithm(self): - mgf = padding.MGF1(hashes.SHA1()) - with pytest.raises(TypeError): - padding.OAEP( - mgf=mgf, - algorithm=b"", - label=None - ) - - @pytest.mark.rsa class TestRSADecryption(object): - @pytest.mark.parametrize( - "vector", - _flatten_pkcs1_examples(load_vectors_from_file( - os.path.join( - "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "oaep-vect.txt"), - load_pkcs1_vectors - )) - ) - def test_decrypt_oaep_vectors(self, vector, backend): - private, public, example = vector - skey = 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"] - ) - message = skey.decrypt( - binascii.unhexlify(example["encryption"]), - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA1()), - algorithm=hashes.SHA1(), - label=None - ), - backend - ) - assert message == binascii.unhexlify(example["message"]) - @pytest.mark.parametrize( "vector", _flatten_pkcs1_examples(load_vectors_from_file( @@ -1307,23 +1265,6 @@ class TestRSADecryption(object): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): private_key.decrypt(b"somedata", DummyPadding(), backend) - def test_unsupported_oaep_mgf(self, backend): - private_key = rsa.RSAPrivateKey.generate( - public_exponent=65537, - key_size=512, - backend=backend - ) - with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): - private_key.decrypt( - b"ciphertext", - padding.OAEP( - mgf=DummyMGF(), - algorithm=hashes.SHA1(), - label=None - ), - backend - ) - def test_decrypt_invalid_decrypt(self, backend): private_key = rsa.RSAPrivateKey.generate( public_exponent=65537, -- cgit v1.2.3 From 27f9ca663def6fdccd97297ef40c55923a43394a Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 15 Apr 2014 17:59:27 -0400 Subject: more docs --- docs/hazmat/primitives/asymmetric/rsa.rst | 33 +++++++++++++++++++++++++++++++ docs/hazmat/primitives/interfaces.rst | 18 +++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index c9de2831..e72e8835 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -116,6 +116,39 @@ RSA :raises ValueError: This is raised when the chosen hash algorithm is too large for the key size. + .. method:: decrypt(ciphertext, padding, backend) + + .. versionadded:: 0.4 + + Decrypt data that was encrypted via the public key. + + :param bytes ciphertext: The ciphertext to decrypt. + + :param padding: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` + provider. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.RSABackend` + provider. + + :return bytes: Decrypted data. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if + the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.RSABackend` or if + the backend does not support the chosen hash or padding algorithm. + + :raises TypeError: This is raised when the padding is not an + :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 cryptography.exceptions.InvalidDecryption: This is raised if + decryption fails due to invalid ciphertext. + .. class:: RSAPublicKey(public_exponent, modulus) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 95fd6f9f..3b837a0d 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -133,6 +133,24 @@ Asymmetric interfaces :returns: :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext` + .. method:: decrypt(ciphertext, padding, backend) + + .. versionadded:: 0.4 + + Decrypt data that was encrypted via the public key. + + :param bytes ciphertext: The ciphertext to decrypt. + + :param padding: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding` + provider. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.RSABackend` + provider. + + :return bytes: Decrypted data. + .. method:: public_key() :return: :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey` -- cgit v1.2.3 From 73dd4d5bd713f80fc0ffe04921d61d993437d458 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 17 Apr 2014 21:39:43 -0400 Subject: remove redundant blinding --- cryptography/hazmat/backends/openssl/backend.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 8b721e84..d7f32cd3 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -532,8 +532,6 @@ class Backend(object): def _decrypt_rsa_098(self, private_key, ciphertext, padding_enum): rsa_cdata = self._rsa_cdata_from_private_key(private_key) rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) - res = self._lib.RSA_blinding_on(rsa_cdata, self._ffi.NULL) - assert res == 1 key_size = self._lib.RSA_size(rsa_cdata) assert key_size > 0 buf = self._ffi.new("unsigned char[]", key_size) -- cgit v1.2.3 From 8ab7a360330daa195ea1c0cf70d606dc7dce88c8 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 19 Apr 2014 09:34:56 -0500 Subject: remove InvalidDecryption and replace with ValueError --- cryptography/exceptions.py | 4 ---- cryptography/hazmat/backends/openssl/backend.py | 8 ++++---- docs/exceptions.rst | 4 ---- docs/hazmat/primitives/asymmetric/rsa.rst | 7 ++----- tests/hazmat/primitives/test_rsa.py | 4 ++-- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py index fe9bf840..b4ee8feb 100644 --- a/cryptography/exceptions.py +++ b/cryptography/exceptions.py @@ -59,7 +59,3 @@ class InvalidKey(Exception): class InvalidToken(Exception): pass - - -class InvalidDecryption(Exception): - pass diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index d7f32cd3..5e13bfc1 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -21,8 +21,8 @@ import six from cryptography import utils from cryptography.exceptions import ( - AlreadyFinalized, InternalError, InvalidDecryption, InvalidSignature, - InvalidTag, UnsupportedAlgorithm, _Reasons + AlreadyFinalized, InternalError, InvalidSignature, InvalidTag, + UnsupportedAlgorithm, _Reasons ) from cryptography.hazmat.backends.interfaces import ( CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend, @@ -525,7 +525,7 @@ class Backend(object): errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02 ) - raise InvalidDecryption + raise ValueError("Decryption failed") return self._ffi.buffer(buf)[:outlen[0]] @@ -550,7 +550,7 @@ class Backend(object): errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_01 or errors[0].reason == self._lib.RSA_R_BLOCK_TYPE_IS_NOT_02 ) - raise InvalidDecryption + raise ValueError("Decryption failed") return self._ffi.buffer(buf)[:res] diff --git a/docs/exceptions.rst b/docs/exceptions.rst index 23e0df0a..28da8ecc 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -43,7 +43,3 @@ Exceptions This is raised when the verify method of a one time password function's computed token does not match the expected token. - -.. class:: InvalidDecryption - - This is raised when RSA decryption does not succeed. diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index e72e8835..aef15691 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -143,11 +143,8 @@ 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 cryptography.exceptions.InvalidDecryption: This is raised if - decryption fails due to invalid ciphertext. + :raises ValueError: This is raised when decryption fails or the chosen + hash algorithm is too large for the key size. .. class:: RSAPublicKey(public_exponent, modulus) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 74a0c111..69fd2933 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -1271,7 +1271,7 @@ class TestRSADecryption(object): key_size=512, backend=backend ) - with pytest.raises(exceptions.InvalidDecryption): + with pytest.raises(ValueError): private_key.decrypt( b"\x00" * 64, padding.PKCS1v15(), @@ -1301,7 +1301,7 @@ class TestRSADecryption(object): b"50b4c14136bd198c2f3c3ed243fce036e168d56517984a263cd66492b80804f1" b"69d210f2b9bdfb48b12f9ea05009c77da257cc600ccefe3a6283789d8ea0" ) - with pytest.raises(exceptions.InvalidDecryption): + with pytest.raises(ValueError): private_key.decrypt( ct, padding.PKCS1v15(), -- cgit v1.2.3 From 8e764396471beb13d0cdfbc9a299b9445f96abb2 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 20 Apr 2014 10:25:48 -0500 Subject: more key length checks, docs update --- docs/hazmat/primitives/asymmetric/rsa.rst | 6 +++--- tests/hazmat/primitives/test_rsa.py | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst index aef15691..c282d9ef 100644 --- a/docs/hazmat/primitives/asymmetric/rsa.rst +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -120,7 +120,7 @@ RSA .. versionadded:: 0.4 - Decrypt data that was encrypted via the public key. + Decrypt data that was encrypted with the public key. :param bytes ciphertext: The ciphertext to decrypt. @@ -251,7 +251,7 @@ If you are trying to load RSA private keys yourself you may find that not all parameters required by ``RSAPrivateKey`` are available. In particular the `Chinese Remainder Theorem`_ (CRT) values ``dmp1``, ``dmq1``, ``iqmp`` may be missing or present in a different form. For example `OpenPGP`_ does not include -the ``iqmp``, ``dmp1`` or ``dmq1`` parameters. +the ``iqmp``, ``dmp1`` or ``dmq1`` parameters. The following functions are provided for users who want to work with keys like this without having to do the math themselves. @@ -271,7 +271,7 @@ this without having to do the math themselves. ``p``. .. function:: rsa_crt_dmq1(private_exponent, q) - + .. versionadded:: 0.4 Generates the ``dmq1`` parameter from the RSA private exponent and prime diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 69fd2933..a5266d57 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -1249,8 +1249,10 @@ class TestRSADecryption(object): public_exponent=private["public_exponent"], modulus=private["modulus"] ) + ciphertext = binascii.unhexlify(example["encryption"]) + assert len(ciphertext) == math.ceil(skey.key_size / 8.0) message = skey.decrypt( - binascii.unhexlify(example["encryption"]), + ciphertext, padding.PKCS1v15(), backend ) -- cgit v1.2.3