diff options
-rw-r--r-- | cryptography/hazmat/backends/interfaces.py | 19 | ||||
-rw-r--r-- | cryptography/hazmat/backends/openssl/backend.py | 72 | ||||
-rw-r--r-- | cryptography/hazmat/bindings/openssl/ssl.py | 1 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/asymmetric/dsa.py | 10 | ||||
-rw-r--r-- | docs/hazmat/backends/interfaces.rst | 36 | ||||
-rw-r--r-- | docs/hazmat/backends/openssl.rst | 1 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/dsa.rst | 49 | ||||
-rw-r--r-- | docs/hazmat/primitives/interfaces.rst | 27 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_dsa.py | 88 |
9 files changed, 296 insertions, 7 deletions
diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index aaaca5e2..e63b079b 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -145,6 +145,25 @@ class DSABackend(object): a DSAParameters object. """ + @abc.abstractmethod + def create_dsa_verification_ctx(self, public_key, signature, algorithm): + """ + Returns an object conforming to the AsymmetricVerificationContext + interface. + """ + + @abc.abstractmethod + def dsa_hash_supported(self, algorithm): + """ + Return True if the hash algorithm is supported by the backend for DSA. + """ + + @abc.abstractmethod + def dsa_parameters_supported(self, p, q, g): + """ + Return True if the parameters are supported by the backend for DSA. + """ + @six.add_metaclass(abc.ABCMeta) class TraditionalOpenSSLSerializationBackend(object): diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index f9154f3b..37deb2ae 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -474,6 +474,35 @@ class Backend(object): y=self._bn_to_int(ctx.pub_key) ) + def create_dsa_verification_ctx(self, public_key, signature, + algorithm): + return _DSAVerificationContext(self, public_key, signature, + algorithm) + + def _dsa_cdata_from_public_key(self, public_key): + # Does not GC the DSA cdata. You *must* make sure it's freed + # correctly yourself! + ctx = self._lib.DSA_new() + assert ctx != self._ffi.NULL + parameters = public_key.parameters() + ctx.p = self._int_to_bn(parameters.p) + ctx.q = self._int_to_bn(parameters.q) + ctx.g = self._int_to_bn(parameters.g) + ctx.pub_key = self._int_to_bn(public_key.y) + return ctx + + def dsa_hash_supported(self, algorithm): + if self._lib.OPENSSL_VERSION_NUMBER < 0x1000000f: + return isinstance(algorithm, hashes.SHA1) + else: + return self.hash_supported(algorithm) + + def dsa_parameters_supported(self, p, q, g): + if self._lib.OPENSSL_VERSION_NUMBER < 0x1000000f: + return (utils.bit_length(p) <= 1024 and utils.bit_length(q) <= 160) + else: + return True + def decrypt_rsa(self, private_key, ciphertext, padding): key_size_bytes = int(math.ceil(private_key.key_size / 8.0)) if key_size_bytes != len(ciphertext): @@ -1297,6 +1326,49 @@ class _RSAVerificationContext(object): raise InvalidSignature +@utils.register_interface(interfaces.AsymmetricVerificationContext) +class _DSAVerificationContext(object): + def __init__(self, backend, public_key, signature, algorithm): + self._backend = backend + self._public_key = public_key + self._signature = signature + 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") + + self._dsa_cdata = self._backend._dsa_cdata_from_public_key( + self._public_key) + self._dsa_cdata = self._backend._ffi.gc(self._dsa_cdata, + self._backend._lib.DSA_free) + + data_to_verify = self._hash_ctx.finalize() + self._hash_ctx = None + + # The first parameter passed to DSA_verify is unused by OpenSSL but + # must be an integer. + res = self._backend._lib.DSA_verify( + 0, data_to_verify, len(data_to_verify), self._signature, + len(self._signature), self._dsa_cdata) + + if res != 1: + errors = self._backend._consume_errors() + assert errors + if res == -1: + assert errors[0].lib == self._backend._lib.ERR_LIB_ASN1 + + raise InvalidSignature + + @utils.register_interface(interfaces.CMACContext) class _CMACContext(object): def __init__(self, backend, algorithm, ctx=None): diff --git a/cryptography/hazmat/bindings/openssl/ssl.py b/cryptography/hazmat/bindings/openssl/ssl.py index 7ed42f9f..0b15411c 100644 --- a/cryptography/hazmat/bindings/openssl/ssl.py +++ b/cryptography/hazmat/bindings/openssl/ssl.py @@ -160,6 +160,7 @@ void SSL_load_error_strings(void); int SSL_library_init(void); /* SSL */ +const char *SSL_state_string_long(const SSL *); SSL_SESSION *SSL_get1_session(SSL *); int SSL_set_session(SSL *, SSL_SESSION *); int SSL_get_verify_mode(const SSL *); diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py index 4c2de36a..57a7ef3d 100644 --- a/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -151,6 +151,16 @@ class DSAPublicKey(object): self._generator = generator self._y = y + def verifier(self, signature, algorithm, backend): + if not isinstance(backend, DSABackend): + raise UnsupportedAlgorithm( + "Backend object does not implement DSABackend", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + return backend.create_dsa_verification_ctx(self, signature, + algorithm) + @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 2f63f3e0..6833f221 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -345,6 +345,42 @@ A specific ``backend`` may provide one or more of these interfaces. 1.0.0 and the key size is larger than 1024; older OpenSSL versions do not support keys larger than 1024 bits. + .. method:: create_dsa_verification_ctx(public_key, signature, algorithm) + + :param public_key: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.DSAPublicKey` + provider. + + :param bytes signature: The signature to verify. DER encoded as + specified in :rfc:`6979`. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricVerificationContext` + + .. method:: dsa_hash_supported(algorithm): + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :returns: ``True`` if the specified ``algorithm`` is supported by this + backend, otherwise ``False``. + + .. method:: dsa_parameters_supported(p, q, g): + + :param int p: The p value of a DSA key. + + :param int q: The q value of a DSA key. + + :param int g: The g value of a DSA key. + + :returns: ``True`` if the given values of ``p``, ``q``, and ``g`` are + supported by this backend, otherwise ``False``. + .. class:: CMACBackend diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst index 43e5d8f3..f15bc282 100644 --- a/docs/hazmat/backends/openssl.rst +++ b/docs/hazmat/backends/openssl.rst @@ -14,6 +14,7 @@ Red Hat Enterprise Linux 5) and greater. Earlier versions may work but are It implements the following interfaces: * :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + * :class:`~cryptography.hazmat.backends.interfaces.DSABackend` * :class:`~cryptography.hazmat.backends.interfaces.HashBackend` * :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` * :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index 2819bbdb..03e476b6 100644 --- a/docs/hazmat/primitives/asymmetric/dsa.rst +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -118,6 +118,55 @@ DSA ``subgroup_order``, ``generator``, or ``y`` do not match the bounds specified in `FIPS 186-4`_. + .. method:: verifier(signature, algorithm, backend) + + .. versionadded:: 0.4 + + Verify data was signed by the private key associated with this public + key. + + .. code-block:: pycon + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import dsa + >>> parameters = dsa.DSAParameters.generate( + ... key_size=1024, + ... backend=default_backend() + ... ) + >>> private_key = dsa.DSAPrivateKey.generate( + ... parameters=parameters, + ... backend=default_backend() + ... ) + >>> signer = private_key.signer( + ... 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, + ... hashes.SHA256(), + ... default_backend() + ... ) + >>> verifier.update(data) + >>> verifier.verify() + + :param bytes signature: The signature to verify. DER encoded as + specified in :rfc:`6979`. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricVerificationContext` .. _`DSA`: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm .. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index c76582c0..feafe941 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -412,17 +412,38 @@ Asymmetric interfaces The bit length of the modulus. + .. attribute:: y + + :type: int + + The public key. + .. method:: parameters() :return: :class:`~cryptography.hazmat.primitives.interfaces.DSAParameters` The DSAParameters object associated with this public key. - .. attribute:: y + .. method:: verifier(signature, algorithm, backend) - :type: int + .. versionadded:: 0.4 - The public key. + Verify data was signed by the private key associated with this public + key. + + :param bytes signature: The signature to verify. DER encoded as + specified in :rfc:`6979`. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + provider. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricVerificationContext` .. class:: AsymmetricSignatureContext diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index bc3b1db6..4c3cd58a 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -18,12 +18,15 @@ import os import pytest -from cryptography.exceptions import _Reasons +from cryptography.exceptions import ( + AlreadyFinalized, InvalidSignature, _Reasons) +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.utils import bit_length from ...utils import ( - load_fips_dsa_key_pair_vectors, load_vectors_from_file, + der_encode_dsa_signature, load_fips_dsa_key_pair_vectors, + load_fips_dsa_sig_vectors, load_vectors_from_file, raises_unsupported_algorithm ) @@ -720,12 +723,89 @@ class TestDSA(object): ) +@pytest.mark.dsa +class TestDSAVerification(object): + _algorithms_dict = { + 'SHA1': hashes.SHA1, + 'SHA224': hashes.SHA224, + 'SHA256': hashes.SHA256, + 'SHA384': hashes.SHA384, + 'SHA512': hashes.SHA512 + } + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "DSA", "FIPS_186-3", "SigVer.rsp"), + load_fips_dsa_sig_vectors + ) + ) + def test_dsa_verification(self, vector, backend): + digest_algorithm = vector['digest_algorithm'].replace("-", "") + algorithm = self._algorithms_dict[digest_algorithm] + if ( + not backend.dsa_parameters_supported( + vector['p'], vector['q'], vector['g'] + ) or not backend.dsa_hash_supported(algorithm) + ): + pytest.skip( + "{0} does not support the provided parameters".format(backend) + ) + + public_key = dsa.DSAPublicKey( + vector['p'], vector['q'], vector['g'], vector['y'] + ) + sig = der_encode_dsa_signature(vector['r'], vector['s']) + verifier = public_key.verifier(sig, algorithm(), backend) + verifier.update(vector['msg']) + if vector['result'] == "F": + with pytest.raises(InvalidSignature): + verifier.verify() + else: + verifier.verify() + + def test_dsa_verify_invalid_asn1(self, backend): + parameters = dsa.DSAParameters.generate(1024, backend) + private_key = dsa.DSAPrivateKey.generate(parameters, backend) + public_key = private_key.public_key() + verifier = public_key.verifier(b'fakesig', hashes.SHA1(), backend) + verifier.update(b'fakesig') + with pytest.raises(InvalidSignature): + verifier.verify() + + def test_use_after_finalize(self, backend): + parameters = dsa.DSAParameters.generate(1024, backend) + private_key = dsa.DSAPrivateKey.generate(parameters, backend) + public_key = private_key.public_key() + verifier = public_key.verifier(b'fakesig', hashes.SHA1(), backend) + verifier.update(b'irrelevant') + with pytest.raises(InvalidSignature): + verifier.verify() + with pytest.raises(AlreadyFinalized): + verifier.verify() + with pytest.raises(AlreadyFinalized): + verifier.update(b"more data") + + def test_dsa_verifier_invalid_backend(self, backend): + pretend_backend = object() + params = dsa.DSAParameters.generate(1024, backend) + private_key = dsa.DSAPrivateKey.generate(params, backend) + public_key = private_key.public_key() + + with raises_unsupported_algorithm( + _Reasons.BACKEND_MISSING_INTERFACE): + public_key.verifier(b"sig", hashes.SHA1(), pretend_backend) + + def test_dsa_generate_invalid_backend(): pretend_backend = object() - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + with raises_unsupported_algorithm( + _Reasons.BACKEND_MISSING_INTERFACE): dsa.DSAParameters.generate(1024, pretend_backend) pretend_parameters = object() - with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + with raises_unsupported_algorithm( + _Reasons.BACKEND_MISSING_INTERFACE): dsa.DSAPrivateKey.generate(pretend_parameters, pretend_backend) |