diff options
Diffstat (limited to 'freezed_deps/ecdsa/keys.py')
-rw-r--r-- | freezed_deps/ecdsa/keys.py | 1219 |
1 files changed, 1219 insertions, 0 deletions
diff --git a/freezed_deps/ecdsa/keys.py b/freezed_deps/ecdsa/keys.py new file mode 100644 index 0000000..172fdf5 --- /dev/null +++ b/freezed_deps/ecdsa/keys.py @@ -0,0 +1,1219 @@ +""" +Primary classes for performing signing and verification operations. + +.. glossary:: + + raw encoding + Conversion of public, private keys and signatures (which in + mathematical sense are integers or pairs of integers) to strings of + bytes that does not use any special tags or encoding rules. + For any given curve, all keys of the same type or signatures will be + encoded to byte strings of the same length. In more formal sense, + the integers are encoded as big-endian, constant length byte strings, + where the string length is determined by the curve order (e.g. + for NIST256p the order is 256 bits long, so the private key will be 32 + bytes long while public key will be 64 bytes long). The encoding of a + single integer is zero-padded on the left if the numerical value is + low. In case of public keys and signatures, which are comprised of two + integers, the integers are simply concatenated. + + uncompressed + The most common formatting specified in PKIX standards. Specified in + X9.62 and SEC1 standards. The only difference between it and + :term:`raw encoding` is the prepending of a 0x04 byte. Thus an + uncompressed NIST256p public key encoding will be 65 bytes long. + + compressed + The public point representation that uses half of bytes of the + :term:`uncompressed` encoding (rounded up). It uses the first byte of + the encoding to specify the sign of the y coordinate and encodes the + x coordinate as-is. The first byte of the encoding is equal to + 0x02 or 0x03. Compressed encoding of NIST256p public key will be 33 + bytes long. + + hybrid + A combination of :term:`uncompressed` and :term:`compressed` encodings. + Both x and y coordinates are stored just as in :term:`compressed` + encoding, but the first byte reflects the sign of the y coordinate. The + first byte of the encoding will be equal to 0x06 or 0x7. Hybrid + encoding of NIST256p public key will be 65 bytes long. + + PEM + The acronym stands for Privacy Enhanced Email, but currently it is used + primarily as the way to encode :term:`DER` objects into text that can + be either easily copy-pasted or transferred over email. + It uses headers like ``-----BEGIN <type of contents>-----`` and footers + like ``-----END <type of contents>-----`` to separate multiple + types of objects in the same file or the object from the surrounding + comments. The actual object stored is base64 encoded. + + DER + Distinguished Encoding Rules, the way to encode :term:`ASN.1` objects + deterministically and uniquely into byte strings. + + ASN.1 + Abstract Syntax Notation 1 is a standard description language for + specifying serialisation and deserialisation of data structures in a + portable and cross-platform way. + + bytes-like object + All the types that implement the buffer protocol. That includes + ``str`` (only on python2), ``bytes``, ``bytesarray``, ``array.array` + and ``memoryview`` of those objects. + Please note that ``array.array` serialisation (converting it to byte + string) is endianess dependant! Signature computed over ``array.array`` + of integers on a big-endian system will not be verified on a + little-endian system and vice-versa. +""" + +import binascii +from hashlib import sha1 +from six import PY3, b +from . import ecdsa +from . import der +from . import rfc6979 +from . import ellipticcurve +from .curves import NIST192p, find_curve +from .numbertheory import square_root_mod_prime, SquareRootError +from .ecdsa import RSZeroError +from .util import string_to_number, number_to_string, randrange +from .util import sigencode_string, sigdecode_string +from .util import oid_ecPublicKey, encoded_oid_ecPublicKey, MalformedSignature +from ._compat import normalise_bytes + + +__all__ = ["BadSignatureError", "BadDigestError", "VerifyingKey", "SigningKey", + "MalformedPointError"] + + +class BadSignatureError(Exception): + """ + Raised when verification of signature failed. + + Will be raised irrespective of reason of the failure: + + * the calculated or provided hash does not match the signature + * the signature does not match the curve/public key + * the encoding of the signature is malformed + * the size of the signature does not match the curve of the VerifyingKey + """ + + pass + + +class BadDigestError(Exception): + """Raised in case the selected hash is too large for the curve.""" + + pass + + +class MalformedPointError(AssertionError): + """Raised in case the encoding of private or public key is malformed.""" + + pass + + +class VerifyingKey(object): + """ + Class for handling keys that can verify signatures (public keys). + + :ivar ecdsa.curves.Curve curve: The Curve over which all the cryptographic + operations will take place + :ivar default_hashfunc: the function that will be used for hashing the + data. Should implement the same API as hashlib.sha1 + :vartype default_hashfunc: callable + :ivar pubkey: the actual public key + :vartype pubkey: ecdsa.ecdsa.Public_key + """ + + def __init__(self, _error__please_use_generate=None): + """Unsupported, please use one of the classmethods to initialise.""" + if not _error__please_use_generate: + raise TypeError("Please use VerifyingKey.generate() to " + "construct me") + self.curve = None + self.default_hashfunc = None + self.pubkey = None + + def __repr__(self): + pub_key = self.to_string("compressed") + return "VerifyingKey.from_string({0!r}, {1!r}, {2})".format( + pub_key, self.curve, self.default_hashfunc().name) + + def __eq__(self, other): + """Return True if the points are identical, False otherwise.""" + if isinstance(other, VerifyingKey): + return self.curve == other.curve \ + and self.pubkey == other.pubkey + return NotImplemented + + @classmethod + def from_public_point(cls, point, curve=NIST192p, hashfunc=sha1, + validate_point=True): + """ + Initialise the object from a Point object. + + This is a low-level method, generally you will not want to use it. + + :param point: The point to wrap around, the actual public key + :type point: ecdsa.ellipticcurve.Point + :param curve: The curve on which the point needs to reside, defaults + to NIST192p + :type curve: ecdsa.curves.Curve + :param hashfunc: The default hash function that will be used for + verification, needs to implement the same interface + as hashlib.sha1 + :type hashfunc: callable + :type bool validate_point: whether to check if the point lies on curve + should always be used if the public point is not a result + of our own calculation + + :raises MalformedPointError: if the public point does not lie on the + curve + + :return: Initialised VerifyingKey object + :rtype: VerifyingKey + """ + self = cls(_error__please_use_generate=True) + if not isinstance(point, ellipticcurve.PointJacobi): + point = ellipticcurve.PointJacobi.from_affine(point) + self.curve = curve + self.default_hashfunc = hashfunc + try: + self.pubkey = ecdsa.Public_key(curve.generator, point, + validate_point) + except ecdsa.InvalidPointError: + raise MalformedPointError("Point does not lie on the curve") + self.pubkey.order = curve.order + return self + + def precompute(self): + self.pubkey.point = ellipticcurve.PointJacobi.from_affine( + self.pubkey.point, True) + + @staticmethod + def _from_raw_encoding(string, curve): + """ + Decode public point from :term:`raw encoding`. + + :term:`raw encoding` is the same as the :term:`uncompressed` encoding, + but without the 0x04 byte at the beginning. + """ + order = curve.order + # real assert, from_string() should not call us with different length + assert len(string) == curve.verifying_key_length + xs = string[:curve.baselen] + ys = string[curve.baselen:] + if len(xs) != curve.baselen: + raise MalformedPointError("Unexpected length of encoded x") + if len(ys) != curve.baselen: + raise MalformedPointError("Unexpected length of encoded y") + x = string_to_number(xs) + y = string_to_number(ys) + + return ellipticcurve.PointJacobi(curve.curve, x, y, 1, order) + + @staticmethod + def _from_compressed(string, curve): + """Decode public point from compressed encoding.""" + if string[:1] not in (b('\x02'), b('\x03')): + raise MalformedPointError("Malformed compressed point encoding") + + is_even = string[:1] == b('\x02') + x = string_to_number(string[1:]) + order = curve.order + p = curve.curve.p() + alpha = (pow(x, 3, p) + (curve.curve.a() * x) + curve.curve.b()) % p + try: + beta = square_root_mod_prime(alpha, p) + except SquareRootError as e: + raise MalformedPointError( + "Encoding does not correspond to a point on curve", e) + if is_even == bool(beta & 1): + y = p - beta + else: + y = beta + return ellipticcurve.PointJacobi(curve.curve, x, y, 1, order) + + @classmethod + def _from_hybrid(cls, string, curve, validate_point): + """Decode public point from hybrid encoding.""" + # real assert, from_string() should not call us with different types + assert string[:1] in (b('\x06'), b('\x07')) + + # primarily use the uncompressed as it's easiest to handle + point = cls._from_raw_encoding(string[1:], curve) + + # but validate if it's self-consistent if we're asked to do that + if validate_point \ + and (point.y() & 1 and string[:1] != b('\x07') + or (not point.y() & 1) and string[:1] != b('\x06')): + raise MalformedPointError("Inconsistent hybrid point encoding") + + return point + + @classmethod + def from_string(cls, string, curve=NIST192p, hashfunc=sha1, + validate_point=True): + """ + Initialise the object from byte encoding of public key. + + The method does accept and automatically detect the type of point + encoding used. It supports the :term:`raw encoding`, + :term:`uncompressed`, :term:`compressed` and :term:`hybrid` encodings. + + Note, while the method is named "from_string" it's a misnomer from + Python 2 days when there were no binary strings. In Python 3 the + input needs to be a bytes-like object. + + :param string: single point encoding of the public key + :type string: :term:`bytes-like object` + :param curve: the curve on which the public key is expected to lie + :type curve: ecdsa.curves.Curve + :param hashfunc: The default hash function that will be used for + verification, needs to implement the same interface as hashlib.sha1 + :type hashfunc: callable + :param validate_point: whether to verify that the point lies on the + provided curve or not, defaults to True + :type validate_point: bool + + :raises MalformedPointError: if the public point does not lie on the + curve or the encoding is invalid + + :return: Initialised VerifyingKey object + :rtype: VerifyingKey + """ + string = normalise_bytes(string) + sig_len = len(string) + if sig_len == curve.verifying_key_length: + point = cls._from_raw_encoding(string, curve) + elif sig_len == curve.verifying_key_length + 1: + if string[:1] in (b('\x06'), b('\x07')): + point = cls._from_hybrid(string, curve, validate_point) + elif string[:1] == b('\x04'): + point = cls._from_raw_encoding(string[1:], curve) + else: + raise MalformedPointError( + "Invalid X9.62 encoding of the public point") + elif sig_len == curve.baselen + 1: + point = cls._from_compressed(string, curve) + else: + raise MalformedPointError( + "Length of string does not match lengths of " + "any of the supported encodings of {0} " + "curve.".format(curve.name)) + return cls.from_public_point(point, curve, hashfunc, + validate_point) + + @classmethod + def from_pem(cls, string, hashfunc=sha1): + """ + Initialise from public key stored in :term:`PEM` format. + + The PEM header of the key should be ``BEGIN PUBLIC KEY``. + + See the :func:`~VerifyingKey.from_der()` method for details of the + format supported. + + Note: only a single PEM object encoding is supported in provided + string. + + :param string: text with PEM-encoded public ECDSA key + :type string: str + + :return: Initialised VerifyingKey object + :rtype: VerifyingKey + """ + return cls.from_der(der.unpem(string), hashfunc=hashfunc) + + @classmethod + def from_der(cls, string, hashfunc=sha1): + """ + Initialise the key stored in :term:`DER` format. + + The expected format of the key is the SubjectPublicKeyInfo structure + from RFC5912 (for RSA keys, it's known as the PKCS#1 format):: + + SubjectPublicKeyInfo {PUBLIC-KEY: IOSet} ::= SEQUENCE { + algorithm AlgorithmIdentifier {PUBLIC-KEY, {IOSet}}, + subjectPublicKey BIT STRING + } + + Note: only public EC keys are supported by this method. The + SubjectPublicKeyInfo.algorithm.algorithm field must specify + id-ecPublicKey (see RFC3279). + + Only the named curve encoding is supported, thus the + SubjectPublicKeyInfo.algorithm.parameters field needs to be an + object identifier. A sequence in that field indicates an explicit + parameter curve encoding, this format is not supported. A NULL object + in that field indicates an "implicitlyCA" encoding, where the curve + parameters come from CA certificate, those, again, are not supported. + + :param string: binary string with the DER encoding of public ECDSA key + :type string: bytes-like object + + :return: Initialised VerifyingKey object + :rtype: VerifyingKey + """ + string = normalise_bytes(string) + # [[oid_ecPublicKey,oid_curve], point_str_bitstring] + s1, empty = der.remove_sequence(string) + if empty != b"": + raise der.UnexpectedDER("trailing junk after DER pubkey: %s" % + binascii.hexlify(empty)) + s2, point_str_bitstring = der.remove_sequence(s1) + # s2 = oid_ecPublicKey,oid_curve + oid_pk, rest = der.remove_object(s2) + oid_curve, empty = der.remove_object(rest) + if empty != b"": + raise der.UnexpectedDER("trailing junk after DER pubkey objects: %s" % + binascii.hexlify(empty)) + if not oid_pk == oid_ecPublicKey: + raise der.UnexpectedDER("Unexpected object identifier in DER " + "encoding: {0!r}".format(oid_pk)) + curve = find_curve(oid_curve) + point_str, empty = der.remove_bitstring(point_str_bitstring, 0) + if empty != b"": + raise der.UnexpectedDER("trailing junk after pubkey pointstring: %s" % + binascii.hexlify(empty)) + # raw encoding of point is invalid in DER files + if len(point_str) == curve.verifying_key_length: + raise der.UnexpectedDER("Malformed encoding of public point") + return cls.from_string(point_str, curve, hashfunc=hashfunc) + + @classmethod + def from_public_key_recovery(cls, signature, data, curve, hashfunc=sha1, + sigdecode=sigdecode_string): + """ + Return keys that can be used as verifiers of the provided signature. + + Tries to recover the public key that can be used to verify the + signature, usually returns two keys like that. + + :param signature: the byte string with the encoded signature + :type signature: bytes-like object + :param data: the data to be hashed for signature verification + :type data: bytes-like object + :param curve: the curve over which the signature was performed + :type curve: ecdsa.curves.Curve + :param hashfunc: The default hash function that will be used for + verification, needs to implement the same interface as hashlib.sha1 + :type hashfunc: callable + :param sigdecode: Callable to define the way the signature needs to + be decoded to an object, needs to handle `signature` as the + first parameter, the curve order (an int) as the second and return + a tuple with two integers, "r" as the first one and "s" as the + second one. See :func:`ecdsa.util.sigdecode_string` and + :func:`ecdsa.util.sigdecode_der` for examples. + :type sigdecode: callable + + :return: Initialised VerifyingKey objects + :rtype: list of VerifyingKey + """ + data = normalise_bytes(data) + digest = hashfunc(data).digest() + return cls.from_public_key_recovery_with_digest( + signature, digest, curve, hashfunc=hashfunc, + sigdecode=sigdecode) + + @classmethod + def from_public_key_recovery_with_digest( + cls, signature, digest, curve, + hashfunc=sha1, sigdecode=sigdecode_string): + """ + Return keys that can be used as verifiers of the provided signature. + + Tries to recover the public key that can be used to verify the + signature, usually returns two keys like that. + + :param signature: the byte string with the encoded signature + :type signature: bytes-like object + :param digest: the hash value of the message signed by the signature + :type digest: bytes-like object + :param curve: the curve over which the signature was performed + :type curve: ecdsa.curves.Curve + :param hashfunc: The default hash function that will be used for + verification, needs to implement the same interface as hashlib.sha1 + :type hashfunc: callable + :param sigdecode: Callable to define the way the signature needs to + be decoded to an object, needs to handle `signature` as the + first parameter, the curve order (an int) as the second and return + a tuple with two integers, "r" as the first one and "s" as the + second one. See :func:`ecdsa.util.sigdecode_string` and + :func:`ecdsa.util.sigdecode_der` for examples. + :type sigdecode: callable + + + :return: Initialised VerifyingKey object + :rtype: VerifyingKey + """ + generator = curve.generator + r, s = sigdecode(signature, generator.order()) + sig = ecdsa.Signature(r, s) + + digest = normalise_bytes(digest) + digest_as_number = string_to_number(digest) + pks = sig.recover_public_keys(digest_as_number, generator) + + # Transforms the ecdsa.Public_key object into a VerifyingKey + verifying_keys = [cls.from_public_point(pk.point, curve, hashfunc) + for pk in pks] + return verifying_keys + + def _raw_encode(self): + """Convert the public key to the :term:`raw encoding`.""" + order = self.pubkey.order + x_str = number_to_string(self.pubkey.point.x(), order) + y_str = number_to_string(self.pubkey.point.y(), order) + return x_str + y_str + + def _compressed_encode(self): + """Encode the public point into the compressed form.""" + order = self.pubkey.order + x_str = number_to_string(self.pubkey.point.x(), order) + if self.pubkey.point.y() & 1: + return b('\x03') + x_str + else: + return b('\x02') + x_str + + def _hybrid_encode(self): + """Encode the public point into the hybrid form.""" + raw_enc = self._raw_encode() + if self.pubkey.point.y() & 1: + return b('\x07') + raw_enc + else: + return b('\x06') + raw_enc + + def to_string(self, encoding="raw"): + """ + Convert the public key to a byte string. + + The method by default uses the :term:`raw encoding` (specified + by `encoding="raw"`. It can also output keys in :term:`uncompressed`, + :term:`compressed` and :term:`hybrid` formats. + + Remember that the curve identification is not part of the encoding + so to decode the point using :func:`~VerifyingKey.from_string`, curve + needs to be specified. + + Note: while the method is called "to_string", it's a misnomer from + Python 2 days when character strings and byte strings shared type. + On Python 3 the returned type will be `bytes`. + + :return: :term:`raw encoding` of the public key (public point) on the + curve + :rtype: bytes + """ + assert encoding in ("raw", "uncompressed", "compressed", "hybrid") + if encoding == "raw": + return self._raw_encode() + elif encoding == "uncompressed": + return b('\x04') + self._raw_encode() + elif encoding == "hybrid": + return self._hybrid_encode() + else: + return self._compressed_encode() + + def to_pem(self, point_encoding="uncompressed"): + """ + Convert the public key to the :term:`PEM` format. + + The PEM header of the key will be ``BEGIN PUBLIC KEY``. + + The format of the key is described in the + :func:`~VerifyingKey.from_der()` method. + This method supports only "named curve" encoding of keys. + + :param str point_encoding: specification of the encoding format + of public keys. "uncompressed" is most portable, "compressed" is + smallest. "hybrid" is uncommon and unsupported by most + implementations, it is as big as "uncompressed". + + :return: portable encoding of the public key + :rtype: str + """ + return der.topem(self.to_der(point_encoding), "PUBLIC KEY") + + def to_der(self, point_encoding="uncompressed"): + """ + Convert the public key to the :term:`DER` format. + + The format of the key is described in the + :func:`~VerifyingKey.from_der()` method. + This method supports only "named curve" encoding of keys. + + :param str point_encoding: specification of the encoding format + of public keys. "uncompressed" is most portable, "compressed" is + smallest. "hybrid" is uncommon and unsupported by most + implementations, it is as big as "uncompressed". + + :return: DER encoding of the public key + :rtype: bytes + """ + if point_encoding == "raw": + raise ValueError("raw point_encoding not allowed in DER") + point_str = self.to_string(point_encoding) + return der.encode_sequence(der.encode_sequence(encoded_oid_ecPublicKey, + self.curve.encoded_oid), + # 0 is the number of unused bits in the + # bit string + der.encode_bitstring(point_str, 0)) + + def verify(self, signature, data, hashfunc=None, + sigdecode=sigdecode_string): + """ + Verify a signature made over provided data. + + Will hash `data` to verify the signature. + + By default expects signature in :term:`raw encoding`. Can also be used + to verify signatures in ASN.1 DER encoding by using + :func:`ecdsa.util.sigdecode_der` + as the `sigdecode` parameter. + + :param signature: encoding of the signature + :type signature: sigdecode method dependant + :param data: data signed by the `signature`, will be hashed using + `hashfunc`, if specified, or default hash function + :type data: bytes like object + :param hashfunc: The default hash function that will be used for + verification, needs to implement the same interface as hashlib.sha1 + :type hashfunc: callable + :param sigdecode: Callable to define the way the signature needs to + be decoded to an object, needs to handle `signature` as the + first parameter, the curve order (an int) as the second and return + a tuple with two integers, "r" as the first one and "s" as the + second one. See :func:`ecdsa.util.sigdecode_string` and + :func:`ecdsa.util.sigdecode_der` for examples. + :type sigdecode: callable + + :raises BadSignatureError: if the signature is invalid or malformed + + :return: True if the verification was successful + :rtype: bool + """ + # signature doesn't have to be a bytes-like-object so don't normalise + # it, the decoders will do that + data = normalise_bytes(data) + + hashfunc = hashfunc or self.default_hashfunc + digest = hashfunc(data).digest() + return self.verify_digest(signature, digest, sigdecode, True) + + def verify_digest(self, signature, digest, sigdecode=sigdecode_string, + allow_truncate=False): + """ + Verify a signature made over provided hash value. + + By default expects signature in :term:`raw encoding`. Can also be used + to verify signatures in ASN.1 DER encoding by using + :func:`ecdsa.util.sigdecode_der` + as the `sigdecode` parameter. + + :param signature: encoding of the signature + :type signature: sigdecode method dependant + :param digest: raw hash value that the signature authenticates. + :type digest: bytes like object + :param sigdecode: Callable to define the way the signature needs to + be decoded to an object, needs to handle `signature` as the + first parameter, the curve order (an int) as the second and return + a tuple with two integers, "r" as the first one and "s" as the + second one. See :func:`ecdsa.util.sigdecode_string` and + :func:`ecdsa.util.sigdecode_der` for examples. + :type sigdecode: callable + :param bool allow_truncate: if True, the provided digest can have + bigger bit-size than the order of the curve, the extra bits (at + the end of the digest) will be truncated. Use it when verifying + SHA-384 output using NIST256p or in similar situations. + + :raises BadSignatureError: if the signature is invalid or malformed + :raises BadDigestError: if the provided digest is too big for the curve + associated with this VerifyingKey and allow_truncate was not set + + :return: True if the verification was successful + :rtype: bool + """ + # signature doesn't have to be a bytes-like-object so don't normalise + # it, the decoders will do that + digest = normalise_bytes(digest) + if allow_truncate: + digest = digest[:self.curve.baselen] + if len(digest) > self.curve.baselen: + raise BadDigestError("this curve (%s) is too short " + "for your digest (%d)" % (self.curve.name, + 8 * len(digest))) + number = string_to_number(digest) + try: + r, s = sigdecode(signature, self.pubkey.order) + except (der.UnexpectedDER, MalformedSignature) as e: + raise BadSignatureError("Malformed formatting of signature", e) + sig = ecdsa.Signature(r, s) + if self.pubkey.verifies(number, sig): + return True + raise BadSignatureError("Signature verification failed") + + +class SigningKey(object): + """ + Class for handling keys that can create signatures (private keys). + + :ivar ecdsa.curves.Curve curve: The Curve over which all the cryptographic + operations will take place + :ivar default_hashfunc: the function that will be used for hashing the + data. Should implement the same API as hashlib.sha1 + :ivar int baselen: the length of a :term:`raw encoding` of private key + :ivar ecdsa.keys.VerifyingKey verifying_key: the public key + associated with this private key + :ivar ecdsa.ecdsa.Private_key privkey: the actual private key + """ + + def __init__(self, _error__please_use_generate=None): + """Unsupported, please use one of the classmethods to initialise.""" + if not _error__please_use_generate: + raise TypeError("Please use SigningKey.generate() to construct me") + self.curve = None + self.default_hashfunc = None + self.baselen = None + self.verifying_key = None + self.privkey = None + + def __eq__(self, other): + """Return True if the points are identical, False otherwise.""" + if isinstance(other, SigningKey): + return self.curve == other.curve \ + and self.verifying_key == other.verifying_key \ + and self.privkey == other.privkey + return NotImplemented + + @classmethod + def generate(cls, curve=NIST192p, entropy=None, hashfunc=sha1): + """ + Generate a random private key. + + :param curve: The curve on which the point needs to reside, defaults + to NIST192p + :type curve: ecdsa.curves.Curve + :param entropy: Source of randomness for generating the private keys, + should provide cryptographically secure random numbers if the keys + need to be secure. Uses os.urandom() by default. + :type entropy: callable + :param hashfunc: The default hash function that will be used for + signing, needs to implement the same interface + as hashlib.sha1 + :type hashfunc: callable + + :return: Initialised SigningKey object + :rtype: SigningKey + """ + secexp = randrange(curve.order, entropy) + return cls.from_secret_exponent(secexp, curve, hashfunc) + + @classmethod + def from_secret_exponent(cls, secexp, curve=NIST192p, hashfunc=sha1): + """ + Create a private key from a random integer. + + Note: it's a low level method, it's recommended to use the + :func:`~SigningKey.generate` method to create private keys. + + :param int secexp: secret multiplier (the actual private key in ECDSA). + Needs to be an integer between 1 and the curve order. + :param curve: The curve on which the point needs to reside + :type curve: ecdsa.curves.Curve + :param hashfunc: The default hash function that will be used for + signing, needs to implement the same interface + as hashlib.sha1 + :type hashfunc: callable + + :raises MalformedPointError: when the provided secexp is too large + or too small for the curve selected + :raises RuntimeError: if the generation of public key from private + key failed + + :return: Initialised SigningKey object + :rtype: SigningKey + """ + self = cls(_error__please_use_generate=True) + self.curve = curve + self.default_hashfunc = hashfunc + self.baselen = curve.baselen + n = curve.order + if not 1 <= secexp < n: + raise MalformedPointError( + "Invalid value for secexp, expected integer between 1 and {0}" + .format(n)) + pubkey_point = curve.generator * secexp + if hasattr(pubkey_point, "scale"): + pubkey_point = pubkey_point.scale() + self.verifying_key = VerifyingKey.from_public_point(pubkey_point, curve, + hashfunc, False) + pubkey = self.verifying_key.pubkey + self.privkey = ecdsa.Private_key(pubkey, secexp) + self.privkey.order = n + return self + + @classmethod + def from_string(cls, string, curve=NIST192p, hashfunc=sha1): + """ + Decode the private key from :term:`raw encoding`. + + Note: the name of this method is a misnomer coming from days of + Python 2, when binary strings and character strings shared a type. + In Python 3, the expected type is `bytes`. + + :param string: the raw encoding of the private key + :type string: bytes like object + :param curve: The curve on which the point needs to reside + :type curve: ecdsa.curves.Curve + :param hashfunc: The default hash function that will be used for + signing, needs to implement the same interface + as hashlib.sha1 + :type hashfunc: callable + + :raises MalformedPointError: if the length of encoding doesn't match + the provided curve or the encoded values is too large + :raises RuntimeError: if the generation of public key from private + key failed + + :return: Initialised SigningKey object + :rtype: SigningKey + """ + string = normalise_bytes(string) + if len(string) != curve.baselen: + raise MalformedPointError( + "Invalid length of private key, received {0}, expected {1}" + .format(len(string), curve.baselen)) + secexp = string_to_number(string) + return cls.from_secret_exponent(secexp, curve, hashfunc) + + @classmethod + def from_pem(cls, string, hashfunc=sha1): + """ + Initialise from key stored in :term:`PEM` format. + + Note, the only PEM format supported is the un-encrypted RFC5915 + (the sslay format) supported by OpenSSL, the more common PKCS#8 format + is NOT supported (see: + https://github.com/warner/python-ecdsa/issues/113 ) + + ``openssl ec -in pkcs8.pem -out sslay.pem`` can be used to + convert PKCS#8 file to this legacy format. + + The legacy format files have the header with the string + ``BEGIN EC PRIVATE KEY``. + Encrypted files (ones that include the string + ``Proc-Type: 4,ENCRYPTED`` + right after the PEM header) are not supported. + + See :func:`~SigningKey.from_der` for ASN.1 syntax of the objects in + this files. + + :param string: text with PEM-encoded private ECDSA key + :type string: str + + :raises MalformedPointError: if the length of encoding doesn't match + the provided curve or the encoded values is too large + :raises RuntimeError: if the generation of public key from private + key failed + :raises UnexpectedDER: if the encoding of the PEM file is incorrect + + :return: Initialised VerifyingKey object + :rtype: VerifyingKey + """ + # the privkey pem may have multiple sections, commonly it also has + # "EC PARAMETERS", we need just "EC PRIVATE KEY". + if PY3 and isinstance(string, str): + string = string.encode() + privkey_pem = string[string.index(b("-----BEGIN EC PRIVATE KEY-----")):] + return cls.from_der(der.unpem(privkey_pem), hashfunc) + + @classmethod + def from_der(cls, string, hashfunc=sha1): + """ + Initialise from key stored in :term:`DER` format. + + Note, the only DER format supported is the RFC5915 + (the sslay format) supported by OpenSSL, the more common PKCS#8 format + is NOT supported (see: + https://github.com/warner/python-ecdsa/issues/113 ) + + ``openssl ec -in pkcs8.pem -outform der -out sslay.der`` can be + used to convert PKCS#8 file to this legacy format. + + The encoding of the ASN.1 object in those files follows following + syntax specified in RFC5915:: + + ECPrivateKey ::= SEQUENCE { + version INTEGER { ecPrivkeyVer1(1) }} (ecPrivkeyVer1), + privateKey OCTET STRING, + parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + publicKey [1] BIT STRING OPTIONAL + } + + The only format supported for the `parameters` field is the named + curve method. Explicit encoding of curve parameters is not supported. + + While `parameters` field is defined as optional, this implementation + requires its presence for correct parsing of the keys. + + `publicKey` field is ignored completely (errors, if any, in it will + be undetected). + + :param string: binary string with DER-encoded private ECDSA key + :type string: bytes like object + + :raises MalformedPointError: if the length of encoding doesn't match + the provided curve or the encoded values is too large + :raises RuntimeError: if the generation of public key from private + key failed + :raises UnexpectedDER: if the encoding of the DER file is incorrect + + :return: Initialised VerifyingKey object + :rtype: VerifyingKey + """ + string = normalise_bytes(string) + s, empty = der.remove_sequence(string) + if empty != b(""): + raise der.UnexpectedDER("trailing junk after DER privkey: %s" % + binascii.hexlify(empty)) + one, s = der.remove_integer(s) + if one != 1: + raise der.UnexpectedDER("expected '1' at start of DER privkey," + " got %d" % one) |