123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922 |
- # ===================================================================
- #
- # 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 Cryptodome.Util.py3compat import bord, tobytes, tostr, bchr, is_string
- from Cryptodome.Math.Numbers import Integer
- from Cryptodome.Random import get_random_bytes
- from Cryptodome.Util.asn1 import (DerObjectId, DerOctetString, DerSequence,
- DerBitString)
- from Cryptodome.IO import PKCS8, PEM
- from Cryptodome.PublicKey import (_expand_subject_public_key_info,
- _create_subject_public_key_info,
- _extract_subject_public_key_info)
- 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):
- self._x = Integer(x)
- self._y = Integer(y)
- # Buffers
- self._common = Integer(0)
- self._tmp1 = Integer(0)
- self._x3 = Integer(0)
- self._y3 = Integer(0)
- def set(self, point):
- self._x = Integer(point._x)
- self._y = Integer(point._y)
- return self
- def __eq__(self, point):
- return self._x == point._x and self._y == point._y
- def __neg__(self):
- if self.is_point_at_infinity():
- return self.point_at_infinity()
- return EccPoint(self._x, _curve.p - self._y)
- def copy(self):
- return EccPoint(self._x, self._y)
- def is_point_at_infinity(self):
- return not (self._x or self._y)
- @staticmethod
- def point_at_infinity():
- return EccPoint(0, 0)
- @property
- def x(self):
- if self.is_point_at_infinity():
- raise ValueError("Point at infinity")
- return self._x
- @property
- def y(self):
- if self.is_point_at_infinity():
- raise ValueError("Point at infinity")
- return self._y
- def double(self):
- """Double this point (in-place operation).
- :Return:
- :class:`EccPoint` : this same object (to enable chaining)
- """
- if not self._y:
- return self.point_at_infinity()
- common = self._common
- tmp1 = self._tmp1
- x3 = self._x3
- y3 = self._y3
- # common = (pow(self._x, 2, _curve.p) * 3 - 3) * (self._y << 1).inverse(_curve.p) % _curve.p
- common.set(self._x)
- common.inplace_pow(2, _curve.p)
- common *= 3
- common -= 3
- tmp1.set(self._y)
- tmp1 <<= 1
- tmp1.inplace_inverse(_curve.p)
- common *= tmp1
- common %= _curve.p
- # x3 = (pow(common, 2, _curve.p) - 2 * self._x) % _curve.p
- x3.set(common)
- x3.inplace_pow(2, _curve.p)
- x3 -= self._x
- x3 -= self._x
- while x3.is_negative():
- x3 += _curve.p
- # y3 = ((self._x - x3) * common - self._y) % _curve.p
- y3.set(self._x)
- y3 -= x3
- y3 *= common
- y3 -= self._y
- y3 %= _curve.p
- self._x.set(x3)
- self._y.set(y3)
- return self
- def __iadd__(self, point):
- """Add a second point to this one"""
- if self.is_point_at_infinity():
- return self.set(point)
- if point.is_point_at_infinity():
- return self
- if self == point:
- return self.double()
- if self._x == point._x:
- return self.set(self.point_at_infinity())
- common = self._common
- tmp1 = self._tmp1
- x3 = self._x3
- y3 = self._y3
- # common = (point._y - self._y) * (point._x - self._x).inverse(_curve.p) % _curve.p
- common.set(point._y)
- common -= self._y
- tmp1.set(point._x)
- tmp1 -= self._x
- tmp1.inplace_inverse(_curve.p)
- common *= tmp1
- common %= _curve.p
- # x3 = (pow(common, 2, _curve.p) - self._x - point._x) % _curve.p
- x3.set(common)
- x3.inplace_pow(2, _curve.p)
- x3 -= self._x
- x3 -= point._x
- while x3.is_negative():
- x3 += _curve.p
- # y3 = ((self._x - x3) * common - self._y) % _curve.p
- y3.set(self._x)
- y3 -= x3
- y3 *= common
- y3 -= self._y
- y3 %= _curve.p
- self._x.set(x3)
- self._y.set(y3)
- return self
- def __add__(self, point):
- """Return a new point, the addition of this one and another"""
- result = self.copy()
- result += point
- return result
- def __mul__(self, scalar):
- """Return a new point, the scalar product of this one"""
- if scalar < 0:
- raise ValueError("Scalar multiplication only defined for non-negative integers")
- # Trivial results
- if scalar == 0 or self.is_point_at_infinity():
- return self.point_at_infinity()
- elif scalar == 1:
- return self.copy()
- # Scalar randomization
- scalar_blind = Integer.random(exact_bits=64) * _curve.order + scalar
- # Montgomery key ladder
- r = [self.point_at_infinity().copy(), self.copy()]
- bit_size = int(scalar_blind.size_in_bits())
- scalar_int = int(scalar_blind)
- for i in range(bit_size, -1, -1):
- di = scalar_int >> i & 1
- r[di ^ 1] += r[di]
- r[di].double()
- return r[0]
- _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),
- ("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:`Cryptodome.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:`Cryptodome.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:`Cryptodome.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):
- kwargs["point"] = EccPoint(point_x, point_y)
- # Validate that the point is on the P-256 curve
- eq1 = pow(Integer(point_y), 2, _curve.p)
- x = Integer(point_x)
- eq2 = pow(x, 3, _curve.p)
- x *= -3
- eq2 += x
- eq2 += _curve.b
- eq2 %= _curve.p
- if eq1 != eq2:
- raise ValueError("The point is not on the curve")
- # 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.x != point_x or pub_key.y != 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.
- unrestricted_oid = "1.2.840.10045.2.1" # Restrictions can be captured
- # in the key usage certificate
- # extension
- 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)" % 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:
- l = struct.unpack(">I", keystring[:4])[0]
- keyparts.append(keystring[4:4 + l])
- keystring = keystring[4 + l:]
- 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
- start = time.time()
- count = 30
- for x in range(count):
- _ = point * d
- print((time.time() - start) / count * 1000, "ms")
|