mirror of
				https://github.com/Legrandin/pycryptodome.git
				synced 2025-11-03 23:21:33 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			926 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			926 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# ===================================================================
 | 
						|
#
 | 
						|
# Copyright (c) 2015, Legrandin <helderijs@gmail.com>
 | 
						|
# All rights reserved.
 | 
						|
#
 | 
						|
# Redistribution and use in source and binary forms, with or without
 | 
						|
# modification, are permitted provided that the following conditions
 | 
						|
# are met:
 | 
						|
#
 | 
						|
# 1. Redistributions of source code must retain the above copyright
 | 
						|
#    notice, this list of conditions and the following disclaimer.
 | 
						|
# 2. Redistributions in binary form must reproduce the above copyright
 | 
						|
#    notice, this list of conditions and the following disclaimer in
 | 
						|
#    the documentation and/or other materials provided with the
 | 
						|
#    distribution.
 | 
						|
#
 | 
						|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
						|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
						|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 | 
						|
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 | 
						|
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 | 
						|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 | 
						|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 | 
						|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 | 
						|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
						|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 | 
						|
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | 
						|
# POSSIBILITY OF SUCH DAMAGE.
 | 
						|
# ===================================================================
 | 
						|
 | 
						|
from __future__ import print_function
 | 
						|
 | 
						|
import struct
 | 
						|
import binascii
 | 
						|
from collections import namedtuple
 | 
						|
 | 
						|
from Crypto.Util.py3compat import bord, tobytes, tostr, bchr, is_string
 | 
						|
from Crypto.Util.number import bytes_to_long, long_to_bytes
 | 
						|
 | 
						|
from Crypto.Math.Numbers import Integer
 | 
						|
from Crypto.Random import get_random_bytes
 | 
						|
from Crypto.Util.asn1 import (DerObjectId, DerOctetString, DerSequence,
 | 
						|
                              DerBitString)
 | 
						|
 | 
						|
from Crypto.IO import PKCS8, PEM
 | 
						|
from Crypto.PublicKey import (_expand_subject_public_key_info,
 | 
						|
                              _create_subject_public_key_info,
 | 
						|
                              _extract_subject_public_key_info)
 | 
						|
 | 
						|
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
 | 
						|
                                  SmartPointer, c_size_t, c_uint8_ptr,
 | 
						|
                                  c_ulonglong, c_uint)
 | 
						|
 | 
						|
from Crypto.Random.random import getrandbits
 | 
						|
 | 
						|
_ec_lib = load_pycryptodome_raw_lib("Crypto.PublicKey._ec_ws", """
 | 
						|
typedef void EcContext;
 | 
						|
typedef void EcPoint;
 | 
						|
int ec_ws_new_context(EcContext **pec_ctx,
 | 
						|
                      const uint8_t *modulus,
 | 
						|
                      const uint8_t *b,
 | 
						|
                      const uint8_t *order,
 | 
						|
                      size_t len);
 | 
						|
void ec_free_context(EcContext *ec_ctx);
 | 
						|
int ec_ws_new_point(EcPoint **pecp,
 | 
						|
                    uint8_t *x,
 | 
						|
                    uint8_t *y,
 | 
						|
                    size_t len,
 | 
						|
                    const EcContext *ec_ctx,
 | 
						|
                    unsigned is_generator);
 | 
						|
void ec_free_point(EcPoint *ecp);
 | 
						|
int ec_ws_get_xy(uint8_t *x, uint8_t *y, size_t len, const EcPoint *ecp);
 | 
						|
int ec_ws_double(EcPoint *p);
 | 
						|
int ec_ws_add(EcPoint *ecpa, EcPoint *ecpb);
 | 
						|
int ec_ws_scalar_multiply(EcPoint *ecp, const uint8_t *k, size_t len, uint64_t seed);
 | 
						|
int ec_ws_clone(EcPoint **pecp2, const EcPoint *ecp);
 | 
						|
int ec_ws_cmp(const EcPoint *ecp1, const EcPoint *ecp2);
 | 
						|
int ec_ws_neg(EcPoint *p);
 | 
						|
""")
 | 
						|
 | 
						|
p256_modulus = long_to_bytes(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff, 32)
 | 
						|
p256_b = long_to_bytes(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b, 32)
 | 
						|
p256_order = long_to_bytes(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551, 32)
 | 
						|
assert(len(p256_modulus) == 32)
 | 
						|
assert(len(p256_b) == 32)
 | 
						|
assert(len(p256_order) == 32)
 | 
						|
 | 
						|
_ec_p256_context = VoidPointer()
 | 
						|
result = _ec_lib.ec_ws_new_context(_ec_p256_context.address_of(),
 | 
						|
                                   c_uint8_ptr(p256_modulus),
 | 
						|
                                   c_uint8_ptr(p256_b),
 | 
						|
                                   c_uint8_ptr(p256_order),
 | 
						|
                                   c_size_t(len(p256_modulus)))
 | 
						|
if result:
 | 
						|
    raise ImportError("Error %d initializing P256 context" % result)
 | 
						|
_ec_p256_context = SmartPointer(_ec_p256_context.get(),
 | 
						|
                                _ec_lib.ec_free_context)
 | 
						|
 | 
						|
 | 
						|
