Source code for M2Crypto.EC

"""
M2Crypto wrapper for OpenSSL ECDH/ECDSA API.

@requires: OpenSSL 0.9.8 or newer

Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.

Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam.
All rights reserved."""

from typing import Callable, Dict, List, NoReturn, Optional, Tuple, Union  # noqa
from M2Crypto import BIO, Err, EVP, m2, types as C, util

EC_Key = bytes


[docs] class ECError(Exception): pass
m2.ec_init(ECError) # Curve identifier constants NID_secp112r1: int = m2.NID_secp112r1 NID_secp112r2: int = m2.NID_secp112r2 NID_secp128r1: int = m2.NID_secp128r1 NID_secp128r2: int = m2.NID_secp128r2 NID_secp160k1: int = m2.NID_secp160k1 NID_secp160r1: int = m2.NID_secp160r1 NID_secp160r2: int = m2.NID_secp160r2 NID_secp192k1: int = m2.NID_secp192k1 NID_secp224k1: int = m2.NID_secp224k1 NID_secp224r1: int = m2.NID_secp224r1 NID_secp256k1: int = m2.NID_secp256k1 NID_secp384r1: int = m2.NID_secp384r1 NID_secp521r1: int = m2.NID_secp521r1 NID_sect113r1: int = m2.NID_sect113r1 NID_sect113r2: int = m2.NID_sect113r2 NID_sect131r1: int = m2.NID_sect131r1 NID_sect131r2: int = m2.NID_sect131r2 NID_sect163k1: int = m2.NID_sect163k1 NID_sect163r1: int = m2.NID_sect163r1 NID_sect163r2: int = m2.NID_sect163r2 NID_sect193r1: int = m2.NID_sect193r1 NID_sect193r2: int = m2.NID_sect193r2 # default for secg.org TLS test server NID_sect233k1: int = m2.NID_sect233k1 NID_sect233r1: int = m2.NID_sect233r1 NID_sect239k1: int = m2.NID_sect239k1 NID_sect283k1: int = m2.NID_sect283k1 NID_sect283r1: int = m2.NID_sect283r1 NID_sect409k1: int = m2.NID_sect409k1 NID_sect409r1: int = m2.NID_sect409r1 NID_sect571k1: int = m2.NID_sect571k1 NID_sect571r1: int = m2.NID_sect571r1 NID_prime192v1: int = m2.NID_X9_62_prime192v1 NID_prime192v2: int = m2.NID_X9_62_prime192v2 NID_prime192v3: int = m2.NID_X9_62_prime192v3 NID_prime239v1: int = m2.NID_X9_62_prime239v1 NID_prime239v2: int = m2.NID_X9_62_prime239v2 NID_prime239v3: int = m2.NID_X9_62_prime239v3 NID_prime256v1: int = m2.NID_X9_62_prime256v1 NID_c2pnb163v1: int = m2.NID_X9_62_c2pnb163v1 NID_c2pnb163v2: int = m2.NID_X9_62_c2pnb163v2 NID_c2pnb163v3: int = m2.NID_X9_62_c2pnb163v3 NID_c2pnb176v1: int = m2.NID_X9_62_c2pnb176v1 NID_c2tnb191v1: int = m2.NID_X9_62_c2tnb191v1 NID_c2tnb191v2: int = m2.NID_X9_62_c2tnb191v2 NID_c2tnb191v3: int = m2.NID_X9_62_c2tnb191v3 NID_c2pnb208w1: int = m2.NID_X9_62_c2pnb208w1 NID_c2tnb239v1: int = m2.NID_X9_62_c2tnb239v1 NID_c2tnb239v2: int = m2.NID_X9_62_c2tnb239v2 NID_c2tnb239v3: int = m2.NID_X9_62_c2tnb239v3 NID_c2pnb272w1: int = m2.NID_X9_62_c2pnb272w1 NID_c2pnb304w1: int = m2.NID_X9_62_c2pnb304w1 NID_c2tnb359v1: int = m2.NID_X9_62_c2tnb359v1 NID_c2pnb368w1: int = m2.NID_X9_62_c2pnb368w1 NID_c2tnb431r1: int = m2.NID_X9_62_c2tnb431r1 # To preserve compatibility with older names NID_X9_62_prime192v1: int = NID_prime192v1 NID_X9_62_prime192v2: int = NID_prime192v2 NID_X9_62_prime192v3: int = NID_prime192v3 NID_X9_62_prime239v1: int = NID_prime239v1 NID_X9_62_prime239v2: int = NID_prime239v2 NID_X9_62_prime239v3: int = NID_prime239v3 NID_X9_62_prime256v1: int = NID_prime256v1 NID_X9_62_c2pnb163v1: int = NID_c2pnb163v1 NID_X9_62_c2pnb163v2: int = NID_c2pnb163v2 NID_X9_62_c2pnb163v3: int = NID_c2pnb163v3 NID_X9_62_c2pnb176v1: int = NID_c2pnb176v1 NID_X9_62_c2tnb191v1: int = NID_c2tnb191v1 NID_X9_62_c2tnb191v2: int = NID_c2tnb191v2 NID_X9_62_c2tnb191v3: int = NID_c2tnb191v3 NID_X9_62_c2pnb208w1: int = NID_c2pnb208w1 NID_X9_62_c2tnb239v1: int = NID_c2tnb239v1 NID_X9_62_c2tnb239v2: int = NID_c2tnb239v2 NID_X9_62_c2tnb239v3: int = NID_c2tnb239v3 NID_X9_62_c2pnb272w1: int = NID_c2pnb272w1 NID_X9_62_c2pnb304w1: int = NID_c2pnb304w1 NID_X9_62_c2tnb359v1: int = NID_c2tnb359v1 NID_X9_62_c2pnb368w1: int = NID_c2pnb368w1 NID_X9_62_c2tnb431r1: int = NID_c2tnb431r1 NID_wap_wsg_idm_ecid_wtls1: int = m2.NID_wap_wsg_idm_ecid_wtls1 NID_wap_wsg_idm_ecid_wtls3: int = m2.NID_wap_wsg_idm_ecid_wtls3 NID_wap_wsg_idm_ecid_wtls4: int = m2.NID_wap_wsg_idm_ecid_wtls4 NID_wap_wsg_idm_ecid_wtls5: int = m2.NID_wap_wsg_idm_ecid_wtls5 NID_wap_wsg_idm_ecid_wtls6: int = m2.NID_wap_wsg_idm_ecid_wtls6 NID_wap_wsg_idm_ecid_wtls7: int = m2.NID_wap_wsg_idm_ecid_wtls7 NID_wap_wsg_idm_ecid_wtls8: int = m2.NID_wap_wsg_idm_ecid_wtls8 NID_wap_wsg_idm_ecid_wtls9: int = m2.NID_wap_wsg_idm_ecid_wtls9 NID_wap_wsg_idm_ecid_wtls10: int = m2.NID_wap_wsg_idm_ecid_wtls10 NID_wap_wsg_idm_ecid_wtls11: int = m2.NID_wap_wsg_idm_ecid_wtls11 NID_wap_wsg_idm_ecid_wtls12: int = m2.NID_wap_wsg_idm_ecid_wtls12 # The following two curves, according to OpenSSL, have a # "Questionable extension field!" and are not supported by # the OpenSSL inverse function. ECError: no inverse. # As such they cannot be used for signing. They might, # however, be usable for encryption but that has not # been tested. Until thir usefulness can be established, # they are not supported at this time. # NID_ipsec3 = m2.NID_ipsec3 # NID_ipsec4 = m2.NID_ipsec4
[docs] class EC(object): """ Object interface to a EC key pair. """ def __init__(self, ec: C.EC_KEY, _pyfree: int = 0) -> None: assert m2.ec_key_type_check(ec), "'ec' type error" self.ec = ec self._pyfree = _pyfree
[docs] @staticmethod def m2_ec_key_free(ec: C.EC_KEY) -> None: m2.ec_key_free(ec)
def __del__(self) -> None: if getattr(self, "_pyfree", 0): self.m2_ec_key_free(self.ec) def __len__(self) -> int: assert m2.ec_key_type_check(self.ec), "'ec' type error" return m2.ec_key_keylen(self.ec)
[docs] def gen_key(self) -> None: """ Generates the key pair from its parameters. Use:: keypair = EC.gen_params(curve) keypair.gen_key() to create an EC key pair. """ assert m2.ec_key_type_check(self.ec), "'ec' type error" m2.ec_key_gen_key(self.ec)
[docs] def pub(self) -> "EC_pub": # Don't let python free return EC_pub(self.ec, 0)
[docs] def sign_dsa(self, digest: bytes) -> Tuple[bytes, bytes]: """ Sign the given digest using ECDSA. Returns a tuple (r,s), the two ECDSA signature parameters. """ assert self._check_key_type(), "'ec' type error" return m2.ecdsa_sign(self.ec, digest)
[docs] def verify_dsa(self, digest: bytes, r: bytes, s: bytes) -> int: """ Verify the given digest using ECDSA. r and s are the ECDSA signature parameters. """ assert self._check_key_type(), "'ec' type error" return m2.ecdsa_verify(self.ec, digest, r, s)
[docs] def sign_dsa_asn1(self, digest: bytes) -> bytes: assert self._check_key_type(), "'ec' type error" return m2.ecdsa_sign_asn1(self.ec, digest)
[docs] def verify_dsa_asn1(self, digest: bytes, blob: bytes) -> int: assert self._check_key_type(), "'ec' type error" return m2.ecdsa_verify_asn1(self.ec, digest, blob)
[docs] def compute_dh_key(self, pub_key: "EC") -> Optional[bytes]: """ Compute the ECDH shared key of this key pair and the given public key object. They must both use the same curve. Returns the shared key in binary as a buffer object. No Key Derivation Function is applied. """ assert self.check_key(), "key is not initialised" return m2.ecdh_compute_key(self.ec, pub_key.ec)
[docs] def save_key_bio( self, bio: BIO.BIO, cipher: Optional[str] = "aes_128_cbc", callback: Callable = util.passphrase_callback, ) -> int: """ Save the key pair to an M2Crypto.BIO.BIO object in PEM format. :param bio: M2Crypto.BIO.BIO object to save key to. :param cipher: Symmetric cipher to protect the key. The default cipher is 'aes_128_cbc'. If cipher is None, then the key is saved in the clear. :param callback: A Python callable object that is invoked to acquire a passphrase with which to protect the key. The default is util.passphrase_callback. """ if cipher is None: return m2.ec_key_write_bio_no_cipher(self.ec, bio._ptr(), callback) else: ciph = getattr(m2, cipher, None) if ciph is None: raise ValueError("not such cipher %s" % cipher) return m2.ec_key_write_bio(self.ec, bio._ptr(), ciph(), callback)
[docs] def save_key( self, file: Union[str, bytes], cipher: Optional[str] = "aes_128_cbc", callback: Callable = util.passphrase_callback, ) -> int: """ Save the key pair to a file in PEM format. :param file: Name of filename to save key to. :param cipher: Symmetric cipher to protect the key. The default cipher is 'aes_128_cbc'. If cipher is None, then the key is saved in the clear. :param callback: A Python callable object that is invoked to acquire a passphrase with which to protect the key. The default is util.passphrase_callback. """ with BIO.openfile(file, "wb") as bio: return self.save_key_bio(bio, cipher, callback)
[docs] def save_pub_key_bio(self, bio: BIO.BIO) -> int: """ Save the public key to an M2Crypto.BIO.BIO object in PEM format. :param bio: M2Crypto.BIO.BIO object to save key to. """ return m2.ec_key_write_pubkey(self.ec, bio._ptr())
[docs] def save_pub_key(self, file: Union[str, bytes]) -> int: """ Save the public key to a filename in PEM format. :param file: Name of filename to save key to. """ with BIO.openfile(file, "wb") as bio: return m2.ec_key_write_pubkey(self.ec, bio._ptr())
[docs] def as_pem( self, cipher: str = "aes_128_cbc", callback: Callable = util.passphrase_callback, ) -> bytes: """ Returns the key(pair) as a string in PEM format. If no password is passed and the cipher is set it exits with error """ with BIO.MemoryBuffer() as bio: self.save_key_bio(bio, cipher, callback) return bio.read()
def _check_key_type(self) -> int: return m2.ec_key_type_check(self.ec)
[docs] def check_key(self) -> int: assert m2.ec_key_type_check(self.ec), "'ec' type error" return m2.ec_key_check_key(self.ec)
[docs] class EC_pub(EC): """ Object interface to an EC public key. ((don't like this implementation inheritance)) """ def __init__(self, ec: C.EC_KEY, _pyfree: int = 0) -> None: EC.__init__(self, ec, _pyfree) self.der: Optional[bytes] = None
[docs] def get_der(self) -> bytes: """ Returns the public key in DER format as a buffer object. """ assert self.check_key(), "key is not initialised" if self.der is None: self.der = m2.ec_key_get_public_der(self.ec) return self.der
[docs] def get_key(self) -> bytes: """ Returns the public key as a byte string. """ assert self.check_key(), "key is not initialised" return m2.ec_key_get_public_key(self.ec)
[docs] def as_pem(self): """ Returns the key(pair) as a string in PEM format. If no password is passed and the cipher is set it exits with error """ with BIO.MemoryBuffer() as bio: self.save_key_bio(bio) return bio.read()
save_key: Callable = EC.save_pub_key # type: ignore[assignment] save_key_bio: Callable = EC.save_pub_key_bio # type: ignore[assignment]
[docs] def gen_params(curve: int) -> EC: """ Factory function that generates EC parameters and instantiates a EC object from the output. :param curve: This is the OpenSSL nid of the curve to use. """ assert curve in [ x["NID"] for x in m2.ec_get_builtin_curves() ], "Elliptic curve %s is not available on this system." % m2.obj_nid2sn(curve) return EC(m2.ec_key_new_by_curve_name(curve), 1)
[docs] def load_key( file: Union[str, bytes], callback: Callable = util.passphrase_callback, ) -> EC: """ Factory function that instantiates a EC object. :param file: Names the filename that contains the PEM representation of the EC key pair. :param callback: Python callback object that will be invoked if the EC key pair is passphrase-protected. """ with BIO.openfile(file) as bio: return load_key_bio(bio, callback)
[docs] def load_key_string(string: str, callback: Callable = util.passphrase_callback) -> EC: """ Load an EC key pair from a string. :param string: String containing EC key pair in PEM format. :param callback: A Python callable object that is invoked to acquire a passphrase with which to unlock the key. The default is util.passphrase_callback. :return: M2Crypto.EC.EC object. """ with BIO.MemoryBuffer(string.encode()) as bio: return load_key_bio(bio, callback)
[docs] def load_key_bio(bio: BIO.BIO, callback: Callable = util.passphrase_callback) -> EC: """ Factory function that instantiates a EC object. :param bio: M2Crypto.BIO object that contains the PEM representation of the EC key pair. :param callback: Python callback object that will be invoked if the EC key pair is passphrase-protected. """ key = m2.ec_key_read_bio(bio._ptr(), callback) if key is None: raise IOError( "Cannot read EC key pair from PEM file {}.".format( getattr(bio, "fname", "in-memory buffer") ) ) return EC(key, 1)
[docs] def load_pub_key(file: Union[str, bytes]) -> EC_pub: """ Load an EC public key from filename. :param file: Name of filename containing EC public key in PEM format. :return: M2Crypto.EC.EC_pub object. """ with BIO.openfile(file) as bio: return load_pub_key_bio(bio)
[docs] def load_key_string_pubkey( string: str, callback: Callable = util.passphrase_callback ) -> "EVP.PKey": """ Load an M2Crypto.EVP.PKey from a public key as a string. :param string: String containing the key in PEM format. :param callback: A Python callable object that is invoked to acquire a passphrase with which to protect the key. :return: M2Crypto.EVP.PKey object. """ from M2Crypto.EVP import load_key_bio_pubkey with BIO.MemoryBuffer(string.encode()) as bio: return load_key_bio_pubkey(bio, callback)
[docs] def load_pub_key_bio(bio: BIO.BIO) -> EC_pub: """ Load an EC public key from an M2Crypto.BIO.BIO object. :param bio: M2Crypto.BIO.BIO object containing EC public key in PEM format. :return: M2Crypto.EC.EC_pub object. """ ec = m2.ec_key_read_pubkey(bio._ptr()) if ec is None: ec_error() return EC_pub(ec, 1)
[docs] def ec_error() -> NoReturn: raise ECError(Err.get_error_message())
[docs] def pub_key_from_der(der: bytes) -> EC_pub: """ Create EC_pub from DER. """ return EC_pub(m2.ec_key_from_pubkey_der(der), 1)
[docs] def pub_key_from_params(curve: int, bytes: bytes) -> EC_pub: """ Create EC_pub from curve name and octet string. """ return EC_pub(m2.ec_key_from_pubkey_params(curve, bytes), 1)
[docs] def get_builtin_curves() -> List[Dict[str, Union[int, str]]]: return m2.ec_get_builtin_curves()