aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst2
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py136
-rw-r--r--docs/hazmat/primitives/asymmetric/rsa.rst67
-rw-r--r--tests/hazmat/backends/test_openssl.py11
-rw-r--r--tests/hazmat/primitives/test_rsa.py197
5 files changed, 399 insertions, 14 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 fa21260c..d2744cf3 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
@@ -696,21 +697,57 @@ 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
+
if not isinstance(padding, interfaces.AsymmetricPadding):
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"
+ )
+
+ # 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. Use a larger "
+ "key.")
+
+ 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)
@@ -755,6 +792,26 @@ 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,
+ _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(
+ 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 *")
@@ -769,7 +826,14 @@ 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. Try using "
+ "MAX_LENGTH instead.")
+
return self._backend._ffi.buffer(buf)[:]
def _finalize_pkcs1(self, evp_pkey, pkey_size, evp_md):
@@ -786,6 +850,44 @@ 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,
+ _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()
+ 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. Try using "
+ "MAX_LENGTH instead.")
+
+ 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]
+
@utils.register_interface(interfaces.AsymmetricVerificationContext)
class _RSAVerificationContext(object):
@@ -793,6 +895,7 @@ class _RSAVerificationContext(object):
self._backend = backend
self._public_key = public_key
self._signature = signature
+
if not isinstance(padding, interfaces.AsymmetricPadding):
raise TypeError(
"Expected provider of interfaces.AsymmetricPadding")
@@ -809,6 +912,15 @@ 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)
+ 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. Check that you have the "
+ "correct key and digest algorithm."
+ )
+
if not self._backend.mgf1_hash_supported(padding._mgf._algorithm):
raise UnsupportedHash(
"When OpenSSL is older than 1.0.1 then only SHA1 is "
@@ -862,7 +974,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+
@@ -937,18 +1055,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/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst
index 03a7caed..57c8eec2 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,25 @@ 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 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`
+ 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 +160,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 +209,24 @@ 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 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`
+ 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 5c6efbaf..3747f436 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -153,6 +153,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 67189c24..eb7e1e60 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,162 @@ 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()
+
+ @pytest.mark.parametrize(
+ "hash_alg",
+ [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), 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()),
+ 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,
+ 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 +625,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):
@@ -558,7 +725,7 @@ class TestRSAVerification(object):
padding.PSS(
mgf=padding.MGF1(
algorithm=hashes.SHA1(),
- salt_length=padding.MGF1.MAX_LENGTH
+ salt_length=20
)
),
hashes.SHA1(),
@@ -719,6 +886,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"