aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cryptography/hazmat/backends/interfaces.py19
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py72
-rw-r--r--cryptography/hazmat/primitives/asymmetric/dsa.py10
-rw-r--r--docs/hazmat/backends/interfaces.rst36
-rw-r--r--docs/hazmat/backends/openssl.rst1
-rw-r--r--docs/hazmat/primitives/asymmetric/dsa.rst49
-rw-r--r--docs/hazmat/primitives/interfaces.rst27
-rw-r--r--tests/hazmat/primitives/test_dsa.py88
8 files changed, 295 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/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)