From 9aaeee0dc62189204f38097c815a0913fabe006c Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Thu, 30 Apr 2015 14:06:47 -0400 Subject: Add an Elliptic Curve Key Exchange Algorithm(ECDH) The ECDH Key Exchange algorithm as standardized in NIST publication 800-56A Revision 2 Includes tests with vectors from NIST. Signed-off-by: Simo Sorce --- docs/hazmat/primitives/asymmetric/ec.rst | 44 ++++++++++ src/cryptography/exceptions.py | 1 + src/cryptography/hazmat/backends/interfaces.py | 6 ++ src/cryptography/hazmat/backends/multibackend.py | 6 ++ .../hazmat/backends/openssl/backend.py | 20 +++++ .../hazmat/primitives/asymmetric/ec.py | 25 ++++++ tests/hazmat/backends/test_multibackend.py | 12 ++- tests/hazmat/backends/test_openssl.py | 14 ++++ tests/hazmat/primitives/test_ec.py | 94 +++++++++++++++++++++- 9 files changed, 219 insertions(+), 3 deletions(-) diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index 6356c278..910ce5d8 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -122,6 +122,48 @@ Elliptic Curve Signature Algorithms :returns: A new instance of a :class:`EllipticCurvePublicKey` provider. +Elliptic Curve Key Exchange algorithm +------------------------------------- + +.. class:: ECDH(private_key) + + .. versionadded:: 1.1 + + The ECDH Key Exchange algorithm first standardized in NIST publication + `800-56A`_, and later in `800-56Ar2`_. + + :param private_key: An instance of :class:`EllipticCurvePrivateKey`. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives.asymmetric import ec + >>> private_key = ec.generate_private_key( + ... ec.SECP384R1(), default_backend() + ... ) + >>> peer_public_key = ec.generate_private_key( + ... ec.SECP384R1(), default_backend() + ... ).public_key() + >>> ecdh = ec.ECDH(private_key) + >>> sharedkey = ecdh.compute_key(peer_public_key) + + .. attribute:: private_key + + :type: :class:`EllipticCurvePrivateKey` + + The private key associated to this object + + .. method:: public_key() + + The public key associated to the object's private key. + + .. method:: compute_key(peer_public_key) + + :param peer_public_key: A :class:`EllipticCurvePublicKey` object. + + :returns: A ``bytes`` object containing the computed key. + + Elliptic Curves --------------- @@ -419,6 +461,8 @@ Key Interfaces .. _`FIPS 186-3`: http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf .. _`FIPS 186-4`: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +.. _`800-56A`: http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf +.. _`800-56Ar2`: http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf .. _`some concern`: https://crypto.stackexchange.com/questions/10263/should-we-trust-the-nist-recommended-ecc-parameters .. _`less than 224 bits`: http://www.ecrypt.eu.org/ecrypt2/documents/D.SPA.20.pdf .. _`elliptic curve diffie-hellman is faster than diffie-hellman`: http://digitalcommons.unl.edu/cgi/viewcontent.cgi?article=1100&context=cseconfwork diff --git a/src/cryptography/exceptions.py b/src/cryptography/exceptions.py index 29be22be..3bf8a75b 100644 --- a/src/cryptography/exceptions.py +++ b/src/cryptography/exceptions.py @@ -20,6 +20,7 @@ class _Reasons(Enum): UNSUPPORTED_ELLIPTIC_CURVE = 6 UNSUPPORTED_SERIALIZATION = 7 UNSUPPORTED_X509 = 8 + UNSUPPORTED_EXCHANGE_ALGORITHM = 9 class UnsupportedAlgorithm(Exception): diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index a43621a7..faa0b313 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -215,6 +215,12 @@ class EllipticCurveBackend(object): Return an EllipticCurvePublicKey provider using the given numbers. """ + @abc.abstractmethod + def elliptic_curve_exchange_algorithm_supported(self): + """ + Returns whether the exchange algorithm is supported by this backend. + """ + @six.add_metaclass(abc.ABCMeta) class PEMSerializationBackend(object): diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index 9db32aa5..77a45ccd 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -271,6 +271,12 @@ class MultiBackend(object): _Reasons.UNSUPPORTED_ELLIPTIC_CURVE ) + def elliptic_curve_exchange_algorithm_supported(self): + return any( + b.elliptic_curve_exchange_algorithm_supported() + for b in self._filtered_backends(EllipticCurveBackend) + ) + def load_pem_private_key(self, data, password): for b in self._filtered_backends(PEMSerializationBackend): return b.load_pem_private_key(data, password) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 0d3b3dd4..d82f3834 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -1671,6 +1671,26 @@ class Backend(object): return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) + def elliptic_curve_exchange_algorithm_supported(self): + return (self._lib.Cryptography_HAS_EC == 1 and + self._lib.Cryptography_HAS_ECDH == 1) + + def ecdh_compute_key(self, private_key, peer_public_key): + pri_key = private_key._ec_key + pub_key = peer_public_key._ec_key + + group = self._lib.EC_KEY_get0_group(pri_key) + z_len = (self._lib.EC_GROUP_get_degree(group) + 7) // 8 + self.openssl_assert(z_len > 0) + z_buf = self._ffi.new("uint8_t[]", z_len) + peer_key = self._lib.EC_KEY_get0_public_key(pub_key) + + r = self._lib.ECDH_compute_key(z_buf, z_len, + peer_key, pri_key, + self._ffi.NULL) + self.openssl_assert(r > 0) + return self._ffi.buffer(z_buf)[:z_len] + def _ec_cdata_to_evp_pkey(self, ec_cdata): evp_pkey = self._lib.EVP_PKEY_new() self.openssl_assert(evp_pkey != self._ffi.NULL) diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index f1d39eed..978a7c41 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -8,6 +8,7 @@ import abc import six +from cryptography import exceptions from cryptography import utils @@ -302,3 +303,27 @@ class EllipticCurvePrivateNumbers(object): def __ne__(self, other): return not self == other + + +class ECDH(object): + def __init__(self, private_key): + if not isinstance(private_key, EllipticCurvePrivateKey): + raise TypeError("Private Key must be a EllipticCurvePrivateKey") + self._private_key = private_key + self._backend = private_key._backend + if not self._backend.elliptic_curve_exchange_algorithm_supported(): + raise exceptions.UnsupportedAlgorithm( + "This backend does not support the ECDH algorithm.", + exceptions._Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + ) + + private_key = utils.read_only_property("_private_key") + + def public_key(self): + return self._private_key.public_key() + + def compute_key(self, peer_public_key): + if not isinstance(peer_public_key, EllipticCurvePublicKey): + raise TypeError("Peer Public Key must be a EllipticCurvePublicKey") + return self._backend.ecdh_compute_key(self._private_key, + peer_public_key) diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index 4d17cdb0..57aa7f44 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -138,8 +138,9 @@ class DummyCMACBackend(object): @utils.register_interface(EllipticCurveBackend) class DummyEllipticCurveBackend(object): - def __init__(self, supported_curves): + def __init__(self, supported_curves, exchange_supported): self._curves = supported_curves + self.exchange_supported = exchange_supported def elliptic_curve_supported(self, curve): return any( @@ -170,6 +171,9 @@ class DummyEllipticCurveBackend(object): if not self.elliptic_curve_supported(numbers.curve): raise UnsupportedAlgorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE) + def elliptic_curve_exchange_algorithm_supported(self): + return self.exchange_supported + @utils.register_interface(PEMSerializationBackend) class DummyPEMSerializationBackend(object): @@ -400,7 +404,7 @@ class TestMultiBackend(object): backend = MultiBackend([ DummyEllipticCurveBackend([ ec.SECT283K1 - ]) + ], True) ]) assert backend.elliptic_curve_supported(ec.SECT283K1()) is True @@ -462,6 +466,10 @@ class TestMultiBackend(object): ) ) + assert backend.elliptic_curve_exchange_algorithm_supported() is True + backend2 = MultiBackend([DummyEllipticCurveBackend([], False)]) + assert backend2.elliptic_curve_exchange_algorithm_supported() is False + def test_pem_serialization_backend(self): backend = MultiBackend([DummyPEMSerializationBackend()]) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 8fd0d711..13162046 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -534,6 +534,11 @@ class DummyLibrary(object): Cryptography_HAS_EC = 0 +class DummyLibraryECDH(object): + Cryptography_HAS_EC = 1 + Cryptography_HAS_ECDH = 0 + + class TestOpenSSLEllipticCurve(object): def test_elliptic_curve_supported(self, monkeypatch): monkeypatch.setattr(backend, "_lib", DummyLibrary()) @@ -551,6 +556,15 @@ class TestOpenSSLEllipticCurve(object): with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): _sn_to_elliptic_curve(backend, b"fake") + def test_elliptic_curve_exchange_algorithm_supported(self, monkeypatch): + monkeypatch.setattr(backend, "_lib", DummyLibrary()) + + assert backend.elliptic_curve_exchange_algorithm_supported() is False + + monkeypatch.setattr(backend, "_lib", DummyLibraryECDH()) + + assert backend.elliptic_curve_exchange_algorithm_supported() is False + @pytest.mark.requires_backend_interface(interface=RSABackend) class TestRSAPEMSerialization(object): diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index 5467464a..c3a99e5d 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -7,6 +7,8 @@ from __future__ import absolute_import, division, print_function import itertools import os +from binascii import hexlify + import pytest from cryptography import exceptions, utils @@ -21,7 +23,8 @@ from cryptography.hazmat.primitives.asymmetric.utils import ( from ...utils import ( load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors, - load_vectors_from_file, raises_unsupported_algorithm + load_kasvs_ecdh_vectors, load_vectors_from_file, + raises_unsupported_algorithm ) _HASH_TYPES = { @@ -54,6 +57,15 @@ def _skip_curve_unsupported(backend, curve): ) +def _skip_exchange_algorithm_unsupported(backend): + if not backend.elliptic_curve_exchange_algorithm_supported(): + pytest.skip( + "Exchange algorithm is not supported by this backend {0}".format( + backend + ) + ) + + @utils.register_interface(ec.EllipticCurve) class DummyCurve(object): name = "dummy-curve" @@ -749,3 +761,83 @@ class TestECDSAVerification(object): public_key = key.public_key() with pytest.raises(TypeError): public_key.verifier(1234, ec.ECDSA(hashes.SHA256())) + + +class DummyECDHBackend(object): + @classmethod + def elliptic_curve_exchange_algorithm_supported(cls): + return False + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDHVectors(object): + + def test_unsupported_ecdh_arguments(self, backend): + with pytest.raises(TypeError): + ec.ECDH(None) + curve = ec.SECP521R1 + _skip_curve_unsupported(backend, curve) + prikey = ec.generate_private_key(curve, backend) + ecdh = ec.ECDH(prikey) + ecdh.compute_key(ecdh.public_key()) + with pytest.raises(TypeError): + ecdh.compute_key(None) + with pytest.raises(exceptions.UnsupportedAlgorithm): + prikey._backend = DummyECDHBackend() + ecdh = ec.ECDH(prikey) + _skip_exchange_algorithm_unsupported(DummyECDHBackend()) + + def key_exchange(self, backend, vector): + key_numbers = vector['IUT'] + peer_numbers = vector['CAVS'] + + prikey = ec.EllipticCurvePrivateNumbers( + key_numbers['d'], + ec.EllipticCurvePublicNumbers( + key_numbers['x'], + key_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + ).private_key(backend) + + peerkey = ec.EllipticCurvePrivateNumbers( + peer_numbers['d'], + ec.EllipticCurvePublicNumbers( + peer_numbers['x'], + peer_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + ).private_key(backend) + peerpubkey = peerkey.public_key() + + ecdh = ec.ECDH(prikey) + z = ecdh.compute_key(peerpubkey) + + return int(hexlify(z).decode('ascii'), 16) + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDH", + "KASValidityTest_ECCStaticUnified_NOKC_ZZOnly_init.fax"), + load_kasvs_ecdh_vectors + ) + ) + def test_key_exchange_with_vectors(self, backend, vector): + _skip_curve_unsupported(backend, ec._CURVE_TYPES[vector['curve']]) + _skip_exchange_algorithm_unsupported(backend) + + try: + z = self.key_exchange(backend, vector) + except ValueError: + assert vector['fail'] is True + + if vector['fail']: + # Errno 7 denotes a changed private key. Errno 8 denotes a changed + # shared key. Both these errors will not cause a failure in the + # exchange but should lead to a non-matching derived shared key. + if vector['errno'] in [7, 8]: + assert z != vector['Z'] + else: + assert z == vector['Z'] -- cgit v1.2.3 From 5cdfba5c8d06ed10510310de03e1df0265a89bcc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 17 Oct 2015 16:33:04 -0400 Subject: a refactor to the API --- docs/hazmat/primitives/asymmetric/ec.rst | 27 +------ src/cryptography/hazmat/backends/interfaces.py | 2 +- src/cryptography/hazmat/backends/multibackend.py | 4 +- .../hazmat/backends/openssl/backend.py | 25 ++---- src/cryptography/hazmat/backends/openssl/ec.py | 25 ++++++ .../hazmat/primitives/asymmetric/ec.py | 22 +---- tests/hazmat/backends/test_multibackend.py | 27 ++++--- tests/hazmat/backends/test_openssl.py | 14 +--- tests/hazmat/primitives/test_ec.py | 94 ++++++++++------------ 9 files changed, 98 insertions(+), 142 deletions(-) diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index 910ce5d8..9b2e61fb 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -125,14 +125,12 @@ Elliptic Curve Signature Algorithms Elliptic Curve Key Exchange algorithm ------------------------------------- -.. class:: ECDH(private_key) +.. class:: ECDH() .. versionadded:: 1.1 - The ECDH Key Exchange algorithm first standardized in NIST publication - `800-56A`_, and later in `800-56Ar2`_. - - :param private_key: An instance of :class:`EllipticCurvePrivateKey`. + The Elliptic Curve Diffie-Hellman Key Exchange algorithm first standardized + in NIST publication `800-56A`_, and later in `800-56Ar2`_. .. doctest:: @@ -144,24 +142,7 @@ Elliptic Curve Key Exchange algorithm >>> peer_public_key = ec.generate_private_key( ... ec.SECP384R1(), default_backend() ... ).public_key() - >>> ecdh = ec.ECDH(private_key) - >>> sharedkey = ecdh.compute_key(peer_public_key) - - .. attribute:: private_key - - :type: :class:`EllipticCurvePrivateKey` - - The private key associated to this object - - .. method:: public_key() - - The public key associated to the object's private key. - - .. method:: compute_key(peer_public_key) - - :param peer_public_key: A :class:`EllipticCurvePublicKey` object. - - :returns: A ``bytes`` object containing the computed key. + >>> shared_key = private_key.exchange(ec.ECDH(), peer_public_key) Elliptic Curves diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py index faa0b313..dbebc883 100644 --- a/src/cryptography/hazmat/backends/interfaces.py +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -216,7 +216,7 @@ class EllipticCurveBackend(object): """ @abc.abstractmethod - def elliptic_curve_exchange_algorithm_supported(self): + def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): """ Returns whether the exchange algorithm is supported by this backend. """ diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py index 77a45ccd..c4d2c133 100644 --- a/src/cryptography/hazmat/backends/multibackend.py +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -271,9 +271,9 @@ class MultiBackend(object): _Reasons.UNSUPPORTED_ELLIPTIC_CURVE ) - def elliptic_curve_exchange_algorithm_supported(self): + def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): return any( - b.elliptic_curve_exchange_algorithm_supported() + b.elliptic_curve_exchange_algorithm_supported(algorithm, curve) for b in self._filtered_backends(EllipticCurveBackend) ) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index d82f3834..f86c3aa1 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -1671,25 +1671,12 @@ class Backend(object): return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) - def elliptic_curve_exchange_algorithm_supported(self): - return (self._lib.Cryptography_HAS_EC == 1 and - self._lib.Cryptography_HAS_ECDH == 1) - - def ecdh_compute_key(self, private_key, peer_public_key): - pri_key = private_key._ec_key - pub_key = peer_public_key._ec_key - - group = self._lib.EC_KEY_get0_group(pri_key) - z_len = (self._lib.EC_GROUP_get_degree(group) + 7) // 8 - self.openssl_assert(z_len > 0) - z_buf = self._ffi.new("uint8_t[]", z_len) - peer_key = self._lib.EC_KEY_get0_public_key(pub_key) - - r = self._lib.ECDH_compute_key(z_buf, z_len, - peer_key, pri_key, - self._ffi.NULL) - self.openssl_assert(r > 0) - return self._ffi.buffer(z_buf)[:z_len] + def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): + return ( + self.elliptic_curve_supported(curve) and + self._lib.Cryptography_HAS_ECDH == 1 and + isinstance(algorithm, ec.ECDH) + ) def _ec_cdata_to_evp_pkey(self, ec_cdata): evp_pkey = self._lib.EVP_PKEY_new() diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py index 939a3f90..cfd559ae 100644 --- a/src/cryptography/hazmat/backends/openssl/ec.py +++ b/src/cryptography/hazmat/backends/openssl/ec.py @@ -171,6 +171,31 @@ class _EllipticCurvePrivateKey(object): "Unsupported elliptic curve signature algorithm.", _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + def exchange(self, algorithm, peer_public_key): + if not ( + self._backend.elliptic_curve_exchange_algorithm_supported( + algorithm, self.curve + ) + ): + raise UnsupportedAlgorithm( + "This backend does not support the ECDH algorithm.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + ) + + group = self._backend._lib.EC_KEY_get0_group(self._ec_key) + z_len = (self._backend._lib.EC_GROUP_get_degree(group) + 7) // 8 + self._backend.openssl_assert(z_len > 0) + z_buf = self._backend._ffi.new("uint8_t[]", z_len) + peer_key = self._backend._lib.EC_KEY_get0_public_key( + peer_public_key._ec_key + ) + + r = self._backend._lib.ECDH_compute_key( + z_buf, z_len, peer_key, self._ec_key, self._backend._ffi.NULL + ) + self._backend.openssl_assert(r > 0) + return self._backend._ffi.buffer(z_buf)[:z_len] + def public_key(self): group = self._backend._lib.EC_KEY_get0_group(self._ec_key) self._backend.openssl_assert(group != self._backend._ffi.NULL) diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index 978a7c41..544894a9 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -306,24 +306,4 @@ class EllipticCurvePrivateNumbers(object): class ECDH(object): - def __init__(self, private_key): - if not isinstance(private_key, EllipticCurvePrivateKey): - raise TypeError("Private Key must be a EllipticCurvePrivateKey") - self._private_key = private_key - self._backend = private_key._backend - if not self._backend.elliptic_curve_exchange_algorithm_supported(): - raise exceptions.UnsupportedAlgorithm( - "This backend does not support the ECDH algorithm.", - exceptions._Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM - ) - - private_key = utils.read_only_property("_private_key") - - def public_key(self): - return self._private_key.public_key() - - def compute_key(self, peer_public_key): - if not isinstance(peer_public_key, EllipticCurvePublicKey): - raise TypeError("Peer Public Key must be a EllipticCurvePublicKey") - return self._backend.ecdh_compute_key(self._private_key, - peer_public_key) + pass diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py index 57aa7f44..2a533750 100644 --- a/tests/hazmat/backends/test_multibackend.py +++ b/tests/hazmat/backends/test_multibackend.py @@ -138,9 +138,8 @@ class DummyCMACBackend(object): @utils.register_interface(EllipticCurveBackend) class DummyEllipticCurveBackend(object): - def __init__(self, supported_curves, exchange_supported): + def __init__(self, supported_curves): self._curves = supported_curves - self.exchange_supported = exchange_supported def elliptic_curve_supported(self, curve): return any( @@ -153,10 +152,7 @@ class DummyEllipticCurveBackend(object): ): return ( isinstance(signature_algorithm, ec.ECDSA) and - any( - isinstance(curve, curve_type) - for curve_type in self._curves - ) + self.elliptic_curve_supported(curve) ) def generate_elliptic_curve_private_key(self, curve): @@ -171,8 +167,11 @@ class DummyEllipticCurveBackend(object): if not self.elliptic_curve_supported(numbers.curve): raise UnsupportedAlgorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE) - def elliptic_curve_exchange_algorithm_supported(self): - return self.exchange_supported + def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): + return ( + isinstance(algorithm, ec.ECDH) and + self.elliptic_curve_supported(curve) + ) @utils.register_interface(PEMSerializationBackend) @@ -404,7 +403,7 @@ class TestMultiBackend(object): backend = MultiBackend([ DummyEllipticCurveBackend([ ec.SECT283K1 - ], True) + ]) ]) assert backend.elliptic_curve_supported(ec.SECT283K1()) is True @@ -466,9 +465,13 @@ class TestMultiBackend(object): ) ) - assert backend.elliptic_curve_exchange_algorithm_supported() is True - backend2 = MultiBackend([DummyEllipticCurveBackend([], False)]) - assert backend2.elliptic_curve_exchange_algorithm_supported() is False + assert backend.elliptic_curve_exchange_algorithm_supported( + ec.ECDH(), ec.SECT283K1() + ) + backend2 = MultiBackend([DummyEllipticCurveBackend([])]) + assert not backend2.elliptic_curve_exchange_algorithm_supported( + ec.ECDH(), ec.SECT163K1() + ) def test_pem_serialization_backend(self): backend = MultiBackend([DummyPEMSerializationBackend()]) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 13162046..85331595 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -534,11 +534,6 @@ class DummyLibrary(object): Cryptography_HAS_EC = 0 -class DummyLibraryECDH(object): - Cryptography_HAS_EC = 1 - Cryptography_HAS_ECDH = 0 - - class TestOpenSSLEllipticCurve(object): def test_elliptic_curve_supported(self, monkeypatch): monkeypatch.setattr(backend, "_lib", DummyLibrary()) @@ -558,12 +553,9 @@ class TestOpenSSLEllipticCurve(object): def test_elliptic_curve_exchange_algorithm_supported(self, monkeypatch): monkeypatch.setattr(backend, "_lib", DummyLibrary()) - - assert backend.elliptic_curve_exchange_algorithm_supported() is False - - monkeypatch.setattr(backend, "_lib", DummyLibraryECDH()) - - assert backend.elliptic_curve_exchange_algorithm_supported() is False + assert not backend.elliptic_curve_exchange_algorithm_supported( + ec.ECDH(), ec.SECP256R1() + ) @pytest.mark.requires_backend_interface(interface=RSABackend) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index c3a99e5d..2594d5db 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -57,8 +57,10 @@ def _skip_curve_unsupported(backend, curve): ) -def _skip_exchange_algorithm_unsupported(backend): - if not backend.elliptic_curve_exchange_algorithm_supported(): +def _skip_exchange_algorithm_unsupported(backend, algorithm, curve): + if not backend.elliptic_curve_exchange_algorithm_supported( + algorithm, curve + ): pytest.skip( "Exchange algorithm is not supported by this backend {0}".format( backend @@ -771,50 +773,6 @@ class DummyECDHBackend(object): @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) class TestECDHVectors(object): - - def test_unsupported_ecdh_arguments(self, backend): - with pytest.raises(TypeError): - ec.ECDH(None) - curve = ec.SECP521R1 - _skip_curve_unsupported(backend, curve) - prikey = ec.generate_private_key(curve, backend) - ecdh = ec.ECDH(prikey) - ecdh.compute_key(ecdh.public_key()) - with pytest.raises(TypeError): - ecdh.compute_key(None) - with pytest.raises(exceptions.UnsupportedAlgorithm): - prikey._backend = DummyECDHBackend() - ecdh = ec.ECDH(prikey) - _skip_exchange_algorithm_unsupported(DummyECDHBackend()) - - def key_exchange(self, backend, vector): - key_numbers = vector['IUT'] - peer_numbers = vector['CAVS'] - - prikey = ec.EllipticCurvePrivateNumbers( - key_numbers['d'], - ec.EllipticCurvePublicNumbers( - key_numbers['x'], - key_numbers['y'], - ec._CURVE_TYPES[vector['curve']]() - ) - ).private_key(backend) - - peerkey = ec.EllipticCurvePrivateNumbers( - peer_numbers['d'], - ec.EllipticCurvePublicNumbers( - peer_numbers['x'], - peer_numbers['y'], - ec._CURVE_TYPES[vector['curve']]() - ) - ).private_key(backend) - peerpubkey = peerkey.public_key() - - ecdh = ec.ECDH(prikey) - z = ecdh.compute_key(peerpubkey) - - return int(hexlify(z).decode('ascii'), 16) - @pytest.mark.parametrize( "vector", load_vectors_from_file( @@ -825,19 +783,49 @@ class TestECDHVectors(object): ) ) def test_key_exchange_with_vectors(self, backend, vector): - _skip_curve_unsupported(backend, ec._CURVE_TYPES[vector['curve']]) - _skip_exchange_algorithm_unsupported(backend) + _skip_exchange_algorithm_unsupported( + backend, ec.ECDH(), ec._CURVE_TYPES[vector['curve']] + ) + key_numbers = vector['IUT'] try: - z = self.key_exchange(backend, vector) + private_key = ec.EllipticCurvePrivateNumbers( + key_numbers['d'], + ec.EllipticCurvePublicNumbers( + key_numbers['x'], + key_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + ).private_key(backend) except ValueError: - assert vector['fail'] is True + # Errno 5 and 6 indicates a bad public key, this doesn't test the + # ECDH code at all + assert vector['fail'] and vector['errno'] in [5, 6] + return - if vector['fail']: + peer_numbers = vector['CAVS'] + try: + peer_pubkey = ec.EllipticCurvePublicNumbers( + peer_numbers['x'], + peer_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ).public_key(backend) + except ValueError: + # Errno 1 and 2 indicates a bad public key, this doesn't test the + # ECDH code at all + assert vector['fail'] and vector['errno'] in [1, 2] + return + + if vector['fail'] and vector['errno'] not in [7, 8]: + with pytest.raises(ValueError): + private_key.exchange(ec.ECDH(), peer_pubkey) + else: + z = private_key.exchange(ec.ECDH(), peer_pubkey) + z = int(hexlify(z).decode('ascii'), 16) # Errno 7 denotes a changed private key. Errno 8 denotes a changed # shared key. Both these errors will not cause a failure in the # exchange but should lead to a non-matching derived shared key. if vector['errno'] in [7, 8]: assert z != vector['Z'] - else: - assert z == vector['Z'] + else: + assert z == vector['Z'] -- cgit v1.2.3 From 2708323845ee4c51481a60ecda2334d83844b590 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 17 Oct 2015 16:33:57 -0400 Subject: changelog --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fdea8c35..34e3f9a7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ Changelog .. note:: This version is not yet released and is under active development. +* Added support for + :class:`cryptography.hazmat.primitives.asymmetric.ec.ECDH `. * Added :class:`~cryptography.hazmat.primitives.kdf.x963kdf.X963KDF`. 1.0.2 - 2015-09-27 -- cgit v1.2.3 From 4c081ad1fb54dae846a1ade2b15fe15e9ff31b0d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 17 Oct 2015 16:34:33 -0400 Subject: unused --- tests/hazmat/primitives/test_ec.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index 2594d5db..13bc11c9 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -765,12 +765,6 @@ class TestECDSAVerification(object): public_key.verifier(1234, ec.ECDSA(hashes.SHA256())) -class DummyECDHBackend(object): - @classmethod - def elliptic_curve_exchange_algorithm_supported(cls): - return False - - @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) class TestECDHVectors(object): @pytest.mark.parametrize( -- cgit v1.2.3 From 91113b9c37fbaeeb55a2486313d8314ecdc8dfbf Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 17 Oct 2015 16:35:05 -0400 Subject: unused import --- src/cryptography/hazmat/primitives/asymmetric/ec.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index 544894a9..052ae742 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -8,7 +8,6 @@ import abc import six -from cryptography import exceptions from cryptography import utils -- cgit v1.2.3 From 7adb92cadaa46bfb23c8eabeb99091f5058bf244 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 17 Oct 2015 16:38:19 -0400 Subject: backwards syntax --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 34e3f9a7..c1100a49 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,7 +7,7 @@ Changelog .. note:: This version is not yet released and is under active development. * Added support for - :class:`cryptography.hazmat.primitives.asymmetric.ec.ECDH `. + :class:`Elliptic Curve Diffie-Hellman`. * Added :class:`~cryptography.hazmat.primitives.kdf.x963kdf.X963KDF`. 1.0.2 - 2015-09-27 -- cgit v1.2.3 From 39d38b92e015e78269e5bbb248e30e14e22ea599 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 17 Oct 2015 16:44:15 -0400 Subject: clearer language --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c1100a49..227a4404 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,8 +6,8 @@ Changelog .. note:: This version is not yet released and is under active development. -* Added support for - :class:`Elliptic Curve Diffie-Hellman`. +* Added support for Elliptic Curve Diffie-Hellman with + :class:`<~cryptography.hazmat.primitives.asymmetric.ec.ECDH>`. * Added :class:`~cryptography.hazmat.primitives.kdf.x963kdf.X963KDF`. 1.0.2 - 2015-09-27 -- cgit v1.2.3 From d29498902d40a446c65de1cafd567a79a4bbf84e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 17 Oct 2015 16:45:55 -0400 Subject: one more --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 227a4404..ec27596c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,7 +7,7 @@ Changelog .. note:: This version is not yet released and is under active development. * Added support for Elliptic Curve Diffie-Hellman with - :class:`<~cryptography.hazmat.primitives.asymmetric.ec.ECDH>`. + :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDH`. * Added :class:`~cryptography.hazmat.primitives.kdf.x963kdf.X963KDF`. 1.0.2 - 2015-09-27 -- cgit v1.2.3 From 68b3441d90fa4230b62d19a43c49dd2aa155db47 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 17 Oct 2015 18:09:03 -0400 Subject: better document hte iface --- docs/hazmat/primitives/asymmetric/ec.rst | 19 +++++++++++++++++++ src/cryptography/hazmat/primitives/asymmetric/ec.py | 7 +++++++ 2 files changed, 26 insertions(+) diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst index 9b2e61fb..525bd6cb 100644 --- a/docs/hazmat/primitives/asymmetric/ec.rst +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -132,6 +132,9 @@ Elliptic Curve Key Exchange algorithm The Elliptic Curve Diffie-Hellman Key Exchange algorithm first standardized in NIST publication `800-56A`_, and later in `800-56Ar2`_. + For most applications the ``shared_key`` should be passed to a key + derivation function. + .. doctest:: >>> from cryptography.hazmat.backends import default_backend @@ -337,6 +340,22 @@ Key Interfaces :returns: :class:`~cryptography.hazmat.primitives.asymmetric.AsymmetricSignatureContext` + .. method:: exchange(algorithm, peer_public_key) + + Perform's a key exchange operation using the provided algorithm with + the peer's public key. + + For most applications the result should be passed to a key derivation + function. + + :param algorithm: The key exchange algorithm, currently only + :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDH` is + supported. + :param EllipticCurvePublicKey peer_public_key: The public key for the + peer. + + :returns bytes: A shared key. + .. method:: public_key() :return: :class:`EllipticCurvePublicKey` diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py index 052ae742..c6f83667 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/ec.py +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -43,6 +43,13 @@ class EllipticCurvePrivateKey(object): Returns an AsymmetricSignatureContext used for signing data. """ + @abc.abstractmethod + def exchange(self, algorithm, peer_public_key): + """ + Performs a key exchange operation using the provided algorithm with the + provided peer's public key. + """ + @abc.abstractmethod def public_key(self): """ -- cgit v1.2.3 From 799b33f69a8bfd1aaf7f90a81b6ee3fdebc777a1 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 18 Oct 2015 16:59:08 -0400 Subject: be more pro-active in handling invalid keys --- tests/hazmat/primitives/test_ec.py | 50 ++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index 13bc11c9..a634afe8 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -782,33 +782,37 @@ class TestECDHVectors(object): ) key_numbers = vector['IUT'] - try: - private_key = ec.EllipticCurvePrivateNumbers( - key_numbers['d'], - ec.EllipticCurvePublicNumbers( - key_numbers['x'], - key_numbers['y'], - ec._CURVE_TYPES[vector['curve']]() - ) - ).private_key(backend) - except ValueError: - # Errno 5 and 6 indicates a bad public key, this doesn't test the - # ECDH code at all - assert vector['fail'] and vector['errno'] in [5, 6] + private_numbers = ec.EllipticCurvePrivateNumbers( + key_numbers['d'], + ec.EllipticCurvePublicNumbers( + key_numbers['x'], + key_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + ) + # Errno 5 and 6 indicates a bad public key, this doesn't test the ECDH + # code at all + if vector['fail'] and vector['errno'] in [5, 6]: + with pytest.raises(ValueError): + private_numbers.private_key(backend) return + else: + private_key = private_numbers.private_key(backend) peer_numbers = vector['CAVS'] - try: - peer_pubkey = ec.EllipticCurvePublicNumbers( - peer_numbers['x'], - peer_numbers['y'], - ec._CURVE_TYPES[vector['curve']]() - ).public_key(backend) - except ValueError: - # Errno 1 and 2 indicates a bad public key, this doesn't test the - # ECDH code at all - assert vector['fail'] and vector['errno'] in [1, 2] + public_numbers = ec.EllipticCurvePublicNumbers( + peer_numbers['x'], + peer_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + # Errno 1 and 2 indicates a bad public key, this doesn't test the ECDH + # code at all + if vector['fail'] and vector['errno'] in [1, 2]: + with pytest.raises(ValueError): + public_numbers.public_key(backend) return + else: + peer_pubkey = public_numbers.public_key(backend) if vector['fail'] and vector['errno'] not in [7, 8]: with pytest.raises(ValueError): -- cgit v1.2.3 From 7c60ffc621320349f88cf36b12cd087d8bd9b6a3 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 19 Oct 2015 08:04:17 -0400 Subject: removed unused code, and added a test --- tests/hazmat/primitives/test_ec.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index a634afe8..6c184522 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -90,6 +90,12 @@ def test_skip_curve_unsupported(backend): _skip_curve_unsupported(backend, DummyCurve()) +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_skip_exchange_algorithm_unsupported(backend): + with pytest.raises(pytest.skip.Exception): + _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), DummyCurve()) + + def test_ec_numbers(): numbers = ec.EllipticCurvePrivateNumbers( 1, @@ -814,16 +820,11 @@ class TestECDHVectors(object): else: peer_pubkey = public_numbers.public_key(backend) - if vector['fail'] and vector['errno'] not in [7, 8]: - with pytest.raises(ValueError): - private_key.exchange(ec.ECDH(), peer_pubkey) + z = private_key.exchange(ec.ECDH(), peer_pubkey) + z = int(hexlify(z).decode('ascii'), 16) + # At this point fail indicates that one of the underlying keys was + # changed. This results in a non-matching derived key. + if vector['fail']: + assert z != vector['Z'] else: - z = private_key.exchange(ec.ECDH(), peer_pubkey) - z = int(hexlify(z).decode('ascii'), 16) - # Errno 7 denotes a changed private key. Errno 8 denotes a changed - # shared key. Both these errors will not cause a failure in the - # exchange but should lead to a non-matching derived shared key. - if vector['errno'] in [7, 8]: - assert z != vector['Z'] - else: - assert z == vector['Z'] + assert z == vector['Z'] -- cgit v1.2.3 From aaf4e8bccd9cac827b5f740371feaa7faeebcb93 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 19 Oct 2015 08:07:43 -0400 Subject: another test --- tests/hazmat/backends/test_openssl.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 85331595..3ccc54c8 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -557,6 +557,13 @@ class TestOpenSSLEllipticCurve(object): ec.ECDH(), ec.SECP256R1() ) + def test_elliptic_curve_exchange_unsupported_algorithm(self): + key = ec.generate_private_key(ec.SECP256R1(), backend=backend) + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + ): + key.exchange(None, key.public_key()) + @pytest.mark.requires_backend_interface(interface=RSABackend) class TestRSAPEMSerialization(object): -- cgit v1.2.3 From 7a40209a64c800be1b964a0eded2ab1f40accf50 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 19 Oct 2015 08:26:27 -0400 Subject: better place for this test --- tests/hazmat/backends/test_openssl.py | 7 ------- tests/hazmat/primitives/test_ec.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 3ccc54c8..85331595 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -557,13 +557,6 @@ class TestOpenSSLEllipticCurve(object): ec.ECDH(), ec.SECP256R1() ) - def test_elliptic_curve_exchange_unsupported_algorithm(self): - key = ec.generate_private_key(ec.SECP256R1(), backend=backend) - with raises_unsupported_algorithm( - _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM - ): - key.exchange(None, key.public_key()) - @pytest.mark.requires_backend_interface(interface=RSABackend) class TestRSAPEMSerialization(object): diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index 6c184522..4c4d5b90 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -828,3 +828,19 @@ class TestECDHVectors(object): assert z != vector['Z'] else: assert z == vector['Z'] + + def test_exchange_unsupported_algorithm(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + ): + key.exchange(None, key.public_key()) -- cgit v1.2.3