class UnsupportedEccFeature(ValueError):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class EccPoint(object):
 | 
						|
    """A class to abstract a point over an Elliptic Curve.
 | 
						|
 | 
						|
    :ivar x: The X-coordinate of the ECC point
 | 
						|
    :vartype x: integer
 | 
						|
 | 
						|
    :ivar y: The Y-coordinate of the ECC point
 | 
						|
    :vartype y: integer
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, x, y, is_generator=False):
 | 
						|
        xb = long_to_bytes(x, 32)
 | 
						|
        yb = long_to_bytes(y, 32)
 | 
						|
        assert(len(xb) == 32)
 | 
						|
        assert(len(yb) == 32)
 | 
						|
 | 
						|
        self._point = VoidPointer()
 | 
						|
        result = _ec_lib.ec_ws_new_point(self._point.address_of(),
 | 
						|
                                         c_uint8_ptr(xb),
 | 
						|
                                         c_uint8_ptr(yb),
 | 
						|
                                         c_size_t(len(xb)),
 | 
						|
                                         _ec_p256_context.get(),
 | 
						|
                                         c_uint(is_generator))
 | 
						|
        if result:
 | 
						|
            if result == 15:
 | 
						|
                raise ValueError("The EC point does not belong to the curve")
 | 
						|
            raise ValueError("Error %d while instantiating an EC point" % result)
 | 
						|
 | 
						|
        # Ensure that object disposal of this Python object will (eventually)
 | 
						|
        # free the memory allocated by the raw library for the EC point
 | 
						|
        self._point = SmartPointer(self._point.get(),
 | 
						|
                                   _ec_lib.ec_free_point)
 | 
						|
 | 
						|
    def set(self, point):
 | 
						|
        self._point = VoidPointer()
 | 
						|
        result = _ec_lib.ec_ws_clone(self._point.address_of(),
 | 
						|
                                     point._point.get())
 | 
						|
        if result:
 | 
						|
            raise ValueError("Error %d while cloning an EC point" % result)
 | 
						|
 | 
						|
        self._point = SmartPointer(self._point.get(),
 | 
						|
                                   _ec_lib.ec_free_point)
 | 
						|
        return self
 | 
						|
 | 
						|
    def __eq__(self, point):
 | 
						|
        return 0 == _ec_lib.ec_ws_cmp(self._point.get(), point._point.get())
 | 
						|
 | 
						|
    def __neg__(self):
 | 
						|
        np = self.copy()
 | 
						|
        result = _ec_lib.ec_ws_neg(np._point_get())
 | 
						|
        if result:
 | 
						|
            raise ValueError("Error %d while negating an EC point" % result)
 | 
						|
        return np
 | 
						|
 | 
						|
    def copy(self):
 | 
						|
        x, y = self.xy
 | 
						|
        np = EccPoint(x, y)
 | 
						|
        return np
 | 
						|
 | 
						|
    def is_point_at_infinity(self):
 | 
						|
        return self.xy == (0, 0)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def point_at_infinity():
 | 
						|
        return EccPoint(0, 0)
 | 
						|
 | 
						|
    @property
 | 
						|
    def x(self):
 | 
						|
        return self.xy[0]
 | 
						|
 | 
						|
    @property
 | 
						|
    def y(self):
 | 
						|
        return self.xy[1]
 | 
						|
 | 
						|
    @property
 | 
						|
    def xy(self):
 | 
						|
        xb = bytearray(32)
 | 
						|
        yb = bytearray(32)
 | 
						|
        result = _ec_lib.ec_ws_get_xy(c_uint8_ptr(xb),
 | 
						|
                                      c_uint8_ptr(yb),
 | 
						|
                                      c_size_t(len(xb)),
 | 
						|
                                      self._point.get())
 | 
						|
        if result:
 | 
						|
            raise ValueError("Error %d while encoding an EC point" % result)
 | 
						|
 | 
						|
        return [Integer(bytes_to_long(c)) for c in (xb, yb)]
 | 
						|
 | 
						|
    def double(self):
 | 
						|
        """Double this point (in-place operation).
 | 
						|
 | 
						|
        :Return:
 | 
						|
            :class:`EccPoint` : this same object (to enable chaining)
 | 
						|
        """
 | 
						|
 | 
						|
        result = _ec_lib.ec_ws_double(self._point.get())
 | 
						|
        if result:
 | 
						|
            raise ValueError("Error %d while doubling an EC point" % result)
 | 
						|
        return self
 | 
						|
 | 
						|
    def __iadd__(self, point):
 | 
						|
        """Add a second point to this one"""
 | 
						|
 | 
						|
        result = _ec_lib.ec_ws_add(self._point.get(), point._point.get())
 | 
						|
        if result:
 | 
						|
            if result == 16:
 | 
						|
                raise ValueError("Trying to add two EC point that are not on the same curve")
 | 
						|
            raise ValueError("Error %d while adding two EC points" % result)
 | 
						|
        return self
 | 
						|
 | 
						|
    def __add__(self, point):
 | 
						|
        """Return a new point, the addition of this one and another"""
 | 
						|
 | 
						|
        np = self.copy()
 | 
						|
        np += point
 | 
						|
        return np
 | 
						|
 | 
						|
    def __imul__(self, scalar):
 | 
						|
        """Multiply this point by a scalar"""
 | 
						|
 | 
						|
        if scalar < 0:
 | 
						|
            raise ValueError("Scalar multiplication is only defined for non-negative integers")
 | 
						|
        sb = long_to_bytes(scalar)
 | 
						|
        result = _ec_lib.ec_ws_scalar_multiply(self._point.get(),
 | 
						|
                                               c_uint8_ptr(sb),
 | 
						|
                                               c_size_t(len(sb)),
 | 
						|
                                               c_ulonglong(getrandbits(64)))
 | 
						|
        if result:
 | 
						|
            raise ValueError("Error %d during scalar multiplication" % result)
 | 
						|
        return self
 | 
						|
 | 
						|
    def __mul__(self, scalar):
 | 
						|
        """Return a new point, the scalar product of this one"""
 | 
						|
 | 
						|
        np = self.copy()
 | 
						|
        np *= scalar
 | 
						|
        return np
 | 
						|
 | 
						|
 | 
						|
_Curve = namedtuple("_Curve", "p b order Gx Gy G names oid")
 | 
						|
 | 
						|
_curve_gx = Integer(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296)
 | 
						|
_curve_gy = Integer(0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5)
 | 
						|
 | 
						|
_curve = _Curve(
 | 
						|
    Integer(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff),
 | 
						|
    Integer(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b),
 | 
						|
    Integer(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551),
 | 
						|
    _curve_gx,
 | 
						|
    _curve_gy,
 | 
						|
    EccPoint(_curve_gx, _curve_gy, is_generator=True),
 | 
						|
    ("P-256", "prime256v1", "secp256r1"),
 | 
						|
    "1.2.840.10045.3.1.7",
 | 
						|
)
 | 
						|
del namedtuple
 | 
						|
 | 
						|
 | 
						|
class EccKey(object):
 | 
						|
    r"""Class defining an ECC key.
 | 
						|
    Do not instantiate directly.
 | 
						|
    Use :func:`generate`, :func:`construct` or :func:`import_key` instead.
 | 
						|
 | 
						|
    :ivar curve: The name of the ECC curve
 | 
						|
    :vartype curve: string
 | 
						|
 | 
						|
    :ivar pointQ: an ECC point representating the public component
 | 
						|
    :vartype pointQ: :class:`EccPoint`
 | 
						|
 | 
						|
    :ivar d: A scalar representating the private component
 | 
						|
    :vartype d: integer
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, **kwargs):
 | 
						|
        """Create a new ECC key
 | 
						|
 | 
						|
        Keywords:
 | 
						|
          curve : string
 | 
						|
            It must be *"P-256"*, *"prime256v1"* or *"secp256r1"*.
 | 
						|
          d : integer
 | 
						|
            Only for a private key. It must be in the range ``[1..order-1]``.
 | 
						|
          point : EccPoint
 | 
						|
            Mandatory for a public key. If provided for a private key,
 | 
						|
            the implementation will NOT check whether it matches ``d``.
 | 
						|
        """
 | 
						|
 | 
						|
        kwargs_ = dict(kwargs)
 | 
						|
        self.curve = kwargs_.pop("curve", None)
 | 
						|
        self._d = kwargs_.pop("d", None)
 | 
						|
        self._point = kwargs_.pop("point", None)
 | 
						|
        if kwargs_:
 | 
						|
            raise TypeError("Unknown parameters: " + str(kwargs_))
 | 
						|
 | 
						|
        if self.curve not in _curve.names:
 | 
						|
            raise ValueError("Unsupported curve (%s)", self.curve)
 | 
						|
 | 
						|
        if self._d is None:
 | 
						|
            if self._point is None:
 | 
						|
                raise ValueError("Either private or public ECC component must be specified")
 | 
						|
        else:
 | 
						|
            self._d = Integer(self._d)
 | 
						|
            if not 1 <= self._d < _curve.order:
 | 
						|
                raise ValueError("Invalid ECC private component")
 | 
						|
 | 
						|
    def __eq__(self, other):
 | 
						|
        if other.has_private() != self.has_private():
 | 
						|
            return False
 | 
						|
 | 
						|
        return (other.pointQ.x == self.pointQ.x) and (other.pointQ.y == self.pointQ.y)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        if self.has_private():
 | 
						|
            extra = ", d=%d" % int(self._d)
 | 
						|
        else:
 | 
						|
            extra = ""
 | 
						|
        return "EccKey(curve='P-256', x=%d, y=%d%s)" %\
 | 
						|
               (self.pointQ.x, self.pointQ.y, extra)
 | 
						|
 | 
						|
    def has_private(self):
 | 
						|
        """``True`` if this key can be used for making signatures or decrypting data."""
 | 
						|
 | 
						|
        return self._d is not None
 | 
						|
 | 
						|
    def _sign(self, z, k):
 | 
						|
        assert 0 < k < _curve.order
 | 
						|
 | 
						|
        blind = Integer.random_range(min_inclusive=1,
 | 
						|
                                     max_exclusive=_curve.order)
 | 
						|
 | 
						|
        blind_d = self._d * blind
 | 
						|
        inv_blind_k = (blind * k).inverse(_curve.order)
 | 
						|
 | 
						|
        r = (_curve.G * k).x % _curve.order
 | 
						|
        s = inv_blind_k * (blind * z + blind_d * r) % _curve.order
 | 
						|
        return (r, s)
 | 
						|
 | 
						|
    def _verify(self, z, rs):
 | 
						|
        sinv = rs[1].inverse(_curve.order)
 | 
						|
        point1 = _curve.G * ((sinv * z) % _curve.order)
 | 
						|
        point2 = self.pointQ * ((sinv * rs[0]) % _curve.order)
 | 
						|
        return (point1 + point2).x == rs[0]
 | 
						|
 | 
						|
    @property
 | 
						|
    def d(self):
 | 
						|
        if not self.has_private():
 | 
						|
            raise ValueError("This is not a private ECC key")
 | 
						|
        return self._d
 | 
						|
 | 
						|
    @property
 | 
						|
    def pointQ(self):
 | 
						|
        if self._point is None:
 | 
						|
            self._point = _curve.G * self._d
 | 
						|
        return self._point
 | 
						|
 | 
						|
    def public_key(self):
 | 
						|
        """A matching ECC public key.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            a new :class:`EccKey` object
 | 
						|
        """
 | 
						|
 | 
						|
        return EccKey(curve="P-256", point=self.pointQ)
 | 
						|
 | 
						|
    def _export_subjectPublicKeyInfo(self, compress):
 | 
						|
 | 
						|
        # See 2.2 in RFC5480 and 2.3.3 in SEC1
 | 
						|
        # The first byte is:
 | 
						|
        # - 0x02:   compressed, only X-coordinate, Y-coordinate is even
 | 
						|
        # - 0x03:   compressed, only X-coordinate, Y-coordinate is odd
 | 
						|
        # - 0x04:   uncompressed, X-coordinate is followed by Y-coordinate
 | 
						|
        #
 | 
						|
        # PAI is in theory encoded as 0x00.
 | 
						|
 | 
						|
        order_bytes = _curve.order.size_in_bytes()
 | 
						|
 | 
						|
        if compress:
 | 
						|
            first_byte = 2 + self.pointQ.y.is_odd()
 | 
						|
            public_key = (bchr(first_byte) +
 | 
						|
                          self.pointQ.x.to_bytes(order_bytes))
 | 
						|
        else:
 | 
						|
            public_key = (b'\x04' +
 | 
						|
                          self.pointQ.x.to_bytes(order_bytes) +
 | 
						|
                          self.pointQ.y.to_bytes(order_bytes))
 | 
						|
 | 
						|
        unrestricted_oid = "1.2.840.10045.2.1"
 | 
						|
        return _create_subject_public_key_info(unrestricted_oid,
 | 
						|
                                               public_key,
 | 
						|
                                               DerObjectId(_curve.oid))
 | 
						|
 | 
						|
    def _export_private_der(self, include_ec_params=True):
 | 
						|
 | 
						|
        assert self.has_private()
 | 
						|
 | 
						|
        # ECPrivateKey ::= SEQUENCE {
 | 
						|
        #           version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
 | 
						|
        #           privateKey     OCTET STRING,
 | 
						|
        #           parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
 | 
						|
        #           publicKey  [1] BIT STRING OPTIONAL
 | 
						|
        #    }
 | 
						|
 | 
						|
        # Public key - uncompressed form
 | 
						|
        order_bytes = _curve.order.size_in_bytes()
 | 
						|
        public_key = (b'\x04' +
 | 
						|
                      self.pointQ.x.to_bytes(order_bytes) +
 | 
						|
                      self.pointQ.y.to_bytes(order_bytes))
 | 
						|
 | 
						|
        seq = [1,
 | 
						|
               DerOctetString(self.d.to_bytes(order_bytes)),
 | 
						|
               DerObjectId(_curve.oid, explicit=0),
 | 
						|
               DerBitString(public_key, explicit=1)]
 | 
						|
 | 
						|
        if not include_ec_params:
 | 
						|
            del seq[2]
 | 
						|
 | 
						|
        return DerSequence(seq).encode()
 | 
						|
 | 
						|
    def _export_pkcs8(self, **kwargs):
 | 
						|
        if kwargs.get('passphrase', None) is not None and 'protection' not in kwargs:
 | 
						|
            raise ValueError("At least the 'protection' parameter should be present")
 | 
						|
        unrestricted_oid = "1.2.840.10045.2.1"
 | 
						|
        private_key = self._export_private_der(include_ec_params=False)
 | 
						|
        result = PKCS8.wrap(private_key,
 | 
						|
                            unrestricted_oid,
 | 
						|
                            key_params=DerObjectId(_curve.oid),
 | 
						|
                            **kwargs)
 | 
						|
        return result
 | 
						|
 | 
						|
    def _export_public_pem(self, compress):
 | 
						|
        encoded_der = self._export_subjectPublicKeyInfo(compress)
 | 
						|
        return PEM.encode(encoded_der, "PUBLIC KEY")
 | 
						|
 | 
						|
    def _export_private_pem(self, passphrase, **kwargs):
 | 
						|
        encoded_der = self._export_private_der()
 | 
						|
        return PEM.encode(encoded_der, "EC PRIVATE KEY", passphrase, **kwargs)
 | 
						|
 | 
						|
    def _export_private_clear_pkcs8_in_clear_pem(self):
 | 
						|
        encoded_der = self._export_pkcs8()
 | 
						|
        return PEM.encode(encoded_der, "PRIVATE KEY")
 | 
						|
 | 
						|
    def _export_private_encrypted_pkcs8_in_clear_pem(self, passphrase, **kwargs):
 | 
						|
        assert passphrase
 | 
						|
        if 'protection' not in kwargs:
 | 
						|
            raise ValueError("At least the 'protection' parameter should be present")
 | 
						|
        encoded_der = self._export_pkcs8(passphrase=passphrase, **kwargs)
 | 
						|
        return PEM.encode(encoded_der, "ENCRYPTED PRIVATE KEY")
 | 
						|
 | 
						|
    def _export_openssh(self, compress):
 | 
						|
        if self.has_private():
 | 
						|
            raise ValueError("Cannot export OpenSSH private keys")
 | 
						|
 | 
						|
        desc = "ecdsa-sha2-nistp256"
 | 
						|
        order_bytes = _curve.order.size_in_bytes()
 | 
						|
 | 
						|
        if compress:
 | 
						|
            first_byte = 2 + self.pointQ.y.is_odd()
 | 
						|
            public_key = (bchr(first_byte) +
 | 
						|
                          self.pointQ.x.to_bytes(order_bytes))
 | 
						|
        else:
 | 
						|
            public_key = (b'\x04' +
 | 
						|
                          self.pointQ.x.to_bytes(order_bytes) +
 | 
						|
                          self.pointQ.y.to_bytes(order_bytes))
 | 
						|
 | 
						|
        comps = (tobytes(desc), b"nistp256", public_key)
 | 
						|
        blob = b"".join([struct.pack(">I", len(x)) + x for x in comps])
 | 
						|
        return desc + " " + tostr(binascii.b2a_base64(blob))
 | 
						|
 | 
						|
    def export_key(self, **kwargs):
 | 
						|
        """Export this ECC key.
 | 
						|
 | 
						|
        Args:
 | 
						|
          format (string):
 | 
						|
            The format to use for encoding the key:
 | 
						|
 | 
						|
            - *'DER'*. The key will be encoded in ASN.1 DER format (binary).
 | 
						|
              For a public key, the ASN.1 ``subjectPublicKeyInfo`` structure
 | 
						|
              defined in `RFC5480`_ will be used.
 | 
						|
              For a private key, the ASN.1 ``ECPrivateKey`` structure defined
 | 
						|
              in `RFC5915`_ is used instead (possibly within a PKCS#8 envelope,
 | 
						|
              see the ``use_pkcs8`` flag below).
 | 
						|
            - *'PEM'*. The key will be encoded in a PEM_ envelope (ASCII).
 | 
						|
            - *'OpenSSH'*. The key will be encoded in the OpenSSH_ format
 | 
						|
              (ASCII, public keys only).
 | 
						|
 | 
						|
          passphrase (byte string or string):
 | 
						|
            The passphrase to use for protecting the private key.
 | 
						|
 | 
						|
          use_pkcs8 (boolean):
 | 
						|
            If ``True`` (default and recommended), the `PKCS#8`_ representation
 | 
						|
            will be used.
 | 
						|
 | 
						|
            If ``False``, the much weaker `PEM encryption`_ mechanism will be used.
 | 
						|
 | 
						|
          protection (string):
 | 
						|
            When a private key is exported with password-protection
 | 
						|
            and PKCS#8 (both ``DER`` and ``PEM`` formats), this parameter MUST be
 | 
						|
            present and be a valid algorithm supported by :mod:`Crypto.IO.PKCS8`.
 | 
						|
            It is recommended to use ``PBKDF2WithHMAC-SHA1AndAES128-CBC``.
 | 
						|
 | 
						|
          compress (boolean):
 | 
						|
            If ``True``, a more compact representation of the public key
 | 
						|
            (X-coordinate only) is used.
 | 
						|
 | 
						|
            If ``False`` (default), the full public key (in both its
 | 
						|
            coordinates) will be exported.
 | 
						|
 | 
						|
        .. warning::
 | 
						|
            If you don't provide a passphrase, the private key will be
 | 
						|
            exported in the clear!
 | 
						|
 | 
						|
        .. note::
 | 
						|
            When exporting a private key with password-protection and `PKCS#8`_
 | 
						|
            (both ``DER`` and ``PEM`` formats), any extra parameters
 | 
						|
            is passed to :mod:`Crypto.IO.PKCS8`.
 | 
						|
 | 
						|
        .. _PEM:        http://www.ietf.org/rfc/rfc1421.txt
 | 
						|
        .. _`PEM encryption`: http://www.ietf.org/rfc/rfc1423.txt
 | 
						|
        .. _`PKCS#8`:   http://www.ietf.org/rfc/rfc5208.txt
 | 
						|
        .. _OpenSSH:    http://www.openssh.com/txt/rfc5656.txt
 | 
						|
        .. _RFC5480:    https://tools.ietf.org/html/rfc5480
 | 
						|
        .. _RFC5915:    http://www.ietf.org/rfc/rfc5915.txt
 | 
						|
 | 
						|
        Returns:
 | 
						|
            A multi-line string (for PEM and OpenSSH) or bytes (for DER) with the encoded key.
 | 
						|
        """
 | 
						|
 | 
						|
        args = kwargs.copy()
 | 
						|
        ext_format = args.pop("format")
 | 
						|
        if ext_format not in ("PEM", "DER", "OpenSSH"):
 | 
						|
            raise ValueError("Unknown format '%s'" % ext_format)
 | 
						|
 | 
						|
        compress = args.pop("compress", False)
 | 
						|
 | 
						|
        if self.has_private():
 | 
						|
            passphrase = args.pop("passphrase", None)
 | 
						|
            if is_string(passphrase):
 | 
						|
                passphrase = tobytes(passphrase)
 | 
						|
                if not passphrase:
 | 
						|
                    raise ValueError("Empty passphrase")
 | 
						|
            use_pkcs8 = args.pop("use_pkcs8", True)
 | 
						|
            if ext_format == "PEM":
 | 
						|
                if use_pkcs8:
 | 
						|
                    if passphrase:
 | 
						|
                        return self._export_private_encrypted_pkcs8_in_clear_pem(passphrase, **args)
 | 
						|
                    else:
 | 
						|
                        return self._export_private_clear_pkcs8_in_clear_pem()
 | 
						|
                else:
 | 
						|
                    return self._export_private_pem(passphrase, **args)
 | 
						|
            elif ext_format == "DER":
 | 
						|
                # DER
 | 
						|
                if passphrase and not use_pkcs8:
 | 
						|
                    raise ValueError("Private keys can only be encrpyted with DER using PKCS#8")
 | 
						|
                if use_pkcs8:
 | 
						|
                    return self._export_pkcs8(passphrase=passphrase, **args)
 | 
						|
                else:
 | 
						|
                    return self._export_private_der()
 | 
						|
            else:
 | 
						|
                raise ValueError("Private keys cannot be exported in OpenSSH format")
 | 
						|
        else:  # Public key
 | 
						|
            if args:
 | 
						|
                raise ValueError("Unexpected parameters: '%s'" % args)
 | 
						|
            if ext_format == "PEM":
 | 
						|
                return self._export_public_pem(compress)
 | 
						|
            elif ext_format == "DER":
 | 
						|
                return self._export_subjectPublicKeyInfo(compress)
 | 
						|
            else:
 | 
						|
                return self._export_openssh(compress)
 | 
						|
 | 
						|
 | 
						|
