#!/usr/bin/env python """ Python wrapper of the botan crypto library http://botan.randombit.net (C) 2015,2017 Jack Lloyd (C) 2015 Uri Blumenthal (extensions and patches) Botan is released under the Simplified BSD License (see license.txt) This module uses the ctypes module and is usable by programs running under at least CPython 2.7, CPython 3.4 and 3.5, or PyPy. It uses botan's ffi module, which exposes a C API. It suppports all versions of Botan >= 2.0 """ import sys from ctypes import CDLL, POINTER, byref, c_void_p, c_size_t, c_uint32, c_char, c_char_p, create_string_buffer from binascii import hexlify, unhexlify, b2a_base64 from datetime import datetime import time # # Base exception for all exceptions raised from this module # class BotanException(Exception): pass # # Module initialization # if sys.platform == 'darwin': botan = CDLL('libbotan-2.dylib') # pylint: disable=invalid-name else: botan = CDLL('libbotan-2.so') # pylint: disable=invalid-name if botan.botan_ffi_supports_api(20151015) is False: raise BotanException("The Botan library does not support the FFI API expected by this version of the Python module") # # Internal utilities # def _call_fn_returning_vec(guess, fn): buf = create_string_buffer(guess) buf_len = c_size_t(len(buf)) rc = fn(buf, byref(buf_len)) if rc < 0: if buf_len.value > len(buf): #print("Calling again with %d" % (buf_len.value)) return _call_fn_returning_vec(buf_len.value, fn) else: raise BotanException("Call failed: %d" % (rc)) assert buf_len.value <= len(buf) return buf.raw[0:buf_len.value] def _call_fn_returning_string(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) return v.decode('ascii')[:-1] def _ctype_str(s): assert isinstance(s, str) if sys.version_info[0] < 3: return s else: return s.encode('utf-8') def _ctype_bits(s): if sys.version_info[0] < 3: if isinstance(s, str): return s else: assert False else: if isinstance(s, bytes): return s elif isinstance(s, str): return s.encode('utf-8') else: assert False def _ctype_bufout(buf): if sys.version_info[0] < 3: return str(buf.raw) else: return buf.raw def hex_encode(buf): return hexlify(buf).decode('ascii') def hex_decode(buf): return unhexlify(buf.encode('ascii')) # # Versions # def version_major(): return botan.botan_version_major() def version_minor(): return botan.botan_version_minor() def version_patch(): return botan.botan_version_patch() def version_string(): botan.botan_version_string.restype = c_char_p return botan.botan_version_string().decode('ascii') # # RNG # class rng(object): # pylint: disable=invalid-name # Can also use type "system" def __init__(self, rng_type='system'): botan.botan_rng_init.argtypes = [c_void_p, c_char_p] self.rng = c_void_p(0) rc = botan.botan_rng_init(byref(self.rng), _ctype_str(rng_type)) if rc != 0 or self.rng is None: raise BotanException("No rng " + rng_type + " available") def __del__(self): botan.botan_rng_destroy.argtypes = [c_void_p] botan.botan_rng_destroy(self.rng) def reseed(self, bits=256): botan.botan_rng_reseed.argtypes = [c_void_p, c_size_t] botan.botan_rng_reseed(self.rng, bits) def get(self, length): botan.botan_rng_get.argtypes = [c_void_p, POINTER(c_char), c_size_t] out = create_string_buffer(length) l = c_size_t(length) rc = botan.botan_rng_get(self.rng, out, l) if rc == 0: return _ctype_bufout(out) else: return None # # Hash function # class hash_function(object): # pylint: disable=invalid-name def __init__(self, algo): botan.botan_hash_init.argtypes = [c_void_p, c_char_p, c_uint32] flags = c_uint32(0) # always zero in this API version self.hash = c_void_p(0) rc = botan.botan_hash_init(byref(self.hash), _ctype_str(algo), flags) if rc != 0 or self.hash is None: raise BotanException("No hash " + algo + " for you!") def __del__(self): botan.botan_hash_destroy.argtypes = [c_void_p] botan.botan_hash_destroy(self.hash) def clear(self): botan.botan_hash_clear.argtypes = [c_void_p] return botan.botan_hash_clear(self.hash) def output_length(self): botan.botan_hash_output_length.argtypes = [c_void_p, POINTER(c_size_t)] l = c_size_t(0) rc = botan.botan_hash_output_length(self.hash, byref(l)) if rc == 0: return l.value raise BotanException("botan_hash_output_length failed") def update(self, x): botan.botan_hash_update.argtypes = [c_void_p, POINTER(c_char), c_size_t] botan.botan_hash_update(self.hash, _ctype_bits(x), len(x)) def final(self): botan.botan_hash_final.argtypes = [c_void_p, POINTER(c_char)] out = create_string_buffer(self.output_length()) botan.botan_hash_final(self.hash, out) return _ctype_bufout(out) # # Message authentication codes # class message_authentication_code(object): # pylint: disable=invalid-name def __init__(self, algo): botan.botan_mac_init.argtypes = [c_void_p, c_char_p, c_uint32] flags = c_uint32(0) # always zero in this API version self.mac = c_void_p(0) rc = botan.botan_mac_init(byref(self.mac), _ctype_str(algo), flags) if rc != 0 or self.mac is None: raise BotanException("No mac " + algo + " for you!") def __del__(self): botan.botan_mac_destroy.argtypes = [c_void_p] botan.botan_mac_destroy(self.mac) def clear(self): botan.botan_mac_clear.argtypes = [c_void_p] return botan.botan_mac_clear(self.mac) def output_length(self): botan.botan_mac_output_length.argtypes = [c_void_p, POINTER(c_size_t)] l = c_size_t(0) rc = botan.botan_mac_output_length(self.mac, byref(l)) if rc == 0: return l.value raise BotanException("botan_mac_output_length failed") def set_key(self, key): botan.botan_mac_set_key.argtypes = [c_void_p, POINTER(c_char), c_size_t] return botan.botan_mac_set_key(self.mac, key, len(key)) def update(self, x): botan.botan_mac_update.argtypes = [c_void_p, POINTER(c_char), c_size_t] botan.botan_mac_update(self.mac, x, len(x)) def final(self): botan.botan_mac_final.argtypes = [c_void_p, POINTER(c_char)] out = create_string_buffer(self.output_length()) botan.botan_mac_final(self.mac, out) return _ctype_bufout(out) class cipher(object): # pylint: disable=invalid-name def __init__(self, algo, encrypt=True): botan.botan_cipher_init.argtypes = [c_void_p, c_char_p, c_uint32] flags = 0 if encrypt else 1 self.cipher = c_void_p(0) rc = botan.botan_cipher_init(byref(self.cipher), _ctype_str(algo), flags) if rc != 0 or self.cipher is None: raise BotanException("No cipher " + algo + " for you!") def __del__(self): botan.botan_cipher_destroy.argtypes = [c_void_p] botan.botan_cipher_destroy(self.cipher) def default_nonce_length(self): botan.botan_cipher_get_default_nonce_length.argtypes = [c_void_p, POINTER(c_size_t)] l = c_size_t(0) botan.botan_cipher_get_default_nonce_length(self.cipher, byref(l)) return l.value def update_granularity(self): botan.botan_cipher_get_update_granularity.argtypes = [c_void_p, POINTER(c_size_t)] l = c_size_t(0) botan.botan_cipher_get_update_granularity(self.cipher, byref(l)) return l.value def key_length(self): kmin = c_size_t(0) kmax = c_size_t(0) botan.botan_cipher_query_keylen(self.cipher, byref(kmin), byref(kmax)) return kmin.value, kmax.value def tag_length(self): botan.botan_cipher_get_tag_length.argtypes = [c_void_p, POINTER(c_size_t)] l = c_size_t(0) botan.botan_cipher_get_tag_length(self.cipher, byref(l)) return l.value def is_authenticated(self): return self.tag_length() > 0 def valid_nonce_length(self, nonce_len): botan.botan_cipher_valid_nonce_length.argtypes = [c_void_p, c_size_t] rc = botan.botan_cipher_valid_nonce_length(self.cipher, nonce_len) if rc < 0: raise BotanException('Error calling valid_nonce_length') return True if rc == 1 else False def clear(self): botan.botan_cipher_clear.argtypes = [c_void_p] botan.botan_cipher_clear(self.cipher) def set_key(self, key): botan.botan_cipher_set_key.argtypes = [c_void_p, POINTER(c_char), c_size_t] botan.botan_cipher_set_key(self.cipher, key, len(key)) def set_assoc_data(self, ad): botan.botan_cipher_set_associated_data.argtypes = [c_void_p, POINTER(c_char), c_size_t] botan.botan_cipher_set_associated_data(self.cipher, ad, len(ad)) def start(self, nonce): botan.botan_cipher_start.argtypes = [c_void_p, POINTER(c_char), c_size_t] botan.botan_cipher_start(self.cipher, nonce, len(nonce)) def _update(self, txt, final): botan.botan_cipher_update.argtypes = [c_void_p, c_uint32, POINTER(c_char), c_size_t, POINTER(c_size_t), POINTER(c_char), c_size_t, POINTER(c_size_t)] inp = txt if txt else '' inp_sz = c_size_t(len(inp)) inp_consumed = c_size_t(0) out = create_string_buffer(inp_sz.value + (self.tag_length() if final else 0)) out_sz = c_size_t(len(out)) out_written = c_size_t(0) flags = c_uint32(1 if final else 0) botan.botan_cipher_update(self.cipher, flags, out, out_sz, byref(out_written), _ctype_bits(inp), inp_sz, byref(inp_consumed)) # buffering not supported yet assert inp_consumed.value == inp_sz.value return out.raw[0:out_written.value] def update(self, txt): return self._update(txt, False) def finish(self, txt=None): return self._update(txt, True) def bcrypt(passwd, rng_instance, work_factor=10): """ Bcrypt password hashing """ botan.botan_bcrypt_generate.argtypes = [POINTER(c_char), POINTER(c_size_t), c_char_p, c_void_p, c_size_t, c_uint32] out_len = c_size_t(64) out = create_string_buffer(out_len.value) flags = c_uint32(0) rc = botan.botan_bcrypt_generate(out, byref(out_len), _ctype_str(passwd), rng_instance.rng, c_size_t(work_factor), flags) if rc != 0: raise BotanException('botan bcrypt failed, error %s' % (rc)) b = out.raw[0:out_len.value-1] if b[-1] == '\x00': b = b[:-1] return b def check_bcrypt(passwd, passwd_hash): rc = botan.botan_bcrypt_is_valid(_ctype_str(passwd), passwd_hash) return rc == 0 # # PBKDF # def pbkdf(algo, password, out_len, iterations=10000, salt=rng().get(12)): botan.botan_pbkdf.argtypes = [c_char_p, POINTER(c_char), c_size_t, c_char_p, c_void_p, c_size_t, c_size_t] out_buf = create_string_buffer(out_len) botan.botan_pbkdf(_ctype_str(algo), out_buf, out_len, _ctype_str(password), salt, len(salt), iterations) return (salt, iterations, out_buf.raw) def pbkdf_timed(algo, password, out_len, ms_to_run=300, salt=rng().get(12)): botan.botan_pbkdf_timed.argtypes = [c_char_p, POINTER(c_char), c_size_t, c_char_p, c_void_p, c_size_t, c_size_t, POINTER(c_size_t)] out_buf = create_string_buffer(out_len) iterations = c_size_t(0) botan.botan_pbkdf_timed( _ctype_str(algo), out_buf, out_len, _ctype_str(password), salt, len(salt), ms_to_run, byref(iterations)) return (salt, iterations.value, out_buf.raw) # # KDF # def kdf(algo, secret, out_len, salt, label): botan.botan_kdf.argtypes = [c_char_p, POINTER(c_char), c_size_t, POINTER(c_char), c_size_t, POINTER(c_char), c_size_t, POINTER(c_char), c_size_t] out_buf = create_string_buffer(out_len) out_sz = c_size_t(out_len) botan.botan_kdf(_ctype_str(algo), out_buf, out_sz, secret, len(secret), salt, len(salt), label, len(label)) return out_buf.raw[0:out_sz.value] # # Public and private keys # class public_key(object): # pylint: disable=invalid-name def __init__(self, obj=c_void_p(0)): self.pubkey = obj def __del__(self): botan.botan_pubkey_destroy.argtypes = [c_void_p] botan.botan_pubkey_destroy(self.pubkey) def estimated_strength(self): botan.botan_pubkey_estimated_strength.argtypes = [c_void_p, POINTER(c_size_t)] r = c_size_t(0) botan.botan_pubkey_estimated_strength(self.pubkey, byref(r)) return r.value def algo_name(self): botan.botan_pubkey_algo_name.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] return _call_fn_returning_string(32, lambda b, bl: botan.botan_pubkey_algo_name(self.pubkey, b, bl)) def encoding(self, pem=False): botan.botan_pubkey_export.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t), c_uint32] flag = 1 if pem else 0 return _call_fn_returning_vec(0, lambda b, bl: botan.botan_pubkey_export(self.pubkey, b, bl, flag)) def fingerprint(self, hash_algorithm='SHA-256'): botan.botan_pubkey_fingerprint.argtypes = [c_void_p, c_char_p, POINTER(c_char), POINTER(c_size_t)] n = hash_function(hash_algorithm).output_length() buf = create_string_buffer(n) buf_len = c_size_t(n) botan.botan_pubkey_fingerprint(self.pubkey, _ctype_str(hash_algorithm), buf, byref(buf_len)) return hex_encode(buf[0:buf_len.value]) class private_key(object): # pylint: disable=invalid-name def __init__(self, alg, param, rng_instance): botan.botan_privkey_create_rsa.argtypes = [c_void_p, c_void_p, c_size_t] botan.botan_privkey_create_ecdsa.argtypes = [c_void_p, c_void_p, c_char_p] botan.botan_privkey_create_ecdh.argtypes = [c_void_p, c_void_p, c_char_p] botan.botan_privkey_create_mceliece.argtypes = [c_void_p, c_void_p, c_size_t, c_size_t] self.privkey = c_void_p(0) if alg == 'rsa': botan.botan_privkey_create_rsa(byref(self.privkey), rng_instance.rng, param) elif alg == 'ecdsa': botan.botan_privkey_create_ecdsa(byref(self.privkey), rng_instance.rng, _ctype_str(param)) elif alg == 'ecdh': botan.botan_privkey_create_ecdh(byref(self.privkey), rng_instance.rng, _ctype_str(param)) elif alg in ['mce', 'mceliece']: botan.botan_privkey_create_mceliece(byref(self.privkey), rng_instance.rng, param[0], param[1]) else: raise BotanException('Unknown public key algo ' + alg) if self.privkey is None: raise BotanException('Error creating ' + alg + ' key') def __del__(self): botan.botan_privkey_destroy.argtypes = [c_void_p] botan.botan_privkey_destroy(self.privkey) def get_public_key(self): botan.botan_privkey_export_pubkey.argtypes = [c_void_p, c_void_p] pub = c_void_p(0) botan.botan_privkey_export_pubkey(byref(pub), self.privkey) return public_key(pub) def export(self): botan.botan_privkey_export.argtypes = [c_void_p, POINTER(c_char), c_void_p] n = 4096 buf = create_string_buffer(n) buf_len = c_size_t(n) rc = botan.botan_privkey_export(self.privkey, buf, byref(buf_len)) if rc != 0: buf = create_string_buffer(buf_len.value) botan.botan_privkey_export(self.privkey, buf, byref(buf_len)) return buf[0:buf_len.value] class pk_op_encrypt(object): # pylint: disable=invalid-name def __init__(self, key, padding): botan.botan_pk_op_encrypt_create.argtypes = [c_void_p, c_void_p, c_char_p, c_uint32] self.op = c_void_p(0) flags = c_uint32(0) # always zero in this ABI print("Padding is ", padding) botan.botan_pk_op_encrypt_create(byref(self.op), key.pubkey, _ctype_str(padding), flags) if not self.op: raise BotanException("No pk op for you") def __del__(self): botan.botan_pk_op_encrypt_destroy.argtypes = [c_void_p] botan.botan_pk_op_encrypt_destroy(self.op) def encrypt(self, msg, rng_instance): botan.botan_pk_op_encrypt.argtypes = [c_void_p, c_void_p, POINTER(c_char), POINTER(c_size_t), POINTER(c_char), c_size_t] outbuf_sz = c_size_t(4096) #?!?! outbuf = create_string_buffer(outbuf_sz.value) ll = len(msg) #print("encrypt: len=%d" % ll) #if sys.version_info[0] > 2: # msg = cast(msg, c_char_p) # ll = c_size_t(ll) botan.botan_pk_op_encrypt(self.op, rng_instance.rng, outbuf, byref(outbuf_sz), msg, ll) #print("encrypt: outbuf_sz.value=%d" % outbuf_sz.value) return outbuf.raw[0:outbuf_sz.value] class pk_op_decrypt(object): # pylint: disable=invalid-name def __init__(self, key, padding): botan.botan_pk_op_decrypt_create.argtypes = [c_void_p, c_void_p, c_char_p, c_uint32] self.op = c_void_p(0) flags = c_uint32(0) # always zero in this ABI botan.botan_pk_op_decrypt_create(byref(self.op), key.privkey, _ctype_str(padding), flags) if not self.op: raise BotanException("No pk op for you") def __del__(self): botan.botan_pk_op_decrypt_destroy.argtypes = [c_void_p] botan.botan_pk_op_decrypt_destroy(self.op) def decrypt(self, msg): botan.botan_pk_op_decrypt.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t), POINTER(c_char), c_size_t] outbuf_sz = c_size_t(4096) #?!?! outbuf = create_string_buffer(outbuf_sz.value) ll = len(msg) botan.botan_pk_op_decrypt(self.op, outbuf, byref(outbuf_sz), _ctype_bits(msg), ll) return outbuf.raw[0:outbuf_sz.value] class pk_op_sign(object): # pylint: disable=invalid-name def __init__(self, key, padding): botan.botan_pk_op_sign_create.argtypes = [c_void_p, c_void_p, c_char_p, c_uint32] self.op = c_void_p(0) flags = c_uint32(0) # always zero in this ABI botan.botan_pk_op_sign_create(byref(self.op), key.privkey, _ctype_str(padding), flags) if not self.op: raise BotanException("No pk op for you") def __del__(self): botan.botan_pk_op_sign_destroy.argtypes = [c_void_p] botan.botan_pk_op_sign_destroy(self.op) def update(self, msg): botan.botan_pk_op_sign_update.argtypes = [c_void_p, POINTER(c_char), c_size_t] botan.botan_pk_op_sign_update(self.op, _ctype_str(msg), len(msg)) def finish(self, rng_instance): botan.botan_pk_op_sign_finish.argtypes = [c_void_p, c_void_p, POINTER(c_char), POINTER(c_size_t)] outbuf_sz = c_size_t(4096) #?!?! outbuf = create_string_buffer(outbuf_sz.value) botan.botan_pk_op_sign_finish(self.op, rng_instance.rng, outbuf, byref(outbuf_sz)) return outbuf.raw[0:outbuf_sz.value] class pk_op_verify(object): # pylint: disable=invalid-name def __init__(self, key, padding): botan.botan_pk_op_verify_create.argtypes = [c_void_p, c_void_p, c_char_p, c_uint32] self.op = c_void_p(0) flags = c_uint32(0) # always zero in this ABI botan.botan_pk_op_verify_create(byref(self.op), key.pubkey, _ctype_str(padding), flags) if not self.op: raise BotanException("No pk op for you") def __del__(self): botan.botan_pk_op_verify_destroy.argtypes = [c_void_p] botan.botan_pk_op_verify_destroy(self.op) def update(self, msg): botan.botan_pk_op_verify_update.argtypes = [c_void_p, POINTER(c_char), c_size_t] botan.botan_pk_op_verify_update(self.op, _ctype_bits(msg), len(msg)) def check_signature(self, signature): botan.botan_pk_op_verify_finish.argtypes = [c_void_p, POINTER(c_char), c_size_t] rc = botan.botan_pk_op_verify_finish(self.op, _ctype_bits(signature), len(signature)) if rc == 0: return True return False # # MCEIES encryption # Must be used with McEliece keys # def mceies_encrypt(mce, rng_instance, aead, pt, ad): botan.botan_mceies_encrypt.argtypes = [c_void_p, c_void_p, c_char_p, POINTER(c_char), c_size_t, POINTER(c_char), c_size_t, POINTER(c_char), POINTER(c_size_t)] return _call_fn_returning_vec(0, lambda b, bl: botan.botan_mceies_encrypt(mce.pubkey, rng_instance.rng, _ctype_str(aead), _ctype_bits(pt), len(pt), _ctype_bits(ad), len(ad), b, bl)) def mceies_decrypt(mce, aead, pt, ad): botan.botan_mceies_decrypt.argtypes = [c_void_p, c_char_p, POINTER(c_char), c_size_t, POINTER(c_char), c_size_t, POINTER(c_char), POINTER(c_size_t)] #msg = cast(msg, c_char_p) #ll = c_size_t(ll) return _call_fn_returning_vec(0, lambda b, bl: botan.botan_mceies_decrypt(mce.privkey, _ctype_str(aead), _ctype_bits(pt), len(pt), _ctype_bits(ad), len(ad), b, bl)) class pk_op_key_agreement(object): # pylint: disable=invalid-name def __init__(self, key, kdf_name): botan.botan_pk_op_key_agreement_create.argtypes = [c_void_p, c_void_p, c_char_p, c_uint32] botan.botan_pk_op_key_agreement_export_public.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] self.op = c_void_p(0) flags = c_uint32(0) # always zero in this ABI botan.botan_pk_op_key_agreement_create(byref(self.op), key.privkey, kdf_name, flags) if not self.op: raise BotanException("No key agreement for you") self.m_public_value = _call_fn_returning_vec( 0, lambda b, bl: botan.botan_pk_op_key_agreement_export_public(key.privkey, b, bl)) def __del__(self): botan.botan_pk_op_key_agreement_destroy.argtypes = [c_void_p] botan.botan_pk_op_key_agreement_destroy(self.op) def public_value(self): return self.m_public_value def agree(self, other, key_len, salt): botan.botan_pk_op_key_agreement.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t), POINTER(c_char), c_size_t, POINTER(c_char), c_size_t] return _call_fn_returning_vec(key_len, lambda b, bl: botan.botan_pk_op_key_agreement(self.op, b, bl, other, len(other), salt, len(salt))) # # X.509 certificates # class x509_cert(object): # pylint: disable=invalid-name def __init__(self, filename=None, buf=None): if filename is None and buf is None: raise BotanException("No filename or buf given") if filename is not None and buf is not None: raise BotanException("Both filename and buf given") elif filename is not None: botan.botan_x509_cert_load_file.argtypes = [POINTER(c_void_p), c_char_p] self.x509_cert = c_void_p(0) botan.botan_x509_cert_load_file(byref(self.x509_cert), _ctype_str(filename)) elif buf is not None: botan.botan_x509_cert_load.argtypes = [POINTER(c_void_p), POINTER(c_char), c_size_t] self.x509_cert = c_void_p(0) botan.botan_x509_cert_load(byref(self.x509_cert), _ctype_bits(buf), len(buf)) def __del__(self): botan.botan_x509_cert_destroy.argtypes = [c_void_p] botan.botan_x509_cert_destroy(self.x509_cert) def time_starts(self): botan.botan_x509_cert_get_time_starts.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] starts = _call_fn_returning_string( 16, lambda b, bl: botan.botan_x509_cert_get_time_starts(self.x509_cert, b, bl)) if len(starts) == 13: # UTC time struct_time = time.strptime(starts, "%y%m%d%H%M%SZ") elif len(starts) == 15: # Generalized time struct_time = time.strptime(starts, "%Y%m%d%H%M%SZ") else: raise BotanException("Wrong date/time format") return datetime.fromtimestamp(time.mktime(struct_time)) def time_expires(self): botan.botan_x509_cert_get_time_expires.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] expires = _call_fn_returning_string( 16, lambda b, bl: botan.botan_x509_cert_get_time_expires(self.x509_cert, b, bl)) if len(expires) == 13: # UTC time struct_time = time.strptime(expires, "%y%m%d%H%M%SZ") elif len(expires) == 15: # Generalized time struct_time = time.strptime(expires, "%Y%m%d%H%M%SZ") else: raise BotanException("Wrong date/time format") return datetime.fromtimestamp(time.mktime(struct_time)) def to_string(self): botan.botan_x509_cert_to_string.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] return _call_fn_returning_string( 0, lambda b, bl: botan.botan_x509_cert_to_string(self.x509_cert, b, bl)) def fingerprint(self, hash_algo='SHA-256'): botan.botan_x509_cert_get_fingerprint.argtypes = [c_void_p, c_char_p, POINTER(c_char), POINTER(c_size_t)] n = hash_function(hash_algo).output_length() * 3 return _call_fn_returning_string( n, lambda b, bl: botan.botan_x509_cert_get_fingerprint(self.x509_cert, _ctype_str(hash_algo), b, bl)) def serial_number(self): botan.botan_x509_cert_get_serial_number.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] return _call_fn_returning_vec( 0, lambda b, bl: botan.botan_x509_cert_get_serial_number(self.x509_cert, b, bl)) def authority_key_id(self): botan.botan_x509_cert_get_authority_key_id.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] return _call_fn_returning_vec( 0, lambda b, bl: botan.botan_x509_cert_get_authority_key_id(self.x509_cert, b, bl)) def subject_key_id(self): botan.botan_x509_cert_get_subject_key_id.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] return _call_fn_returning_vec( 0, lambda b, bl: botan.botan_x509_cert_get_subject_key_id(self.x509_cert, b, bl)) def subject_public_key_bits(self): botan.botan_x509_cert_get_public_key_bits.argtypes = [c_void_p, POINTER(c_char), POINTER(c_size_t)] return _call_fn_returning_vec( 0, lambda b, bl: botan.botan_x509_cert_get_public_key_bits(self.x509_cert, b, bl)) def subject_public_key(self): botan.botan_x509_cert_get_public_key.argtypes = [c_void_p, c_void_p] pub = c_void_p(0) botan.botan_x509_cert_get_public_key(self.x509_cert, byref(pub)) return public_key(pub) def subject_dn(self, key, index): botan.botan_x509_cert_get_subject_dn.argtypes = [ c_void_p, c_char_p, c_size_t, POINTER(c_char), POINTER(c_size_t)] return _call_fn_returning_string( 0, lambda b, bl: botan.botan_x509_cert_get_subject_dn(self.x509_cert, _ctype_str(key), index, b, bl)) # # Tests and examples # def test(): def test_version(): print("\n%s" % version_string()) print("v%d.%d.%d\n" % (version_major(), version_minor(), version_patch())) print("\nPython %s\n" % sys.version.replace('\n', ' ')) def test_kdf(): print("KDF2(SHA-1) %s" % hex_encode(kdf('KDF2(SHA-1)', hex_decode('701F3480DFE95F57941F804B1B2413EF'), 7, hex_decode('55A4E9DD5F4CA2EF82'), hex_decode('')))) def test_pbkdf(): print("PBKDF2(SHA-1) %s" % hex_encode(pbkdf('PBKDF2(SHA-1)', '', 32, 10000, hex_decode('0001020304050607'))[2])) print("good output %s\n" % '59B2B1143B4CB1059EC58D9722FB1C72471E0D85C6F7543BA5228526375B0127') (salt, iterations, psk) = pbkdf_timed('PBKDF2(SHA-256)', 'xyz', 32, 200) print("PBKDF2(SHA-256) x=timed, y=iterated; salt = %s (len=%d) #iterations = %d\n" % (hex_encode(salt), len(salt), iterations)) print('x %s' % hex_encode(psk)) print('y %s\n' % (hex_encode(pbkdf('PBKDF2(SHA-256)', 'xyz', 32, iterations, salt)[2]))) def test_bcrypt(): print("Testing Bcrypt...") r = rng() phash = bcrypt('testing', r) print("bcrypt returned %s (%d bytes)" % (hex_encode(phash), len(phash))) print("validating the hash produced: %r" % (check_bcrypt('testing', phash))) print("\n") def test_hmac(): hmac = message_authentication_code('HMAC(SHA-256)') hmac.set_key(hex_decode('0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20')) hmac.update(hex_decode('616263')) hmac_vec = hex_decode('A21B1F5D4CF4F73A4DD939750F7A066A7F98CC131CB16A6692759021CFAB8181') hmac_output = hmac.final() if hmac_output != hmac_vec: print("Bad HMAC:\t%s" % hex_encode(hmac_output)) print("vs good: \t%s" % hex_encode(hmac_vec)) else: print("HMAC output correct: %s\n" % hex_encode(hmac_output)) def test_rng(): user_rng = rng("user") print("rng output:\n\t%s\n\t%s\n\t%s\n" % (hex_encode(user_rng.get(42)), hex_encode(user_rng.get(13)), hex_encode(user_rng.get(9)))) def test_hash(): md5 = hash_function('MD5') assert md5.output_length() == 16 md5.update('h') md5.update('i') h1 = md5.final() print("md5 hash: %s (%s)\n" % (hex_encode(h1), '49f68a5c8493ec2c0bf489821c21fc3b')) md5.update(hex_decode('f468025b')) h2 = md5.final() print("md5 hash: %s (%s)\n" % (hex_encode(h2), '47efd2be302a937775e93dea281b6751')) def test_cipher(): for mode in ['AES-128/CTR-BE', 'Serpent/GCM', 'ChaCha20Poly1305']: enc = cipher(mode, encrypt=True) (kmin, kmax) = enc.key_length() print("%s: default nonce=%d update_size=%d key_min=%d key_max=%d" % (mode, enc.default_nonce_length(), enc.update_granularity(), kmin, kmax)) iv = rng().get(enc.default_nonce_length()) key = rng().get(kmax) pt = rng().get(21) print(" plaintext %s (%d)" % (hex_encode(pt), len(pt))) enc.set_key(key) enc.start(iv) update_result = enc.update('') assert not update_result ct = enc.finish(pt) print(" ciphertext %s (%d)" % (hex_encode(ct), len(ct))) dec = cipher(mode, encrypt=False) dec.set_key(key) dec.start(iv) decrypted = dec.finish(ct) print(" decrypted %s (%d)\n" % (hex_encode(decrypted), len(decrypted))) def test_mceliece(): mce_priv = private_key('mce', [2960, 57], rng()) mce_pub = mce_priv.get_public_key() mce_plaintext = 'mce plaintext' mce_ad = 'mce AD' mce_ciphertext = mceies_encrypt(mce_pub, rng(), 'ChaCha20Poly1305', mce_plaintext, mce_ad) print("mceies len(pt)=%d len(ct)=%d" % (len(mce_plaintext), len(mce_ciphertext))) mce_decrypt = mceies_decrypt(mce_priv, 'ChaCha20Poly1305', mce_ciphertext, mce_ad) print(" mceies plaintext \'%s\' (%d)" % (mce_plaintext, len(mce_plaintext))) # Since mceies_decrypt() returns bytes in Python3, the following line # needs .decode('utf-8') to convert mce_decrypt from bytes to a # text string (Unicode). # You don't need to add .decode() if # (a) your expected output is bytes rather than a text string, or # (b) you are using Python2 rather than Python3. print(" mceies decrypted \'%s\' (%d)" % (mce_decrypt.decode('utf-8'), len(mce_decrypt))) print("mce_pub %s/SHA-1 fingerprint: %s\nEstimated strength %s bits (len %d)\n" % ( mce_pub.algo_name(), mce_pub.fingerprint("SHA-1"), mce_pub.estimated_strength(), len(mce_pub.encoding()) )) def test_rsa(): rsapriv = private_key('rsa', 1536, rng()) rsapub = rsapriv.get_public_key() print("rsapub %s SHA-1 fingerprint: %s estimated strength %d (len %d)" % ( rsapub.algo_name(), rsapub.fingerprint("SHA-1"), rsapub.estimated_strength(), len(rsapub.encoding()) )) dec = pk_op_decrypt(rsapriv, "EME1(SHA-256)") enc = pk_op_encrypt(rsapub, "EME1(SHA-256)") sys_rng = rng() symkey = sys_rng.get(32) ctext = enc.encrypt(symkey, sys_rng) print("ptext \'%s\' (%d)" % (hex_encode(symkey), len(symkey))) print("ctext \'%s\' (%d)" % (hex_encode(ctext), len(ctext))) print("decrypt \'%s\' (%d)\n" % (hex_encode(dec.decrypt(ctext)), len(dec.decrypt(ctext)))) signer = pk_op_sign(rsapriv, 'EMSA4(SHA-384)') signer.update('messa') signer.update('ge') sig = signer.finish(rng()) print("EMSA4(SHA-384) signature: %s" % hex_encode(sig)) verify = pk_op_verify(rsapub, 'EMSA4(SHA-384)') verify.update('mess') verify.update('age') print("good sig accepted? %s" % verify.check_signature(sig)) verify.update('mess of things') verify.update('age') print("bad sig accepted? %s" % verify.check_signature(sig)) verify.update('message') print("good sig accepted? %s\n" % verify.check_signature(sig)) def test_dh(): a_rng = rng('user') b_rng = rng('user') for dh_grp in ['secp256r1', 'curve25519']: dh_kdf = 'KDF2(SHA-384)'.encode('utf-8') a_dh_priv = private_key('ecdh', dh_grp, rng()) b_dh_priv = private_key('ecdh', dh_grp, rng()) a_dh = pk_op_key_agreement(a_dh_priv, dh_kdf) b_dh = pk_op_key_agreement(b_dh_priv, dh_kdf) a_dh_pub = a_dh.public_value() b_dh_pub = b_dh.public_value() a_salt = a_rng.get(8) b_salt = b_rng.get(8) print("ecdh %s pubs:\n %s (salt %s)\n %s (salt %s)\n" % (dh_grp, hex_encode(a_dh_pub), hex_encode(a_salt), hex_encode(b_dh_pub), hex_encode(b_salt))) a_key = a_dh.agree(b_dh_pub, 32, a_salt + b_salt) b_key = b_dh.agree(a_dh_pub, 32, a_salt + b_salt) print("ecdh %s shared:\n %s\n %s\n" % (dh_grp, hex_encode(a_key), hex_encode(b_key))) def test_certs(): cert = x509_cert(filename="src/tests/data/ecc/CSCA.CSCA.csca-germany.1.crt") print("CSCA (Germany) Certificate\nDetails:") print("SHA-1 fingerprint: %s" % cert.fingerprint("SHA-1")) print("Expected: 32:42:1C:C3:EC:54:D7:E9:43:EC:51:F0:19:23:BD:85:1D:F2:1B:B9") print("Not before: %s" % cert.time_starts()) print("Not after: %s" % cert.time_expires()) print("Serial number: %s" % hex_encode(cert.serial_number())) print("Authority Key ID: %s" % hex_encode(cert.authority_key_id())) print("Subject Key ID: %s" % hex_encode(cert.subject_key_id())) print("Public key bits:\n%s\n" % b2a_base64(cert.subject_public_key_bits())) pubkey = cert.subject_public_key() print("Public key algo: %s" % pubkey.algo_name()) print("Public key strength: %s" % pubkey.estimated_strength() + " bits") dn_fields = ("Name", "Email", "Organization", "Organizational Unit", "Country") for field in dn_fields: try: print("%s: %s" % (field, cert.subject_dn(field, 0))) except BotanException: print("Field: %s not found in certificate" % field) print(cert.to_string()) test_version() test_kdf() test_pbkdf() test_bcrypt() test_hmac() test_rng() test_hash() test_cipher() test_mceliece() test_rsa() test_dh() test_certs() def main(args=None): if args is None: args = sys.argv test() if __name__ == '__main__': sys.exit(main())