aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2019-04-18 14:17:02 -0400
committerJack Lloyd <[email protected]>2019-04-18 14:18:36 -0400
commitd10e7d152444a864488d9e113f34b64a75aa541c (patch)
tree5a5991ea2e856c1ed1b42391a9f371a5be4904d1
parenta72a85da15335fd6346bf0f17bbb8b4e6fe38ce0 (diff)
Support loading keys in Python
Which was missing for whatever bad reason. GH #1900
-rw-r--r--doc/manual/python.rst30
-rwxr-xr-xsrc/python/botan2.py71
-rw-r--r--src/scripts/test_python.py89
3 files changed, 134 insertions, 56 deletions
diff --git a/doc/manual/python.rst b/doc/manual/python.rst
index 381346e5f..5022d9bf8 100644
--- a/doc/manual/python.rst
+++ b/doc/manual/python.rst
@@ -272,22 +272,34 @@ Public Key
Private Key
----------------------------------------
-.. py:class:: PrivateKey(algo, param, rng)
+.. py:class:: PrivateKey
Previously ``private_key``
- Constructor creates a new private key. The parameter type/value
- depends on the algorithm. For "rsa" is is the size of the key in
- bits. For "ecdsa" and "ecdh" it is a group name (for instance
- "secp256r1"). For "ecdh" there is also a special case for group
- "curve25519" (which is actually a completely distinct key type
- with a non-standard encoding).
+ .. py:classmethod:: create(algo, param, rng)
+
+ Creates a new private key. The parameter type/value depends on
+ the algorithm. For "rsa" is is the size of the key in bits.
+ For "ecdsa" and "ecdh" it is a group name (for instance
+ "secp256r1"). For "ecdh" there is also a special case for group
+ "curve25519" (which is actually a completely distinct key type
+ with a non-standard encoding).
+
+ .. py:classmethod:: load(val, passphrase="")
+
+ Load a private key (DER or PEM formats accepted)
.. py:method:: get_public_key()
- Return a public_key object
+ Return a public_key object
+
+ .. py:method:: to_pem()
+
+ Return the PEM encoded private key (unencrypted)
+
+ .. py:method:: to_der()
- .. py:method:: export()
+ Return the PEM encoded private key (unencrypted)
Public Key Operations
----------------------------------------
diff --git a/src/python/botan2.py b/src/python/botan2.py
index 19348f6aa..01479ea67 100755
--- a/src/python/botan2.py
+++ b/src/python/botan2.py
@@ -232,6 +232,9 @@ botan.botan_kdf.errcheck = errcheck_for('botan_kdf')
botan.botan_pubkey_destroy.argtypes = [c_void_p]
botan.botan_pubkey_destroy.errcheck = errcheck_for('botan_pubkey_destroy')
+botan.botan_pubkey_load.argtypes = [c_void_p, POINTER(c_char), c_size_t]
+botan.botan_pubkey_load.errcheck = errcheck_for('botan_pubkey_load')
+
botan.botan_pubkey_estimated_strength.argtypes = [c_void_p, POINTER(c_size_t)]
botan.botan_pubkey_estimated_strength.errcheck = errcheck_for('botan_pubkey_estimated_strength')
@@ -248,6 +251,9 @@ botan.botan_pubkey_fingerprint.errcheck = errcheck_for('botan_pubkey_fingerprint
botan.botan_privkey_create.argtypes = [c_void_p, c_char_p, c_char_p, c_void_p]
botan.botan_privkey_create.errcheck = errcheck_for('botan_privkey_create')
+botan.botan_privkey_load.argtypes = [c_void_p, c_void_p, POINTER(c_char), c_size_t, POINTER(c_char)]
+botan.botan_privkey_load.errcheck = errcheck_for('botan_privkey_load')
+
botan.botan_privkey_algo_name.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)]
botan.botan_privkey_algo_name.errcheck = errcheck_for('botan_privkey_algo_name')
@@ -530,7 +536,7 @@ def _call_fn_returning_vec(guess, fn):
assert buf_len.value <= len(buf)
return buf.raw[0:int(buf_len.value)]
-def _call_fn_returning_string(guess, fn):
+def _call_fn_returning_str(guess, fn):
# Assumes that anything called with this is returning plain ASCII strings
# (base64 data, algorithm names, etc)
v = _call_fn_returning_vec(guess, fn)
@@ -634,7 +640,7 @@ class HashFunction(object):
botan.botan_hash_destroy(self.__obj)
def algo_name(self):
- return _call_fn_returning_string(32, lambda b, bl: botan.botan_hash_name(self.__obj, b, bl))
+ return _call_fn_returning_str(32, lambda b, bl: botan.botan_hash_name(self.__obj, b, bl))
def clear(self):
botan.botan_hash_clear(self.__obj)
@@ -679,7 +685,7 @@ class MsgAuthCode(object):
botan.botan_mac_clear(self.__obj)
def algo_name(self):
- return _call_fn_returning_string(32, lambda b, bl: botan.botan_mac_name(self.__obj, b, bl))
+ return _call_fn_returning_str(32, lambda b, bl: botan.botan_mac_name(self.__obj, b, bl))
def output_length(self):
return self.__output_length
@@ -711,7 +717,7 @@ class SymmetricCipher(object):
botan.botan_cipher_destroy(self.__obj)
def algo_name(self):
- return _call_fn_returning_string(32, lambda b, bl: botan.botan_cipher_name(self.__obj, b, bl))
+ return _call_fn_returning_str(32, lambda b, bl: botan.botan_cipher_name(self.__obj, b, bl))
def default_nonce_length(self):
l = c_size_t(0)
@@ -854,9 +860,16 @@ def kdf(algo, secret, out_len, salt, label):
# Public key
#
class PublicKey(object): # pylint: disable=invalid-name
+
def __init__(self, obj=c_void_p(0)):
self.__obj = obj
+ @classmethod
+ def load(cls, val):
+ obj = c_void_p(0)
+ botan.botan_pubkey_load(byref(obj), val, len(val))
+ return PublicKey(obj)
+
def __del__(self):
botan.botan_pubkey_destroy(self.__obj)
@@ -869,11 +882,13 @@ class PublicKey(object): # pylint: disable=invalid-name
return r.value
def algo_name(self):
- return _call_fn_returning_string(32, lambda b, bl: botan.botan_pubkey_algo_name(self.__obj, b, bl))
+ return _call_fn_returning_str(32, lambda b, bl: botan.botan_pubkey_algo_name(self.__obj, b, bl))
def export(self, pem=False):
- flag = 1 if pem else 0
- return _call_fn_returning_vec(4096, lambda b, bl: botan.botan_pubkey_export(self.__obj, b, bl, flag))
+ if pem:
+ return _call_fn_returning_str(4096, lambda b, bl: botan.botan_pubkey_export(self.__obj, b, bl, 1))
+ else:
+ return _call_fn_returning_vec(4096, lambda b, bl: botan.botan_pubkey_export(self.__obj, b, bl, 0))
def encoding(self, pem=False):
return self.export(pem)
@@ -897,10 +912,19 @@ class PublicKey(object): # pylint: disable=invalid-name
# Private Key
#
class PrivateKey(object):
- def __init__(self, algo, params, rng_obj):
- self.__obj = c_void_p(0)
+ def __init__(self, obj=c_void_p(0)):
+ self.__obj = obj
+
+ @classmethod
+ def load(cls, val, passphrase=""):
+ obj = c_void_p(0)
+ rng_obj = c_void_p(0) # unused in recent versions
+ botan.botan_privkey_load(byref(obj), rng_obj, val, len(val), _ctype_str(passphrase))
+ return PrivateKey(obj)
+ @classmethod
+ def create(cls, algo, params, rng_obj):
if algo == 'rsa':
algo = 'RSA'
params = "%d" % (params)
@@ -918,8 +942,10 @@ class PrivateKey(object):
algo = 'McEliece'
params = "%d,%d" % (params[0], params[1])
- botan.botan_privkey_create(byref(self.__obj),
- _ctype_str(algo), _ctype_str(params), rng_obj.handle_())
+ obj = c_void_p(0)
+
+ botan.botan_privkey_create(byref(obj), _ctype_str(algo), _ctype_str(params), rng_obj.handle_())
+ return PrivateKey(obj)
def __del__(self):
botan.botan_privkey_destroy(self.__obj)
@@ -928,13 +954,12 @@ class PrivateKey(object):
return self.__obj
def algo_name(self):
- return _call_fn_returning_string(32, lambda b, bl: botan.botan_privkey_algo_name(self.__obj, b, bl))
+ return _call_fn_returning_str(32, lambda b, bl: botan.botan_privkey_algo_name(self.__obj, b, bl))
def get_public_key(self):
-
pub = c_void_p(0)
botan.botan_privkey_export_pubkey(byref(pub), self.__obj)
- return public_key(pub)
+ return PublicKey(pub)
def to_der(self):
return self.export(False)
@@ -943,8 +968,10 @@ class PrivateKey(object):
return self.export(True)
def export(self, pem=False):
- flag = 1 if pem else 0
- return _call_fn_returning_vec(4096, lambda b, bl: botan.botan_privkey_export(self.__obj, b, bl, flag))
+ if pem:
+ return _call_fn_returning_str(4096, lambda b, bl: botan.botan_privkey_export(self.__obj, b, bl, 1))
+ else:
+ return _call_fn_returning_vec(4096, lambda b, bl: botan.botan_privkey_export(self.__obj, b, bl, 0))
class PKEncrypt(object):
def __init__(self, key, padding):
@@ -1087,7 +1114,7 @@ class X509Cert(object): # pylint: disable=invalid-name
botan.botan_x509_cert_destroy(self.__obj)
def time_starts(self):
- starts = _call_fn_returning_string(
+ starts = _call_fn_returning_str(
16, lambda b, bl: botan.botan_x509_cert_get_time_starts(self.__obj, b, bl))
if len(starts) == 13:
# UTC time
@@ -1101,7 +1128,7 @@ class X509Cert(object): # pylint: disable=invalid-name
return datetime.fromtimestamp(mktime(struct_time))
def time_expires(self):
- expires = _call_fn_returning_string(
+ expires = _call_fn_returning_str(
16, lambda b, bl: botan.botan_x509_cert_get_time_expires(self.__obj, b, bl))
if len(expires) == 13:
# UTC time
@@ -1115,12 +1142,12 @@ class X509Cert(object): # pylint: disable=invalid-name
return datetime.fromtimestamp(mktime(struct_time))
def to_string(self):
- return _call_fn_returning_string(
+ return _call_fn_returning_str(
4096, lambda b, bl: botan.botan_x509_cert_to_string(self.__obj, b, bl))
def fingerprint(self, hash_algo='SHA-256'):
n = HashFunction(hash_algo).output_length() * 3
- return _call_fn_returning_string(
+ return _call_fn_returning_str(
n, lambda b, bl: botan.botan_x509_cert_get_fingerprint(self.__obj, _ctype_str(hash_algo), b, bl))
def serial_number(self):
@@ -1142,10 +1169,10 @@ class X509Cert(object): # pylint: disable=invalid-name
def subject_public_key(self):
pub = c_void_p(0)
botan.botan_x509_cert_get_public_key(self.__obj, byref(pub))
- return public_key(pub)
+ return PublicKey(pub)
def subject_dn(self, key, index):
- return _call_fn_returning_string(
+ return _call_fn_returning_str(
0, lambda b, bl: botan.botan_x509_cert_get_subject_dn(self.__obj, _ctype_str(key), index, b, bl))
diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py
index edfedd70e..9e2e53db9 100644
--- a/src/scripts/test_python.py
+++ b/src/scripts/test_python.py
@@ -58,7 +58,7 @@ class BotanPythonTests(unittest.TestCase):
self.assertEqual(hex_encode(scrypt), "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162")
def test_bcrypt(self):
- r = botan2.rng()
+ r = botan2.RandomNumberGenerator()
phash = botan2.bcrypt('testing', r)
self.assertTrue(isinstance(phash, str))
self.assertTrue(phash.startswith("$2a$"))
@@ -70,7 +70,7 @@ class BotanPythonTests(unittest.TestCase):
def test_mac(self):
- hmac = botan2.message_authentication_code('HMAC(SHA-256)')
+ hmac = botan2.MsgAuthCode('HMAC(SHA-256)')
self.assertEqual(hmac.algo_name(), 'HMAC(SHA-256)')
self.assertEqual(hmac.minimum_keylength(), 0)
self.assertEqual(hmac.maximum_keylength(), 4096)
@@ -83,7 +83,7 @@ class BotanPythonTests(unittest.TestCase):
self.assertEqual(hex_encode(expected), hex_encode(produced))
def test_rng(self):
- user_rng = botan2.rng("user")
+ user_rng = botan2.RandomNumberGenerator("user")
output1 = user_rng.get(32)
output2 = user_rng.get(32)
@@ -95,14 +95,14 @@ class BotanPythonTests(unittest.TestCase):
output3 = user_rng.get(1021)
self.assertEqual(len(output3), 1021)
- system_rng = botan2.rng('system')
+ system_rng = botan2.RandomNumberGenerator('system')
user_rng.reseed_from_rng(system_rng, 256)
user_rng.add_entropy('seed material...')
def test_hash(self):
- h = botan2.hash_function('SHA-256')
+ h = botan2.HashFunction('SHA-256')
self.assertEqual(h.algo_name(), 'SHA-256')
assert h.output_length() == 32
h.update('ignore this please')
@@ -118,7 +118,7 @@ class BotanPythonTests(unittest.TestCase):
def test_cipher(self):
for mode in ['AES-128/CTR-BE', 'Serpent/GCM', 'ChaCha20Poly1305']:
- enc = botan2.cipher(mode, encrypt=True)
+ enc = botan2.SymmetricCipher(mode, encrypt=True)
if mode == 'AES-128/CTR-BE':
self.assertEqual(enc.algo_name(), 'CTR-BE(AES-128)')
@@ -131,7 +131,7 @@ class BotanPythonTests(unittest.TestCase):
self.assertTrue(kmin <= kmax)
- rng = botan2.rng()
+ rng = botan2.RandomNumberGenerator()
iv = rng.get(enc.default_nonce_length())
key = rng.get(kmax)
pt = rng.get(21)
@@ -144,7 +144,7 @@ class BotanPythonTests(unittest.TestCase):
ct = enc.finish(pt)
- dec = botan2.cipher(mode, encrypt=False)
+ dec = botan2.SymmetricCipher(mode, encrypt=False)
dec.set_key(key)
dec.start(iv)
decrypted = dec.finish(ct)
@@ -153,22 +153,61 @@ class BotanPythonTests(unittest.TestCase):
def test_mceliece(self):
- rng = botan2.rng()
- mce_priv = botan2.private_key('mce', [2960, 57], rng)
+ rng = botan2.RandomNumberGenerator()
+ mce_priv = botan2.PrivateKey.create('mce', [2960, 57], rng)
mce_pub = mce_priv.get_public_key()
self.assertEqual(mce_pub.estimated_strength(), 128)
mce_plaintext = rng.get(16)
mce_ad = rng.get(48)
- mce_ciphertext = botan2.mceies_encrypt(mce_pub, botan2.rng(), 'ChaCha20Poly1305', mce_plaintext, mce_ad)
+ mce_ciphertext = botan2.mceies_encrypt(mce_pub, rng, 'ChaCha20Poly1305', mce_plaintext, mce_ad)
mce_decrypt = botan2.mceies_decrypt(mce_priv, 'ChaCha20Poly1305', mce_ciphertext, mce_ad)
self.assertEqual(mce_plaintext, mce_decrypt)
+ def test_rsa_load_store(self):
+
+ rsa_priv_pem = """-----BEGIN PRIVATE KEY-----
+MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALWtiBjcofJW/4+r
+CIjQZn2V3yCYsNIBpMdVkNPr36FZ3ZHGSv2ggmCe+IWy0fTcBVyP+fo3HC8zmOC2
+EsYDFRExyB2zIsjRXlPrVrTfcyXwUEaInLJQId5CguFrmyj1y7K43ezg+OTop39n
+TyaukrciCSCh++Q/UQOanHnR8ctrAgMBAAECgYBPfKySgBmk31ZyA7k4rsFgye01
+JEkcoNZ41iGG7ujJffl4maLew9a3MmZ2jI3azVbVMDMFPA5rQm5tRowBMYEJ5oBc
+LP4AP41Lujfa+vua6l3t94bAV+CufZiY0297FcPbGqNu+xSQ2Bol2uHh9mrcgQUs
+fevA50KOLR9hv4zH6QJBAPCOKiExONtVhJn8qVPCBlJ8Vjjnt9Uno5EzMBAKMbZi
+OySkGwo9/9LUWO03r7tjrGSy5jJk+iOrcLeDl6zETfkCQQDBV6PpD/3ccQ1IfWcw
+jG8yik0bIuXgrD0uW4g8Cvj+05wrv7RYPHuFtj3Rtb94YjtgYn7QvjH7y88XmTC4
+2k2DAkEA4E9Ae7kBUoz42/odDswyxwHICMIRyoJu5Ht9yscmufH5Ql6AFFnhzf9S
+eMjfZfY4j6G+Q6mjElXQAl+DtIdMSQJBAJzdMkuBggI8Zv6NYA9voThsJSsDIWcr
+12epM9sjO+nkXizQmM2OJNnThkyDHRna+Tm2MBXEemFEdn06+ODBnWkCQQChAbG4
+255RiCuYdrfiTPF/WLtvRyGd1LRwHcYIW4mJFPzxYAMTwQKbppLAnxw73vyef/zC
+2BgXEW02tjRBtgZ+
+-----END PRIVATE KEY-----
+"""
+
+ rsa_pub_pem = """-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1rYgY3KHyVv+PqwiI0GZ9ld8g
+mLDSAaTHVZDT69+hWd2Rxkr9oIJgnviFstH03AVcj/n6NxwvM5jgthLGAxURMcgd
+syLI0V5T61a033Ml8FBGiJyyUCHeQoLha5so9cuyuN3s4Pjk6Kd/Z08mrpK3Igkg
+ofvkP1EDmpx50fHLawIDAQAB
+-----END PUBLIC KEY-----
+"""
+
+ rsapriv = botan2.PrivateKey.load(rsa_priv_pem)
+
+ self.assertEqual(rsapriv.to_pem(), rsa_priv_pem)
+
+ rsapub = rsapriv.get_public_key()
+ self.assertEqual(rsapub.to_pem(), rsa_pub_pem)
+
+ rsapub = botan2.PublicKey.load(rsa_pub_pem)
+ self.assertEqual(rsapub.to_pem(), rsa_pub_pem)
+
def test_rsa(self):
- rng = botan2.rng()
- rsapriv = botan2.private_key('RSA', '1024', rng)
+ # pylint: disable=too-many-locals
+ rng = botan2.RandomNumberGenerator()
+ rsapriv = botan2.PrivateKey.create('RSA', '1024', rng)
self.assertEqual(rsapriv.algo_name(), 'RSA')
priv_pem = rsapriv.to_pem()
@@ -187,8 +226,8 @@ class BotanPythonTests(unittest.TestCase):
self.assertEqual(pub_pem[0:27], b"-----BEGIN PUBLIC KEY-----\n")
self.assertTrue(len(pub_pem) > len(pub_der))
- enc = botan2.pk_op_encrypt(rsapub, "OAEP(SHA-256)")
- dec = botan2.pk_op_decrypt(rsapriv, "OAEP(SHA-256)")
+ enc = botan2.PKEncrypt(rsapub, "OAEP(SHA-256)")
+ dec = botan2.PKDecrypt(rsapriv, "OAEP(SHA-256)")
symkey = rng.get(32)
ctext = enc.encrypt(symkey, rng)
@@ -197,13 +236,13 @@ class BotanPythonTests(unittest.TestCase):
self.assertEqual(ptext, symkey)
- signer = botan2.pk_op_sign(rsapriv, 'EMSA4(SHA-384)')
+ signer = botan2.PKSign(rsapriv, 'EMSA4(SHA-384)')
signer.update('messa')
signer.update('ge')
- sig = signer.finish(botan2.rng())
+ sig = signer.finish(botan2.RandomNumberGenerator())
- verify = botan2.pk_op_verify(rsapub, 'EMSA4(SHA-384)')
+ verify = botan2.PKVerify(rsapub, 'EMSA4(SHA-384)')
verify.update('mess')
verify.update('age')
@@ -217,16 +256,16 @@ class BotanPythonTests(unittest.TestCase):
self.assertTrue(verify.check_signature(sig))
def test_dh(self):
- a_rng = botan2.rng('user')
- b_rng = botan2.rng('user')
+ a_rng = botan2.RandomNumberGenerator('user')
+ b_rng = botan2.RandomNumberGenerator('user')
for dh_grp in ['secp256r1', 'curve25519']:
dh_kdf = 'KDF2(SHA-384)'.encode('utf-8')
- a_dh_priv = botan2.private_key('ecdh', dh_grp, a_rng)
- b_dh_priv = botan2.private_key('ecdh', dh_grp, b_rng)
+ a_dh_priv = botan2.PrivateKey.create('ecdh', dh_grp, a_rng)
+ b_dh_priv = botan2.PrivateKey.create('ecdh', dh_grp, b_rng)
- a_dh = botan2.pk_op_key_agreement(a_dh_priv, dh_kdf)
- b_dh = botan2.pk_op_key_agreement(b_dh_priv, dh_kdf)
+ a_dh = botan2.PKKeyAgreement(a_dh_priv, dh_kdf)
+ b_dh = botan2.PKKeyAgreement(b_dh_priv, dh_kdf)
a_dh_pub = a_dh.public_value()
b_dh_pub = b_dh.public_value()
@@ -239,7 +278,7 @@ class BotanPythonTests(unittest.TestCase):
self.assertEqual(a_key, b_key)
def test_certs(self):
- cert = botan2.x509_cert(filename="src/tests/data/x509/ecc/CSCA.CSCA.csca-germany.1.crt")
+ cert = botan2.X509Cert(filename="src/tests/data/x509/ecc/CSCA.CSCA.csca-germany.1.crt")
pubkey = cert.subject_public_key()
self.assertEqual(pubkey.algo_name(), 'ECDSA')