def generate(**kwargs):
 | 
						|
    """Generate a new private key on the given curve.
 | 
						|
 | 
						|
    Args:
 | 
						|
 | 
						|
      curve (string):
 | 
						|
        Mandatory. It must be "P-256", "prime256v1" or "secp256r1".
 | 
						|
 | 
						|
      randfunc (callable):
 | 
						|
        Optional. The RNG to read randomness from.
 | 
						|
        If ``None``, :func:`Crypto.Random.get_random_bytes` is used.
 | 
						|
    """
 | 
						|
 | 
						|
    curve = kwargs.pop("curve")
 | 
						|
    randfunc = kwargs.pop("randfunc", get_random_bytes)
 | 
						|
    if kwargs:
 | 
						|
        raise TypeError("Unknown parameters: " + str(kwargs))
 | 
						|
 | 
						|
    d = Integer.random_range(min_inclusive=1,
 | 
						|
                             max_exclusive=_curve.order,
 | 
						|
                             randfunc=randfunc)
 | 
						|
 | 
						|
    return EccKey(curve=curve, d=d)
 | 
						|
 | 
						|
 | 
						|
def construct(**kwargs):
 | 
						|
    """Build a new ECC key (private or public) starting
 | 
						|
    from some base components.
 | 
						|
 | 
						|
    Args:
 | 
						|
 | 
						|
      curve (string):
 | 
						|
        Mandatory. It must be "P-256", "prime256v1" or "secp256r1".
 | 
						|
 | 
						|
      d (integer):
 | 
						|
        Only for a private key. It must be in the range ``[1..order-1]``.
 | 
						|
 | 
						|
      point_x (integer):
 | 
						|
        Mandatory for a public key. X coordinate (affine) of the ECC point.
 | 
						|
 | 
						|
      point_y (integer):
 | 
						|
        Mandatory for a public key. Y coordinate (affine) of the ECC point.
 | 
						|
 | 
						|
    Returns:
 | 
						|
      :class:`EccKey` : a new ECC key object
 | 
						|
    """
 | 
						|
 | 
						|
    point_x = kwargs.pop("point_x", None)
 | 
						|
    point_y = kwargs.pop("point_y", None)
 | 
						|
 | 
						|
    if "point" in kwargs:
 | 
						|
        raise TypeError("Unknown keyword: point")
 | 
						|
 | 
						|
    if None not in (point_x, point_y):
 | 
						|
        # ValueError is raised if the point is not on the curve
 | 
						|
        kwargs["point"] = EccPoint(point_x, point_y)
 | 
						|
 | 
						|
    # Validate that the private key matches the public one
 | 
						|
    d = kwargs.get("d", None)
 | 
						|
    if d is not None and "point" in kwargs:
 | 
						|
        pub_key = _curve.G * d
 | 
						|
        if pub_key.xy != (point_x, point_y):
 | 
						|
            raise ValueError("Private and public ECC keys do not match")
 | 
						|
 | 
						|
    return EccKey(**kwargs)
 | 
						|
 | 
						|
 | 
						|
