summaryrefslogtreecommitdiff
path: root/src/M2Crypto/RSA.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/M2Crypto/RSA.py')
-rw-r--r--src/M2Crypto/RSA.py466
1 files changed, 466 insertions, 0 deletions
diff --git a/src/M2Crypto/RSA.py b/src/M2Crypto/RSA.py
new file mode 100644
index 0000000..d6e5c64
--- /dev/null
+++ b/src/M2Crypto/RSA.py
@@ -0,0 +1,466 @@
+from __future__ import absolute_import
+
+"""M2Crypto wrapper for OpenSSL RSA API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import sys
+
+from M2Crypto import BIO, Err, m2, util
+from typing import Any, AnyStr, Callable, IO, Optional, Tuple # noqa
+
+
+class RSAError(Exception):
+ pass
+
+
+m2.rsa_init(RSAError)
+
+no_padding = m2.no_padding
+pkcs1_padding = m2.pkcs1_padding
+sslv23_padding = m2.sslv23_padding
+pkcs1_oaep_padding = m2.pkcs1_oaep_padding
+
+
+class RSA(object):
+ """
+ RSA Key Pair.
+ """
+
+ m2_rsa_free = m2.rsa_free
+
+ def __init__(self, rsa, _pyfree=0):
+ # type: (bytes, int) -> None
+ """
+ :param rsa: binary representation of OpenSSL RSA type
+ """
+ assert m2.rsa_type_check(rsa), "'rsa' type error"
+ self.rsa = rsa
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ # type: () -> None
+ if getattr(self, '_pyfree', 0):
+ self.m2_rsa_free(self.rsa)
+
+ def __len__(self):
+ # type: () -> int
+ return int(m2.rsa_size(self.rsa) << 3)
+
+ def __getattr__(self, name):
+ # type: (str) -> bytes
+ if name == 'e':
+ return m2.rsa_get_e(self.rsa)
+ elif name == 'n':
+ return m2.rsa_get_n(self.rsa)
+ else:
+ raise AttributeError
+
+ def pub(self):
+ # type: () -> Tuple[bytes, bytes]
+ assert self.check_key(), 'key is not initialised'
+ return m2.rsa_get_e(self.rsa), m2.rsa_get_n(self.rsa)
+
+ def public_encrypt(self, data, padding):
+ # type: (bytes, int) -> bytes
+ assert self.check_key(), 'key is not initialised'
+ return m2.rsa_public_encrypt(self.rsa, data, padding)
+
+ def public_decrypt(self, data, padding):
+ # type: (bytes, int) -> bytes
+ assert self.check_key(), 'key is not initialised'
+ return m2.rsa_public_decrypt(self.rsa, data, padding)
+
+ def private_encrypt(self, data, padding):
+ # type: (bytes, int) -> bytes
+ assert self.check_key(), 'key is not initialised'
+ return m2.rsa_private_encrypt(self.rsa, data, padding)
+
+ def private_decrypt(self, data, padding):
+ # type: (bytes, int) -> bytes
+ assert self.check_key(), 'key is not initialised'
+ return m2.rsa_private_decrypt(self.rsa, data, padding)
+
+ def save_key_bio(self, bio, cipher='aes_128_cbc',
+ callback=util.passphrase_callback):
+ # type: (BIO.BIO, Optional[str], Callable) -> 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.rsa_write_key_no_cipher(self.rsa, bio._ptr(), callback)
+ else:
+ ciph = getattr(m2, cipher, None)
+ if ciph is None:
+ raise RSAError('not such cipher %s' % cipher)
+ else:
+ ciph = ciph()
+ return m2.rsa_write_key(self.rsa, bio._ptr(), ciph, callback)
+
+ def save_key(self, file, cipher='aes_128_cbc',
+ callback=util.passphrase_callback):
+ # type: (AnyStr, Optional[str], Callable) -> int
+ """
+ Save the key pair to a file in PEM format.
+
+ :param file: Name of file 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)
+
+ save_pem = save_key
+
+ def as_pem(self, cipher='aes_128_cbc', callback=util.passphrase_callback):
+ # type: (Optional[str], Callable) -> bytes
+ """
+ Returns the key(pair) as a string in PEM format.
+ """
+ bio = BIO.MemoryBuffer()
+ self.save_key_bio(bio, cipher, callback)
+ return bio.read()
+
+ def save_key_der_bio(self, bio):
+ # type: (BIO.BIO) -> int
+ """
+ Save the key pair to an M2Crypto.BIO.BIO object in DER format.
+
+ :param bio: M2Crypto.BIO.BIO object to save key to.
+ """
+ return m2.rsa_write_key_der(self.rsa, bio._ptr())
+
+ def save_key_der(self, file):
+ # type: (AnyStr) -> int
+ """
+ Save the key pair to a file in DER format.
+
+ :param file: Filename to save key to
+ """
+ with BIO.openfile(file, 'wb') as bio:
+ return self.save_key_der_bio(bio)
+
+ def save_pub_key_bio(self, bio):
+ # type: (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.rsa_write_pub_key(self.rsa, bio._ptr())
+
+ def save_pub_key(self, file):
+ # type: (AnyStr) -> int
+ """
+ Save the public key to a file in PEM format.
+
+ :param file: Name of file to save key to.
+ """
+ with BIO.openfile(file, 'wb') as bio:
+ return m2.rsa_write_pub_key(self.rsa, bio._ptr())
+
+ def check_key(self):
+ # type: () -> int
+ """
+ Validate RSA keys.
+
+ It checks that p and q are in fact prime, and that n = p*q.
+
+ :return: returns 1 if rsa is a valid RSA key, and 0 otherwise.
+ -1 is returned if an error occurs while checking the key.
+ If the key is invalid or an error occurred, the reason
+ code can be obtained using ERR_get_error(3).
+ """
+ return m2.rsa_check_key(self.rsa)
+
+ def sign_rsassa_pss(self, digest, algo='sha1', salt_length=20):
+ # type: (bytes, str, int) -> bytes
+ """
+ Signs a digest with the private key using RSASSA-PSS
+
+ :param digest: A digest created by using the digest method
+
+ :param salt_length: The length of the salt to use
+
+ :param algo: The hash algorithm to use
+ Legal values like 'sha1','sha224', 'sha256',
+ 'ripemd160', and 'md5'.
+
+ :return: a string which is the signature
+ """
+ hash = getattr(m2, algo, None)
+
+ if hash is None:
+ raise RSAError('not such hash algorithm %s' % algo)
+
+ signature = m2.rsa_padding_add_pkcs1_pss(self.rsa, digest, hash(), salt_length)
+
+ return self.private_encrypt(signature, m2.no_padding)
+
+ def verify_rsassa_pss(self, data, signature, algo='sha1', salt_length=20):
+ # type: (bytes, bytes, str, int) -> int
+ """
+ Verifies the signature RSASSA-PSS
+
+ :param data: Data that has been signed
+
+ :param signature: The signature signed with RSASSA-PSS
+
+ :param salt_length: The length of the salt that was used
+
+ :param algo: The hash algorithm to use
+ Legal values are for example 'sha1','sha224',
+ 'sha256', 'ripemd160', and 'md5'.
+
+ :return: 1 or 0, depending on whether the signature was
+ verified or not.
+ """
+ hash = getattr(m2, algo, None)
+
+ if hash is None:
+ raise RSAError('not such hash algorithm %s' % algo)
+
+ plain_signature = self.public_decrypt(signature, m2.no_padding)
+
+ return m2.rsa_verify_pkcs1_pss(self.rsa, data, plain_signature, hash(), salt_length)
+
+ def sign(self, digest, algo='sha1'):
+ # type: (bytes, str) -> bytes
+ """
+ Signs a digest with the private key
+
+ :param digest: A digest created by using the digest method
+
+ :param algo: The method that created the digest.
+ Legal values like 'sha1','sha224', 'sha256',
+ 'ripemd160', and 'md5'.
+
+ :return: a string which is the signature
+ """
+ digest_type = getattr(m2, 'NID_' + algo, None)
+ if digest_type is None:
+ raise ValueError('unknown algorithm', algo)
+
+ return m2.rsa_sign(self.rsa, digest, digest_type)
+
+ def verify(self, data, signature, algo='sha1'):
+ # type: (bytes, bytes, str) -> int
+ """
+ Verifies the signature with the public key
+
+ :param data: Data that has been signed
+
+ :param signature: The signature signed with the private key
+
+ :param algo: The method use to create digest from the data
+ before it was signed. Legal values like
+ 'sha1','sha224', 'sha256', 'ripemd160', and 'md5'.
+
+ :return: 1 or 0, depending on whether the signature was
+ verified or not.
+ """
+ digest_type = getattr(m2, 'NID_' + algo, None)
+ if digest_type is None:
+ raise ValueError('unknown algorithm', algo)
+
+ return m2.rsa_verify(self.rsa, data, signature, digest_type)
+
+
+class RSA_pub(RSA):
+
+ """
+ Object interface to an RSA public key.
+ """
+
+ def __setattr__(self, name, value):
+ # type: (str, bytes) -> None
+ if name in ['e', 'n']:
+ raise RSAError('use factory function new_pub_key() to set (e, n)')
+ else:
+ self.__dict__[name] = value
+
+ def private_encrypt(self, *argv):
+ # type: (*Any) -> None
+ raise RSAError('RSA_pub object has no private key')
+
+ def private_decrypt(self, *argv):
+ # type: (*Any) -> None
+ raise RSAError('RSA_pub object has no private key')
+
+ def save_key(self, file, *args, **kw):
+ # type: (AnyStr, *Any, **Any) -> int
+ """
+ Save public key to file.
+ """
+ return self.save_pub_key(file)
+
+ def save_key_bio(self, bio, *args, **kw):
+ # type: (BIO.BIO, *Any, **Any) -> int
+ """
+ Save public key to BIO.
+ """
+ return self.save_pub_key_bio(bio)
+
+ # save_key_der
+
+ # save_key_der_bio
+
+ def check_key(self):
+ # type: () -> int
+ return m2.rsa_check_pub_key(self.rsa)
+
+
+def rsa_error():
+ # type: () -> None
+ raise RSAError(Err.get_error_message())
+
+
+def keygen_callback(p, n, out=sys.stdout):
+ # type: (int, Any, IO[str]) -> None
+ """
+ Default callback for gen_key().
+ """
+ ch = ['.', '+', '*', '\n']
+ out.write(ch[p])
+ out.flush()
+
+
+def gen_key(bits, e, callback=keygen_callback):
+ # type: (int, int, Callable) -> RSA
+ """
+ Generate an RSA key pair.
+
+ :param bits: Key length, in bits.
+
+ :param e: The RSA public exponent.
+
+ :param callback: A Python callable object that is invoked
+ during key generation; its usual purpose is to
+ provide visual feedback. The default callback is
+ keygen_callback.
+
+ :return: M2Crypto.RSA.RSA object.
+ """
+ return RSA(m2.rsa_generate_key(bits, e, callback), 1)
+
+
+def load_key(file, callback=util.passphrase_callback):
+ # type: (AnyStr, Callable) -> RSA
+ """
+ Load an RSA key pair from file.
+
+ :param file: Name of file containing RSA public key 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.RSA.RSA object.
+ """
+ with BIO.openfile(file) as bio:
+ return load_key_bio(bio, callback)
+
+
+def load_key_bio(bio, callback=util.passphrase_callback):
+ # type: (BIO.BIO, Callable) -> RSA
+ """
+ Load an RSA key pair from an M2Crypto.BIO.BIO object.
+
+ :param bio: M2Crypto.BIO.BIO object containing RSA 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.RSA.RSA object.
+ """
+ rsa = m2.rsa_read_key(bio._ptr(), callback)
+ if rsa is None:
+ rsa_error()
+ return RSA(rsa, 1)
+
+
+def load_key_string(string, callback=util.passphrase_callback):
+ # type: (AnyStr, Callable) -> RSA
+ """
+ Load an RSA key pair from a string.
+
+ :param string: String containing RSA 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.RSA.RSA object.
+ """
+ bio = BIO.MemoryBuffer(string)
+ return load_key_bio(bio, callback)
+
+
+def load_pub_key(file):
+ # type: (AnyStr) -> RSA_pub
+ """
+ Load an RSA public key from file.
+
+ :param file: Name of file containing RSA public key in PEM format.
+
+ :return: M2Crypto.RSA.RSA_pub object.
+ """
+ with BIO.openfile(file) as bio:
+ return load_pub_key_bio(bio)
+
+
+def load_pub_key_bio(bio):
+ # type: (BIO.BIO) -> RSA_pub
+ """
+ Load an RSA public key from an M2Crypto.BIO.BIO object.
+
+ :param bio: M2Crypto.BIO.BIO object containing RSA public key in PEM
+ format.
+
+ :return: M2Crypto.RSA.RSA_pub object.
+ """
+ rsa = m2.rsa_read_pub_key(bio._ptr())
+ if rsa is None:
+ rsa_error()
+ return RSA_pub(rsa, 1)
+
+
+def new_pub_key(e_n):
+ # type: (Tuple[bytes, bytes]) -> RSA_pub
+ """
+ Instantiate an RSA_pub object from an (e, n) tuple.
+
+ :param e: The RSA public exponent; it is a string in OpenSSL's MPINT
+ format - 4-byte big-endian bit-count followed by the
+ appropriate number of bits.
+
+ :param n: The RSA composite of primes; it is a string in OpenSSL's
+ MPINT format - 4-byte big-endian bit-count followed by the
+ appropriate number of bits.
+
+ :return: M2Crypto.RSA.RSA_pub object.
+ """
+ (e, n) = e_n
+ rsa = m2.rsa_new()
+ m2.rsa_set_en(rsa, e, n)
+ return RSA_pub(rsa, 1)