diff options
author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2014-05-01 15:34:42 -0500 |
---|---|---|
committer | Paul Kehrer <paul.l.kehrer@gmail.com> | 2014-05-01 20:29:56 -0500 |
commit | 0b3ff3b09b71f93ec278e0da73d27b5976241721 (patch) | |
tree | 67f5c9a72068b36617e945733cfd032ec35672e7 | |
parent | 8c977a3bac7476b1d8a59c42bc96cf7ad08f430e (diff) | |
download | cryptography-0b3ff3b09b71f93ec278e0da73d27b5976241721.tar.gz cryptography-0b3ff3b09b71f93ec278e0da73d27b5976241721.tar.bz2 cryptography-0b3ff3b09b71f93ec278e0da73d27b5976241721.zip |
DSA signing support (this is mostly skeuomorf's work, credit to him)
-rw-r--r-- | cryptography/hazmat/backends/interfaces.py | 7 | ||||
-rw-r--r-- | cryptography/hazmat/backends/openssl/backend.py | 58 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/asymmetric/dsa.py | 9 | ||||
-rw-r--r-- | docs/hazmat/backends/interfaces.rst | 13 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/dsa.rst | 43 | ||||
-rw-r--r-- | docs/hazmat/primitives/interfaces.rst | 17 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_dsa.py | 54 |
7 files changed, 201 insertions, 0 deletions
diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index e63b079b..264c5afb 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -146,6 +146,13 @@ class DSABackend(object): """ @abc.abstractmethod + def create_dsa_signature_ctx(self, private_key, algorithm): + """ + Returns an object conforming to the AsymmetricSignatureContext + interface. + """ + + @abc.abstractmethod def create_dsa_verification_ctx(self, public_key, signature, algorithm): """ Returns an object conforming to the AsymmetricVerificationContext diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 37deb2ae..348e4151 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -474,6 +474,9 @@ class Backend(object): y=self._bn_to_int(ctx.pub_key) ) + def create_dsa_signature_ctx(self, private_key, algorithm): + return _DSASignatureContext(self, private_key, algorithm) + def create_dsa_verification_ctx(self, public_key, signature, algorithm): return _DSAVerificationContext(self, public_key, signature, @@ -491,6 +494,19 @@ class Backend(object): ctx.pub_key = self._int_to_bn(public_key.y) return ctx + def _dsa_cdata_from_private_key(self, private_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 = private_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.priv_key = self._int_to_bn(private_key.x) + ctx.pub_key = self._int_to_bn(private_key.y) + return ctx + def dsa_hash_supported(self, algorithm): if self._lib.OPENSSL_VERSION_NUMBER < 0x1000000f: return isinstance(algorithm, hashes.SHA1) @@ -1369,6 +1385,48 @@ class _DSAVerificationContext(object): raise InvalidSignature +@utils.register_interface(interfaces.AsymmetricSignatureContext) +class _DSASignatureContext(object): + def __init__(self, backend, private_key, algorithm): + self._backend = backend + self._private_key = private_key + self._algorithm = algorithm + self._hash_ctx = _HashContext(backend, self._algorithm) + self._dsa_cdata = self._backend._dsa_cdata_from_private_key( + self._private_key) + self._dsa_cdata = self._backend._ffi.gc(self._dsa_cdata, + self._backend._lib.DSA_free) + + def update(self, data): + if self._hash_ctx is None: + raise AlreadyFinalized("Context has already been finalized") + + self._hash_ctx.update(data) + + def finalize(self): + if self._hash_ctx is None: + raise AlreadyFinalized("Context has already been finalized") + + data_to_sign = self._hash_ctx.finalize() + self._hash_ctx = None + sig_buf_len = self._backend._lib.DSA_size(self._dsa_cdata) + sig_buf = self._backend._ffi.new("unsigned char[]", sig_buf_len) + buflen = self._backend._ffi.new("unsigned int *") + + # The first parameter passed to DSA_sign is unused by OpenSSL but + # must be an integer. + res = self._backend._lib.DSA_sign( + 0, data_to_sign, len(data_to_sign), sig_buf, + buflen, self._dsa_cdata) + + if res != 1: + errors = self._backend._consume_errors() + assert errors + raise InvalidSignature + + return self._backend._ffi.buffer(sig_buf)[:] + + @utils.register_interface(interfaces.CMACContext) class _CMACContext(object): def __init__(self, backend, algorithm, ctx=None): diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py index 57a7ef3d..aa3cdc90 100644 --- a/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -118,6 +118,15 @@ class DSAPrivateKey(object): return backend.generate_dsa_private_key(parameters) + def signer(self, algorithm, backend): + if not isinstance(backend, DSABackend): + raise UnsupportedAlgorithm( + "Backend object does not implement DSABackend", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + return backend.create_dsa_signature_ctx(self, 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 6833f221..f363b541 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -345,6 +345,19 @@ 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_signature_ctx(private_key, algorithm) + + :param private_key: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.DSAPrivateKey` + provider. + + :param algorithm: An instance of a + :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` + provider + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext` + .. method:: create_dsa_verification_ctx(public_key, signature, algorithm) :param public_key: An instance of a diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index 03e476b6..98aebb6b 100644 --- a/docs/hazmat/primitives/asymmetric/dsa.rst +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -97,6 +97,49 @@ DSA or if the OpenSSL version is older than 1.0.0 and the key size is larger than 1024 because older OpenSSL versions don't support a key size larger than 1024. + .. method:: signer(algorithm, backend) + + .. versionadded:: 0.4 + + Sign data which can be verified later by others using the 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() + + :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.AsymmetricSignatureContext` + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if + the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + .. class:: DSAPublicKey(modulus, subgroup_order, generator, y) diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index feafe941..dc09a26f 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -381,6 +381,23 @@ Asymmetric interfaces The DSAParameters object associated with this private key. + .. method:: signer(algorithm, backend) + + .. versionadded:: 0.4 + + Sign data which can be verified later by others using the public key. + + :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.AsymmetricSignatureContext` + .. attribute:: key_size :type: int diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index 4c3cd58a..1bfea2e3 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -798,6 +798,60 @@ class TestDSAVerification(object): public_key.verifier(b"sig", hashes.SHA1(), pretend_backend) +@pytest.mark.dsa +class TestDSASignature(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", "SigGen.txt"), + load_fips_dsa_sig_vectors + ) + ) + def test_dsa_signing(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) + ) + + private_key = dsa.DSAPrivateKey( + vector['p'], vector['q'], vector['g'], vector['x'], vector['y'] + ) + signer = private_key.signer(algorithm(), backend) + signer.update(vector['msg']) + signature = signer.finalize() + assert signature + + public_key = private_key.public_key() + verifier = public_key.verifier(signature, algorithm(), backend) + verifier.update(vector['msg']) + verifier.verify() + + def test_use_after_finalize(self, backend): + parameters = dsa.DSAParameters.generate(1024, backend) + private_key = dsa.DSAPrivateKey.generate(parameters, backend) + signer = private_key.signer(hashes.SHA1(), backend) + signer.update(b"data") + signer.finalize() + with pytest.raises(AlreadyFinalized): + signer.finalize() + with pytest.raises(AlreadyFinalized): + signer.update(b"more data") + + def test_dsa_generate_invalid_backend(): pretend_backend = object() |