aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Stapleton <alexs@prol.etari.at>2014-05-17 13:19:15 +0100
committerAlex Stapleton <alexs@prol.etari.at>2014-06-07 19:04:27 +0100
commite47bafb9b620b557aeb48fce4734a568d6dc0b38 (patch)
tree6fe6155fca2d6943f7615ea391e43538d6394c38
parentddadf40234e97cd5b7e5f7b3a3a03d38900cb291 (diff)
downloadcryptography-e47bafb9b620b557aeb48fce4734a568d6dc0b38.tar.gz
cryptography-e47bafb9b620b557aeb48fce4734a568d6dc0b38.tar.bz2
cryptography-e47bafb9b620b557aeb48fce4734a568d6dc0b38.zip
ECDSA backend
-rw-r--r--cryptography/exceptions.py1
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py398
-rw-r--r--cryptography/hazmat/primitives/asymmetric/ec.py186
-rw-r--r--docs/hazmat/primitives/asymmetric/ec.rst182
-rw-r--r--docs/hazmat/primitives/interfaces.rst5
-rw-r--r--docs/spelling_wordlist.txt1
-rw-r--r--pytest.ini1
-rw-r--r--tests/conftest.py11
-rw-r--r--tests/hazmat/backends/test_openssl.py23
-rw-r--r--tests/hazmat/primitives/test_ec.py294
10 files changed, 1045 insertions, 57 deletions
diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py
index b4ee8feb..c64b67f4 100644
--- a/cryptography/exceptions.py
+++ b/cryptography/exceptions.py
@@ -21,6 +21,7 @@ class _Reasons(object):
UNSUPPORTED_PADDING = object()
UNSUPPORTED_MGF = object()
UNSUPPORTED_PUBLIC_KEY_ALGORITHM = object()
+ UNSUPPORTED_ELLIPTIC_CURVE = object()
class UnsupportedAlgorithm(Exception):
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index ffe09663..9cf92f9b 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -25,13 +25,13 @@ from cryptography.exceptions import (
UnsupportedAlgorithm, _Reasons
)
from cryptography.hazmat.backends.interfaces import (
- CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend,
- PBKDF2HMACBackend, PKCS8SerializationBackend, RSABackend,
+ CMACBackend, CipherBackend, DSABackend, EllipticCurveBackend, HMACBackend,
+ HashBackend, PBKDF2HMACBackend, PKCS8SerializationBackend, RSABackend,
TraditionalOpenSSLSerializationBackend
)
from cryptography.hazmat.bindings.openssl.binding import Binding
from cryptography.hazmat.primitives import hashes, interfaces
-from cryptography.hazmat.primitives.asymmetric import dsa, rsa
+from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa
from cryptography.hazmat.primitives.asymmetric.padding import (
MGF1, OAEP, PKCS1v15, PSS
)
@@ -51,12 +51,13 @@ _OpenSSLError = collections.namedtuple("_OpenSSLError",
@utils.register_interface(CipherBackend)
@utils.register_interface(CMACBackend)
@utils.register_interface(DSABackend)
+@utils.register_interface(EllipticCurveBackend)
@utils.register_interface(HashBackend)
@utils.register_interface(HMACBackend)
@utils.register_interface(PBKDF2HMACBackend)
+@utils.register_interface(PKCS8SerializationBackend)
@utils.register_interface(RSABackend)
@utils.register_interface(TraditionalOpenSSLSerializationBackend)
-@utils.register_interface(PKCS8SerializationBackend)
class Backend(object):
"""
OpenSSL API binding interfaces.
@@ -425,8 +426,8 @@ class Backend(object):
The char* is the storage for the BIO and it must stay alive until the
BIO is finished with.
"""
- data_char_p = backend._ffi.new("char[]", data)
- bio = backend._lib.BIO_new_mem_buf(
+ data_char_p = self._ffi.new("char[]", data)
+ bio = self._lib.BIO_new_mem_buf(
data_char_p, len(data)
)
assert bio != self._ffi.NULL
@@ -890,6 +891,239 @@ class Backend(object):
return self._evp_pkey_to_private_key(evp_pkey)
+ def elliptic_curve_supported(self, curve):
+ if self._lib.Cryptography_HAS_EC != 1:
+ return False
+
+ curves = self._supported_curves()
+ return curve.name.encode("ascii") in curves
+
+ def elliptic_curve_signature_algorithm_supported(
+ self, signature_algorithm, curve
+ ):
+ if self._lib.Cryptography_HAS_EC != 1:
+ return False
+
+ # We only support ECDSA right now.
+ if isinstance(signature_algorithm, ec.ECDSA) is False:
+ return False
+
+ # Before 0.9.8m OpenSSL can't cope with digests longer than the curve.
+ if (
+ self._lib.OPENSSL_VERSION_NUMBER < 0x009080df and
+ curve.key_size < signature_algorithm.algorithm.digest_size * 8
+ ):
+ return False
+
+ if not self.elliptic_curve_supported(curve):
+ return False
+ else:
+ return True
+
+ def _supported_curves(self):
+ if self._lib.Cryptography_HAS_EC != 1:
+ return []
+
+ num_curves = self._lib.EC_get_builtin_curves(self._ffi.NULL, 0)
+ curve_array = self._ffi.new("EC_builtin_curve[]", num_curves)
+ num_curves_assigned = self._lib.EC_get_builtin_curves(
+ curve_array, num_curves)
+ assert num_curves == num_curves_assigned
+
+ curves = [
+ self._ffi.string(self._lib.OBJ_nid2sn(curve.nid)).decode()
+ for curve in curve_array
+ ]
+
+ curve_aliases = {
+ "prime192v1": "secp192r1",
+ "prime256v1": "secp256r1"
+ }
+ return [
+ curve_aliases.get(curve, curve)
+ for curve in curves
+ ]
+
+ def _create_ecdsa_signature_ctx(self, private_key, ecdsa):
+ return _ECDSASignatureContext(self, private_key, ecdsa.algorithm)
+
+ def _create_ecdsa_verification_ctx(self, public_key, signature, ecdsa):
+ return _ECDSAVerificationContext(self, public_key, signature,
+ ecdsa.algorithm)
+
+ def generate_elliptic_curve_private_key(self, curve):
+ """
+ Generate a new private key on the named curve.
+ """
+
+ curve_nid = self._elliptic_curve_to_nid(curve)
+
+ ctx = self._lib.EC_KEY_new_by_curve_name(curve_nid)
+ assert ctx != self._ffi.NULL
+ ctx = self._ffi.gc(ctx, self._lib.EC_KEY_free)
+
+ res = self._lib.EC_KEY_generate_key(ctx)
+ assert res == 1
+
+ res = self._lib.EC_KEY_check_key(ctx)
+ assert res == 1
+
+ return _EllipticCurvePrivateKey(self, ctx, curve)
+
+ def elliptic_curve_private_key_from_numbers(self, numbers):
+ ec_key = self._ec_key_cdata_from_private_numbers(numbers)
+ return _EllipticCurvePrivateKey(self, ec_key,
+ numbers.public_numbers.curve)
+
+ def elliptic_curve_public_key_from_numbers(self, numbers):
+ ec_key = self._ec_key_cdata_from_public_numbers(numbers)
+ return _EllipticCurvePublicKey(self, ec_key, numbers.curve)
+
+ def _elliptic_curve_to_nid(self, curve):
+ """
+ Get the NID for a curve name.
+ """
+
+ curve_aliases = {
+ "secp192r1": "prime192v1",
+ "secp256r1": "prime256v1"
+ }
+
+ curve_name = curve_aliases.get(curve.name, curve.name)
+
+ curve_nid = self._lib.OBJ_sn2nid(curve_name.encode())
+ if curve_nid == self._lib.NID_undef:
+ raise UnsupportedAlgorithm(
+ "{0} is not a supported elliptic curve".format(curve.name),
+ _Reasons.UNSUPPORTED_ELLIPTIC_CURVE
+ )
+ return curve_nid
+
+ def _ec_key_cdata_from_private_numbers(self, numbers):
+ """
+ Build an EC_KEY from a private key object.
+ """
+
+ public = numbers.public_numbers
+
+ curve_nid = self._elliptic_curve_to_nid(public.curve)
+
+ ctx = self._lib.EC_KEY_new_by_curve_name(curve_nid)
+ assert ctx != self._ffi.NULL
+ ctx = self._ffi.gc(ctx, self._lib.EC_KEY_free)
+
+ ctx = self._ec_key_set_public_key_affine_coordinates(
+ ctx, public.x, public.y)
+
+ res = self._lib.EC_KEY_set_private_key(
+ ctx, self._int_to_bn(numbers.private_value))
+ assert res == 1
+
+ return ctx
+
+ def _ec_key_cdata_from_public_numbers(self, numbers):
+ """
+ Build an EC_KEY from a public key object.
+ """
+
+ curve_nid = self._elliptic_curve_to_nid(numbers.curve)
+
+ ctx = self._lib.EC_KEY_new_by_curve_name(curve_nid)
+ assert ctx != self._ffi.NULL
+ ctx = self._ffi.gc(ctx, self._lib.EC_KEY_free)
+
+ ctx = self._ec_key_set_public_key_affine_coordinates(
+ ctx, numbers.x, numbers.y)
+
+ return ctx
+
+ def _public_ec_key_from_private_ec_key(self, private_key_cdata):
+ """
+ Copy the public portions out of one EC key into a new one.
+ """
+
+ group = self._lib.EC_KEY_get0_group(private_key_cdata)
+ assert group != self._ffi.NULL
+
+ curve_nid = self._lib.EC_GROUP_get_curve_name(group)
+
+ ctx = self._lib.EC_KEY_new_by_curve_name(curve_nid)
+ assert ctx != self._ffi.NULL
+ ctx = self._ffi.gc(ctx, self._lib.EC_KEY_free)
+
+ point = self._lib.EC_KEY_get0_public_key(private_key_cdata)
+ assert point != self._ffi.NULL
+
+ res = self._lib.EC_KEY_set_public_key(ctx, point)
+ assert res == 1
+
+ return ctx
+
+ def _ec_key_set_public_key_affine_coordinates(self, ctx, x, y):
+ """
+ This is a port of EC_KEY_set_public_key_affine_coordinates that was
+ added in 1.0.1.
+
+ Sets the public key point in the EC_KEY context to the affine x and y
+ values.
+ """
+
+ assert ctx != self._ffi.NULL
+
+ bn_x = self._int_to_bn(x)
+ bn_y = self._int_to_bn(y)
+
+ nid_two_field = self._lib.OBJ_sn2nid(b"characteristic-two-field")
+ assert nid_two_field != self._lib.NID_undef
+
+ bn_ctx = self._lib.BN_CTX_new()
+ assert bn_ctx != self._ffi.NULL
+ bn_ctx = self._ffi.gc(bn_ctx, self._lib.BN_CTX_free)
+
+ group = self._lib.EC_KEY_get0_group(ctx)
+ assert group != self._ffi.NULL
+
+ point = self._lib.EC_POINT_new(group)
+ assert point != self._ffi.NULL
+ point = self._ffi.gc(point, self._lib.EC_POINT_free)
+
+ method = self._lib.EC_GROUP_method_of(group)
+ assert method != self._ffi.NULL
+
+ nid = self._lib.EC_METHOD_get_field_type(method)
+ assert nid != self._lib.NID_undef
+
+ check_x = self._lib.BN_CTX_get(bn_ctx)
+ check_y = self._lib.BN_CTX_get(bn_ctx)
+
+ if nid == nid_two_field and self._lib.Cryptography_HAS_EC2M:
+ set_func = self._lib.EC_POINT_set_affine_coordinates_GF2m
+ get_func = self._lib.EC_POINT_get_affine_coordinates_GF2m
+ else:
+ set_func = self._lib.EC_POINT_set_affine_coordinates_GFp
+ get_func = self._lib.EC_POINT_get_affine_coordinates_GFp
+
+ assert set_func and get_func
+
+ res = set_func(group, point, bn_x, bn_y, bn_ctx)
+ assert res == 1
+
+ res = get_func(group, point, check_x, check_y, bn_ctx)
+ assert res == 1
+
+ assert (
+ self._lib.BN_cmp(bn_x, check_x) == 0 and
+ self._lib.BN_cmp(bn_y, check_y) == 0
+ )
+
+ res = self._lib.EC_KEY_set_public_key(ctx, point)
+ assert res == 1
+
+ res = self._lib.EC_KEY_check_key(ctx)
+ assert res == 1
+
+ return ctx
+
class GetCipherByName(object):
def __init__(self, fmt):
@@ -1727,4 +1961,156 @@ class _CMACContext(object):
)
+def _truncate_digest_for_ecdsa(ec_key_cdata, digest, backend):
+ _lib = backend._lib
+ _ffi = backend._ffi
+
+ digest_len = len(digest)
+
+ group = _lib.EC_KEY_get0_group(ec_key_cdata)
+
+ bn_ctx = _lib.BN_CTX_new()
+ assert bn_ctx != _ffi.NULL
+ bn_ctx = _ffi.gc(bn_ctx, _lib.BN_CTX_free)
+
+ order = _lib.BN_CTX_get(bn_ctx)
+ assert order != _ffi.NULL
+
+ res = _lib.EC_GROUP_get_order(group, order, bn_ctx)
+ assert res == 1
+
+ order_bits = _lib.BN_num_bits(order)
+
+ if 8 * digest_len > order_bits:
+ digest_len = (order_bits + 7) // 8
+ digest = digest[:digest_len]
+
+ if 8 * digest_len > order_bits:
+ rshift = 8 - (order_bits & 0x7)
+ assert rshift > 0 and rshift < 8
+
+ mask = 0xFF >> rshift << rshift
+
+ # Set the bottom rshift bits to 0
+ digest = digest[:-1] + six.int2byte(six.byte2int(digest[-1]) & mask)
+
+ return digest
+
+
+@utils.register_interface(interfaces.AsymmetricSignatureContext)
+class _ECDSASignatureContext(object):
+ def __init__(self, backend, private_key, algorithm):
+ self._backend = backend
+ self._private_key = private_key
+ self._digest = hashes.Hash(algorithm, backend)
+
+ def update(self, data):
+ self._digest.update(data)
+
+ def finalize(self):
+ ec_key = self._private_key._ec_key
+
+ digest = self._digest.finalize()
+
+ digest = _truncate_digest_for_ecdsa(ec_key, digest, self._backend)
+
+ max_size = self._backend._lib.ECDSA_size(ec_key)
+ assert max_size > 0
+
+ sigbuf = self._backend._ffi.new("char[]", max_size)
+ siglen_ptr = self._backend._ffi.new("unsigned int[]", 1)
+ res = self._backend._lib.ECDSA_sign(
+ 0,
+ digest,
+ len(digest),
+ sigbuf,
+ siglen_ptr,
+ ec_key
+ )
+ assert res == 1
+ return self._backend._ffi.buffer(sigbuf)[:siglen_ptr[0]]
+
+
+@utils.register_interface(interfaces.AsymmetricVerificationContext)
+class _ECDSAVerificationContext(object):
+ def __init__(self, backend, public_key, signature, algorithm):
+ self._backend = backend
+ self._public_key = public_key
+ self._signature = signature
+ self._digest = hashes.Hash(algorithm, backend)
+
+ def update(self, data):
+ self._digest.update(data)
+
+ def verify(self):
+ ec_key = self._public_key._ec_key
+
+ digest = self._digest.finalize()
+
+ digest = _truncate_digest_for_ecdsa(ec_key, digest, self._backend)
+
+ res = self._backend._lib.ECDSA_verify(
+ 0,
+ digest,
+ len(digest),
+ self._signature,
+ len(self._signature),
+ ec_key
+ )
+ if res != 1:
+ self._backend._consume_errors()
+ raise InvalidSignature
+ return True
+
+
+@utils.register_interface(interfaces.EllipticCurvePrivateKey)
+class _EllipticCurvePrivateKey(object):
+ def __init__(self, backend, ec_key_cdata, curve):
+ self._backend = backend
+ self._ec_key = ec_key_cdata
+ self._curve = curve
+
+ @property
+ def curve(self):
+ return self._curve
+
+ def signer(self, signature_algorithm):
+ if isinstance(signature_algorithm, ec.ECDSA):
+ return self._backend._create_ecdsa_signature_ctx(
+ self, signature_algorithm)
+ else:
+ raise UnsupportedAlgorithm(
+ "Unsupported elliptic curve signature algorithm.",
+ _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM)
+
+ def public_key(self):
+ public_ec_key = self._backend._public_ec_key_from_private_ec_key(
+ self._ec_key
+ )
+
+ return _EllipticCurvePublicKey(
+ self._backend, public_ec_key, self._curve)
+
+
+@utils.register_interface(interfaces.EllipticCurvePublicKey)
+class _EllipticCurvePublicKey(object):
+ def __init__(self, backend, ec_key_cdata, curve):
+ self._backend = backend
+ self._ec_key = ec_key_cdata
+ self._curve = curve
+
+ @property
+ def curve(self):
+ return self._curve
+
+ def verifier(self, signature, signature_algorithm):
+ if isinstance(signature_algorithm, ec.ECDSA):
+ return self._backend._create_ecdsa_verification_ctx(
+ self, signature, signature_algorithm)
+ else:
+ raise UnsupportedAlgorithm(
+ "Unsupported elliptic curve signature algorithm.",
+ _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM)
+
+
backend = Backend()
diff --git a/cryptography/hazmat/primitives/asymmetric/ec.py b/cryptography/hazmat/primitives/asymmetric/ec.py
index 1e49ad7b..220a419c 100644
--- a/cryptography/hazmat/primitives/asymmetric/ec.py
+++ b/cryptography/hazmat/primitives/asymmetric/ec.py
@@ -15,9 +15,189 @@ from __future__ import absolute_import, division, print_function
import six
+from cryptography import utils
from cryptography.hazmat.primitives import interfaces
+@utils.register_interface(interfaces.EllipticCurve)
+class SECT571R1(object):
+ @property
+ def name(self):
+ return "sect571r1"
+
+ @property
+ def key_size(self):
+ return 571
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECT409R1(object):
+ @property
+ def name(self):
+ return "sect409r1"
+
+ @property
+ def key_size(self):
+ return 409
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECT283R1(object):
+ @property
+ def name(self):
+ return "sect283r1"
+
+ @property
+ def key_size(self):
+ return 283
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECT233R1(object):
+ @property
+ def name(self):
+ return "sect233r1"
+
+ @property
+ def key_size(self):
+ return 233
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECT163R2(object):
+ @property
+ def name(self):
+ return "sect163r2"
+
+ @property
+ def key_size(self):
+ return 163
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECT571K1(object):
+ @property
+ def name(self):
+ return "sect571k1"
+
+ @property
+ def key_size(self):
+ return 571
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECT409K1(object):
+ @property
+ def name(self):
+ return "sect409k1"
+
+ @property
+ def key_size(self):
+ return 409
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECT283K1(object):
+ @property
+ def name(self):
+ return "sect283k1"
+
+ @property
+ def key_size(self):
+ return 283
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECT233K1(object):
+ @property
+ def name(self):
+ return "sect233k1"
+
+ @property
+ def key_size(self):
+ return 233
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECT163K1(object):
+ @property
+ def name(self):
+ return "sect163k1"
+
+ @property
+ def key_size(self):
+ return 163
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECP521R1(object):
+ @property
+ def name(self):
+ return "secp521r1"
+
+ @property
+ def key_size(self):
+ return 521
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECP384R1(object):
+ @property
+ def name(self):
+ return "secp384r1"
+
+ @property
+ def key_size(self):
+ return 384
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECP256R1(object):
+ @property
+ def name(self):
+ return "secp256r1"
+
+ @property
+ def key_size(self):
+ return 256
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECP224R1(object):
+ @property
+ def name(self):
+ return "secp224r1"
+
+ @property
+ def key_size(self):
+ return 224
+
+
+@utils.register_interface(interfaces.EllipticCurve)
+class SECP192R1(object):
+ @property
+ def name(self):
+ return "secp192r1"
+
+ @property
+ def key_size(self):
+ return 192
+
+
+@utils.register_interface(interfaces.EllipticCurveSignatureAlgorithm)
+class ECDSA(object):
+ def __init__(self, algorithm):
+ self._algorithm = algorithm
+
+ @property
+ def algorithm(self):
+ return self._algorithm
+
+
+def generate_private_key(curve, backend):
+ return backend.generate_elliptic_curve_private_key(curve)
+
+
class EllipticCurvePublicNumbers(object):
def __init__(self, x, y, curve):
if (
@@ -33,6 +213,9 @@ class EllipticCurvePublicNumbers(object):
self._x = x
self._curve = curve
+ def public_key(self, backend):
+ return backend.elliptic_curve_public_key_from_numbers(self)
+
@property
def curve(self):
return self._curve
@@ -60,6 +243,9 @@ class EllipticCurvePrivateNumbers(object):
self._private_value = private_value
self._public_numbers = public_numbers
+ def private_key(self, backend):
+ return backend.elliptic_curve_private_key_from_numbers(self)
+
@property
def private_value(self):
return self._private_value
diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst
index f88b965a..798fbab1 100644
--- a/docs/hazmat/primitives/asymmetric/ec.rst
+++ b/docs/hazmat/primitives/asymmetric/ec.rst
@@ -1,11 +1,30 @@
.. hazmat::
-Elliptic Curve
-==============
+Elliptic Curve Cryptography
+===========================
.. currentmodule:: cryptography.hazmat.primitives.asymmetric.ec
+,, method:: generate_private_key(curve, backend):
+
+ .. versionadded:: 0.5
+
+ Generate a new private key on ``curve`` for use with ``backend``.
+
+ :param backend: A
+ :class:`~cryptography.hazmat.primtives.interfaces.EllipticCurve`
+ provider.
+
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`
+ provider.
+
+ :returns: A new instance of a
+ :class:`~cryptography.hazmat.primtivies.interfaces.EllipticCurvePrivateKey`
+ provider.
+
+
.. class:: EllipticCurvePrivateNumbers(private_value, public_numbers)
.. versionadded:: 0.5
@@ -25,6 +44,19 @@ Elliptic Curve
The private value.
+ .. method:: private_key(backend)
+
+ Convert a collection of numbers into a private key suitable for doing
+ actual cryptographic operations.
+
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`
+ provider.
+
+ :returns: A new instance of a
+ :class:`~cryptography.hazmat.primtivies.interfaces.EllipticCurvePrivateKey`
+ provider.
+
.. class:: EllipticCurvePublicNumbers(x, y, curve)
@@ -49,3 +81,149 @@ Elliptic Curve
:type: int
The affine y component of the public point used for verifying.
+
+ .. method:: public_key(backend)
+
+ Convert a collection of numbers into a public key suitable for doing
+ actual cryptographic operations.
+
+ :param backend: A
+ :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`
+ provider.
+
+ :returns: A new instance of a
+ :class:`~cryptography.hazmat.primtivies.interfaces.EllipticCurvePublicKey`
+ provider.
+
+
+Elliptic Curve Signature Algorithms
+-----------------------------------
+
+.. class:: ECDSA(algorithm)
+ .. versionadded:: 0.5
+
+ The ECDSA signature algorithm first standardized in NIST publication
+ `FIPS 186-3`_, and later in `FIPS 186-4`_.
+
+ :param algorithm: An instance of a
+ :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm`
+ provider.
+
+ .. code-block:: pycon
+
+ >>> from cryptography.hazmat.backends import default_backend
+ >>> from cryptography.hazmat.primitives import hashes
+ >>> from cryptography.hazmat.primitives.asymmetric import ec
+ >>> private_key = ec.generate_private_key(
+ ... ec.SECT283K1(), default_backend()
+ ... )
+ >>> signer = private_key.signer(ec.ECDSA(hashes.SHA256()))
+ >>> signer.update(b"this is some data I'd like")
+ >>> signer.update(b" to sign")
+ >>> signature = signer.finalize()
+
+Elliptic Curves
+---------------
+
+All named curves are providers of
+:class:`~cryptography.hazmat.primtives.interfaces.EllipticCurve`.
+
+There is `some concern`_ that the non-Koblitz NIST curves (identified by names
+that start with "B" or "P") may have been intentionally weakened by their
+generation process.
+
+
+.. class:: SECT571K1
+ .. versionadded:: 0.5
+
+ SECG curve ``sect571k1``. Also called NIST K-571.
+
+
+.. class:: SECT409K1
+ .. versionadded:: 0.5
+
+ SECG curve ``sect409k1``. Also called NIST K-409.
+
+
+.. class:: SECT283K1
+ .. versionadded:: 0.5
+
+ SECG curve ``sect283k1``. Also called NIST K-283.
+
+
+.. class:: SECT233K1
+ .. versionadded:: 0.5
+
+ SECG curve ``sect233k1``. Also called NIST K-233.
+
+
+.. class:: SECT163K1
+ .. versionadded:: 0.5
+
+ SECG curve ``sect163k1``. Also called NIST K-163.
+
+
+.. class:: SECT571R1
+ .. versionadded:: 0.5
+
+ SECG curve ``sect571r1``. Also called NIST B-571.
+
+
+.. class:: SECT409R1
+ .. versionadded:: 0.5
+
+ SECG curve ``sect409r1``. Also called NIST B-409.
+
+
+.. class:: SECT283R1
+ .. versionadded:: 0.5
+
+ SECG curve ``sect283r1``. Also called NIST B-283.
+
+
+.. class:: SECT233R1
+ .. versionadded:: 0.5
+
+ SECG curve ``sect233r1``. Also called NIST B-233.
+
+
+.. class:: SECT163R2
+ .. versionadded:: 0.5
+
+ SECG curve ``sect163r2``. Also called NIST B-163.
+
+
+.. class:: SECP521R1
+ .. versionadded:: 0.5
+
+ SECG curve ``secp521r1``. Also called NIST P-521.
+
+
+.. class:: SECP384R1
+ .. versionadded:: 0.5
+
+ SECG curve ``secp384r1``. Also called NIST P-384.
+
+
+.. class:: SECP256R1
+ .. versionadded:: 0.5
+
+ SECG curve ``secp256r1``. Also called NIST P-256.
+
+
+.. class:: SECT224R1
+ .. versionadded:: 0.5
+
+ SECG curve ``secp224r1``. Also called NIST P-224.
+
+
+.. class:: SECP192R1
+ .. versionadded:: 0.5
+
+ SECG curve ``secp192r1``. Also called NIST P-192.
+
+
+
+.. _`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
+.. _`some concern`: https://crypto.stackexchange.com/questions/10263/should-we-trust-the-nist-recommended-ecc-parameters
diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst
index 34e4e938..d5ca59ab 100644
--- a/docs/hazmat/primitives/interfaces.rst
+++ b/docs/hazmat/primitives/interfaces.rst
@@ -411,7 +411,6 @@ Asymmetric interfaces
`EdDSA`_.
.. classmethod:: signer(signature_algorithm)
-
Sign data which can be verified later by others using the public key.
:param signature_algorithm: An instance of a
@@ -421,7 +420,6 @@ Asymmetric interfaces
:returns:
:class:`~cryptography.hazmat.primitives.interfaces.AsymmetricSignatureContext`
- .. attribute:: curve
:type: :class:`~cryptography.hazmat.primitives.interfaces.EllipticCurve`
@@ -440,8 +438,7 @@ Asymmetric interfaces
An elliptic curve public key.
- .. classmethod:: verifier(signer, signature_algorithm)
-
+ .. classmethod:: verifier(signature, signature_algorithm)
Verify data was signed by the private key associated with this public
key.
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index 9baf0822..d5a2bee3 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -27,6 +27,7 @@ interoperable
introspectability
invariants
iOS
+Koblitz
metadata
pickleable
plaintext
diff --git a/pytest.ini b/pytest.ini
index 9b44f198..00a2c7fb 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -11,3 +11,4 @@ markers =
traditional_openssl_serialization: this test requires a backend providing TraditionalOpenSSLSerializationBackend
pkcs8_serialization: this test requires a backend providing PKCS8SerializationBackend
supported: parametrized test requiring only_if and skip_message
+ elliptic: this test requires a backend providing EllipticCurveBackend
diff --git a/tests/conftest.py b/tests/conftest.py
index b1326dc8..af146386 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -17,8 +17,8 @@ import pytest
from cryptography.hazmat.backends import _available_backends
from cryptography.hazmat.backends.interfaces import (
- CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend,
- PBKDF2HMACBackend, PKCS8SerializationBackend, RSABackend,
+ CMACBackend, CipherBackend, DSABackend, EllipticCurveBackend, HMACBackend,
+ HashBackend, PBKDF2HMACBackend, PKCS8SerializationBackend, RSABackend,
TraditionalOpenSSLSerializationBackend
)
from .utils import check_backend_support, check_for_iface, select_backends
@@ -46,11 +46,8 @@ def pytest_runtest_setup(item):
TraditionalOpenSSLSerializationBackend,
item
)
- check_for_iface(
- "pkcs8_serialization",
- PKCS8SerializationBackend,
- item
- )
+ check_for_iface("pkcs8_serialization", PKCS8SerializationBackend, item)
+ check_for_iface("elliptic", EllipticCurveBackend, item)
check_backend_support(item)
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index bfcdf14a..aa2122fb 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -19,7 +19,9 @@ import pytest
from cryptography import utils
from cryptography.exceptions import InternalError, _Reasons
-from cryptography.hazmat.backends.openssl.backend import Backend, backend
+from cryptography.hazmat.backends.openssl.backend import (
+ Backend, backend
+)
from cryptography.hazmat.primitives import hashes, interfaces
from cryptography.hazmat.primitives.asymmetric import dsa, padding, rsa
from cryptography.hazmat.primitives.ciphers import Cipher
@@ -445,3 +447,22 @@ class TestOpenSSLSerialisationWithOpenSSL(object):
key = pretend.stub(type="unsupported")
with raises_unsupported_algorithm(None):
backend._evp_pkey_to_private_key(key)
+
+
+class TestOpenSSLNoEllipticCurve(object):
+ def test_elliptic_curve_supported(self, monkeypatch):
+ monkeypatch.setattr(backend._lib, "Cryptography_HAS_EC", 0)
+
+ assert backend.elliptic_curve_supported(None) is False
+
+ def test_elliptic_curve_signature_algorithm_supported(self, monkeypatch):
+ monkeypatch.setattr(backend._lib, "Cryptography_HAS_EC", 0)
+
+ assert backend.elliptic_curve_signature_algorithm_supported(
+ None, None
+ ) is False
+
+ def test_supported_curves(self, monkeypatch):
+ monkeypatch.setattr(backend._lib, "Cryptography_HAS_EC", 0)
+
+ assert backend._supported_curves() == []
diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py
index 53985fe2..1879f4fc 100644
--- a/tests/hazmat/primitives/test_ec.py
+++ b/tests/hazmat/primitives/test_ec.py
@@ -14,66 +14,286 @@
from __future__ import absolute_import, division, print_function
+import itertools
+import os
+
import pytest
-from cryptography import utils
-from cryptography.hazmat.primitives import interfaces
+from cryptography import exceptions, utils
+from cryptography.hazmat.primitives import hashes, interfaces
from cryptography.hazmat.primitives.asymmetric import ec
+from ...utils import (
+ der_encode_dsa_signature, load_fips_ecdsa_key_pair_vectors,
+ load_fips_ecdsa_signing_vectors, load_vectors_from_file,
+ raises_unsupported_algorithm
+)
+
+_CURVE_TYPES = {
+ "secp192r1": ec.SECP192R1,
+ "secp224r1": ec.SECP224R1,
+ "secp256r1": ec.SECP256R1,
+ "secp384r1": ec.SECP384R1,
+ "secp521r1": ec.SECP521R1,
+
+ "sect163k1": ec.SECT163K1,
+ "sect233k1": ec.SECT233K1,
+ "sect283k1": ec.SECT283K1,
+ "sect409k1": ec.SECT409K1,
+ "sect571k1": ec.SECT571K1,
+
+ "sect163r2": ec.SECT163R2,
+ "sect233r1": ec.SECT233R1,
+ "sect283r1": ec.SECT283R1,
+ "sect409r1": ec.SECT409R1,
+ "sect571r1": ec.SECT571R1,
+}
+
+_HASH_TYPES = {
+ "SHA-1": hashes.SHA1,
+ "SHA-224": hashes.SHA224,
+ "SHA-256": hashes.SHA256,
+ "SHA-384": hashes.SHA384,
+ "SHA-512": hashes.SHA512,
+}
+
+
+def _skip_ecdsa_vector(backend, curve_type, hash_type):
+ if not backend.elliptic_curve_signature_algorithm_supported(
+ ec.ECDSA(hash_type()),
+ curve_type()
+ ):
+ pytest.skip(
+ "ECDSA not supported with this hash {0} and curve {1}".format(
+ hash_type().name, curve_type().name
+ )
+ )
+
@utils.register_interface(interfaces.EllipticCurve)
class DummyCurve(object):
name = "dummy-curve"
+ key_size = 1
-class TestECC(object):
- def test_ec_numbers(self):
- numbers = ec.EllipticCurvePrivateNumbers(
- 1,
+@utils.register_interface(interfaces.EllipticCurveSignatureAlgorithm)
+class DummySignatureAlgorithm(object):
+ pass
+
+
+def test_ec_numbers():
+ numbers = ec.EllipticCurvePrivateNumbers(
+ 1,
+ ec.EllipticCurvePublicNumbers(
+ 2, 3, DummyCurve()
+ )
+ )
+
+ assert numbers.private_value == 1
+ assert numbers.public_numbers.x == 2
+ assert numbers.public_numbers.y == 3
+ assert isinstance(numbers.public_numbers.curve, DummyCurve)
+
+ with pytest.raises(TypeError):
+ ec.EllipticCurvePrivateNumbers(
+ None,
ec.EllipticCurvePublicNumbers(
2, 3, DummyCurve()
)
)
- assert numbers.private_value == 1
- assert numbers.public_numbers.x == 2
- assert numbers.public_numbers.y == 3
- assert isinstance(numbers.public_numbers.curve, DummyCurve)
+ with pytest.raises(TypeError):
+ ec.EllipticCurvePrivateNumbers(
+ 1,
+ ec.EllipticCurvePublicNumbers(
+ None, 3, DummyCurve()
+ )
+ )
- with pytest.raises(TypeError):
- ec.EllipticCurvePrivateNumbers(
- None,
- ec.EllipticCurvePublicNumbers(
- 2, 3, DummyCurve()
- )
+ with pytest.raises(TypeError):
+ ec.EllipticCurvePrivateNumbers(
+ 1,
+ ec.EllipticCurvePublicNumbers(
+ 2, None, DummyCurve()
)
+ )
- with pytest.raises(TypeError):
- ec.EllipticCurvePrivateNumbers(
- 1,
- ec.EllipticCurvePublicNumbers(
- None, 3, DummyCurve()
- )
+ with pytest.raises(TypeError):
+ ec.EllipticCurvePrivateNumbers(
+ 1,
+ ec.EllipticCurvePublicNumbers(
+ 2, 3, None
)
+ )
- with pytest.raises(TypeError):
- ec.EllipticCurvePrivateNumbers(
- 1,
- ec.EllipticCurvePublicNumbers(
- 2, None, DummyCurve()
- )
+ with pytest.raises(TypeError):
+ ec.EllipticCurvePrivateNumbers(
+ 1,
+ None
+ )
+
+
+@pytest.mark.elliptic
+class TestECDSAVectors(object):
+ @pytest.mark.parametrize(
+ ("vector", "hash_type"),
+ list(itertools.product(
+ load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "ECDSA", "FIPS_186-3", "KeyPair.rsp"),
+ load_fips_ecdsa_key_pair_vectors
+ ),
+ _HASH_TYPES.values()
+ ))
+ )
+ def test_signing_with_example_keys(self, backend, vector, hash_type):
+ curve_type = _CURVE_TYPES[vector['curve']]
+
+ _skip_ecdsa_vector(backend, curve_type, hash_type)
+
+ key = ec.EllipticCurvePrivateNumbers(
+ vector['d'],
+ ec.EllipticCurvePublicNumbers(
+ vector['x'],
+ vector['y'],
+ curve_type()
)
+ ).private_key(backend)
+ assert key
+
+ pkey = key.public_key()
+ assert pkey
- with pytest.raises(TypeError):
- ec.EllipticCurvePrivateNumbers(
- 1,
- ec.EllipticCurvePublicNumbers(
- 2, 3, None
+ signer = key.signer(ec.ECDSA(hash_type()))
+ signer.update(b"YELLOW SUBMARINE")
+ signature = signer.finalize()
+
+ verifier = pkey.verifier(signature, ec.ECDSA(hash_type()))
+ verifier.update(b"YELLOW SUBMARINE")
+ verifier.verify()
+
+ @pytest.mark.parametrize(
+ "curve", _CURVE_TYPES.values()
+ )
+ def test_generate_vector_curves(self, backend, curve):
+ if not backend.elliptic_curve_supported(curve()):
+ pytest.skip(
+ "Curve {0} is not supported by this backend {1}".format(
+ curve().name, backend
)
)
- with pytest.raises(TypeError):
- ec.EllipticCurvePrivateNumbers(
- 1,
- None
+ key = ec.generate_private_key(curve(), backend)
+ assert key
+ assert isinstance(key.curve, curve)
+ assert key.curve.key_size
+
+ pkey = key.public_key()
+ assert pkey
+ assert isinstance(pkey.curve, curve)
+ assert key.curve.key_size == pkey.curve.key_size
+
+ def test_generate_unknown_curve(self, backend):
+ with raises_unsupported_algorithm(
+ exceptions._Reasons.UNSUPPORTED_ELLIPTIC_CURVE
+ ):
+ ec.generate_private_key(DummyCurve(), backend)
+
+ assert backend.elliptic_curve_signature_algorithm_supported(
+ ec.ECDSA(hashes.SHA256()),
+ DummyCurve()
+ ) is False
+
+ def test_unknown_signature_algoritm(self, backend):
+ if not backend.elliptic_curve_supported(ec.SECP192R1()):
+ pytest.skip(
+ "Curve secp192r1 is not supported by this backend {0}".format(
+ backend
+ )
)
+
+ key = ec.generate_private_key(ec.SECP192R1(), backend)
+
+ with raises_unsupported_algorithm(
+ exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
+ ):
+ key.signer(DummySignatureAlgorithm())
+
+ with raises_unsupported_algorithm(
+ exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
+ ):
+ key.public_key().verifier(b"", DummySignatureAlgorithm())
+
+ assert backend.elliptic_curve_signature_algorithm_supported(
+ DummySignatureAlgorithm(),
+ ec.SECP192R1()
+ ) is False
+
+ @pytest.mark.parametrize(
+ "vector",
+ load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "ECDSA", "FIPS_186-3", "SigGen.txt"),
+ load_fips_ecdsa_signing_vectors
+ )
+ )
+ def test_signatures(self, backend, vector):
+ hash_type = _HASH_TYPES[vector['digest_algorithm']]
+ curve_type = _CURVE_TYPES[vector['curve']]
+
+ _skip_ecdsa_vector(backend, curve_type, hash_type)
+
+ key = ec.EllipticCurvePublicNumbers(
+ vector['x'],
+ vector['y'],
+ curve_type()
+ ).public_key(backend)
+
+ signature = der_encode_dsa_signature(
+ vector['r'],
+ vector['s']
+ )
+
+ verifier = key.verifier(
+ signature,
+ ec.ECDSA(hash_type())
+ )
+ verifier.update(vector['message'])
+ assert verifier.verify()
+
+ @pytest.mark.parametrize(
+ "vector",
+ load_vectors_from_file(
+ os.path.join(
+ "asymmetric", "ECDSA", "FIPS_186-3", "SigVer.rsp"),
+ load_fips_ecdsa_signing_vectors
+ )
+ )
+ def test_signature_failures(self, backend, vector):
+ hash_type = _HASH_TYPES[vector['digest_algorithm']]
+ curve_type = _CURVE_TYPES[vector['curve']]
+
+ _skip_ecdsa_vector(backend, curve_type, hash_type)
+
+ key = ec.EllipticCurvePublicNumbers(
+ vector['x'],
+ vector['y'],
+ curve_type()
+ ).public_key(backend)
+
+ signature = der_encode_dsa_signature(
+ vector['r'],
+ vector['s']
+ )
+
+ verifier = key.verifier(
+ signature,
+ ec.ECDSA(hash_type())
+ )
+ verifier.update(vector['message'])
+
+ if vector["fail"] is True:
+ with pytest.raises(exceptions.InvalidSignature):
+ verifier.verify()
+ else:
+ verifier.verify()