def _import_public_der(curve_oid, ec_point):
 | 
						|
    """Convert an encoded EC point into an EccKey object
 | 
						|
 | 
						|
    curve_name: string with the OID of the curve
 | 
						|
    ec_point: byte string with the EC point (not DER encoded)
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    # We only support P-256 named curves for now
 | 
						|
    if curve_oid != _curve.oid:
 | 
						|
        raise UnsupportedEccFeature("Unsupported ECC curve (OID: %s)" % curve_oid)
 | 
						|
 | 
						|
    # See 2.2 in RFC5480 and 2.3.3 in SEC1
 | 
						|
    # The first byte is:
 | 
						|
    # - 0x02:   compressed, only X-coordinate, Y-coordinate is even
 | 
						|
    # - 0x03:   compressed, only X-coordinate, Y-coordinate is odd
 | 
						|
    # - 0x04:   uncompressed, X-coordinate is followed by Y-coordinate
 | 
						|
    #
 | 
						|
    # PAI is in theory encoded as 0x00.
 | 
						|
 | 
						|
    order_bytes = _curve.order.size_in_bytes()
 | 
						|
    point_type = bord(ec_point[0])
 | 
						|
 | 
						|
    # Uncompressed point
 | 
						|
    if point_type == 0x04:
 | 
						|
        if len(ec_point) != (1 + 2 * order_bytes):
 | 
						|
            raise ValueError("Incorrect EC point length")
 | 
						|
        x = Integer.from_bytes(ec_point[1:order_bytes+1])
 | 
						|
        y = Integer.from_bytes(ec_point[order_bytes+1:])
 | 
						|
    # Compressed point
 | 
						|
    elif point_type in (0x02, 0x3):
 | 
						|
        if len(ec_point) != (1 + order_bytes):
 | 
						|
            raise ValueError("Incorrect EC point length")
 | 
						|
        x = Integer.from_bytes(ec_point[1:])
 | 
						|
        y = (x**3 - x*3 + _curve.b).sqrt(_curve.p)    # Short Weierstrass
 | 
						|
        if point_type == 0x02 and y.is_odd():
 | 
						|
            y = _curve.p - y
 | 
						|
        if point_type == 0x03 and y.is_even():
 | 
						|
            y = _curve.p - y
 | 
						|
    else:
 | 
						|
        raise ValueError("Incorrect EC point encoding")
 | 
						|
 | 
						|
    return construct(curve="P-256", point_x=x, point_y=y)
 | 
						|
 | 
						|
 | 
						|
def _import_subjectPublicKeyInfo(encoded, *kwargs):
 | 
						|
    """Convert a subjectPublicKeyInfo into an EccKey object"""
 | 
						|
 | 
						|
    # See RFC5480
 | 
						|
 | 
						|
    # Parse the generic subjectPublicKeyInfo structure
 | 
						|
    oid, ec_point, params = _expand_subject_public_key_info(encoded)
 | 
						|
 | 
						|
    # ec_point must be an encoded OCTET STRING
 | 
						|
    # params is encoded ECParameters
 | 
						|
 | 
						|
    # We accept id-ecPublicKey, id-ecDH, id-ecMQV without making any
 | 
						|
    # distiction for now.
 | 
						|
 | 
						|
    # Restrictions can be captured in the key usage certificate
 | 
						|
    # extension
 | 
						|
    unrestricted_oid = "1.2.840.10045.2.1"
 | 
						|
    ecdh_oid = "1.3.132.1.12"
 | 
						|
    ecmqv_oid = "1.3.132.1.13"
 | 
						|
 | 
						|
    if oid not in (unrestricted_oid, ecdh_oid, ecmqv_oid):
 | 
						|
        raise UnsupportedEccFeature("Unsupported ECC purpose (OID: %s)" % oid)
 | 
						|
 | 
						|
    # Parameters are mandatory for all three types
 | 
						|
    if not params:
 | 
						|
        raise ValueError("Missing ECC parameters")
 | 
						|
 | 
						|
    # ECParameters ::= CHOICE {
 | 
						|
    #   namedCurve         OBJECT IDENTIFIER
 | 
						|
    #   -- implicitCurve   NULL
 | 
						|
    #   -- specifiedCurve  SpecifiedECDomain
 | 
						|
    # }
 | 
						|
    #
 | 
						|
    # implicitCurve and specifiedCurve are not supported (as per RFC)
 | 
						|
    curve_oid = DerObjectId().decode(params).value
 | 
						|
 | 
						|
    return _import_public_der(curve_oid, ec_point)
 | 
						|
 | 
						|
 | 
						|
def _import_private_der(encoded, passphrase, curve_name=None):
 | 
						|
 | 
						|
    # ECPrivateKey ::= SEQUENCE {
 | 
						|
    #           version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
 | 
						|
    #           privateKey     OCTET STRING,
 | 
						|
    #           parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
 | 
						|
    #           publicKey  [1] BIT STRING OPTIONAL
 | 
						|
    #    }
 | 
						|
 | 
						|
    private_key = DerSequence().decode(encoded, nr_elements=(3, 4))
 | 
						|
    if private_key[0] != 1:
 | 
						|
        raise ValueError("Incorrect ECC private key version")
 | 
						|
 | 
						|
    try:
 | 
						|
        curve_name = DerObjectId(explicit=0).decode(private_key[2]).value
 | 
						|
    except ValueError:
 | 
						|
        pass
 | 
						|
 | 
						|
    if curve_name != _curve.oid:
 | 
						|
        raise UnsupportedEccFeature("Unsupported ECC curve (OID: %s)" % curve_name)
 | 
						|
 | 
						|
    scalar_bytes = DerOctetString().decode(private_key[1]).payload
 | 
						|
    order_bytes = _curve.order.size_in_bytes()
 | 
						|
    if len(scalar_bytes) != order_bytes:
 | 
						|
        raise ValueError("Private key is too small")
 | 
						|
    d = Integer.from_bytes(scalar_bytes)
 | 
						|
 | 
						|
    # Decode public key (if any, it must be P-256)
 | 
						|
    if len(private_key) == 4:
 | 
						|
        public_key_enc = DerBitString(explicit=1).decode(private_key[3]).value
 | 
						|
        public_key = _import_public_der(curve_name, public_key_enc)
 | 
						|
        point_x = public_key.pointQ.x
 | 
						|
        point_y = public_key.pointQ.y
 | 
						|
    else:
 | 
						|
        point_x = point_y = None
 | 
						|
 | 
						|
    return construct(curve="P-256", d=d, point_x=point_x, point_y=point_y)
 | 
						|
 | 
						|
 | 
						|
def _import_pkcs8(encoded, passphrase):
 | 
						|
 | 
						|
    # From RFC5915, Section 1:
 | 
						|
    #
 | 
						|
    # Distributing an EC private key with PKCS#8 [RFC5208] involves including:
 | 
						|
    # a) id-ecPublicKey, id-ecDH, or id-ecMQV (from [RFC5480]) with the
 | 
						|
    #    namedCurve as the parameters in the privateKeyAlgorithm field; and
 | 
						|
    # b) ECPrivateKey in the PrivateKey field, which is an OCTET STRING.
 | 
						|
 | 
						|
    algo_oid, private_key, params = PKCS8.unwrap(encoded, passphrase)
 | 
						|
 | 
						|
    # We accept id-ecPublicKey, id-ecDH, id-ecMQV without making any
 | 
						|
    # distiction for now.
 | 
						|
    unrestricted_oid = "1.2.840.10045.2.1"
 | 
						|
    ecdh_oid = "1.3.132.1.12"
 | 
						|
    ecmqv_oid = "1.3.132.1.13"
 | 
						|
 | 
						|
    if algo_oid not in (unrestricted_oid, ecdh_oid, ecmqv_oid):
 | 
						|
        raise UnsupportedEccFeature("Unsupported ECC purpose (OID: %s)" % algo_oid)
 | 
						|
 | 
						|
    curve_name = DerObjectId().decode(params).value
 | 
						|
 | 
						|
    return _import_private_der(private_key, passphrase, curve_name)
 | 
						|
 | 
						|
 | 
						|
def _import_x509_cert(encoded, *kwargs):
 | 
						|
 | 
						|
    sp_info = _extract_subject_public_key_info(encoded)
 | 
						|
    return _import_subjectPublicKeyInfo(sp_info)
 | 
						|
 | 
						|
 | 
						|
def _import_der(encoded, passphrase):
 | 
						|
 | 
						|
    try:
 | 
						|
        return _import_subjectPublicKeyInfo(encoded, passphrase)
 | 
						|
    except UnsupportedEccFeature as err:
 | 
						|
        raise err
 | 
						|
    except (ValueError, TypeError, IndexError):
 | 
						|
        pass
 | 
						|
 | 
						|
    try:
 | 
						|
        return _import_x509_cert(encoded, passphrase)
 | 
						|
    except UnsupportedEccFeature as err:
 | 
						|
        raise err
 | 
						|
    except (ValueError, TypeError, IndexError):
 | 
						|
        pass
 | 
						|
 | 
						|
    try:
 | 
						|
        return _import_private_der(encoded, passphrase)
 | 
						|
    except UnsupportedEccFeature as err:
 | 
						|
        raise err
 | 
						|
    except (ValueError, TypeError, IndexError):
 | 
						|
        pass
 | 
						|
 | 
						|
    try:
 | 
						|
        return _import_pkcs8(encoded, passphrase)
 | 
						|
    except UnsupportedEccFeature as err:
 | 
						|
        raise err
 | 
						|
    except (ValueError, TypeError, IndexError):
 | 
						|
        pass
 | 
						|
 | 
						|
    raise ValueError("Not an ECC DER key")
 | 
						|
 | 
						|
 | 
						|
def _import_openssh(encoded):
 | 
						|
    keystring = binascii.a2b_base64(encoded.split(b' ')[1])
 | 
						|
 | 
						|
    keyparts = []
 | 
						|
    while len(keystring) > 4:
 | 
						|
        lk = struct.unpack(">I", keystring[:4])[0]
 | 
						|
        keyparts.append(keystring[4:4 + lk])
 | 
						|
        keystring = keystring[4 + lk:]
 | 
						|
 | 
						|
    if keyparts[1] != b"nistp256":
 | 
						|
        raise ValueError("Unsupported ECC curve")
 | 
						|
 | 
						|
    return _import_public_der(_curve.oid, keyparts[2])
 | 
						|
 | 
						|
 | 
						|
def import_key(encoded, passphrase=None):
 | 
						|
    """Import an ECC key (public or private).
 | 
						|
 | 
						|
    Args:
 | 
						|
      encoded (bytes or multi-line string):
 | 
						|
        The ECC key to import.
 | 
						|
 | 
						|
        An ECC **public** key can be:
 | 
						|
 | 
						|
        - An X.509 certificate, binary (DER) or ASCII (PEM)
 | 
						|
        - An X.509 ``subjectPublicKeyInfo``, binary (DER) or ASCII (PEM)
 | 
						|
        - An OpenSSH line (e.g. the content of ``~/.ssh/id_ecdsa``, ASCII)
 | 
						|
 | 
						|
        An ECC **private** key can be:
 | 
						|
 | 
						|
        - In binary format (DER, see section 3 of `RFC5915`_ or `PKCS#8`_)
 | 
						|
        - In ASCII format (PEM or OpenSSH)
 | 
						|
 | 
						|
        Private keys can be in the clear or password-protected.
 | 
						|
 | 
						|
        For details about the PEM encoding, see `RFC1421`_/`RFC1423`_.
 | 
						|
 | 
						|
      passphrase (byte string):
 | 
						|
        The passphrase to use for decrypting a private key.
 | 
						|
        Encryption may be applied protected at the PEM level or at the PKCS#8 level.
 | 
						|
        This parameter is ignored if the key in input is not encrypted.
 | 
						|
 | 
						|
    Returns:
 | 
						|
      :class:`EccKey` : a new ECC key object
 | 
						|
 | 
						|
    Raises:
 | 
						|
      ValueError: when the given key cannot be parsed (possibly because
 | 
						|
        the pass phrase is wrong).
 | 
						|
 | 
						|
    .. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt
 | 
						|
    .. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt
 | 
						|
    .. _RFC5915: http://www.ietf.org/rfc/rfc5915.txt
 | 
						|
    .. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt
 | 
						|
    """
 | 
						|
 | 
						|
    encoded = tobytes(encoded)
 | 
						|
    if passphrase is not None:
 | 
						|
        passphrase = tobytes(passphrase)
 | 
						|
 | 
						|
    # PEM
 | 
						|
    if encoded.startswith(b'-----'):
 | 
						|
        der_encoded, marker, enc_flag = PEM.decode(tostr(encoded), passphrase)
 | 
						|
        if enc_flag:
 | 
						|
            passphrase = None
 | 
						|
        try:
 | 
						|
            result = _import_der(der_encoded, passphrase)
 | 
						|
        except UnsupportedEccFeature as uef:
 | 
						|
            raise uef
 | 
						|
        except ValueError:
 | 
						|
            raise ValueError("Invalid DER encoding inside the PEM file")
 | 
						|
        return result
 | 
						|
 | 
						|
    # OpenSSH
 | 
						|
    if encoded.startswith(b'ecdsa-sha2-'):
 | 
						|
        return _import_openssh(encoded)
 | 
						|
 | 
						|
    # DER
 | 
						|
    if bord(encoded[0]) == 0x30:
 | 
						|
        return _import_der(encoded, passphrase)
 | 
						|
 | 
						|
    raise ValueError("ECC key format is not supported")
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
 | 
						|
    import time
 | 
						|
 | 
						|
    d = 0xc51e4753afdec1e6b6c6a5b992f43f8dd0c7a8933072708b6522468b2ffb06fd
 | 
						|
 | 
						|
    point = generate(curve="P-256").pointQ
 | 
						|
    count = 3000
 | 
						|
 | 
						|
    start = time.time()
 | 
						|
    for x in range(count):
 | 
						|
        point = point * d
 | 
						|
    print("(mul)", (time.time() - start) / count * 1000, "ms")
 | 
						|
 | 
						|
    start = time.time()
 | 
						|
    for x in range(count):
 | 
						|
        point *= d
 | 
						|
    print("(imul)", (time.time() - start) / count * 1000, "ms")
 | 
						|
 | 
						|
 |