aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py23
-rw-r--r--src/cryptography/hazmat/backends/openssl/rsa.py41
-rw-r--r--tests/hazmat/backends/test_openssl.py76
-rw-r--r--tests/hazmat/primitives/test_rsa.py115
4 files changed, 237 insertions, 18 deletions
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 126a881a..4c621466 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -585,7 +585,21 @@ class Backend(object):
userdata = _PasswordUserdata(password=password)
return _pem_password_cb, userdata
- def _mgf1_hash_supported(self, algorithm):
+ def _oaep_hash_supported(self, algorithm):
+ if self._lib.Cryptography_HAS_RSA_OAEP_MD:
+ return isinstance(
+ algorithm, (
+ hashes.SHA1,
+ hashes.SHA224,
+ hashes.SHA256,
+ hashes.SHA384,
+ hashes.SHA512,
+ )
+ )
+ else:
+ return isinstance(algorithm, hashes.SHA1)
+
+ def _pss_mgf1_hash_supported(self, algorithm):
if self._lib.Cryptography_HAS_MGF1_MD:
return self.hash_supported(algorithm)
else:
@@ -595,9 +609,12 @@ class Backend(object):
if isinstance(padding, PKCS1v15):
return True
elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1):
- return self._mgf1_hash_supported(padding._mgf._algorithm)
+ return self._pss_mgf1_hash_supported(padding._mgf._algorithm)
elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1):
- return isinstance(padding._mgf._algorithm, hashes.SHA1)
+ return (
+ self._oaep_hash_supported(padding._mgf._algorithm) and
+ self._oaep_hash_supported(padding._algorithm)
+ )
else:
return False
diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py
index 920bae06..fa23bf89 100644
--- a/src/cryptography/hazmat/backends/openssl/rsa.py
+++ b/src/cryptography/hazmat/backends/openssl/rsa.py
@@ -43,27 +43,23 @@ def _enc_dec_rsa(backend, key, data, padding):
padding_enum = backend._lib.RSA_PKCS1_PADDING
elif isinstance(padding, OAEP):
padding_enum = backend._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):
+ if not backend.rsa_padding_supported(padding):
raise UnsupportedAlgorithm(
- "This backend supports only SHA1 inside MGF1 when "
- "using OAEP.",
- _Reasons.UNSUPPORTED_HASH
+ "This combination of padding and hash algorithm is not "
+ "supported by this backend.",
+ _Reasons.UNSUPPORTED_PADDING
)
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(
@@ -73,12 +69,12 @@ def _enc_dec_rsa(backend, key, data, padding):
)
if backend._lib.Cryptography_HAS_PKEY_CTX:
- return _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum)
+ return _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding)
else:
return _enc_dec_rsa_098(backend, key, data, padding_enum)
-def _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum):
+def _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding):
if isinstance(key, _RSAPublicKey):
init = backend._lib.EVP_PKEY_encrypt_init
crypt = backend._lib.Cryptography_EVP_PKEY_encrypt
@@ -98,6 +94,21 @@ def _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum):
backend.openssl_assert(res > 0)
buf_size = backend._lib.EVP_PKEY_size(key._evp_pkey)
backend.openssl_assert(buf_size > 0)
+ if (
+ isinstance(padding, OAEP) and
+ backend._lib.Cryptography_HAS_RSA_OAEP_MD
+ ):
+ mgf1_md = backend._lib.EVP_get_digestbyname(
+ padding._mgf._algorithm.name.encode("ascii"))
+ backend.openssl_assert(mgf1_md != backend._ffi.NULL)
+ res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md)
+ backend.openssl_assert(res > 0)
+ oaep_md = backend._lib.EVP_get_digestbyname(
+ padding._algorithm.name.encode("ascii"))
+ backend.openssl_assert(oaep_md != backend._ffi.NULL)
+ res = backend._lib.EVP_PKEY_CTX_set_rsa_oaep_md(pkey_ctx, oaep_md)
+ backend.openssl_assert(res > 0)
+
outlen = backend._ffi.new("size_t *", buf_size)
buf = backend._ffi.new("char[]", buf_size)
res = crypt(pkey_ctx, buf, outlen, data, len(data))
@@ -184,7 +195,9 @@ class _RSASignatureContext(object):
raise ValueError("Digest too large for key size. Use a larger "
"key.")
- if not self._backend._mgf1_hash_supported(padding._mgf._algorithm):
+ if not self._backend._pss_mgf1_hash_supported(
+ padding._mgf._algorithm
+ ):
raise UnsupportedAlgorithm(
"When OpenSSL is older than 1.0.1 then only SHA1 is "
"supported with MGF1.",
@@ -377,7 +390,9 @@ class _RSAVerificationContext(object):
"correct key and digest algorithm."
)
- if not self._backend._mgf1_hash_supported(padding._mgf._algorithm):
+ if not self._backend._pss_mgf1_hash_supported(
+ padding._mgf._algorithm
+ ):
raise UnsupportedAlgorithm(
"When OpenSSL is older than 1.0.1 then only SHA1 is "
"supported with MGF1.",
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 2d09787b..2a49dcbb 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -5,6 +5,7 @@
from __future__ import absolute_import, division, print_function
import datetime
+import itertools
import os
import subprocess
import sys
@@ -393,6 +394,45 @@ class TestOpenSSLRSA(object):
),
) is True
+ @pytest.mark.skipif(
+ backend._lib.Cryptography_HAS_RSA_OAEP_MD == 0,
+ reason="Requires OpenSSL with rsa_oaep_md (1.0.2+)"
+ )
+ def test_rsa_padding_supported_oaep_sha2_combinations(self):
+ hashalgs = [
+ hashes.SHA1(),
+ hashes.SHA224(),
+ hashes.SHA256(),
+ hashes.SHA384(),
+ hashes.SHA512(),
+ ]
+ for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs):
+ assert backend.rsa_padding_supported(
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=mgf1alg),
+ algorithm=oaepalg,
+ label=None
+ ),
+ ) is True
+
+ def test_rsa_padding_unsupported_oaep_ripemd160_sha1(self):
+ assert backend.rsa_padding_supported(
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.RIPEMD160()),
+ algorithm=hashes.SHA1(),
+ label=None
+ ),
+ ) is False
+
+ def test_rsa_padding_unsupported_oaep_sha1_ripemd160(self):
+ assert backend.rsa_padding_supported(
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA1()),
+ algorithm=hashes.RIPEMD160(),
+ label=None
+ ),
+ ) is False
+
def test_rsa_padding_unsupported_mgf(self):
assert backend.rsa_padding_supported(
padding.OAEP(
@@ -406,9 +446,13 @@ class TestOpenSSLRSA(object):
padding.PSS(mgf=DummyMGF(), salt_length=0)
) is False
+ @pytest.mark.skipif(
+ backend._lib.Cryptography_HAS_RSA_OAEP_MD == 1,
+ reason="Requires OpenSSL without rsa_oaep_md (< 1.0.2)"
+ )
def test_unsupported_mgf1_hash_algorithm_decrypt(self):
private_key = RSA_KEY_512.private_key(backend)
- with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH):
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING):
private_key.decrypt(
b"0" * 64,
padding.OAEP(
@@ -418,9 +462,13 @@ class TestOpenSSLRSA(object):
)
)
+ @pytest.mark.skipif(
+ backend._lib.Cryptography_HAS_RSA_OAEP_MD == 1,
+ reason="Requires OpenSSL without rsa_oaep_md (< 1.0.2)"
+ )
def test_unsupported_oaep_hash_algorithm_decrypt(self):
private_key = RSA_KEY_512.private_key(backend)
- with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH):
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING):
private_key.decrypt(
b"0" * 64,
padding.OAEP(
@@ -430,6 +478,30 @@ class TestOpenSSLRSA(object):
)
)
+ def test_unsupported_mgf1_hash_algorithm_ripemd160_decrypt(self):
+ private_key = RSA_KEY_512.private_key(backend)
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING):
+ private_key.decrypt(
+ b"0" * 64,
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.RIPEMD160()),
+ algorithm=hashes.RIPEMD160(),
+ label=None
+ )
+ )
+
+ def test_unsupported_mgf1_hash_algorithm_whirlpool_decrypt(self):
+ private_key = RSA_KEY_512.private_key(backend)
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING):
+ private_key.decrypt(
+ b"0" * 64,
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.Whirlpool()),
+ algorithm=hashes.Whirlpool(),
+ label=None
+ )
+ )
+
def test_unsupported_oaep_label_decrypt(self):
private_key = RSA_KEY_512.private_key(backend)
with pytest.raises(ValueError):
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index 320a96e5..c94e0df9 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -65,6 +65,42 @@ def _flatten_pkcs1_examples(vectors):
return flattened_vectors
+def _build_oaep_sha2_vectors():
+ base_path = os.path.join("asymmetric", "RSA", "oaep-custom")
+ vectors = []
+ hashalgs = [
+ hashes.SHA1(),
+ hashes.SHA224(),
+ hashes.SHA256(),
+ hashes.SHA384(),
+ hashes.SHA512(),
+ ]
+ for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs):
+ if mgf1alg.name == "sha1" and oaepalg.name == "sha1":
+ # We need to generate the cartesian product of the permutations
+ # of all the SHAs above, but SHA1/SHA1 is something we already
+ # tested previously and thus did not generate custom vectors for.
+ continue
+
+ examples = _flatten_pkcs1_examples(
+ load_vectors_from_file(
+ os.path.join(
+ base_path,
+ "oaep-{0}-{1}.txt".format(
+ mgf1alg.name, oaepalg.name
+ )
+ ),
+ load_pkcs1_vectors
+ )
+ )
+ # We've loaded the files, but the loaders don't give us any information
+ # about the mgf1 or oaep hash algorithms. We know this info so we'll
+ # just add that to the end of the tuple
+ for private, public, vector in examples:
+ vectors.append((private, public, vector, mgf1alg, oaepalg))
+ return vectors
+
+
def _skip_pss_hash_algorithm_unsupported(backend, hash_alg):
if not backend.rsa_padding_supported(
padding.PSS(
@@ -1210,6 +1246,44 @@ class TestRSADecryption(object):
@pytest.mark.supported(
only_if=lambda backend: backend.rsa_padding_supported(
padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA224()),
+ algorithm=hashes.SHA224(),
+ label=None
+ )
+ ),
+ skip_message="Does not support OAEP using SHA224 MGF1 and SHA224 hash."
+ )
+ @pytest.mark.parametrize(
+ "vector",
+ _build_oaep_sha2_vectors()
+ )
+ def test_decrypt_oaep_sha2_vectors(self, vector, backend):
+ private, public, example, mgf1_alg, hash_alg = vector
+ skey = rsa.RSAPrivateNumbers(
+ p=private["p"],
+ q=private["q"],
+ d=private["private_exponent"],
+ dmp1=private["dmp1"],
+ dmq1=private["dmq1"],
+ iqmp=private["iqmp"],
+ public_numbers=rsa.RSAPublicNumbers(
+ e=private["public_exponent"],
+ n=private["modulus"]
+ )
+ ).private_key(backend)
+ message = skey.decrypt(
+ binascii.unhexlify(example["encryption"]),
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=mgf1_alg),
+ algorithm=hash_alg,
+ label=None
+ )
+ )
+ assert message == binascii.unhexlify(example["message"])
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.rsa_padding_supported(
+ padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
@@ -1334,6 +1408,47 @@ class TestRSAEncryption(object):
@pytest.mark.supported(
only_if=lambda backend: backend.rsa_padding_supported(
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
+ algorithm=hashes.SHA512(),
+ label=None
+ )
+ ),
+ skip_message="Does not support OAEP using SHA256 MGF1 and SHA512 hash."
+ )
+ @pytest.mark.parametrize(
+ ("mgf1hash", "oaephash"),
+ itertools.product([
+ hashes.SHA1(),
+ hashes.SHA224(),
+ hashes.SHA256(),
+ hashes.SHA384(),
+ hashes.SHA512(),
+ ], [
+ hashes.SHA1(),
+ hashes.SHA224(),
+ hashes.SHA256(),
+ hashes.SHA384(),
+ hashes.SHA512(),
+ ])
+ )
+ def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend):
+ pad = padding.OAEP(
+ mgf=padding.MGF1(algorithm=mgf1hash),
+ algorithm=oaephash,
+ label=None
+ )
+ private_key = RSA_KEY_2048.private_key(backend)
+ pt = b"encrypt me using sha2 hashes!"
+ public_key = private_key.public_key()
+ ct = public_key.encrypt(pt, pad)
+ assert ct != pt
+ assert len(ct) == math.ceil(public_key.key_size / 8.0)
+ recovered_pt = private_key.decrypt(ct, pad)
+ assert recovered_pt == pt
+
+ @pytest.mark.supported(
+ only_if=lambda backend: backend.rsa_padding_supported(
padding.PKCS1v15()
),
skip_message="Does not support PKCS1v1.5."