aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cryptography/hazmat/backends/multibackend.py7
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py110
-rw-r--r--cryptography/hazmat/primitives/asymmetric/rsa.py4
-rw-r--r--docs/exceptions.rst4
-rw-r--r--docs/hazmat/primitives/asymmetric/rsa.rst43
-rw-r--r--docs/hazmat/primitives/interfaces.rst4
-rw-r--r--tests/hazmat/backends/test_multibackend.py11
-rw-r--r--tests/hazmat/primitives/test_rsa.py115
8 files changed, 293 insertions, 5 deletions
diff --git a/cryptography/hazmat/backends/multibackend.py b/cryptography/hazmat/backends/multibackend.py
index f0017191..de1fff7c 100644
--- a/cryptography/hazmat/backends/multibackend.py
+++ b/cryptography/hazmat/backends/multibackend.py
@@ -112,3 +112,10 @@ class MultiBackend(object):
for b in self._filtered_backends(RSABackend):
return b.create_rsa_signature_ctx(private_key, padding, algorithm)
raise UnsupportedAlgorithm
+
+ def create_rsa_verification_ctx(self, public_key, signature, padding,
+ algorithm):
+ for b in self._filtered_backends(RSABackend):
+ return b.create_rsa_verification_ctx(public_key, signature,
+ padding, algorithm)
+ raise UnsupportedAlgorithm
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index 00fdc266..c7ae9141 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -18,7 +18,7 @@ import itertools
from cryptography import utils
from cryptography.exceptions import (
UnsupportedAlgorithm, InvalidTag, InternalError, AlreadyFinalized,
- UnsupportedPadding
+ UnsupportedPadding, InvalidSignature
)
from cryptography.hazmat.backends.interfaces import (
CipherBackend, HashBackend, HMACBackend, PBKDF2HMACBackend, RSABackend
@@ -336,9 +336,22 @@ class Backend(object):
ctx.iqmp = self._int_to_bn(private_key.iqmp)
return ctx
+ def _rsa_cdata_from_public_key(self, public_key):
+ ctx = self._lib.RSA_new()
+ assert ctx != self._ffi.NULL
+ ctx = self._ffi.gc(ctx, self._lib.RSA_free)
+ ctx.e = self._int_to_bn(public_key.e)
+ ctx.n = self._int_to_bn(public_key.n)
+ return ctx
+
def create_rsa_signature_ctx(self, private_key, padding, algorithm):
return _RSASignatureContext(self, private_key, padding, algorithm)
+ def create_rsa_verification_ctx(self, public_key, signature, padding,
+ algorithm):
+ return _RSAVerificationContext(self, public_key, signature, padding,
+ algorithm)
+
class GetCipherByName(object):
def __init__(self, fmt):
@@ -686,4 +699,99 @@ class _RSASignatureContext(object):
return self._backend._ffi.buffer(sig_buf)[:sig_len[0]]
+@utils.register_interface(interfaces.AsymmetricVerificationContext)
+class _RSAVerificationContext(object):
+ def __init__(self, backend, public_key, signature, padding, algorithm):
+ self._backend = backend
+ self._public_key = public_key
+ self._signature = signature
+ if not isinstance(padding, interfaces.AsymmetricPadding):
+ raise TypeError(
+ "Expected provider of interfaces.AsymmetricPadding")
+
+ if padding.name == "EMSA-PKCS1-v1_5":
+ if self._backend._lib.Cryptography_HAS_PKEY_CTX:
+ self._verify_method = self._verify_pkey_ctx
+ self._padding_enum = self._backend._lib.RSA_PKCS1_PADDING
+ else:
+ self._verify_method = self._verify_pkcs1
+ else:
+ raise UnsupportedPadding
+
+ self._padding = padding
+ self._algorithm = algorithm
+ self._hash_ctx = _HashContext(backend, self._algorithm)
+
+ def update(self, data):
+ if self._hash_ctx is None:
+ raise AlreadyFinalized("Context has already been finalized")
+
+ self._hash_ctx.update(data)
+
+ def verify(self):
+ if self._hash_ctx is None:
+ raise AlreadyFinalized("Context has already been finalized")
+
+ evp_pkey = self._backend._lib.EVP_PKEY_new()
+ assert evp_pkey != self._backend._ffi.NULL
+ evp_pkey = backend._ffi.gc(evp_pkey, backend._lib.EVP_PKEY_free)
+ rsa_cdata = backend._rsa_cdata_from_public_key(self._public_key)
+ res = self._backend._lib.RSA_blinding_on(
+ rsa_cdata, self._backend._ffi.NULL)
+ assert res == 1
+ res = self._backend._lib.EVP_PKEY_set1_RSA(evp_pkey, rsa_cdata)
+ assert res == 1
+ evp_md = self._backend._lib.EVP_get_digestbyname(
+ self._algorithm.name.encode("ascii"))
+ assert evp_md != self._backend._ffi.NULL
+
+ self._verify_method(rsa_cdata, evp_pkey, evp_md)
+
+ def _verify_pkey_ctx(self, rsa_cdata, evp_pkey, evp_md):
+ pkey_ctx = self._backend._lib.EVP_PKEY_CTX_new(
+ evp_pkey, self._backend._ffi.NULL
+ )
+ assert pkey_ctx != self._backend._ffi.NULL
+ res = self._backend._lib.EVP_PKEY_verify_init(pkey_ctx)
+ assert res == 1
+ res = self._backend._lib.EVP_PKEY_CTX_set_signature_md(
+ pkey_ctx, evp_md)
+ assert res > 0
+
+ res = self._backend._lib.EVP_PKEY_CTX_set_rsa_padding(
+ pkey_ctx, self._padding_enum)
+ assert res > 0
+ data_to_verify = self._hash_ctx.finalize()
+ self._hash_ctx = None
+ res = self._backend._lib.EVP_PKEY_verify(
+ pkey_ctx,
+ self._signature,
+ len(self._signature),
+ data_to_verify,
+ len(data_to_verify)
+ )
+ # The previous call can return negative numbers in the event of an
+ # error. This is not a signature failure but we need to fail if it
+ # occurs.
+ assert res >= 0
+ if res == 0:
+ raise InvalidSignature
+
+ def _verify_pkcs1(self, rsa_cdata, evp_pkey, evp_md):
+ res = self._backend._lib.EVP_VerifyFinal(
+ self._hash_ctx._ctx,
+ self._signature,
+ len(self._signature),
+ evp_pkey
+ )
+ self._hash_ctx.finalize()
+ self._hash_ctx = None
+ # The previous call can return negative numbers in the event of an
+ # error. This is not a signature failure but we need to fail if it
+ # occurs.
+ assert res >= 0
+ if res == 0:
+ raise InvalidSignature
+
+
backend = Backend()
diff --git a/cryptography/hazmat/primitives/asymmetric/rsa.py b/cryptography/hazmat/primitives/asymmetric/rsa.py
index 2f9e4247..d8254046 100644
--- a/cryptography/hazmat/primitives/asymmetric/rsa.py
+++ b/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -49,6 +49,10 @@ class RSAPublicKey(object):
self._public_exponent = public_exponent
self._modulus = modulus
+ def verifier(self, signature, padding, algorithm, backend):
+ return backend.create_rsa_verification_ctx(self, signature, padding,
+ algorithm)
+
@property
def key_size(self):
return _bit_length(self.modulus)
diff --git a/docs/exceptions.rst b/docs/exceptions.rst
index 0982426f..7f9ae347 100644
--- a/docs/exceptions.rst
+++ b/docs/exceptions.rst
@@ -10,8 +10,8 @@ Exceptions
.. class:: InvalidSignature
- This is raised when the verify method of a hash context's computed digest
- does not match the expected digest.
+ This is raised when signature verification fails. This can occur with
+ HMAC or asymmetric key signature validation.
.. class:: NotYetFinalized
diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst
index 682820b3..7943981e 100644
--- a/docs/hazmat/primitives/asymmetric/rsa.rst
+++ b/docs/hazmat/primitives/asymmetric/rsa.rst
@@ -111,6 +111,49 @@ RSA
or ``modulus`` do not match the bounds specified in
:rfc:`3447`.
+ .. method:: verifier(signature, padding, algorithm, backend)
+
+ .. versionadded:: 0.3
+
+ Verify data was signed by the private key associated with this public
+ key.
+
+ .. doctest::
+
+ >>> from cryptography.hazmat.backends import default_backend
+ >>> from cryptography.hazmat.primitives import hashes
+ >>> from cryptography.hazmat.primitives.asymmetric import rsa, padding
+ >>> private_key = rsa.RSAPrivateKey.generate(
+ ... public_exponent=65537,
+ ... key_size=2048,
+ ... backend=default_backend()
+ ... )
+ >>> signer = private_key.signer(padding.PKCS1v15(), 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.update(data)
+ >>> verifier.verify()
+
+ :param bytes signature: The signature to verify.
+
+ :param padding: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricPadding`
+ provider.
+
+ :param algorithm: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+ provider.
+
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.RSABackend`
+ provider.
+
+ :returns:
+ :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricVerificationContext`
+
.. _`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/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst
index 5be3dd95..15ad1d1b 100644
--- a/docs/hazmat/primitives/interfaces.rst
+++ b/docs/hazmat/primitives/interfaces.rst
@@ -254,8 +254,8 @@ Asymmetric Interfaces
.. method:: verify()
- :raises cryptography.exceptions.InvalidSignature: If signature does not
- validate.
+ :raises cryptography.exceptions.InvalidSignature: If the signature does
+ not validate.
.. class:: AsymmetricPadding
diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py
index be1e76e2..63168180 100644
--- a/tests/hazmat/backends/test_multibackend.py
+++ b/tests/hazmat/backends/test_multibackend.py
@@ -89,6 +89,10 @@ class DummyRSABackend(object):
def create_rsa_signature_ctx(self, private_key, padding, algorithm):
pass
+ def create_rsa_verification_ctx(self, public_key, signature, padding,
+ algorithm):
+ pass
+
class TestMultiBackend(object):
def test_ciphers(self):
@@ -165,6 +169,9 @@ class TestMultiBackend(object):
backend.create_rsa_signature_ctx("private_key", padding.PKCS1v15(),
hashes.MD5())
+ backend.create_rsa_verification_ctx("public_key", "sig",
+ padding.PKCS1v15(), hashes.MD5())
+
backend = MultiBackend([])
with pytest.raises(UnsupportedAlgorithm):
backend.generate_rsa_private_key(key_size=1024, public_exponent=3)
@@ -172,3 +179,7 @@ class TestMultiBackend(object):
with pytest.raises(UnsupportedAlgorithm):
backend.create_rsa_signature_ctx("private_key", padding.PKCS1v15(),
hashes.MD5())
+
+ with pytest.raises(UnsupportedAlgorithm):
+ backend.create_rsa_verification_ctx(
+ "public_key", "sig", padding.PKCS1v15(), hashes.MD5())
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index 647c51b4..79323265 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -443,3 +443,118 @@ class TestRSASignature(object):
)
with pytest.raises(TypeError):
private_key.signer("notpadding", hashes.SHA1(), backend)
+
+
+@pytest.mark.rsa
+class TestRSAVerification(object):
+ @pytest.mark.parametrize(
+ "pkcs1_example",
+ _flatten_pkcs1_examples(load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "RSA", "pkcs1v15sign-vectors.txt"),
+ load_pkcs1_vectors
+ ))
+ )
+ def test_pkcs1v15_verification(self, pkcs1_example, backend):
+ private, public, example = pkcs1_example
+ public_key = rsa.RSAPublicKey(
+ public_exponent=public["public_exponent"],
+ modulus=public["modulus"]
+ )
+ verifier = public_key.verifier(
+ binascii.unhexlify(example["signature"]),
+ padding.PKCS1v15(),
+ hashes.SHA1(),
+ backend
+ )
+ verifier.update(binascii.unhexlify(example["message"]))
+ verifier.verify()
+
+ def test_invalid_pkcs1v15_signature_wrong_data(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ public_key = private_key.public_key()
+ signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1(), backend)
+ signer.update(b"sign me")
+ signature = signer.finalize()
+ verifier = public_key.verifier(
+ signature,
+ padding.PKCS1v15(),
+ hashes.SHA1(),
+ backend
+ )
+ verifier.update(b"incorrect data")
+ with pytest.raises(exceptions.InvalidSignature):
+ verifier.verify()
+
+ def test_invalid_pkcs1v15_signature_wrong_key(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ private_key2 = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ public_key = private_key2.public_key()
+ signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1(), backend)
+ signer.update(b"sign me")
+ signature = signer.finalize()
+ verifier = public_key.verifier(
+ signature,
+ padding.PKCS1v15(),
+ hashes.SHA1(),
+ backend
+ )
+ verifier.update(b"sign me")
+ with pytest.raises(exceptions.InvalidSignature):
+ verifier.verify()
+
+ def test_use_after_finalize(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ public_key = private_key.public_key()
+ signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1(), backend)
+ signer.update(b"sign me")
+ signature = signer.finalize()
+
+ verifier = public_key.verifier(
+ signature,
+ padding.PKCS1v15(),
+ hashes.SHA1(),
+ backend
+ )
+ verifier.update(b"sign me")
+ verifier.verify()
+ with pytest.raises(exceptions.AlreadyFinalized):
+ verifier.verify()
+ with pytest.raises(exceptions.AlreadyFinalized):
+ verifier.update(b"more data")
+
+ def test_unsupported_padding(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ public_key = private_key.public_key()
+ with pytest.raises(exceptions.UnsupportedPadding):
+ public_key.verifier(b"sig", DummyPadding(), hashes.SHA1(), backend)
+
+ def test_padding_incorrect_type(self, backend):
+ private_key = rsa.RSAPrivateKey.generate(
+ public_exponent=65537,
+ key_size=512,
+ backend=backend
+ )
+ public_key = private_key.public_key()
+ with pytest.raises(TypeError):
+ public_key.verifier(b"sig", "notpadding", hashes.SHA1(), backend)