diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cryptography/exceptions.py | 1 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/interfaces.py | 18 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/multibackend.py | 72 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 118 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/dh.py | 182 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/asymmetric/dh.py | 23 |
6 files changed, 399 insertions, 15 deletions
diff --git a/src/cryptography/exceptions.py b/src/cryptography/exceptions.py index ee43fed7..648cf9df 100644 --- a/src/cryptography/exceptions.py +++ b/src/cryptography/exceptions.py @@ -18,6 +18,7 @@ class _Reasons(Enum): UNSUPPORTED_SERIALIZATION = 7 UNSUPPORTED_X509 = 8 UNSUPPORTED_EXCHANGE_ALGORITHM = 9 + UNSUPPORTED_DIFFIE_HELLMAN = 10 class UnsupportedAlgorithm(Exception): diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index e15a7ca4..c5f2951c 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -322,9 +322,10 @@ class X509Backend(object): @six.add_metaclass(abc.ABCMeta) class DHBackend(object): @abc.abstractmethod - def generate_dh_parameters(self, key_size): + def generate_dh_parameters(self, generator, key_size): """ Generate a DHParameters instance with a modulus of key_size bits. + Using the given generator. Often 2 or 5. """ @abc.abstractmethod @@ -335,33 +336,28 @@ class DHBackend(object): """ @abc.abstractmethod - def generate_dh_private_key_and_parameters(self, key_size): + def generate_dh_private_key_and_parameters(self, generator, key_size): """ Generate a DHPrivateKey instance using key size only. + Using the given generator. Often 2 or 5. """ @abc.abstractmethod def load_dh_private_numbers(self, numbers): """ - Returns a DHPrivateKey provider. + Load a DHPrivateKey from DHPrivateNumbers """ @abc.abstractmethod def load_dh_public_numbers(self, numbers): """ - Returns a DHPublicKey provider. + Load a DHPublicKey from DHPublicNumbers. """ @abc.abstractmethod def load_dh_parameter_numbers(self, numbers): """ - Returns a DHParameters provider. - """ - - @abc.abstractmethod - def dh_exchange_algorithm_supported(self, exchange_algorithm): - """ - Returns whether the exchange algorithm is supported by this backend. + Load DHParameters from DHParameterNumbers. """ @abc.abstractmethod diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index bcd9c520..097b4908 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -7,9 +7,10 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.backends.interfaces import ( - CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, - EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, - PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend + CMACBackend, CipherBackend, DERSerializationBackend, DHBackend, + DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, + PBKDF2HMACBackend, PEMSerializationBackend, RSABackend, ScryptBackend, + X509Backend ) @@ -24,6 +25,7 @@ from cryptography.hazmat.backends.interfaces import ( @utils.register_interface(EllipticCurveBackend) @utils.register_interface(PEMSerializationBackend) @utils.register_interface(X509Backend) +@utils.register_interface(DHBackend) @utils.register_interface(ScryptBackend) class MultiBackend(object): name = "multibackend" @@ -424,6 +426,70 @@ class MultiBackend(object): _Reasons.UNSUPPORTED_X509 ) + def generate_dh_parameters(self, generator, key_size): + for b in self._filtered_backends(DHBackend): + return b.generate_dh_parameters(generator, key_size) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def load_dh_parameter_numbers(self, numbers): + for b in self._filtered_backends(DHBackend): + return b.load_dh_parameter_numbers(numbers) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def generate_dh_private_key(self, parameters): + for b in self._filtered_backends(DHBackend): + return b.generate_dh_private_key(parameters) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def load_dh_private_numbers(self, numbers): + for b in self._filtered_backends(DHBackend): + return b.load_dh_private_numbers(numbers) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def load_dh_public_numbers(self, numbers): + for b in self._filtered_backends(DHBackend): + return b.load_dh_public_numbers(numbers) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def generate_dh_private_key_and_parameters(self, generator, key_size): + for b in self._filtered_backends(DHBackend): + return b.generate_dh_private_key_and_parameters(generator, + key_size) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def dh_parameters_supported(self, p, g): + for b in self._filtered_backends(DHBackend): + return b.dh_parameters_supported(p, g) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + def x509_name_bytes(self, name): for b in self._filtered_backends(X509Backend): return b.x509_name_bytes(name) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 4a341fc2..9df113b6 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -16,7 +16,7 @@ import six from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm, _Reasons from cryptography.hazmat.backends.interfaces import ( - CMACBackend, CipherBackend, DERSerializationBackend, DSABackend, + CMACBackend, CipherBackend, DERSerializationBackend, DHBackend, DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend ) @@ -24,6 +24,9 @@ from cryptography.hazmat.backends.openssl.ciphers import ( _AESCTRCipherContext, _CipherContext ) from cryptography.hazmat.backends.openssl.cmac import _CMACContext +from cryptography.hazmat.backends.openssl.dh import ( + _DHParameters, _DHPrivateKey, _DHPublicKey +) from cryptography.hazmat.backends.openssl.dsa import ( _DSAParameters, _DSAPrivateKey, _DSAPublicKey ) @@ -107,6 +110,7 @@ def _pem_password_cb(buf, size, writing, userdata_handle): @utils.register_interface(CipherBackend) @utils.register_interface(CMACBackend) @utils.register_interface(DERSerializationBackend) +@utils.register_interface(DHBackend) @utils.register_interface(DSABackend) @utils.register_interface(EllipticCurveBackend) @utils.register_interface(HashBackend) @@ -329,6 +333,7 @@ class Backend(object): def _bn_to_int(self, bn): assert bn != self._ffi.NULL + if six.PY3: # Python 3 has constant time from_bytes, so use that. bn_num_bytes = self._lib.BN_num_bytes(bn) @@ -1734,6 +1739,117 @@ class Backend(object): serialization._ssh_write_string(public_numbers.encode_point()) ) + def generate_dh_parameters(self, generator, key_size): + if key_size < 512: + raise ValueError("DH key_size must be at least 512 bits") + + if generator not in (2, 5): + raise ValueError("DH generator must be 2 or 5") + + dh_param_cdata = self._lib.DH_new() + self.openssl_assert(dh_param_cdata != self._ffi.NULL) + dh_param_cdata = self._ffi.gc(dh_param_cdata, self._lib.DH_free) + + res = self._lib.DH_generate_parameters_ex( + dh_param_cdata, + key_size, + generator, + self._ffi.NULL + ) + self.openssl_assert(res == 1) + + return _DHParameters(self, dh_param_cdata) + + def generate_dh_private_key(self, parameters): + dh_key_cdata = self._lib.DHparams_dup(parameters._dh_cdata) + self.openssl_assert(dh_key_cdata != self._ffi.NULL) + dh_key_cdata = self._ffi.gc(dh_key_cdata, self._lib.DH_free) + + res = self._lib.DH_generate_key(dh_key_cdata) + self.openssl_assert(res == 1) + + return _DHPrivateKey(self, dh_key_cdata) + + def generate_dh_private_key_and_parameters(self, generator, key_size): + return self.generate_dh_private_key( + self.generate_dh_parameters(generator, key_size)) + + def load_dh_private_numbers(self, numbers): + parameter_numbers = numbers.public_numbers.parameter_numbers + + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + p = self._int_to_bn(parameter_numbers.p) + g = self._int_to_bn(parameter_numbers.g) + pub_key = self._int_to_bn(numbers.public_numbers.y) + priv_key = self._int_to_bn(numbers.x) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + res = self._lib.DH_set0_key(dh_cdata, pub_key, priv_key) + self.openssl_assert(res == 1) + + codes = self._ffi.new("int[]", 1) + res = self._lib.DH_check(dh_cdata, codes) + self.openssl_assert(res == 1) + + if codes[0] != 0: + raise ValueError("DH private numbers did not pass safety checks.") + + return _DHPrivateKey(self, dh_cdata) + + def load_dh_public_numbers(self, numbers): + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + parameter_numbers = numbers.parameter_numbers + + p = self._int_to_bn(parameter_numbers.p) + g = self._int_to_bn(parameter_numbers.g) + pub_key = self._int_to_bn(numbers.y) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + res = self._lib.DH_set0_key(dh_cdata, pub_key, self._ffi.NULL) + self.openssl_assert(res == 1) + + return _DHPublicKey(self, dh_cdata) + + def load_dh_parameter_numbers(self, numbers): + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + p = self._int_to_bn(numbers.p) + g = self._int_to_bn(numbers.g) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + return _DHParameters(self, dh_cdata) + + def dh_parameters_supported(self, p, g): + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + p = self._int_to_bn(p) + g = self._int_to_bn(g) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + codes = self._ffi.new("int[]", 1) + res = self._lib.DH_check(dh_cdata, codes) + self.openssl_assert(res == 1) + + return codes[0] == 0 + def x509_name_bytes(self, name): x509_name = _encode_name_gc(self, name) pp = self._ffi.new("unsigned char **") diff --git a/src/cryptography/hazmat/backends/openssl/dh.py b/src/cryptography/hazmat/backends/openssl/dh.py new file mode 100644 index 00000000..666429f2 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/dh.py @@ -0,0 +1,182 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.hazmat.primitives.asymmetric import dh + + +def _dh_cdata_to_parameters(dh_cdata, backend): + lib = backend._lib + ffi = backend._ffi + + param_cdata = lib.DHparams_dup(dh_cdata) + backend.openssl_assert(param_cdata != ffi.NULL) + param_cdata = ffi.gc(param_cdata, lib.DH_free) + + return _DHParameters(backend, param_cdata) + + +@utils.register_interface(dh.DHParametersWithSerialization) +class _DHParameters(object): + def __init__(self, backend, dh_cdata): + self._backend = backend + self._dh_cdata = dh_cdata + + def parameter_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_pqg(self._dh_cdata, + p, self._backend._ffi.NULL, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + return dh.DHParameterNumbers( + p=self._backend._bn_to_int(p[0]), + g=self._backend._bn_to_int(g[0]) + ) + + def generate_private_key(self): + return self._backend.generate_dh_private_key(self) + + +def _handle_dh_compute_key_error(errors, backend): + lib = backend._lib + + backend.openssl_assert(errors[0][1:] == ( + lib.ERR_LIB_DH, + lib.DH_F_COMPUTE_KEY, + lib.DH_R_INVALID_PUBKEY + )) + + raise ValueError("Public key value is invalid for this exchange.") + + +def _get_dh_num_bits(backend, dh_cdata): + p = backend._ffi.new("BIGNUM **") + backend._lib.DH_get0_pqg(dh_cdata, p, + backend._ffi.NULL, + backend._ffi.NULL) + backend.openssl_assert(p[0] != backend._ffi.NULL) + return backend._lib.BN_num_bits(p[0]) + + +@utils.register_interface(dh.DHPrivateKeyWithSerialization) +class _DHPrivateKey(object): + def __init__(self, backend, dh_cdata): + self._backend = backend + self._dh_cdata = dh_cdata + self._key_size_bytes = self._backend._lib.DH_size(dh_cdata) + + @property + def key_size(self): + return _get_dh_num_bits(self._backend, self._dh_cdata) + + def private_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_pqg(self._dh_cdata, + p, self._backend._ffi.NULL, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + pub_key = self._backend._ffi.new("BIGNUM **") + priv_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(self._dh_cdata, pub_key, priv_key) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(priv_key[0] != self._backend._ffi.NULL) + return dh.DHPrivateNumbers( + public_numbers=dh.DHPublicNumbers( + parameter_numbers=dh.DHParameterNumbers( + p=self._backend._bn_to_int(p[0]), + g=self._backend._bn_to_int(g[0]) + ), + y=self._backend._bn_to_int(pub_key[0]) + ), + x=self._backend._bn_to_int(priv_key[0]) + ) + + def exchange(self, peer_public_key): + + buf = self._backend._ffi.new("unsigned char[]", self._key_size_bytes) + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(peer_public_key._dh_cdata, pub_key, + self._backend._ffi.NULL) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + res = self._backend._lib.DH_compute_key( + buf, + pub_key[0], + self._dh_cdata + ) + + if res == -1: + errors = self._backend._consume_errors() + return _handle_dh_compute_key_error(errors, self._backend) + else: + self._backend.openssl_assert(res >= 1) + + key = self._backend._ffi.buffer(buf)[:res] + pad = self._key_size_bytes - len(key) + + if pad > 0: + key = (b"\x00" * pad) + key + + return key + + def public_key(self): + dh_cdata = self._backend._lib.DHparams_dup(self._dh_cdata) + self._backend.openssl_assert(dh_cdata != self._backend._ffi.NULL) + dh_cdata = self._backend._ffi.gc( + dh_cdata, self._backend._lib.DH_free + ) + + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(self._dh_cdata, + pub_key, self._backend._ffi.NULL) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + pub_key_dup = self._backend._lib.BN_dup(pub_key[0]) + self._backend.openssl_assert(pub_key_dup != self._backend._ffi.NULL) + + res = self._backend._lib.DH_set0_key(dh_cdata, + pub_key_dup, + self._backend._ffi.NULL) + self._backend.openssl_assert(res == 1) + + return _DHPublicKey(self._backend, dh_cdata) + + def parameters(self): + return _dh_cdata_to_parameters(self._dh_cdata, self._backend) + + +@utils.register_interface(dh.DHPublicKeyWithSerialization) +class _DHPublicKey(object): + def __init__(self, backend, dh_cdata): + self._backend = backend + self._dh_cdata = dh_cdata + self._key_size_bits = _get_dh_num_bits(self._backend, self._dh_cdata) + + @property + def key_size(self): + return self._key_size_bits + + def public_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_pqg(self._dh_cdata, + p, self._backend._ffi.NULL, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(self._dh_cdata, + pub_key, self._backend._ffi.NULL) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + return dh.DHPublicNumbers( + parameter_numbers=dh.DHParameterNumbers( + p=self._backend._bn_to_int(p[0]), + g=self._backend._bn_to_int(g[0]) + ), + y=self._backend._bn_to_int(pub_key[0]) + ) + + def parameters(self): + return _dh_cdata_to_parameters(self._dh_cdata, self._backend) diff --git a/src/cryptography/hazmat/primitives/asymmetric/dh.py b/src/cryptography/hazmat/primitives/asymmetric/dh.py index 12d53eed..ec044ddd 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/dh.py +++ b/src/cryptography/hazmat/primitives/asymmetric/dh.py @@ -11,6 +11,10 @@ import six from cryptography import utils +def generate_parameters(generator, key_size, backend): + return backend.generate_dh_parameters(generator, key_size) + + class DHPrivateNumbers(object): def __init__(self, x, public_numbers): if not isinstance(x, six.integer_types): @@ -35,6 +39,9 @@ class DHPrivateNumbers(object): def __ne__(self, other): return not self == other + def private_key(self, backend): + return backend.load_dh_private_numbers(self) + public_numbers = utils.read_only_property("_public_numbers") x = utils.read_only_property("_x") @@ -63,6 +70,9 @@ class DHPublicNumbers(object): def __ne__(self, other): return not self == other + def public_key(self, backend): + return backend.load_dh_public_numbers(self) + y = utils.read_only_property("_y") parameter_numbers = utils.read_only_property("_parameter_numbers") @@ -75,6 +85,9 @@ class DHParameterNumbers(object): ): raise TypeError("p and g must be integers") + if g not in (2, 5): + raise ValueError("DH generator must be 2 or 5") + self._p = p self._g = g @@ -90,6 +103,9 @@ class DHParameterNumbers(object): def __ne__(self, other): return not self == other + def parameters(self, backend): + return backend.load_dh_parameter_numbers(self) + p = utils.read_only_property("_p") g = utils.read_only_property("_g") @@ -141,6 +157,13 @@ class DHPrivateKeyWithSerialization(DHPrivateKey): Returns a DHPrivateNumbers. """ + @abc.abstractmethod + def exchange(self, peer_public_key): + """ + Given peer's DHPublicKey, carry out the key exchange and + return shared key as bytes. + """ + @six.add_metaclass(abc.ABCMeta) class DHPublicKey(object): |