aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst2
-rw-r--r--docs/hazmat/primitives/asymmetric/ec.rst44
-rw-r--r--src/cryptography/exceptions.py1
-rw-r--r--src/cryptography/hazmat/backends/interfaces.py6
-rw-r--r--src/cryptography/hazmat/backends/multibackend.py6
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py7
-rw-r--r--src/cryptography/hazmat/backends/openssl/ec.py25
-rw-r--r--src/cryptography/hazmat/primitives/asymmetric/ec.py11
-rw-r--r--tests/hazmat/backends/test_multibackend.py19
-rw-r--r--tests/hazmat/backends/test_openssl.py6
-rw-r--r--tests/hazmat/primitives/test_ec.py97
11 files changed, 219 insertions, 5 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index fdea8c35..ec27596c 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 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
diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst
index 7c67229c..e4df9b10 100644
--- a/docs/hazmat/primitives/asymmetric/ec.rst
+++ b/docs/hazmat/primitives/asymmetric/ec.rst
@@ -122,6 +122,32 @@ Elliptic Curve Signature Algorithms
:returns: A new instance of a :class:`EllipticCurvePublicKey`
provider.
+Elliptic Curve Key Exchange algorithm
+-------------------------------------
+
+.. class:: ECDH()
+
+ .. versionadded:: 1.1
+
+ 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
+ >>> 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()
+ >>> shared_key = private_key.exchange(ec.ECDH(), peer_public_key)
+
+
Elliptic Curves
---------------
@@ -314,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`
@@ -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 d93968cf..92d9653a 100644
--- a/src/cryptography/hazmat/backends/interfaces.py
+++ b/src/cryptography/hazmat/backends/interfaces.py
@@ -215,6 +215,12 @@ class EllipticCurveBackend(object):
Return an EllipticCurvePrivateKey provider using the given numbers.
"""
+ @abc.abstractmethod
+ def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve):
+ """
+ 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..c4d2c133 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, algorithm, curve):
+ return any(
+ b.elliptic_curve_exchange_algorithm_supported(algorithm, curve)
+ 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..f86c3aa1 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -1671,6 +1671,13 @@ class Backend(object):
return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey)
+ 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()
self.openssl_assert(evp_pkey != self._ffi.NULL)
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 f1d39eed..c6f83667 100644
--- a/src/cryptography/hazmat/primitives/asymmetric/ec.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py
@@ -44,6 +44,13 @@ class EllipticCurvePrivateKey(object):
"""
@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):
"""
The EllipticCurvePublicKey for this private key.
@@ -302,3 +309,7 @@ class EllipticCurvePrivateNumbers(object):
def __ne__(self, other):
return not self == other
+
+
+class ECDH(object):
+ pass
diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py
index 4d17cdb0..2a533750 100644
--- a/tests/hazmat/backends/test_multibackend.py
+++ b/tests/hazmat/backends/test_multibackend.py
@@ -152,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):
@@ -170,6 +167,12 @@ class DummyEllipticCurveBackend(object):
if not self.elliptic_curve_supported(numbers.curve):
raise UnsupportedAlgorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE)
+ def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve):
+ return (
+ isinstance(algorithm, ec.ECDH) and
+ self.elliptic_curve_supported(curve)
+ )
+
@utils.register_interface(PEMSerializationBackend)
class DummyPEMSerializationBackend(object):
@@ -462,6 +465,14 @@ class TestMultiBackend(object):
)
)
+ 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 8fd0d711..85331595 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -551,6 +551,12 @@ 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 not backend.elliptic_curve_exchange_algorithm_supported(
+ ec.ECDH(), ec.SECP256R1()
+ )
+
@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..4c4d5b90 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,17 @@ def _skip_curve_unsupported(backend, curve):
)
+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
+ )
+ )
+
+
@utils.register_interface(ec.EllipticCurve)
class DummyCurve(object):
name = "dummy-curve"
@@ -76,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,
@@ -749,3 +769,78 @@ class TestECDSAVerification(object):
public_key = key.public_key()
with pytest.raises(TypeError):
public_key.verifier(1234, ec.ECDSA(hashes.SHA256()))
+
+
+@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
+class TestECDHVectors(object):
+ @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_exchange_algorithm_unsupported(
+ backend, ec.ECDH(), ec._CURVE_TYPES[vector['curve']]
+ )
+
+ key_numbers = vector['IUT']
+ 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']
+ 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)
+
+ 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:
+ 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())