aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2014-04-05 08:55:09 -0500
committerPaul Kehrer <paul.l.kehrer@gmail.com>2014-04-05 08:55:09 -0500
commit6abaf40a591bbae5e2eeebb8a29e6558aeae047c (patch)
tree5e2a7de8ca3c3f32da66773fcf24a45555e57cd7
parent509343e400942e78f5c1d0d5f380002939b24266 (diff)
parent29474ac7dab3f5c8b664463ed28ec83b7b77250b (diff)
downloadcryptography-6abaf40a591bbae5e2eeebb8a29e6558aeae047c.tar.gz
cryptography-6abaf40a591bbae5e2eeebb8a29e6558aeae047c.tar.bz2
cryptography-6abaf40a591bbae5e2eeebb8a29e6558aeae047c.zip
Merge pull request #739 from skeuomorf/dsa-backend
DSA backend
-rw-r--r--cryptography/hazmat/backends/interfaces.py15
-rw-r--r--cryptography/hazmat/backends/multibackend.py16
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py52
-rw-r--r--cryptography/hazmat/primitives/asymmetric/dsa.py22
-rw-r--r--docs/hazmat/backends/interfaces.rst34
-rw-r--r--docs/hazmat/primitives/asymmetric/dsa.rst59
-rw-r--r--pytest.ini1
-rw-r--r--tests/conftest.py4
-rw-r--r--tests/hazmat/backends/test_multibackend.py33
-rw-r--r--tests/hazmat/backends/test_openssl.py25
-rw-r--r--tests/hazmat/primitives/test_dsa.py50
11 files changed, 304 insertions, 7 deletions
diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py
index 27b609ed..20c21118 100644
--- a/cryptography/hazmat/backends/interfaces.py
+++ b/cryptography/hazmat/backends/interfaces.py
@@ -113,6 +113,21 @@ class RSABackend(six.with_metaclass(abc.ABCMeta)):
"""
+class DSABackend(six.with_metaclass(abc.ABCMeta)):
+ @abc.abstractmethod
+ def generate_dsa_parameters(self, key_size):
+ """
+ Generate a DSAParameters instance with a modulus of key_size bits.
+ """
+
+ @abc.abstractmethod
+ def generate_dsa_private_key(self, parameters):
+ """
+ Generate an DSAPrivateKey instance with parameters as
+ a DSAParameters object.
+ """
+
+
class OpenSSLSerializationBackend(six.with_metaclass(abc.ABCMeta)):
@abc.abstractmethod
def load_openssl_pem_private_key(self, data, password):
diff --git a/cryptography/hazmat/backends/multibackend.py b/cryptography/hazmat/backends/multibackend.py
index aa649dd3..86cded85 100644
--- a/cryptography/hazmat/backends/multibackend.py
+++ b/cryptography/hazmat/backends/multibackend.py
@@ -16,7 +16,8 @@ from __future__ import absolute_import, division, print_function
from cryptography import utils
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
from cryptography.hazmat.backends.interfaces import (
- CipherBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, RSABackend
+ CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
+ RSABackend
)
@@ -25,6 +26,7 @@ from cryptography.hazmat.backends.interfaces import (
@utils.register_interface(HMACBackend)
@utils.register_interface(PBKDF2HMACBackend)
@utils.register_interface(RSABackend)
+@utils.register_interface(DSABackend)
class MultiBackend(object):
name = "multibackend"
@@ -142,3 +144,15 @@ class MultiBackend(object):
padding, algorithm)
raise UnsupportedAlgorithm("RSA is not supported by the backend",
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM)
+
+ def generate_dsa_parameters(self, key_size):
+ for b in self._filtered_backends(DSABackend):
+ return b.generate_dsa_parameters(key_size)
+ raise UnsupportedAlgorithm("DSA is not supported by the backend",
+ _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM)
+
+ def generate_dsa_private_key(self, parameters):
+ for b in self._filtered_backends(DSABackend):
+ return b.generate_dsa_private_key(parameters)
+ raise UnsupportedAlgorithm("DSA is not supported by the backend",
+ _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM)
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index 0c632aee..900d25c2 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -25,11 +25,12 @@ from cryptography.exceptions import (
UnsupportedAlgorithm, _Reasons
)
from cryptography.hazmat.backends.interfaces import (
- CipherBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, RSABackend
+ CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
+ RSABackend
)
from cryptography.hazmat.bindings.openssl.binding import Binding
from cryptography.hazmat.primitives import hashes, interfaces
-from cryptography.hazmat.primitives.asymmetric import rsa
+from cryptography.hazmat.primitives.asymmetric import dsa, rsa
from cryptography.hazmat.primitives.asymmetric.padding import (
MGF1, PKCS1v15, PSS
)
@@ -46,6 +47,7 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError",
@utils.register_interface(CipherBackend)
+@utils.register_interface(DSABackend)
@utils.register_interface(HashBackend)
@utils.register_interface(HMACBackend)
@utils.register_interface(PBKDF2HMACBackend)
@@ -415,6 +417,52 @@ class Backend(object):
else:
return isinstance(algorithm, hashes.SHA1)
+ def generate_dsa_parameters(self, key_size):
+ if key_size not in (1024, 2048, 3072):
+ raise ValueError(
+ "Key size must be 1024 or 2048 or 3072 bits")
+
+ if (self._lib.OPENSSL_VERSION_NUMBER < 0x1000000f and
+ key_size > 1024):
+ raise ValueError(
+ "Key size must be 1024 because OpenSSL < 1.0.0 doesn't "
+ "support larger key sizes")
+
+ ctx = self._lib.DSA_new()
+ assert ctx != self._ffi.NULL
+ ctx = self._ffi.gc(ctx, self._lib.DSA_free)
+
+ res = self._lib.DSA_generate_parameters_ex(
+ ctx, key_size, self._ffi.NULL, 0,
+ self._ffi.NULL, self._ffi.NULL, self._ffi.NULL
+ )
+
+ assert res == 1
+
+ return dsa.DSAParameters(
+ modulus=self._bn_to_int(ctx.p),
+ subgroup_order=self._bn_to_int(ctx.q),
+ generator=self._bn_to_int(ctx.g)
+ )
+
+ def generate_dsa_private_key(self, parameters):
+ ctx = self._lib.DSA_new()
+ assert ctx != self._ffi.NULL
+ ctx = self._ffi.gc(ctx, self._lib.DSA_free)
+ ctx.p = self._int_to_bn(parameters.p)
+ ctx.q = self._int_to_bn(parameters.q)
+ ctx.g = self._int_to_bn(parameters.g)
+
+ self._lib.DSA_generate_key(ctx)
+
+ return dsa.DSAPrivateKey(
+ modulus=self._bn_to_int(ctx.p),
+ subgroup_order=self._bn_to_int(ctx.q),
+ generator=self._bn_to_int(ctx.g),
+ x=self._bn_to_int(ctx.priv_key),
+ y=self._bn_to_int(ctx.pub_key)
+ )
+
class GetCipherByName(object):
def __init__(self, fmt):
diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py
index 974db0a6..4c2de36a 100644
--- a/cryptography/hazmat/primitives/asymmetric/dsa.py
+++ b/cryptography/hazmat/primitives/asymmetric/dsa.py
@@ -16,6 +16,8 @@ from __future__ import absolute_import, division, print_function
import six
from cryptography import utils
+from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
+from cryptography.hazmat.backends.interfaces import DSABackend
from cryptography.hazmat.primitives import interfaces
@@ -49,6 +51,16 @@ class DSAParameters(object):
self._subgroup_order = subgroup_order
self._generator = generator
+ @classmethod
+ def generate(cls, key_size, backend):
+ if not isinstance(backend, DSABackend):
+ raise UnsupportedAlgorithm(
+ "Backend object does not implement DSABackend",
+ _Reasons.BACKEND_MISSING_INTERFACE
+ )
+
+ return backend.generate_dsa_parameters(key_size)
+
@property
def modulus(self):
return self._modulus
@@ -96,6 +108,16 @@ class DSAPrivateKey(object):
self._x = x
self._y = y
+ @classmethod
+ def generate(cls, parameters, backend):
+ if not isinstance(backend, DSABackend):
+ raise UnsupportedAlgorithm(
+ "Backend object does not implement DSABackend",
+ _Reasons.BACKEND_MISSING_INTERFACE
+ )
+
+ return backend.generate_dsa_private_key(parameters)
+
@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 c38f818f..9c401d28 100644
--- a/docs/hazmat/backends/interfaces.rst
+++ b/docs/hazmat/backends/interfaces.rst
@@ -285,3 +285,37 @@ A specific ``backend`` may provide one or more of these interfaces.
:raises cryptography.exceptions.UnsupportedAlgorithm: If the data is
encrypted with an unsupported algorithm.
+
+
+.. class:: DSABackend
+
+ .. versionadded:: 0.4
+
+ A backend with methods for using DSA.
+
+ .. method:: generate_dsa_parameters(key_size)
+
+ :param int key_size: The length of the modulus in bits. It should be
+ either "1024, 2048 or 3072". For keys generated in 2014 this should
+ be at least 2048.
+ Note that some applications (such as SSH) have not yet gained support
+ for larger key sizes specified in FIPS 186-3 and are still restricted
+ to only the 1024-bit keys specified in FIPS 186-2.
+
+ :return: A new instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.DSAParameters`
+ provider.
+
+ .. method:: generate_dsa_private_key(parameters)
+
+ :param parameters: A
+ :class:`~cryptography.hazmat.primitives.interfaces.DSAParameters`
+ provider.
+
+ :return: A new instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.DSAPrivateKey`
+ provider.
+
+ :raises ValueError: This is raised if the key size is not (1024 or 2048 or 3072)
+ 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.
diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst
index 69e8d58e..1a6a6e0e 100644
--- a/docs/hazmat/primitives/asymmetric/dsa.rst
+++ b/docs/hazmat/primitives/asymmetric/dsa.rst
@@ -13,6 +13,16 @@ DSA
DSA Parameters are required for generating a DSA private key.
+ You should use :meth:`~generate` to generate new parameters.
+
+ .. warning::
+ This method only checks a limited set of properties of its arguments.
+ Using DSA parameters that you do not trust or with incorrect arguments
+ may lead to insecure operation, crashes, and other undefined behavior.
+ We recommend that you only ever load parameters that were generated
+ with software you trust.
+
+
This class conforms to the
:class:`~cryptography.hazmat.primitives.interfaces.DSAParameters`
interface.
@@ -23,6 +33,23 @@ DSA
``subgroup_order``, or ``generator`` do
not match the bounds specified in `FIPS 186-4`_.
+ .. classmethod:: generate(key_size, backend)
+
+ Generate a new ``DSAParameters`` instance using ``backend``.
+
+ :param int key_size: The length of the modulus in bits. It should be
+ either "1024, 2048 or 3072". For keys generated in 2014 this should
+ be `at least 2048`_ (See page 41).
+ Note that some applications (such as SSH) have not yet gained support
+ for larger key sizes specified in FIPS 186-3 and are still restricted
+ to only the 1024-bit keys specified in FIPS 186-2.
+
+ :return: A new instance of ``DSAParameters``
+
+ :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if
+ the provided ``backend`` does not implement
+ :class:`~cryptography.hazmat.backends.interfaces.DSABackend`
+
.. class:: DSAPrivateKey(modulus, subgroup_order, generator, x, y)
@@ -30,6 +57,16 @@ DSA
A DSA private key is required for signing messages.
+ You should use :meth:`~generate` to generate new keys.
+
+ .. warning::
+ This method only checks a limited set of properties of its arguments.
+ Using a DSA private key that you do not trust or with incorrect
+ parameters may lead to insecure operation, crashes, and other undefined
+ behavior. We recommend that you only ever load private keys that were
+ generated with software you trust.
+
+
This class conforms to the
:class:`~cryptography.hazmat.primitives.interfaces.DSAPrivateKey`
interface.
@@ -40,6 +77,26 @@ DSA
``subgroup_order``, or ``generator`` do
not match the bounds specified in `FIPS 186-4`_.
+ .. classmethod:: generate(parameters, backend)
+
+ Generate a new ``DSAPrivateKey`` instance using ``backend``.
+
+ :param parameters: A
+ :class:`~cryptography.hazmat.primitives.interfaces.DSAParameters`
+ provider.
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.DSABackend`
+ provider.
+ :return: A new instance of ``DSAPrivateKey``.
+
+ :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if
+ the provided ``backend`` does not implement
+ :class:`~cryptography.hazmat.backends.interfaces.DSABackend`
+
+ :raises ValueError: This is raised if the key size is not (1024 or 2048 or 3072)
+ 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.
+
.. class:: DSAPublicKey(modulus, subgroup_order, generator, y)
@@ -65,4 +122,4 @@ DSA
.. _`DSA`: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm
.. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography
.. _`FIPS 186-4`: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
-
+.. _`at least 2048`: http://www.ecrypt.eu.org/documents/D.SPA.20.pdf
diff --git a/pytest.ini b/pytest.ini
index 3f65e30e..b590d0be 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -2,6 +2,7 @@
addopts = -r s
markers =
cipher: this test requires a backend providing CipherBackend
+ dsa: this test requires a backend providing DSABackend
hash: this test requires a backend providing HashBackend
hmac: this test requires a backend providing HMACBackend
pbkdf2hmac: this test requires a backend providing PBKDF2HMACBackend
diff --git a/tests/conftest.py b/tests/conftest.py
index 8e89af57..1ee2a993 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -17,7 +17,8 @@ import pytest
from cryptography.hazmat.backends import _available_backends
from cryptography.hazmat.backends.interfaces import (
- CipherBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, RSABackend
+ CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
+ RSABackend
)
from .utils import check_backend_support, check_for_iface, select_backends
@@ -37,6 +38,7 @@ def pytest_runtest_setup(item):
check_for_iface("cipher", CipherBackend, item)
check_for_iface("hash", HashBackend, item)
check_for_iface("pbkdf2hmac", PBKDF2HMACBackend, item)
+ check_for_iface("dsa", DSABackend, item)
check_for_iface("rsa", RSABackend, item)
check_backend_support(item)
diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py
index f0be72b2..f46009d4 100644
--- a/tests/hazmat/backends/test_multibackend.py
+++ b/tests/hazmat/backends/test_multibackend.py
@@ -18,7 +18,8 @@ from cryptography.exceptions import (
UnsupportedAlgorithm, _Reasons
)
from cryptography.hazmat.backends.interfaces import (
- CipherBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, RSABackend
+ CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
+ RSABackend
)
from cryptography.hazmat.backends.multibackend import MultiBackend
from cryptography.hazmat.primitives import hashes, hmac
@@ -98,6 +99,15 @@ class DummyRSABackend(object):
pass
+@utils.register_interface(DSABackend)
+class DummyDSABackend(object):
+ def generate_dsa_parameters(self, key_size):
+ pass
+
+ def generate_dsa_private_key(self, parameters):
+ pass
+
+
class TestMultiBackend(object):
def test_ciphers(self):
backend = MultiBackend([
@@ -193,3 +203,24 @@ class TestMultiBackend(object):
):
backend.create_rsa_verification_ctx(
"public_key", "sig", padding.PKCS1v15(), hashes.MD5())
+
+ def test_dsa(self):
+ backend = MultiBackend([
+ DummyDSABackend()
+ ])
+
+ backend.generate_dsa_parameters(key_size=1024)
+
+ parameters = object()
+ backend.generate_dsa_private_key(parameters)
+
+ backend = MultiBackend([])
+ with raises_unsupported_algorithm(
+ _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
+ ):
+ backend.generate_dsa_parameters(key_size=1024)
+
+ with raises_unsupported_algorithm(
+ _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
+ ):
+ backend.generate_dsa_private_key(parameters)
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 016da0fc..6ab16627 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -21,13 +21,15 @@ from cryptography.exceptions import (
)
from cryptography.hazmat.backends.openssl.backend import Backend, backend
from cryptography.hazmat.primitives import hashes, interfaces
-from cryptography.hazmat.primitives.asymmetric import padding, rsa
+from cryptography.hazmat.primitives.asymmetric import dsa, padding, rsa
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import CBC
from ...utils import raises_unsupported_algorithm
+from cryptography.utils import bit_length
+
@utils.register_interface(interfaces.Mode)
class DummyMode(object):
@@ -192,6 +194,27 @@ class TestOpenSSL(object):
res = backend._lib.ENGINE_free(e)
assert res == 1
+ @pytest.mark.skipif(
+ backend._lib.OPENSSL_VERSION_NUMBER >= 0x1000000f,
+ reason="Requires an older OpenSSL. Must be < 1.0.0"
+ )
+ def test_large_key_size_on_old_openssl(self):
+ with pytest.raises(ValueError):
+ dsa.DSAParameters.generate(2048, backend=backend)
+
+ with pytest.raises(ValueError):
+ dsa.DSAParameters.generate(3072, backend=backend)
+
+ @pytest.mark.skipif(
+ backend._lib.OPENSSL_VERSION_NUMBER < 0x1000000f,
+ reason="Requires a newer OpenSSL. Must be >= 1.0.0"
+ )
+ def test_large_key_size_on_new_openssl(self):
+ parameters = dsa.DSAParameters.generate(2048, backend)
+ assert bit_length(parameters.p) == 2048
+ parameters = dsa.DSAParameters.generate(3072, backend)
+ assert bit_length(parameters.p) == 3072
+
class TestOpenSSLRandomEngine(object):
def teardown_method(self, method):
diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py
index 2a2b9bda..2b5d4bb3 100644
--- a/tests/hazmat/primitives/test_dsa.py
+++ b/tests/hazmat/primitives/test_dsa.py
@@ -14,9 +14,18 @@
from __future__ import absolute_import, division, print_function
+import os
+
import pytest
+from cryptography.exceptions import _Reasons
from cryptography.hazmat.primitives.asymmetric import dsa
+from cryptography.utils import bit_length
+
+from ...utils import (
+ load_vectors_from_file, load_fips_dsa_key_pair_vectors,
+ raises_unsupported_algorithm
+)
def _check_dsa_private_key(skey):
@@ -53,6 +62,7 @@ def _check_dsa_private_key(skey):
assert skey_parameters.generator == pkey_parameters.generator
+@pytest.mark.dsa
class TestDSA(object):
_parameters_1024 = {
'p': 'd38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d4b725ef341eabb47'
@@ -157,6 +167,35 @@ class TestDSA(object):
'f90f7dff6d2bae'
}
+ def test_generate_dsa_parameters(self, backend):
+ parameters = dsa.DSAParameters.generate(1024, backend)
+ assert bit_length(parameters.p) == 1024
+
+ def test_generate_invalid_dsa_parameters(self, backend):
+ with pytest.raises(ValueError):
+ dsa.DSAParameters.generate(1, backend)
+
+ @pytest.mark.parametrize(
+ "vector",
+ load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "DSA", "FIPS_186-3", "KeyPair.rsp"),
+ load_fips_dsa_key_pair_vectors
+ )
+ )
+ def test_generate_dsa_keys(self, vector, backend):
+ parameters = dsa.DSAParameters(modulus=vector['p'],
+ subgroup_order=vector['q'],
+ generator=vector['g'])
+ skey = dsa.DSAPrivateKey.generate(parameters, backend)
+
+ skey_parameters = skey.parameters()
+ assert skey_parameters.p == vector['p']
+ assert skey_parameters.q == vector['q']
+ assert skey_parameters.g == vector['g']
+ assert skey.key_size == bit_length(vector['p'])
+ assert skey.y == pow(skey_parameters.g, skey.x, skey_parameters.p)
+
def test_invalid_parameters_argument_types(self):
with pytest.raises(TypeError):
dsa.DSAParameters(None, None, None)
@@ -679,3 +718,14 @@ class TestDSA(object):
generator=int(self._parameters_1024['g'], 16),
y=None
)
+
+
+def test_dsa_generate_invalid_backend():
+ pretend_backend = object()
+
+ 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):
+ dsa.DSAPrivateKey.generate(pretend_parameters, pretend_backend)