diff options
| -rw-r--r-- | cryptography/exceptions.py | 1 | ||||
| -rw-r--r-- | cryptography/hazmat/backends/openssl/backend.py | 398 | ||||
| -rw-r--r-- | cryptography/hazmat/primitives/asymmetric/ec.py | 186 | ||||
| -rw-r--r-- | docs/hazmat/primitives/asymmetric/ec.rst | 182 | ||||
| -rw-r--r-- | docs/hazmat/primitives/interfaces.rst | 5 | ||||
| -rw-r--r-- | docs/spelling_wordlist.txt | 1 | ||||
| -rw-r--r-- | pytest.ini | 1 | ||||
| -rw-r--r-- | tests/conftest.py | 11 | ||||
| -rw-r--r-- | tests/hazmat/backends/test_openssl.py | 23 | ||||
| -rw-r--r-- | tests/hazmat/primitives/test_ec.py | 294 | 
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 @@ -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() | 
