From 0197ed8dedcd24d3b690d1b76eb6866df14f56dd Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 22 Jun 2014 18:06:28 -0600 Subject: DSA opaque OpenSSL --- cryptography/hazmat/backends/openssl/backend.py | 90 +++++++++++------ cryptography/hazmat/backends/openssl/dsa.py | 121 +++++++++++++++++++++-- cryptography/hazmat/primitives/asymmetric/dsa.py | 78 +++++++++++---- tests/hazmat/primitives/test_dsa.py | 72 +++++++++----- tests/hazmat/primitives/test_serialization.py | 68 +++++++------ 5 files changed, 319 insertions(+), 110 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index d08cd3e4..e895da83 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -32,6 +32,7 @@ from cryptography.hazmat.backends.openssl.ciphers import ( _AESCTRCipherContext, _CipherContext ) from cryptography.hazmat.backends.openssl.dsa import ( + _DSAParameters, _DSAPrivateKey, _DSAPublicKey, _DSASignatureContext, _DSAVerificationContext ) from cryptography.hazmat.backends.openssl.ec import ( @@ -467,19 +468,10 @@ class Backend(object): dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey) assert dsa_cdata != self._ffi.NULL dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) - return self._dsa_cdata_to_private_key(dsa_cdata) + return _DSAPrivateKey(self, dsa_cdata) else: raise UnsupportedAlgorithm("Unsupported key type.") - def _dsa_cdata_to_private_key(self, cdata): - return dsa.DSAPrivateKey( - modulus=self._bn_to_int(cdata.p), - subgroup_order=self._bn_to_int(cdata.q), - generator=self._bn_to_int(cdata.g), - x=self._bn_to_int(cdata.priv_key), - y=self._bn_to_int(cdata.pub_key) - ) - def _pem_password_cb(self, password): """ Generate a pem_password_cb function pointer that copied the password to @@ -615,37 +607,79 @@ class Backend(object): 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) - ) + return _DSAParameters(self, ctx) 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) + if isinstance(parameters, dsa.DSAParameters): + ctx.p = self._int_to_bn(parameters.p) + ctx.q = self._int_to_bn(parameters.q) + ctx.g = self._int_to_bn(parameters.g) + else: + ctx.p = self._lib.BN_dup(parameters._dsa_cdata.p) + ctx.q = self._lib.BN_dup(parameters._dsa_cdata.q) + ctx.g = self._lib.BN_dup(parameters._dsa_cdata.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) - ) + return _DSAPrivateKey(self, ctx) def create_dsa_signature_ctx(self, private_key, algorithm): - return _DSASignatureContext(self, private_key, algorithm) + dsa_cdata = self._dsa_cdata_from_private_key(private_key) + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + key = _DSAPrivateKey(self, dsa_cdata) + return _DSASignatureContext(self, key, algorithm) def create_dsa_verification_ctx(self, public_key, signature, algorithm): - return _DSAVerificationContext(self, public_key, signature, - algorithm) + dsa_cdata = self._dsa_cdata_from_public_key(public_key) + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + key = _DSAPublicKey(self, dsa_cdata) + return _DSAVerificationContext(self, key, signature, algorithm) + + def load_dsa_private_numbers(self, numbers): + parameter_numbers = numbers.public_numbers.parameter_numbers + dsa._check_dsa_parameters(parameter_numbers) + dsa_cdata = self._lib.DSA_new() + assert dsa_cdata != self._ffi.NULL + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + + dsa_cdata.p = self._int_to_bn(parameter_numbers.p) + dsa_cdata.q = self._int_to_bn(parameter_numbers.q) + dsa_cdata.g = self._int_to_bn(parameter_numbers.g) + dsa_cdata.pub_key = self._int_to_bn(numbers.public_numbers.y) + dsa_cdata.priv_key = self._int_to_bn(numbers.x) + + return _DSAPrivateKey(self, dsa_cdata) + + def load_dsa_public_numbers(self, numbers): + dsa._check_dsa_parameters(numbers.parameter_numbers) + # TODO check more + dsa_cdata = self._lib.DSA_new() + assert dsa_cdata != self._ffi.NULL + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + + dsa_cdata.p = self._int_to_bn(numbers.parameter_numbers.p) + dsa_cdata.q = self._int_to_bn(numbers.parameter_numbers.q) + dsa_cdata.g = self._int_to_bn(numbers.parameter_numbers.g) + dsa_cdata.pub_key = self._int_to_bn(numbers.y) + + return _DSAPublicKey(self, dsa_cdata) + + def load_dsa_parameter_numbers(self, numbers): + dsa._check_dsa_parameters(numbers) + # TODO check more + dsa_cdata = self._lib.DSA_new() + assert dsa_cdata != self._ffi.NULL + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + + dsa_cdata.p = self._int_to_bn(numbers.p) + dsa_cdata.q = self._int_to_bn(numbers.q) + dsa_cdata.g = self._int_to_bn(numbers.g) + + return _DSAParameters(self, dsa_cdata) def _dsa_cdata_from_public_key(self, public_key): # Does not GC the DSA cdata. You *must* make sure it's freed diff --git a/cryptography/hazmat/backends/openssl/dsa.py b/cryptography/hazmat/backends/openssl/dsa.py index ec05c3aa..d492372f 100644 --- a/cryptography/hazmat/backends/openssl/dsa.py +++ b/cryptography/hazmat/backends/openssl/dsa.py @@ -16,6 +16,10 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes, interfaces +from cryptography.hazmat.primitives.asymmetric import dsa +from cryptography.hazmat.primitives.interfaces import ( + DSAParametersWithNumbers, DSAPrivateKeyWithNumbers, DSAPublicKeyWithNumbers +) @utils.register_interface(interfaces.AsymmetricVerificationContext) @@ -32,9 +36,7 @@ class _DSAVerificationContext(object): self._hash_ctx.update(data) def verify(self): - self._dsa_cdata = self._backend._dsa_cdata_from_public_key( - self._public_key) - self._dsa_cdata = self._backend._ffi.gc(self._dsa_cdata, + self._dsa_cdata = self._backend._ffi.gc(self._public_key._dsa_cdata, self._backend._lib.DSA_free) data_to_verify = self._hash_ctx.finalize() @@ -43,7 +45,7 @@ class _DSAVerificationContext(object): # 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) + len(self._signature), self._public_key._dsa_cdata) if res != 1: errors = self._backend._consume_errors() @@ -61,17 +63,13 @@ class _DSASignatureContext(object): self._private_key = private_key self._algorithm = algorithm self._hash_ctx = hashes.Hash(self._algorithm, self._backend) - 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): self._hash_ctx.update(data) def finalize(self): data_to_sign = self._hash_ctx.finalize() - sig_buf_len = self._backend._lib.DSA_size(self._dsa_cdata) + sig_buf_len = self._backend._lib.DSA_size(self._private_key._dsa_cdata) sig_buf = self._backend._ffi.new("unsigned char[]", sig_buf_len) buflen = self._backend._ffi.new("unsigned int *") @@ -79,8 +77,111 @@ class _DSASignatureContext(object): # must be an integer. res = self._backend._lib.DSA_sign( 0, data_to_sign, len(data_to_sign), sig_buf, - buflen, self._dsa_cdata) + buflen, self._private_key._dsa_cdata) assert res == 1 assert buflen[0] return self._backend._ffi.buffer(sig_buf)[:buflen[0]] + + +@utils.register_interface(DSAParametersWithNumbers) +class _DSAParameters(object): + def __init__(self, backend, dsa_cdata): + self._backend = backend + self._dsa_cdata = dsa_cdata + + def parameter_numbers(self): + return dsa.DSAParameterNumbers( + p=self._backend._bn_to_int(self._dsa_cdata.p), + q=self._backend._bn_to_int(self._dsa_cdata.q), + g=self._backend._bn_to_int(self._dsa_cdata.g) + ) + + +@utils.register_interface(DSAPrivateKeyWithNumbers) +class _DSAPrivateKey(object): + def __init__(self, backend, dsa_cdata): + self._backend = backend + self._dsa_cdata = dsa_cdata + self._key_size = self._backend._lib.BN_num_bits(self._dsa_cdata.p) + + @property + def key_size(self): + return self._key_size + + def signer(self, algorithm): + return _DSASignatureContext(self._backend, self, algorithm) + + def private_numbers(self): + return dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=self._backend._bn_to_int(self._dsa_cdata.p), + q=self._backend._bn_to_int(self._dsa_cdata.q), + g=self._backend._bn_to_int(self._dsa_cdata.g) + ), + y=self._backend._bn_to_int(self._dsa_cdata.pub_key) + ), + x=self._backend._bn_to_int(self._dsa_cdata.priv_key) + ) + + def public_key(self): + dsa_cdata = self._backend._lib.DSA_new() + assert dsa_cdata != self._backend._ffi.NULL + dsa_cdata = self._backend._ffi.gc( + dsa_cdata, self._backend._lib.DSA_free + ) + dsa_cdata.p = self._backend._lib.BN_dup(self._dsa_cdata.p) + dsa_cdata.q = self._backend._lib.BN_dup(self._dsa_cdata.q) + dsa_cdata.g = self._backend._lib.BN_dup(self._dsa_cdata.g) + dsa_cdata.pub_key = self._backend._lib.BN_dup(self._dsa_cdata.pub_key) + return _DSAPublicKey(self._backend, dsa_cdata) + + def parameters(self): + dsa_cdata = self._backend._lib.DSA_new() + assert dsa_cdata != self._backend._ffi.NULL + dsa_cdata = self._backend._ffi.gc( + dsa_cdata, self._backend._lib.DSA_free + ) + dsa_cdata.p = self._backend._lib.BN_dup(self._dsa_cdata.p) + dsa_cdata.q = self._backend._lib.BN_dup(self._dsa_cdata.q) + dsa_cdata.g = self._backend._lib.BN_dup(self._dsa_cdata.g) + return _DSAParameters(self._backend, dsa_cdata) + + +@utils.register_interface(DSAPublicKeyWithNumbers) +class _DSAPublicKey(object): + def __init__(self, backend, dsa_cdata): + self._backend = backend + self._dsa_cdata = dsa_cdata + self._key_size = self._backend._lib.BN_num_bits(self._dsa_cdata.p) + + @property + def key_size(self): + return self._key_size + + def verifier(self, signature, algorithm): + return _DSAVerificationContext( + self._backend, self, signature, algorithm + ) + + def public_numbers(self): + return dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=self._backend._bn_to_int(self._dsa_cdata.p), + q=self._backend._bn_to_int(self._dsa_cdata.q), + g=self._backend._bn_to_int(self._dsa_cdata.g) + ), + y=self._backend._bn_to_int(self._dsa_cdata.pub_key) + ) + + def parameters(self): + dsa_cdata = self._backend._lib.DSA_new() + assert dsa_cdata != self._backend._ffi.NULL + dsa_cdata = self._backend._ffi.gc( + dsa_cdata, self._backend._lib.DSA_free + ) + dsa_cdata.p = self._backend._lib.BN_dup(self._dsa_cdata.p) + dsa_cdata.q = self._backend._lib.BN_dup(self._dsa_cdata.q) + dsa_cdata.g = self._backend._lib.BN_dup(self._dsa_cdata.g) + return _DSAParameters(self._backend, dsa_cdata) diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py index 4d78679e..4675bd0a 100644 --- a/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -21,31 +21,38 @@ from cryptography.hazmat.backends.interfaces import DSABackend from cryptography.hazmat.primitives import interfaces -def _check_dsa_parameters(modulus, subgroup_order, generator): - if ( - not isinstance(modulus, six.integer_types) or - not isinstance(subgroup_order, six.integer_types) or - not isinstance(generator, six.integer_types) - ): - raise TypeError("DSA parameters must be integers.") - - if (utils.bit_length(modulus), - utils.bit_length(subgroup_order)) not in ( +def generate_parameters(key_size, backend): + return backend.generate_dsa_parameters(key_size) + + +def generate_private_key(parameters): + return parameters._backend.generate_dsa_private_key(parameters) + + +def _check_dsa_parameters(parameters): + if (utils.bit_length(parameters.p), + utils.bit_length(parameters.q)) not in ( (1024, 160), (2048, 256), (3072, 256)): - raise ValueError("modulus and subgroup_order lengths must be " + raise ValueError("p and q lengths must be " "one of these pairs (1024, 160) or (2048, 256) " "or (3072, 256).") - if generator <= 1 or generator >= modulus: - raise ValueError("generator must be > 1 and < modulus.") + if parameters.g <= 1 or parameters.g >= parameters.p: + raise ValueError("g must be > 1 and < p.") @utils.register_interface(interfaces.DSAParameters) class DSAParameters(object): def __init__(self, modulus, subgroup_order, generator): - _check_dsa_parameters(modulus, subgroup_order, generator) + _check_dsa_parameters( + DSAParameterNumbers( + p=modulus, + q=subgroup_order, + g=generator + ) + ) self._modulus = modulus self._subgroup_order = subgroup_order @@ -59,7 +66,13 @@ class DSAParameters(object): _Reasons.BACKEND_MISSING_INTERFACE ) - return backend.generate_dsa_parameters(key_size) + parameters = backend.generate_dsa_parameters(key_size) + numbers = parameters.parameter_numbers() + return cls( + modulus=numbers.p, + subgroup_order=numbers.q, + generator=numbers.g + ) @property def modulus(self): @@ -89,7 +102,13 @@ class DSAParameters(object): @utils.register_interface(interfaces.DSAPrivateKey) class DSAPrivateKey(object): def __init__(self, modulus, subgroup_order, generator, x, y): - _check_dsa_parameters(modulus, subgroup_order, generator) + _check_dsa_parameters( + DSAParameterNumbers( + p=modulus, + q=subgroup_order, + g=generator + ) + ) if ( not isinstance(x, six.integer_types) or not isinstance(y, six.integer_types) @@ -116,7 +135,15 @@ class DSAPrivateKey(object): _Reasons.BACKEND_MISSING_INTERFACE ) - return backend.generate_dsa_private_key(parameters) + key = backend.generate_dsa_private_key(parameters) + private_numbers = key.private_numbers() + return cls( + modulus=private_numbers.public_numbers.parameter_numbers.p, + subgroup_order=private_numbers.public_numbers.parameter_numbers.q, + generator=private_numbers.public_numbers.parameter_numbers.g, + x=private_numbers.x, + y=private_numbers.public_numbers.y + ) def signer(self, algorithm, backend): if not isinstance(backend, DSABackend): @@ -151,7 +178,13 @@ class DSAPrivateKey(object): @utils.register_interface(interfaces.DSAPublicKey) class DSAPublicKey(object): def __init__(self, modulus, subgroup_order, generator, y): - _check_dsa_parameters(modulus, subgroup_order, generator) + _check_dsa_parameters( + DSAParameterNumbers( + p=modulus, + q=subgroup_order, + g=generator + ) + ) if not isinstance(y, six.integer_types): raise TypeError("y must be an integer.") @@ -210,6 +243,9 @@ class DSAParameterNumbers(object): def g(self): return self._g + def parameters(self, backend): + return backend.load_dsa_parameter_numbers(self) + class DSAPublicNumbers(object): def __init__(self, y, parameter_numbers): @@ -232,6 +268,9 @@ class DSAPublicNumbers(object): def parameter_numbers(self): return self._parameter_numbers + def public_key(self, backend): + return backend.load_dsa_public_numbers(self) + class DSAPrivateNumbers(object): def __init__(self, x, public_numbers): @@ -252,3 +291,6 @@ class DSAPrivateNumbers(object): @property def public_numbers(self): return self._public_numbers + + def private_key(self, backend): + return backend.load_dsa_private_numbers(self) diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index 1c266baa..76436f79 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -20,7 +20,7 @@ import pytest from cryptography.exceptions import ( AlreadyFinalized, InvalidSignature, _Reasons) -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, interfaces from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.utils import bit_length @@ -70,10 +70,15 @@ def _check_dsa_private_key(skey): @pytest.mark.dsa class TestDSA(object): - def test_generate_dsa_parameters(self, backend): + def test_generate_dsa_parameters_class_method(self, backend): parameters = dsa.DSAParameters.generate(1024, backend) assert bit_length(parameters.p) == 1024 + def test_generate_dsa_parameters(self, backend): + parameters = dsa.generate_parameters(1024, backend) + assert isinstance(parameters, interfaces.DSAParameters) + # TODO: withnumbers check like RSA + def test_generate_invalid_dsa_parameters(self, backend): with pytest.raises(ValueError): dsa.DSAParameters.generate(1, backend) @@ -87,17 +92,31 @@ class TestDSA(object): ) ) 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) + parameters = dsa.DSAParameterNumbers( + p=vector['p'], + q=vector['q'], + g=vector['g'] + ).parameters(backend) + skey = dsa.generate_private_key(parameters) + if isinstance(skey, interfaces.DSAPrivateKeyWithNumbers): + numbers = skey.private_numbers() + skey_parameters = numbers.public_numbers.parameter_numbers + pkey = skey.public_key() + parameters = pkey.parameters() + parameter_numbers = parameters.parameter_numbers() + assert parameter_numbers.p == skey_parameters.p + assert parameter_numbers.q == skey_parameters.q + assert parameter_numbers.g == skey_parameters.g + 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 pkey.key_size == skey.key_size + public_numbers = pkey.public_numbers() + assert numbers.public_numbers.y == public_numbers.y + assert numbers.public_numbers.y == pow( + skey_parameters.g, numbers.x, skey_parameters.p + ) def test_invalid_parameters_argument_types(self): with pytest.raises(TypeError): @@ -654,11 +673,14 @@ class TestDSAVerification(object): "{0} does not support the provided parameters".format(backend) ) - public_key = dsa.DSAPublicKey( - vector['p'], vector['q'], vector['g'], vector['y'] - ) + public_key = dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + vector['p'], vector['q'], vector['g'] + ), + y=vector['y'] + ).public_key(backend) sig = der_encode_dsa_signature(vector['r'], vector['s']) - verifier = public_key.verifier(sig, algorithm(), backend) + verifier = public_key.verifier(sig, algorithm()) verifier.update(vector['msg']) if vector['result'] == "F": with pytest.raises(InvalidSignature): @@ -728,16 +750,22 @@ class TestDSASignature(object): "{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) + private_key = dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + vector['p'], vector['q'], vector['g'] + ), + y=vector['y'] + ), + x=vector['x'] + ).private_key(backend) + signer = private_key.signer(algorithm()) signer.update(vector['msg']) signature = signer.finalize() assert signature public_key = private_key.public_key() - verifier = public_key.verifier(signature, algorithm(), backend) + verifier = public_key.verifier(signature, algorithm()) verifier.update(vector['msg']) verifier.verify() diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index 8a90b30e..30ac4f3d 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -21,7 +21,6 @@ import pytest from cryptography.exceptions import _Reasons from cryptography.hazmat.primitives import interfaces -from cryptography.hazmat.primitives.asymmetric import dsa from cryptography.hazmat.primitives.serialization import ( load_pem_pkcs8_private_key, load_pem_traditional_openssl_private_key ) @@ -73,7 +72,7 @@ class TestTraditionalOpenSSLSerialisation(object): ) assert key - assert isinstance(key, dsa.DSAPrivateKey) + assert isinstance(key, interfaces.DSAPrivateKey) def test_key1_pem_encrypted_values(self, backend): pkey = load_vectors_from_file( @@ -480,41 +479,46 @@ class TestPKCS8Serialisation(object): ) ) assert key - assert isinstance(key, dsa.DSAPrivateKey) + assert isinstance(key, interfaces.DSAPrivateKey) params = key.parameters() - assert isinstance(params, dsa.DSAParameters) - - assert key.x == int("00a535a8e1d0d91beafc8bee1d9b2a3a8de3311203", 16) - assert key.y == int( - "2b260ea97dc6a12ae932c640e7df3d8ff04a8a05a0324f8d5f1b23f15fa1" - "70ff3f42061124eff2586cb11b49a82dcdc1b90fc6a84fb10109cb67db5d" - "2da971aeaf17be5e37284563e4c64d9e5fc8480258b319f0de29d54d8350" - "70d9e287914d77df81491f4423b62da984eb3f45eb2a29fcea5dae525ac6" - "ab6bcce04bfdf5b6", - 16 - ) + assert isinstance(params, interfaces.DSAParameters) + + if isinstance(params, interfaces.DSAParametersWithNumbers): + num = key.private_numbers() + pub = num.public_numbers + parameter_numbers = pub.parameter_numbers + assert num.x == int("00a535a8e1d0d91beafc8bee1d9b2a3a8de3311203", + 16) + assert pub.y == int( + "2b260ea97dc6a12ae932c640e7df3d8ff04a8a05a0324f8d5f1b23f15fa1" + "70ff3f42061124eff2586cb11b49a82dcdc1b90fc6a84fb10109cb67db5d" + "2da971aeaf17be5e37284563e4c64d9e5fc8480258b319f0de29d54d8350" + "70d9e287914d77df81491f4423b62da984eb3f45eb2a29fcea5dae525ac6" + "ab6bcce04bfdf5b6", + 16 + ) - assert params.p == int( - "00aa0930cc145825221caffa28ac2894196a27833de5ec21270791689420" - "7774a2e7b238b0d36f1b2499a2c2585083eb01432924418d867faa212dd1" - "071d4dceb2782794ad393cc08a4d4ada7f68d6e839a5fcd34b4e402d82cb" - "8a8cb40fec31911bf9bd360b034caacb4c5e947992573c9e90099c1b0f05" - "940cabe5d2de49a167", - 16 - ) + assert parameter_numbers.p == int( + "00aa0930cc145825221caffa28ac2894196a27833de5ec21270791689420" + "7774a2e7b238b0d36f1b2499a2c2585083eb01432924418d867faa212dd1" + "071d4dceb2782794ad393cc08a4d4ada7f68d6e839a5fcd34b4e402d82cb" + "8a8cb40fec31911bf9bd360b034caacb4c5e947992573c9e90099c1b0f05" + "940cabe5d2de49a167", + 16 + ) - assert params.q == int("00adc0e869b36f0ac013a681fdf4d4899d69820451", - 16) + assert parameter_numbers.q == int( + "00adc0e869b36f0ac013a681fdf4d4899d69820451", 16) - assert params.g == int( - "008c6b4589afa53a4d1048bfc346d1f386ca75521ccf72ddaa251286880e" - "e13201ff48890bbfc33d79bacaec71e7a778507bd5f1a66422e39415be03" - "e71141ba324f5b93131929182c88a9fa4062836066cebe74b5c6690c7d10" - "1106c240ab7ebd54e4e3301fd086ce6adac922fb2713a2b0887cba13b9bc" - "68ce5cfff241cd3246", - 16 - ) + assert parameter_numbers.g == int( + "008c6b4589afa53a4d1048bfc346d1f386ca75521ccf72ddaa251286880e" + "e13201ff48890bbfc33d79bacaec71e7a778507bd5f1a66422e39415be03" + "e71141ba324f5b93131929182c88a9fa4062836066cebe74b5c6690c7d10" + "1106c240ab7ebd54e4e3301fd086ce6adac922fb2713a2b0887cba13b9bc" + "68ce5cfff241cd3246", + 16 + ) @pytest.mark.parametrize( ("key_file", "password"), -- cgit v1.2.3 From 125857f8ce6a7fc09d0e208d9bcd8321cc5ac2d4 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 25 Jun 2014 08:44:30 -0600 Subject: dsa docs --- docs/hazmat/primitives/asymmetric/dsa.rst | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index 2167e528..42e3af2e 100644 --- a/docs/hazmat/primitives/asymmetric/dsa.rst +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -7,6 +7,43 @@ DSA `DSA`_ is a `public-key`_ algorithm for signing messages. +.. function:: generate_parameters(key_size, backend) + + .. versionadded:: 0.5 + + Generate DSA parameters using the provided ``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. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + provider. + + :return: A :class:`~cryptography.hazmat.primitives.interfaces.DSAParameters` + provider. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if + the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + +.. function:: generate_private_key(parameters) + + .. versionadded:: 0.5 + + Generate an DSA private key using the provided parameters. + + :param parameters: A + :class:`~cryptography.hazmat.primitives.interfaces.DSAParameters` + provider. + + :return: A :class:`~cryptography.hazmat.primitives.interfaces.DSAPrivateKey` + provider. + .. class:: DSAParameters(modulus, subgroup_order, generator) .. versionadded:: 0.4 -- cgit v1.2.3 From a44338b355a628ba7d732063551650cd9f8b2cb8 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 26 Jun 2014 08:13:22 -0600 Subject: move where we gc dsa data, improve dsa key checking --- cryptography/hazmat/backends/openssl/backend.py | 13 +++----- cryptography/hazmat/primitives/asymmetric/dsa.py | 39 +++++++++++++++--------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index e895da83..6245e8e4 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -628,20 +628,19 @@ class Backend(object): def create_dsa_signature_ctx(self, private_key, algorithm): dsa_cdata = self._dsa_cdata_from_private_key(private_key) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) key = _DSAPrivateKey(self, dsa_cdata) return _DSASignatureContext(self, key, algorithm) def create_dsa_verification_ctx(self, public_key, signature, algorithm): dsa_cdata = self._dsa_cdata_from_public_key(public_key) - dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) key = _DSAPublicKey(self, dsa_cdata) return _DSAVerificationContext(self, key, signature, algorithm) def load_dsa_private_numbers(self, numbers): + dsa._check_dsa_private_numbers(numbers) parameter_numbers = numbers.public_numbers.parameter_numbers - dsa._check_dsa_parameters(parameter_numbers) + dsa_cdata = self._lib.DSA_new() assert dsa_cdata != self._ffi.NULL dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) @@ -656,7 +655,6 @@ class Backend(object): def load_dsa_public_numbers(self, numbers): dsa._check_dsa_parameters(numbers.parameter_numbers) - # TODO check more dsa_cdata = self._lib.DSA_new() assert dsa_cdata != self._ffi.NULL dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) @@ -670,7 +668,6 @@ class Backend(object): def load_dsa_parameter_numbers(self, numbers): dsa._check_dsa_parameters(numbers) - # TODO check more dsa_cdata = self._lib.DSA_new() assert dsa_cdata != self._ffi.NULL dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) @@ -682,10 +679,9 @@ class Backend(object): return _DSAParameters(self, dsa_cdata) 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 + ctx = self._ffi.gc(ctx, self._lib.DSA_free) parameters = public_key.parameters() ctx.p = self._int_to_bn(parameters.p) ctx.q = self._int_to_bn(parameters.q) @@ -694,10 +690,9 @@ class Backend(object): 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 + ctx = self._ffi.gc(ctx, self._lib.DSA_free) parameters = private_key.parameters() ctx.p = self._int_to_bn(parameters.p) ctx.q = self._int_to_bn(parameters.q) diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py index 4675bd0a..527b6bbc 100644 --- a/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -39,8 +39,18 @@ def _check_dsa_parameters(parameters): "one of these pairs (1024, 160) or (2048, 256) " "or (3072, 256).") - if parameters.g <= 1 or parameters.g >= parameters.p: - raise ValueError("g must be > 1 and < p.") + if not (1 < parameters.g < parameters.p): + raise ValueError("g, p don't satisfy 1 < g < p.") + + +def _check_dsa_private_numbers(numbers): + parameters = numbers.public_numbers.parameter_numbers + _check_dsa_parameters(parameters) + if numbers.x <= 0 or numbers.x >= parameters.q: + raise ValueError("x must be > 0 and < q.") + + if numbers.public_numbers.y != pow(parameters.g, numbers.x, parameters.p): + raise ValueError("y must be equal to (g ** x % p).") @utils.register_interface(interfaces.DSAParameters) @@ -102,24 +112,25 @@ class DSAParameters(object): @utils.register_interface(interfaces.DSAPrivateKey) class DSAPrivateKey(object): def __init__(self, modulus, subgroup_order, generator, x, y): - _check_dsa_parameters( - DSAParameterNumbers( - p=modulus, - q=subgroup_order, - g=generator - ) - ) if ( not isinstance(x, six.integer_types) or not isinstance(y, six.integer_types) ): raise TypeError("DSAPrivateKey arguments must be integers.") - if x <= 0 or x >= subgroup_order: - raise ValueError("x must be > 0 and < subgroup_order.") - - if y != pow(generator, x, modulus): - raise ValueError("y must be equal to (generator ** x % modulus).") + _check_dsa_private_numbers( + DSAPrivateNumbers( + public_numbers=DSAPublicNumbers( + parameter_numbers=DSAParameterNumbers( + p=modulus, + q=subgroup_order, + g=generator + ), + y=y + ), + x=x + ) + ) self._modulus = modulus self._subgroup_order = subgroup_order -- cgit v1.2.3 From dacb5f9951064d19ac69c1198985af136f71a6db Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 27 Jun 2014 09:15:07 -0600 Subject: add generate_private_key to DSAParameters + add a new function to dsa dsa.generate_private_key(key_size, backend) will allow you to generate a new DSA key and implicitly generate new parameters. This streamlines the common case and will be an avenue to support future backends that don't allow independent generation of DSAParameters (e.g. CommonCrypto) --- cryptography/hazmat/backends/interfaces.py | 6 +++++ cryptography/hazmat/backends/openssl/backend.py | 4 +++ cryptography/hazmat/backends/openssl/dsa.py | 3 +++ cryptography/hazmat/primitives/asymmetric/dsa.py | 4 +-- cryptography/hazmat/primitives/interfaces.py | 6 ++++- docs/hazmat/primitives/asymmetric/dsa.rst | 32 +++++++++++++++--------- docs/hazmat/primitives/interfaces.rst | 11 ++++++++ tests/hazmat/primitives/test_dsa.py | 13 ++++++++-- 8 files changed, 62 insertions(+), 17 deletions(-) diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index e4faf32c..70168a01 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -170,6 +170,12 @@ class DSABackend(object): a DSAParameters object. """ + @abc.abstractmethod + def generate_dsa_private_key_with_key_size(self, key_size): + """ + Generate an DSAPrivateKey instance using key size only. + """ + @abc.abstractmethod def create_dsa_signature_ctx(self, private_key, algorithm): """ diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 6245e8e4..82bdd7cd 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -626,6 +626,10 @@ class Backend(object): return _DSAPrivateKey(self, ctx) + def generate_dsa_private_key_with_key_size(self, key_size): + parameters = self.generate_dsa_parameters(key_size) + return self.generate_dsa_private_key(parameters) + def create_dsa_signature_ctx(self, private_key, algorithm): dsa_cdata = self._dsa_cdata_from_private_key(private_key) key = _DSAPrivateKey(self, dsa_cdata) diff --git a/cryptography/hazmat/backends/openssl/dsa.py b/cryptography/hazmat/backends/openssl/dsa.py index d492372f..5e7a26ff 100644 --- a/cryptography/hazmat/backends/openssl/dsa.py +++ b/cryptography/hazmat/backends/openssl/dsa.py @@ -97,6 +97,9 @@ class _DSAParameters(object): g=self._backend._bn_to_int(self._dsa_cdata.g) ) + def generate_private_key(self): + return self._backend.generate_dsa_private_key(self) + @utils.register_interface(DSAPrivateKeyWithNumbers) class _DSAPrivateKey(object): diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py index 527b6bbc..08bdad3e 100644 --- a/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -25,8 +25,8 @@ def generate_parameters(key_size, backend): return backend.generate_dsa_parameters(key_size) -def generate_private_key(parameters): - return parameters._backend.generate_dsa_private_key(parameters) +def generate_private_key(key_size, backend): + return backend.generate_dsa_private_key_with_key_size(key_size) def _check_dsa_parameters(parameters): diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index ef8640c2..d60f9e0e 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -251,7 +251,11 @@ class RSAPublicKeyWithNumbers(RSAPublicKey): @six.add_metaclass(abc.ABCMeta) class DSAParameters(object): - pass + @abc.abstractmethod + def generate_private_key(self): + """ + Generates and returns a DSAPrivateKey. + """ @six.add_metaclass(abc.ABCMeta) diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst index 42e3af2e..095c49b9 100644 --- a/docs/hazmat/primitives/asymmetric/dsa.rst +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -7,11 +7,12 @@ DSA `DSA`_ is a `public-key`_ algorithm for signing messages. -.. function:: generate_parameters(key_size, backend) +.. function:: generate_private_key(key_size, backend) .. versionadded:: 0.5 - Generate DSA parameters using the provided ``backend``. + Generate a DSA private key from the given key size. This function will + generate a new set of parameters and key in one step. :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 @@ -24,26 +25,33 @@ DSA :class:`~cryptography.hazmat.backends.interfaces.DSABackend` provider. - :return: A :class:`~cryptography.hazmat.primitives.interfaces.DSAParameters` + :return: A :class:`~cryptography.hazmat.primitives.interfaces.DSAPrivateKey` provider. - :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if - the provided ``backend`` does not implement - :class:`~cryptography.hazmat.backends.interfaces.DSABackend` - -.. function:: generate_private_key(parameters) +.. function:: generate_parameters(key_size, backend) .. versionadded:: 0.5 - Generate an DSA private key using the provided parameters. + Generate DSA parameters using the provided ``backend``. - :param parameters: A - :class:`~cryptography.hazmat.primitives.interfaces.DSAParameters` + :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. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` provider. - :return: A :class:`~cryptography.hazmat.primitives.interfaces.DSAPrivateKey` + :return: A :class:`~cryptography.hazmat.primitives.interfaces.DSAParameters` provider. + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if + the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + .. class:: DSAParameters(modulus, subgroup_order, generator) .. versionadded:: 0.4 diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 755cef41..ac47c1e1 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -282,6 +282,17 @@ DSA `DSA`_ parameters. + .. method:: generate_private_key() + + .. versionadded:: 0.5 + + Generate a DSA private key. This method can be used to generate many + new private keys from a single set of parameters. + + :return: A + :class:`~cryptography.hazmat.primitives.interfaces.DSAPrivateKey` + provider. + .. class:: DSAParametersWithNumbers diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index 76436f79..531b448f 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -77,7 +77,6 @@ class TestDSA(object): def test_generate_dsa_parameters(self, backend): parameters = dsa.generate_parameters(1024, backend) assert isinstance(parameters, interfaces.DSAParameters) - # TODO: withnumbers check like RSA def test_generate_invalid_dsa_parameters(self, backend): with pytest.raises(ValueError): @@ -97,7 +96,7 @@ class TestDSA(object): q=vector['q'], g=vector['g'] ).parameters(backend) - skey = dsa.generate_private_key(parameters) + skey = parameters.generate_private_key() if isinstance(skey, interfaces.DSAPrivateKeyWithNumbers): numbers = skey.private_numbers() skey_parameters = numbers.public_numbers.parameter_numbers @@ -118,6 +117,16 @@ class TestDSA(object): skey_parameters.g, numbers.x, skey_parameters.p ) + def test_generate_dsa_private_key_and_parameters(self, backend): + skey = dsa.generate_private_key(1024, backend) + assert skey + if isinstance(skey, interfaces.DSAPrivateKeyWithNumbers): + numbers = skey.private_numbers() + skey_parameters = numbers.public_numbers.parameter_numbers + assert numbers.public_numbers.y == pow( + skey_parameters.g, numbers.x, skey_parameters.p + ) + def test_invalid_parameters_argument_types(self): with pytest.raises(TypeError): dsa.DSAParameters(None, None, None) -- cgit v1.2.3 From 298effd54c11ed47077f580f74d9204f7acce3f5 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 27 Jun 2014 14:07:59 -0600 Subject: rename backend method, add some docs --- cryptography/hazmat/backends/interfaces.py | 8 ++++---- cryptography/hazmat/backends/openssl/backend.py | 2 +- cryptography/hazmat/primitives/asymmetric/dsa.py | 2 +- docs/hazmat/backends/interfaces.rst | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py index 70168a01..5ed49966 100644 --- a/cryptography/hazmat/backends/interfaces.py +++ b/cryptography/hazmat/backends/interfaces.py @@ -166,14 +166,14 @@ class DSABackend(object): @abc.abstractmethod def generate_dsa_private_key(self, parameters): """ - Generate an DSAPrivateKey instance with parameters as - a DSAParameters object. + Generate a DSAPrivateKey instance with parameters as a DSAParameters + object. """ @abc.abstractmethod - def generate_dsa_private_key_with_key_size(self, key_size): + def generate_dsa_private_key_and_parameters(self, key_size): """ - Generate an DSAPrivateKey instance using key size only. + Generate a DSAPrivateKey instance using key size only. """ @abc.abstractmethod diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 82bdd7cd..8d167ab3 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -626,7 +626,7 @@ class Backend(object): return _DSAPrivateKey(self, ctx) - def generate_dsa_private_key_with_key_size(self, key_size): + def generate_dsa_private_key_and_parameters(self, key_size): parameters = self.generate_dsa_parameters(key_size) return self.generate_dsa_private_key(parameters) diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py index 08bdad3e..7a8a61c1 100644 --- a/cryptography/hazmat/primitives/asymmetric/dsa.py +++ b/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -26,7 +26,7 @@ def generate_parameters(key_size, backend): def generate_private_key(key_size, backend): - return backend.generate_dsa_private_key_with_key_size(key_size) + return backend.generate_dsa_private_key_and_parameters(key_size) def _check_dsa_parameters(parameters): diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst index 5cbd47d1..fea935ce 100644 --- a/docs/hazmat/backends/interfaces.rst +++ b/docs/hazmat/backends/interfaces.rst @@ -421,6 +421,22 @@ 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:: generate_dsa_private_key_and_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.DSAPrivateKey` + provider. + + :raises ValueError: This is raised if the key size is not supported + by the backend. + .. method:: create_dsa_signature_ctx(private_key, algorithm) :param private_key: An instance of a -- cgit v1.2.3