From f0aa0fac6b495acf841079316e2c63318ece839b Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Mon, 13 Aug 2018 07:47:47 -0400 Subject: De-inline ffi_guard_thunk Saves about 300 Kb of code space in the FFI object files --- src/lib/ffi/ffi.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib/ffi/ffi.h | 1 + src/lib/ffi/ffi_util.h | 36 ++---------------------------------- 3 files changed, 52 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/lib/ffi/ffi.cpp b/src/lib/ffi/ffi.cpp index 6e683dfdc..7bea1a209 100644 --- a/src/lib/ffi/ffi.cpp +++ b/src/lib/ffi/ffi.cpp @@ -24,6 +24,52 @@ int ffi_error_exception_thrown(const char* func_name, const char* exn, int rc) return rc; } +int ffi_guard_thunk(const char* func_name, std::function thunk) + { + try + { + return thunk(); + } + catch(std::bad_alloc&) + { + return ffi_error_exception_thrown(func_name, "bad_alloc", BOTAN_FFI_ERROR_OUT_OF_MEMORY); + } + catch(Botan_FFI::FFI_Error& e) + { + return ffi_error_exception_thrown(func_name, e.what(), e.error_code()); + } + catch(Botan::Lookup_Error& e) + { + return ffi_error_exception_thrown(func_name, e.what(), BOTAN_FFI_ERROR_NOT_IMPLEMENTED); + } + catch(Botan::Invalid_Key_Length& e) + { + return ffi_error_exception_thrown(func_name, e.what(), BOTAN_FFI_ERROR_INVALID_KEY_LENGTH); + } + catch(Botan::Key_Not_Set& e) + { + return ffi_error_exception_thrown(func_name, e.what(), BOTAN_FFI_ERROR_KEY_NOT_SET); + } + catch(Botan::Invalid_Argument& e) + { + return ffi_error_exception_thrown(func_name, e.what(), BOTAN_FFI_ERROR_BAD_PARAMETER); + } + catch(Botan::Not_Implemented& e) + { + return ffi_error_exception_thrown(func_name, e.what(), BOTAN_FFI_ERROR_NOT_IMPLEMENTED); + } + catch(std::exception& e) + { + return ffi_error_exception_thrown(func_name, e.what()); + } + catch(...) + { + return ffi_error_exception_thrown(func_name, "unknown exception"); + } + + return BOTAN_FFI_ERROR_UNKNOWN_ERROR; + } + } extern "C" { @@ -67,6 +113,9 @@ const char* botan_error_description(int err) case BOTAN_FFI_ERROR_KEY_NOT_SET: return "Key not set on object"; + case BOTAN_FFI_ERROR_INVALID_KEY_LENGTH: + return "Invalid key length"; + case BOTAN_FFI_ERROR_NOT_IMPLEMENTED: return "Not implemented"; diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 96cdf1de4..8ddfcabe2 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -78,6 +78,7 @@ enum BOTAN_FFI_ERROR { BOTAN_FFI_ERROR_NULL_POINTER = -31, BOTAN_FFI_ERROR_BAD_PARAMETER = -32, BOTAN_FFI_ERROR_KEY_NOT_SET = -33, + BOTAN_FFI_ERROR_INVALID_KEY_LENGTH = -34, BOTAN_FFI_ERROR_NOT_IMPLEMENTED = -40, BOTAN_FFI_ERROR_INVALID_OBJECT = -50, diff --git a/src/lib/ffi/ffi_util.h b/src/lib/ffi/ffi_util.h index 623b4bf20..2b9206f0b 100644 --- a/src/lib/ffi/ffi_util.h +++ b/src/lib/ffi/ffi_util.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -69,40 +70,7 @@ T& safe_get(botan_struct* p) throw FFI_Error("Invalid object pointer", BOTAN_FFI_ERROR_INVALID_OBJECT); } -template -int ffi_guard_thunk(const char* func_name, Thunk thunk) - { - try - { - return thunk(); - } - catch(std::bad_alloc&) - { - return ffi_error_exception_thrown(func_name, "bad_alloc", BOTAN_FFI_ERROR_OUT_OF_MEMORY); - } - catch(Botan_FFI::FFI_Error& e) - { - return ffi_error_exception_thrown(func_name, e.what(), e.error_code()); - } - catch(Botan::Key_Not_Set& e) - { - return ffi_error_exception_thrown(func_name, e.what(), BOTAN_FFI_ERROR_KEY_NOT_SET); - } - catch(Botan::Invalid_Argument& e) - { - return ffi_error_exception_thrown(func_name, e.what(), BOTAN_FFI_ERROR_BAD_PARAMETER); - } - catch(std::exception& e) - { - return ffi_error_exception_thrown(func_name, e.what()); - } - catch(...) - { - return ffi_error_exception_thrown(func_name, "unknown exception"); - } - - return BOTAN_FFI_ERROR_UNKNOWN_ERROR; - } +int ffi_guard_thunk(const char* func_name, std::function); template int apply_fn(botan_struct* o, const char* func_name, F func) -- cgit v1.2.3 From 69ba4b0b46db42114f2b98ed41d4ddf4a4392961 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Mon, 13 Aug 2018 07:50:41 -0400 Subject: Add MPI and FPE to Python wrapper Also make all member variables private (__ prefix), and rename classes to match Python conventions --- doc/manual/python.rst | 114 +++++++- src/python/botan2.py | 670 +++++++++++++++++++++++++++++++++++---------- src/scripts/test_python.py | 102 ++++++- 3 files changed, 727 insertions(+), 159 deletions(-) (limited to 'src') diff --git a/doc/manual/python.rst b/doc/manual/python.rst index f758769fe..19cf10622 100644 --- a/doc/manual/python.rst +++ b/doc/manual/python.rst @@ -11,6 +11,10 @@ Python Binding The Python binding is based on the `ffi` module of botan and the `ctypes` module of the Python standard library. +Starting in 2.8, the class names were renamed to match Python standard +conventions. However aliases are defined which allow older code to +continue to work; the older names are mentioned as "previously X". + Versioning ---------------------------------------- .. py:function:: version_major() @@ -31,7 +35,10 @@ Versioning Random Number Generators ---------------------------------------- -.. py:class:: rng(rng_type = 'system') + +.. py:class:: RandomNumberGenerator(rng_type = 'system') + + Previously ``rng`` Type 'user' also allowed (userspace HKDF RNG seeded from system rng). The system RNG is very cheap to create, as just a single file @@ -47,10 +54,22 @@ Random Number Generators Meaningless on system RNG, on userspace RNG causes a reseed/rekey + .. py:method:: reseed_from_rng(source_rng, bits = 256) + + Take bits from the source RNG and use it to seed ``self`` + + .. py:method:: add_entropy(seed) + + Add some unpredictable seed data to the RNG + + Hash Functions ---------------------------------------- -.. py:class:: hash_function(algo) + +.. py:class:: HashFunction(algo) + + Previously ``hash_function`` Algo is a string (eg 'SHA-1', 'SHA-384', 'Skein-512') @@ -77,7 +96,10 @@ Hash Functions Message Authentication Codes ---------------------------------------- -.. py:class:: message_authentication_code(algo) + +.. py:class:: MsgAuthCode(algo) + + Previously ``message_authentication_code`` Algo is a string (eg 'HMAC(SHA-256)', 'Poly1305', 'CMAC(AES-256)') @@ -108,12 +130,15 @@ Message Authentication Codes Ciphers ---------------------------------------- -.. py:class:: cipher(object, algo, encrypt = True) - The algorithm is spcified as a string (eg 'AES-128/GCM', - 'Serpent/OCB(12)', 'Threefish-512/EAX'). +.. py:class:: SymmetricCipher(object, algo, encrypt = True) + + Previously ``cipher`` + + The algorithm is spcified as a string (eg 'AES-128/GCM', + 'Serpent/OCB(12)', 'Threefish-512/EAX'). - Set the second param to False for decryption + Set the second param to False for decryption .. py:method:: algo_name() @@ -205,6 +230,8 @@ PBKDF Scrypt --------------- +.. versionadded:: 2.8.0 + .. py:function:: scrypt(out_len, password, salt, N=1024, r=8, p=8) Runs Scrypt key derivation function over the specified password @@ -218,10 +245,12 @@ KDF the provided secret and salt values. Returns a value of the specified length. -Public and Private Keys +Public Key ---------------------------------------- -.. py:class:: public_key(object) +.. py:class:: PublicKey(object) + + Previously ``public_key`` .. py:method:: fingerprint(hash = 'SHA-256') @@ -240,8 +269,12 @@ Public and Private Keys Returns the encoding of the key, PEM if set otherwise DER +Private Key +---------------------------------------- + +.. py:class:: PrivateKey(algo, param, rng) -.. py:class:: private_key(algo, param, rng) + 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 @@ -258,25 +291,36 @@ Public and Private Keys Public Key Operations ---------------------------------------- -.. py:class:: pk_op_encrypt(pubkey, padding) + +.. py:class:: PKEncrypt(pubkey, padding) + + Previously ``pk_op_encrypt`` .. py:method:: encrypt(msg, rng) -.. py:class:: pk_op_decrypt(privkey, padding) +.. py:class:: PKDecrypt(privkey, padding) + + Previously ``pk_op_decrypt`` .. py:method:: decrypt(msg) -.. py:class:: pk_op_sign(privkey, hash_w_padding) +.. py:class:: PKSign(privkey, hash_w_padding) + + Previously ``pk_op_sign`` .. py:method:: update(msg) .. py:method:: finish(rng) -.. py:class:: pk_op_verify(pubkey, hash_w_padding) +.. py:class:: PKVerify(pubkey, hash_w_padding) + + Previously ``pk_op_verify`` .. py:method:: update(msg) .. py:method:: check_signature(signature) -.. py:class:: pk_op_key_agreement(privkey, kdf) +.. py:class:: PKKeyAgreement(privkey, kdf) + + Previously ``pk_op_key_agreement`` .. py:method:: public_value() @@ -286,3 +330,43 @@ Public Key Operations Returns a key derived by the KDF. +Multiple Precision Integers (MPI) +------------------------------------- +.. versionadded:: 2.8.0 + +.. py:class:: MPI(initial_value=None) + + Initialize an MPI object with specified value, left as zero otherwise. The + ``initial_value`` should be an ``int``, ``str``, or ``MPI``. + + Most of the usual arithmetic operators (``__add__``, ``__mul__``, etc) are + defined. + + .. py:method:: inverse_mod(modulus) + + Return the inverse of ``self`` modulo modulus, or zero if no inverse exists + + .. py:method:: is_prime(rng, prob=128) + + Test if ``self`` is prime + + .. py:method:: pow_mod(exponent, modulus): + + Return ``self`` to the ``exponent`` power modulo ``modulus`` + +Format Preserving Encryption (FE1 scheme) +----------------------------------------- +.. versionadded:: 2.8.0 + +.. py:class:: FormatPreservingEncryptionFE1(modulus, key, rounds=5, compat_mode=False) + + Initialize an instance for format preserving encryption + + .. py:method:: encrypt(msg, tweak) + + The msg should be a botan2.MPI or an object which can be converted to one + + .. py:method:: decrypt(msg, tweak) + + The msg should be a botan2.MPI or an object which can be converted to one + diff --git a/src/python/botan2.py b/src/python/botan2.py index 209805a66..989a07160 100755 --- a/src/python/botan2.py +++ b/src/python/botan2.py @@ -10,16 +10,16 @@ https://botan.randombit.net 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. +under at least CPython 2.7, CPython 3.x, and PyPy -It uses botan's ffi module, which exposes a C API. - -This version of the module requires FFI API version 20180713, which was -introduced in Botan 2.8 +It uses botan's ffi module, which exposes a C API. This version of the +module requires FFI API version 20180713, which was introduced in +Botan 2.8 """ -from ctypes import CDLL, POINTER, byref, c_void_p, c_size_t, c_uint32, c_int, c_char, c_char_p, create_string_buffer +from ctypes import CDLL, POINTER, byref, create_string_buffer, \ + c_void_p, c_size_t, c_uint8, c_uint32, c_int, c_char, c_char_p from sys import version_info from time import strptime, mktime @@ -35,12 +35,16 @@ class BotanException(Exception): def __init__(self, message, rc=0): + self.__rc = rc + if rc == 0: super(BotanException, self).__init__(message) else: descr = botan.botan_error_description(rc).decode('ascii') super(BotanException, self).__init__("%s: %d (%s)" % (message, rc, descr)) - self.rc = rc + + def error_code(self): + return self.__rc # # Module initialization @@ -100,6 +104,12 @@ botan.botan_rng_destroy.errcheck = errcheck_for('botan_rng_destroy') botan.botan_rng_reseed.argtypes = [c_void_p, c_size_t] botan.botan_rng_reseed.errcheck = errcheck_for('botan_rng_reseed') +botan.botan_rng_reseed_from_rng.argtypes = [c_void_p, c_void_p, c_size_t] +botan.botan_rng_reseed_from_rng.errcheck = errcheck_for('botan_rng_reseed_from_rng') + +botan.botan_rng_add_entropy.argtypes = [c_void_p, c_char_p, c_size_t] +botan.botan_rng_add_entropy.errcheck = errcheck_for('botan_rng_add_entropy') + botan.botan_rng_get.argtypes = [c_void_p, POINTER(c_char), c_size_t] botan.botan_rng_get.errcheck = errcheck_for('botan_rng_get') @@ -367,6 +377,126 @@ botan.botan_x509_cert_get_public_key.errcheck = errcheck_for('botan_x509_cert_ge botan.botan_x509_cert_get_subject_dn.argtypes = [c_void_p, c_char_p, c_size_t, POINTER(c_char), POINTER(c_size_t)] botan.botan_x509_cert_get_subject_dn.errcheck = errcheck_for('botan_x509_cert_get_subject_dn') +# MPI +botan.botan_mp_init.argtypes = [c_void_p] +botan.botan_mp_init.errcheck = errcheck_for('botan_mp_init') +botan.botan_mp_destroy.argtypes = [c_void_p] +botan.botan_mp_destroy.errcheck = errcheck_for('botan_mp_destroy') + +botan.botan_mp_to_hex.argtypes = [c_void_p, POINTER(c_char)] +botan.botan_mp_to_hex.errcheck = errcheck_for('botan_mp_to_hex') +botan.botan_mp_to_str.argtypes = [c_void_p, c_uint8, POINTER(c_char), POINTER(c_size_t)] +botan.botan_mp_to_str.errcheck = errcheck_for('botan_mp_to_str') + +botan.botan_mp_clear.argtypes = [c_void_p] +botan.botan_mp_clear.errcheck = errcheck_for('botan_mp_clear') + +botan.botan_mp_set_from_int.argtypes = [c_void_p, c_int] +botan.botan_mp_set_from_int.errcheck = errcheck_for('botan_mp_set_from_int') +botan.botan_mp_set_from_mp.argtypes = [c_void_p, c_void_p] +botan.botan_mp_set_from_mp.errcheck = errcheck_for('botan_mp_set_from_mp') +botan.botan_mp_set_from_str.argtypes = [c_void_p, POINTER(c_char)] +botan.botan_mp_set_from_str.errcheck = errcheck_for('botan_mp_set_from_str') +botan.botan_mp_set_from_radix_str.argtypes = [c_void_p, POINTER(c_char), c_size_t] +botan.botan_mp_set_from_radix_str.errcheck = errcheck_for('botan_mp_set_from_radix_str') + +botan.botan_mp_num_bits.argtypes = [c_void_p, POINTER(c_size_t)] +botan.botan_mp_num_bits.errcheck = errcheck_for('botan_mp_num_bits') +botan.botan_mp_num_bytes.argtypes = [c_void_p, POINTER(c_size_t)] +botan.botan_mp_num_bytes.errcheck = errcheck_for('botan_mp_num_bytes') + +botan.botan_mp_to_bin.argtypes = [c_void_p, POINTER(c_uint8)] +botan.botan_mp_to_bin.errcheck = errcheck_for('botan_mp_to_bin') +botan.botan_mp_from_bin.argtypes = [c_void_p, POINTER(c_uint8), c_size_t] +botan.botan_mp_from_bin.errcheck = errcheck_for('botan_mp_from_bin') + +botan.botan_mp_to_uint32.argtypes = [c_void_p, POINTER(c_uint32)] +botan.botan_mp_to_uint32.errcheck = errcheck_for('botan_mp_to_uint32') + +botan.botan_mp_is_positive.argtypes = [c_void_p] +botan.botan_mp_is_positive.errcheck = errcheck_for('botan_mp_is_positive') + +botan.botan_mp_is_negative.argtypes = [c_void_p] +botan.botan_mp_is_negative.errcheck = errcheck_for('botan_mp_is_negative') + +botan.botan_mp_flip_sign.argtypes = [c_void_p] +botan.botan_mp_flip_sign.errcheck = errcheck_for('botan_mp_flip_sign') + +botan.botan_mp_is_zero.argtypes = [c_void_p] +botan.botan_mp_is_zero.errcheck = errcheck_for('botan_mp_is_zero') +botan.botan_mp_is_odd.argtypes = [c_void_p] +botan.botan_mp_is_odd.errcheck = errcheck_for('botan_mp_is_odd') +botan.botan_mp_is_even.argtypes = [c_void_p] +botan.botan_mp_is_even.errcheck = errcheck_for('botan_mp_is_even') + +botan.botan_mp_add.argtypes = [c_void_p, c_void_p, c_void_p] +botan.botan_mp_add.errcheck = errcheck_for('botan_mp_add') +botan.botan_mp_sub.argtypes = [c_void_p, c_void_p, c_void_p] +botan.botan_mp_sub.errcheck = errcheck_for('botan_mp_sub') +botan.botan_mp_mul.argtypes = [c_void_p, c_void_p, c_void_p] +botan.botan_mp_mul.errcheck = errcheck_for('botan_mp_mul') + +botan.botan_mp_div.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p] +botan.botan_mp_div.errcheck = errcheck_for('botan_mp_div') + +botan.botan_mp_mod_mul.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p] +botan.botan_mp_mod_mul.errcheck = errcheck_for('botan_mp_mod_mul') + +botan.botan_mp_equal.argtypes = [c_void_p, c_void_p] +botan.botan_mp_equal.errcheck = errcheck_for('botan_mp_equal') + +botan.botan_mp_cmp.argtypes = [POINTER(c_int), c_void_p, c_void_p] +botan.botan_mp_cmp.errcheck = errcheck_for('botan_mp_cmp') + +botan.botan_mp_swap.argtypes = [c_void_p, c_void_p] +botan.botan_mp_swap.errcheck = errcheck_for('botan_mp_swap') + +botan.botan_mp_powmod.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p] +botan.botan_mp_powmod.errcheck = errcheck_for('botan_mp_powmod') + +botan.botan_mp_lshift.argtypes = [c_void_p, c_void_p, c_size_t] +botan.botan_mp_lshift.errcheck = errcheck_for('botan_mp_lshift') +botan.botan_mp_rshift.argtypes = [c_void_p, c_void_p, c_size_t] +botan.botan_mp_rshift.errcheck = errcheck_for('botan_mp_rshift') + +botan.botan_mp_mod_inverse.argtypes = [c_void_p, c_void_p, c_void_p] +botan.botan_mp_mod_inverse.errcheck = errcheck_for('botan_mp_mod_inverse') + +botan.botan_mp_rand_bits.argtypes = [c_void_p, c_void_p, c_size_t] +botan.botan_mp_rand_bits.errcheck = errcheck_for('botan_mp_rand_bits') + +botan.botan_mp_rand_range.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p] +botan.botan_mp_rand_range.errcheck = errcheck_for('botan_mp_rand_range') + +botan.botan_mp_gcd.argtypes = [c_void_p, c_void_p, c_void_p] +botan.botan_mp_gcd.errcheck = errcheck_for('botan_mp_gcd') + +botan.botan_mp_is_prime.argtypes = [c_void_p, c_void_p, c_size_t] +botan.botan_mp_is_prime.errcheck = errcheck_for('botan_mp_is_prime') + +botan.botan_mp_get_bit.argtypes = [c_void_p, c_size_t] +botan.botan_mp_get_bit.errcheck = errcheck_for('botan_mp_get_bit') + +botan.botan_mp_set_bit.argtypes = [c_void_p, c_size_t] +botan.botan_mp_set_bit.errcheck = errcheck_for('botan_mp_set_bit') + +botan.botan_mp_clear_bit.argtypes = [c_void_p, c_size_t] +botan.botan_mp_clear_bit.errcheck = errcheck_for('botan_mp_clear_bit') + +# +# FPE +# +botan.botan_fpe_fe1_init.argtypes = [c_void_p, c_void_p, POINTER(c_char), c_size_t, c_size_t, c_uint32] +botan.botan_fpe_fe1_init.errcheck = errcheck_for('botan_fpe_fe1_init') + +botan.botan_fpe_destroy.argtypes = [c_void_p] +botan.botan_fpe_destroy.errcheck = errcheck_for('botan_fpe_destroy') + +botan.botan_fpe_encrypt.argtypes = [c_void_p, c_void_p, POINTER(c_char), c_size_t] +botan.botan_fpe_encrypt.errcheck = errcheck_for('botan_fpe_encrypt') +botan.botan_fpe_decrypt.argtypes = [c_void_p, c_void_p, POINTER(c_char), c_size_t] +botan.botan_fpe_decrypt.errcheck = errcheck_for('botan_fpe_decrypt') + # # Internal utilities # @@ -442,140 +572,178 @@ def version_string(): # # RNG # -class rng(object): # pylint: disable=invalid-name +class RandomNumberGenerator(object): # Can also use type "system" def __init__(self, rng_type='system'): - self.rng = c_void_p(0) - botan.botan_rng_init(byref(self.rng), _ctype_str(rng_type)) + self.__obj = c_void_p(0) + botan.botan_rng_init(byref(self.__obj), _ctype_str(rng_type)) def __del__(self): - botan.botan_rng_destroy(self.rng) + botan.botan_rng_destroy(self.__obj) + + def handle_(self): + return self.__obj def reseed(self, bits=256): - botan.botan_rng_reseed(self.rng, bits) + botan.botan_rng_reseed(self.__obj, bits) + + def reseed_from_rng(self, source_rng, bits=256): + botan.botan_rng_reseed_from_rng(self.__obj, source_rng.handle_(), bits) + + def add_entropy(self, seed): + botan.botan_rng_add_entropy(self.__obj, _ctype_bits(seed), len(seed)) def get(self, length): out = create_string_buffer(length) l = c_size_t(length) - botan.botan_rng_get(self.rng, out, l) + botan.botan_rng_get(self.__obj, out, l) return _ctype_bufout(out) # # Hash function # -class hash_function(object): # pylint: disable=invalid-name +class HashFunction(object): def __init__(self, algo): flags = c_uint32(0) # always zero in this API version - self.hash = c_void_p(0) - botan.botan_hash_init(byref(self.hash), _ctype_str(algo), flags) + self.__obj = c_void_p(0) + botan.botan_hash_init(byref(self.__obj), _ctype_str(algo), flags) + + output_length = c_size_t(0) + botan.botan_hash_output_length(self.__obj, byref(output_length)) + self.__output_length = output_length.value def __del__(self): - botan.botan_hash_destroy(self.hash) + botan.botan_hash_destroy(self.__obj) def algo_name(self): - return _call_fn_returning_string(32, lambda b, bl: botan.botan_hash_name(self.hash, b, bl)) + return _call_fn_returning_string(32, lambda b, bl: botan.botan_hash_name(self.__obj, b, bl)) def clear(self): - botan.botan_hash_clear(self.hash) + botan.botan_hash_clear(self.__obj) def output_length(self): - l = c_size_t(0) - botan.botan_hash_output_length(self.hash, byref(l)) - return l.value + return self.__output_length def update(self, x): - botan.botan_hash_update(self.hash, _ctype_bits(x), len(x)) + botan.botan_hash_update(self.__obj, _ctype_bits(x), len(x)) def final(self): out = create_string_buffer(self.output_length()) - botan.botan_hash_final(self.hash, out) + botan.botan_hash_final(self.__obj, out) return _ctype_bufout(out) # # Message authentication codes # -class message_authentication_code(object): # pylint: disable=invalid-name +class MsgAuthCode(object): def __init__(self, algo): flags = c_uint32(0) # always zero in this API version - self.mac = c_void_p(0) - botan.botan_mac_init(byref(self.mac), _ctype_str(algo), flags) + self.__obj = c_void_p(0) + botan.botan_mac_init(byref(self.__obj), _ctype_str(algo), flags) + + min_keylen = c_size_t(0) + max_keylen = c_size_t(0) + mod_keylen = c_size_t(0) + botan.botan_mac_get_keyspec(self.__obj, byref(min_keylen), byref(max_keylen), byref(mod_keylen)) + + self.__min_keylen = min_keylen.value + self.__max_keylen = max_keylen.value + self.__mod_keylen = mod_keylen.value + + output_length = c_size_t(0) + botan.botan_mac_output_length(self.__obj, byref(output_length)) + self.__output_length = output_length.value def __del__(self): - botan.botan_mac_destroy(self.mac) + botan.botan_mac_destroy(self.__obj) def clear(self): - botan.botan_mac_clear(self.mac) + botan.botan_mac_clear(self.__obj) def algo_name(self): - return _call_fn_returning_string(32, lambda b, bl: botan.botan_mac_name(self.mac, b, bl)) + return _call_fn_returning_string(32, lambda b, bl: botan.botan_mac_name(self.__obj, b, bl)) def output_length(self): - l = c_size_t(0) - botan.botan_mac_output_length(self.mac, byref(l)) - return l.value + return self.__output_length + + def minimum_keylength(self): + return self.__min_keylen + + def maximum_keylength(self): + return self.__max_keylen def set_key(self, key): - botan.botan_mac_set_key(self.mac, key, len(key)) + botan.botan_mac_set_key(self.__obj, key, len(key)) def update(self, x): - botan.botan_mac_update(self.mac, x, len(x)) + botan.botan_mac_update(self.__obj, x, len(x)) def final(self): out = create_string_buffer(self.output_length()) - botan.botan_mac_final(self.mac, out) + botan.botan_mac_final(self.__obj, out) return _ctype_bufout(out) -class cipher(object): # pylint: disable=invalid-name +class SymmetricCipher(object): def __init__(self, algo, encrypt=True): flags = 0 if encrypt else 1 - self.cipher = c_void_p(0) - botan.botan_cipher_init(byref(self.cipher), _ctype_str(algo), flags) + self.__obj = c_void_p(0) + botan.botan_cipher_init(byref(self.__obj), _ctype_str(algo), flags) def __del__(self): - botan.botan_cipher_destroy(self.cipher) + botan.botan_cipher_destroy(self.__obj) def algo_name(self): - return _call_fn_returning_string(32, lambda b, bl: botan.botan_cipher_name(self.cipher, b, bl)) + return _call_fn_returning_string(32, lambda b, bl: botan.botan_cipher_name(self.__obj, b, bl)) def default_nonce_length(self): l = c_size_t(0) - botan.botan_cipher_get_default_nonce_length(self.cipher, byref(l)) + botan.botan_cipher_get_default_nonce_length(self.__obj, byref(l)) return l.value def update_granularity(self): l = c_size_t(0) - botan.botan_cipher_get_update_granularity(self.cipher, byref(l)) + botan.botan_cipher_get_update_granularity(self.__obj, 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)) + botan.botan_cipher_query_keylen(self.__obj, byref(kmin), byref(kmax)) return kmin.value, kmax.value + def minimum_keylength(self): + l = c_size_t(0) + botan.botan_cipher_get_keyspec(self.__obj, byref(l), None, None) + return l.value + + def maximum_keylength(self): + l = c_size_t(0) + botan.botan_cipher_get_keyspec(self.__obj, None, byref(l), None) + return l.value + def tag_length(self): l = c_size_t(0) - botan.botan_cipher_get_tag_length(self.cipher, byref(l)) + botan.botan_cipher_get_tag_length(self.__obj, byref(l)) return l.value def is_authenticated(self): return self.tag_length() > 0 def valid_nonce_length(self, nonce_len): - rc = botan.botan_cipher_valid_nonce_length(self.cipher, nonce_len) + rc = botan.botan_cipher_valid_nonce_length(self.__obj, nonce_len) return True if rc == 1 else False def clear(self): - botan.botan_cipher_clear(self.cipher) + botan.botan_cipher_clear(self.__obj) def set_key(self, key): - botan.botan_cipher_set_key(self.cipher, key, len(key)) + botan.botan_cipher_set_key(self.__obj, key, len(key)) def set_assoc_data(self, ad): - botan.botan_cipher_set_associated_data(self.cipher, ad, len(ad)) + botan.botan_cipher_set_associated_data(self.__obj, ad, len(ad)) def start(self, nonce): - botan.botan_cipher_start(self.cipher, nonce, len(nonce)) + botan.botan_cipher_start(self.__obj, nonce, len(nonce)) def _update(self, txt, final): @@ -587,7 +755,7 @@ class cipher(object): # pylint: disable=invalid-name out_written = c_size_t(0) flags = c_uint32(1 if final else 0) - botan.botan_cipher_update(self.cipher, flags, + botan.botan_cipher_update(self.__obj, flags, out, out_sz, byref(out_written), _ctype_bits(inp), inp_sz, byref(inp_consumed)) @@ -601,8 +769,7 @@ class cipher(object): # pylint: disable=invalid-name def finish(self, txt=None): return self._update(txt, True) - -def bcrypt(passwd, rng_instance, work_factor=10): +def bcrypt(passwd, rng_obj, work_factor=10): """ Bcrypt password hashing """ @@ -610,7 +777,7 @@ def bcrypt(passwd, rng_instance, work_factor=10): out = create_string_buffer(out_len.value) flags = c_uint32(0) botan.botan_bcrypt_generate(out, byref(out_len), _ctype_str(passwd), - rng_instance.rng, c_size_t(work_factor), flags) + rng_obj.handle_(), c_size_t(work_factor), flags) b = out.raw[0:int(out_len.value)-1] if b[-1] == '\x00': b = b[:-1] @@ -625,7 +792,7 @@ def check_bcrypt(passwd, passwd_hash): # def pbkdf(algo, password, out_len, iterations=10000, salt=None): if salt is None: - salt = rng().get(12) + salt = RandomNumberGenerator().get(12) out_buf = create_string_buffer(out_len) botan.botan_pbkdf(_ctype_str(algo), out_buf, out_len, _ctype_str(password), salt, len(salt), iterations) @@ -633,7 +800,7 @@ def pbkdf(algo, password, out_len, iterations=10000, salt=None): def pbkdf_timed(algo, password, out_len, ms_to_run=300, salt=None): if salt is None: - salt = rng().get(12) + salt = RandomNumberGenerator().get(12) 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), @@ -663,40 +830,46 @@ def kdf(algo, secret, out_len, salt, label): return out_buf.raw[0:int(out_sz.value)] # -# Public and private keys +# Public key # -class public_key(object): # pylint: disable=invalid-name +class PublicKey(object): # pylint: disable=invalid-name def __init__(self, obj=c_void_p(0)): - self.pubkey = obj + self.__obj = obj def __del__(self): - botan.botan_pubkey_destroy(self.pubkey) + botan.botan_pubkey_destroy(self.__obj) + + def handle_(self): + return self.__obj def estimated_strength(self): r = c_size_t(0) - botan.botan_pubkey_estimated_strength(self.pubkey, byref(r)) + botan.botan_pubkey_estimated_strength(self.__obj, byref(r)) return r.value def algo_name(self): - return _call_fn_returning_string(32, lambda b, bl: botan.botan_pubkey_algo_name(self.pubkey, b, bl)) + return _call_fn_returning_string(32, lambda b, bl: botan.botan_pubkey_algo_name(self.__obj, b, bl)) def encoding(self, pem=False): flag = 1 if pem else 0 - return _call_fn_returning_vec(4096, lambda b, bl: botan.botan_pubkey_export(self.pubkey, b, bl, flag)) + return _call_fn_returning_vec(4096, lambda b, bl: botan.botan_pubkey_export(self.__obj, b, bl, flag)) def fingerprint(self, hash_algorithm='SHA-256'): - n = hash_function(hash_algorithm).output_length() + n = HashFunction(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)) + botan.botan_pubkey_fingerprint(self.__obj, _ctype_str(hash_algorithm), buf, byref(buf_len)) return _hex_encode(buf[0:int(buf_len.value)]) -class private_key(object): # pylint: disable=invalid-name - def __init__(self, algo, params, rng_instance): +# +# Private Key +# +class PrivateKey(object): + def __init__(self, algo, params, rng_obj): - self.privkey = c_void_p(0) + self.__obj = c_void_p(0) if algo == 'rsa': algo = 'RSA' @@ -715,19 +888,22 @@ class private_key(object): # pylint: disable=invalid-name algo = 'McEliece' params = "%d,%d" % (params[0], params[1]) - botan.botan_privkey_create(byref(self.privkey), - _ctype_str(algo), _ctype_str(params), rng_instance.rng) + botan.botan_privkey_create(byref(self.__obj), + _ctype_str(algo), _ctype_str(params), rng_obj.handle_()) def __del__(self): - botan.botan_privkey_destroy(self.privkey) + botan.botan_privkey_destroy(self.__obj) + + def handle_(self): + return self.__obj def algo_name(self): - return _call_fn_returning_string(32, lambda b, bl: botan.botan_privkey_algo_name(self.privkey, b, bl)) + return _call_fn_returning_string(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.privkey) + botan.botan_privkey_export_pubkey(byref(pub), self.__obj) return public_key(pub) def export(self): @@ -736,91 +912,111 @@ class private_key(object): # pylint: disable=invalid-name buf = create_string_buffer(n) buf_len = c_size_t(n) - rc = botan.botan_privkey_export(self.privkey, buf, byref(buf_len)) + rc = botan.botan_privkey_export(self.__obj, buf, byref(buf_len)) if rc != 0: buf = create_string_buffer(buf_len.value) - botan.botan_privkey_export(self.privkey, buf, byref(buf_len)) + botan.botan_privkey_export(self.__obj, buf, byref(buf_len)) return buf[0:int(buf_len.value)] -class pk_op_encrypt(object): # pylint: disable=invalid-name +class PKEncrypt(object): def __init__(self, key, padding): - self.op = c_void_p(0) + self.__obj = c_void_p(0) flags = c_uint32(0) # always zero in this ABI - botan.botan_pk_op_encrypt_create(byref(self.op), key.pubkey, _ctype_str(padding), flags) + botan.botan_pk_op_encrypt_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) def __del__(self): - botan.botan_pk_op_encrypt_destroy(self.op) + botan.botan_pk_op_encrypt_destroy(self.__obj) - def encrypt(self, msg, rng_instance): + def encrypt(self, msg, rng_obj): outbuf_sz = c_size_t(0) - botan.botan_pk_op_encrypt_output_length(self.op, len(msg), byref(outbuf_sz)) + botan.botan_pk_op_encrypt_output_length(self.__obj, len(msg), byref(outbuf_sz)) outbuf = create_string_buffer(outbuf_sz.value) - botan.botan_pk_op_encrypt(self.op, rng_instance.rng, outbuf, byref(outbuf_sz), msg, len(msg)) + botan.botan_pk_op_encrypt(self.__obj, rng_obj.handle_(), outbuf, byref(outbuf_sz), msg, len(msg)) return outbuf.raw[0:int(outbuf_sz.value)] -class pk_op_decrypt(object): # pylint: disable=invalid-name +class PKDecrypt(object): def __init__(self, key, padding): - self.op = c_void_p(0) + self.__obj = 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) + botan.botan_pk_op_decrypt_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) def __del__(self): - botan.botan_pk_op_decrypt_destroy(self.op) + botan.botan_pk_op_decrypt_destroy(self.__obj) def decrypt(self, msg): outbuf_sz = c_size_t(0) - botan.botan_pk_op_decrypt_output_length(self.op, len(msg), byref(outbuf_sz)) + botan.botan_pk_op_decrypt_output_length(self.__obj, len(msg), byref(outbuf_sz)) outbuf = create_string_buffer(outbuf_sz.value) - botan.botan_pk_op_decrypt(self.op, outbuf, byref(outbuf_sz), _ctype_bits(msg), len(msg)) + botan.botan_pk_op_decrypt(self.__obj, outbuf, byref(outbuf_sz), _ctype_bits(msg), len(msg)) return outbuf.raw[0:int(outbuf_sz.value)] -class pk_op_sign(object): # pylint: disable=invalid-name +class PKSign(object): # pylint: disable=invalid-name def __init__(self, key, padding): - self.op = c_void_p(0) + self.__obj = 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) + botan.botan_pk_op_sign_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) def __del__(self): - botan.botan_pk_op_sign_destroy(self.op) + botan.botan_pk_op_sign_destroy(self.__obj) def update(self, msg): - botan.botan_pk_op_sign_update(self.op, _ctype_str(msg), len(msg)) + botan.botan_pk_op_sign_update(self.__obj, _ctype_str(msg), len(msg)) - def finish(self, rng_instance): + def finish(self, rng_obj): outbuf_sz = c_size_t(0) - botan.botan_pk_op_sign_output_length(self.op, byref(outbuf_sz)) + botan.botan_pk_op_sign_output_length(self.__obj, byref(outbuf_sz)) outbuf = create_string_buffer(outbuf_sz.value) - botan.botan_pk_op_sign_finish(self.op, rng_instance.rng, outbuf, byref(outbuf_sz)) + botan.botan_pk_op_sign_finish(self.__obj, rng_obj.handle_(), outbuf, byref(outbuf_sz)) return outbuf.raw[0:int(outbuf_sz.value)] -class pk_op_verify(object): # pylint: disable=invalid-name +class PKVerify(object): def __init__(self, key, padding): - self.op = c_void_p(0) + self.__obj = 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) + botan.botan_pk_op_verify_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) def __del__(self): - botan.botan_pk_op_verify_destroy(self.op) + botan.botan_pk_op_verify_destroy(self.__obj) def update(self, msg): - botan.botan_pk_op_verify_update(self.op, _ctype_bits(msg), len(msg)) + botan.botan_pk_op_verify_update(self.__obj, _ctype_bits(msg), len(msg)) def check_signature(self, signature): - rc = botan.botan_pk_op_verify_finish(self.op, _ctype_bits(signature), len(signature)) + rc = botan.botan_pk_op_verify_finish(self.__obj, _ctype_bits(signature), len(signature)) if rc == 0: return True return False +class PKKeyAgreement(object): + def __init__(self, key, kdf_name): + self.__obj = c_void_p(0) + flags = c_uint32(0) # always zero in this ABI + botan.botan_pk_op_key_agreement_create(byref(self.__obj), key.handle_(), kdf_name, flags) + + self.m_public_value = _call_fn_returning_vec( + 0, lambda b, bl: botan.botan_pk_op_key_agreement_export_public(key.handle_(), b, bl)) + + def __del__(self): + botan.botan_pk_op_key_agreement_destroy(self.__obj) + + def public_value(self): + return self.m_public_value + + def agree(self, other, key_len, salt): + return _call_fn_returning_vec(key_len, lambda b, bl: + botan.botan_pk_op_key_agreement(self.__obj, b, bl, + other, len(other), + salt, len(salt))) # # MCEIES encryption # Must be used with McEliece keys # -def mceies_encrypt(mce, rng_instance, aead, pt, ad): +def mceies_encrypt(mce, rng_obj, aead, pt, ad): return _call_fn_returning_vec(len(pt) + 1024, lambda b, bl: - botan.botan_mceies_encrypt(mce.pubkey, - rng_instance.rng, + botan.botan_mceies_encrypt(mce.handle_(), + rng_obj.handle_(), _ctype_str(aead), _ctype_bits(pt), len(pt), @@ -834,7 +1030,7 @@ def mceies_decrypt(mce, aead, ct, ad): #ll = c_size_t(ll) return _call_fn_returning_vec(len(ct), lambda b, bl: - botan.botan_mceies_decrypt(mce.privkey, + botan.botan_mceies_decrypt(mce.handle_(), _ctype_str(aead), _ctype_bits(ct), len(ct), @@ -842,49 +1038,29 @@ def mceies_decrypt(mce, aead, ct, ad): len(ad), b, bl)) -class pk_op_key_agreement(object): # pylint: disable=invalid-name - def __init__(self, key, kdf_name): - 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) - - 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(self.op) - - def public_value(self): - return self.m_public_value - - def agree(self, other, key_len, salt): - 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 +class X509Cert(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: - self.x509_cert = c_void_p(0) - botan.botan_x509_cert_load_file(byref(self.x509_cert), _ctype_str(filename)) + self.__obj = c_void_p(0) + botan.botan_x509_cert_load_file(byref(self.__obj), _ctype_str(filename)) elif buf is not None: - self.x509_cert = c_void_p(0) - botan.botan_x509_cert_load(byref(self.x509_cert), _ctype_bits(buf), len(buf)) + self.__obj = c_void_p(0) + botan.botan_x509_cert_load(byref(self.__obj), _ctype_bits(buf), len(buf)) def __del__(self): - botan.botan_x509_cert_destroy(self.x509_cert) + botan.botan_x509_cert_destroy(self.__obj) def time_starts(self): starts = _call_fn_returning_string( - 16, lambda b, bl: botan.botan_x509_cert_get_time_starts(self.x509_cert, b, bl)) + 16, lambda b, bl: botan.botan_x509_cert_get_time_starts(self.__obj, b, bl)) if len(starts) == 13: # UTC time struct_time = strptime(starts, "%y%m%d%H%M%SZ") @@ -898,7 +1074,7 @@ class x509_cert(object): # pylint: disable=invalid-name def time_expires(self): expires = _call_fn_returning_string( - 16, lambda b, bl: botan.botan_x509_cert_get_time_expires(self.x509_cert, b, bl)) + 16, lambda b, bl: botan.botan_x509_cert_get_time_expires(self.__obj, b, bl)) if len(expires) == 13: # UTC time struct_time = strptime(expires, "%y%m%d%H%M%SZ") @@ -912,34 +1088,242 @@ class x509_cert(object): # pylint: disable=invalid-name def to_string(self): return _call_fn_returning_string( - 4096, lambda b, bl: botan.botan_x509_cert_to_string(self.x509_cert, b, bl)) + 4096, lambda b, bl: botan.botan_x509_cert_to_string(self.__obj, b, bl)) def fingerprint(self, hash_algo='SHA-256'): - n = hash_function(hash_algo).output_length() * 3 + n = HashFunction(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)) + n, lambda b, bl: botan.botan_x509_cert_get_fingerprint(self.__obj, _ctype_str(hash_algo), b, bl)) def serial_number(self): return _call_fn_returning_vec( - 32, lambda b, bl: botan.botan_x509_cert_get_serial_number(self.x509_cert, b, bl)) + 32, lambda b, bl: botan.botan_x509_cert_get_serial_number(self.__obj, b, bl)) def authority_key_id(self): return _call_fn_returning_vec( - 32, lambda b, bl: botan.botan_x509_cert_get_authority_key_id(self.x509_cert, b, bl)) + 32, lambda b, bl: botan.botan_x509_cert_get_authority_key_id(self.__obj, b, bl)) def subject_key_id(self): return _call_fn_returning_vec( - 32, lambda b, bl: botan.botan_x509_cert_get_subject_key_id(self.x509_cert, b, bl)) + 32, lambda b, bl: botan.botan_x509_cert_get_subject_key_id(self.__obj, b, bl)) def subject_public_key_bits(self): return _call_fn_returning_vec( - 512, lambda b, bl: botan.botan_x509_cert_get_public_key_bits(self.x509_cert, b, bl)) + 512, lambda b, bl: botan.botan_x509_cert_get_public_key_bits(self.__obj, b, bl)) def subject_public_key(self): pub = c_void_p(0) - botan.botan_x509_cert_get_public_key(self.x509_cert, byref(pub)) + botan.botan_x509_cert_get_public_key(self.__obj, byref(pub)) return public_key(pub) def subject_dn(self, key, index): 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)) + 0, lambda b, bl: botan.botan_x509_cert_get_subject_dn(self.__obj, _ctype_str(key), index, b, bl)) + + +class MPI(object): + + def __init__(self, initial_value=None): + + self.__obj = c_void_p(0) + botan.botan_mp_init(byref(self.__obj)) + + if initial_value is None: + pass # left as zero + elif isinstance(initial_value, MPI): + botan.botan_mp_set_from_mp(self.__obj, initial_value.handle_()) + elif isinstance(initial_value, str): + botan.botan_mp_set_from_str(self.__obj, _ctype_str(initial_value)) + else: + # For int or long (or whatever else), try converting to string: + botan.botan_mp_set_from_str(self.__obj, _ctype_str(str(initial_value))) + + def __del__(self): + botan.botan_mp_destroy(self.__obj) + + def handle_(self): + return self.__obj + + def __int__(self): + out = create_string_buffer(2*self.byte_count() + 1) + botan.botan_mp_to_hex(self.__obj, out) + val = int(out.value, 16) + if self.is_negative(): + return -val + else: + return val + + def __repr__(self): + # Should have a better size estimate than this ... + out_len = c_size_t(self.bit_count() // 2) + out = create_string_buffer(out_len.value) + + botan.botan_mp_to_str(self.__obj, c_uint8(10), out, byref(out_len)) + + out = out.raw[0:int(out_len.value)] + if out[-1] == '\x00': + out = out[:-1] + s = _ctype_to_str(out) + if s[0] == '0': + return s[1:] + else: + return s + + def is_negative(self): + rc = botan.botan_mp_is_negative(self.__obj) + return rc == 1 + + def flip_sign(self): + botan.botan_mp_flip_sign(self.__obj) + + def cmp(self, other): + r = c_int(0) + botan.botan_mp_cmp(byref(r), self.__obj, other.handle_()) + return r.value + + def __eq__(self, other): + return self.cmp(other) == 0 + + def __ne__(self, other): + return self.cmp(other) != 0 + + def __lt__(self, other): + return self.cmp(other) < 0 + + def __le__(self, other): + return self.cmp(other) <= 0 + + def __gt__(self, other): + return self.cmp(other) > 0 + + def __ge__(self, other): + return self.cmp(other) >= 0 + + def __add__(self, other): + r = MPI() + botan.botan_mp_add(r.handle_(), self.__obj, other.handle_()) + return r + + def __iadd__(self, other): + botan.botan_mp_add(self.__obj, self.__obj, other.handle_()) + return self + + def __sub__(self, other): + r = MPI() + botan.botan_mp_sub(r.handle_(), self.__obj, other.handle_()) + return r + + def __isub__(self, other): + botan.botan_mp_sub(self.__obj, self.__obj, other.handle_()) + return self + + def __mul__(self, other): + r = MPI() + botan.botan_mp_mul(r.handle_(), self.__obj, other.handle_()) + return r + + def __imul__(self, other): + botan.botan_mp_mul(self.__obj, self.__obj, other.handle_()) + return self + + def __divmod__(self, other): + d = MPI() + q = MPI() + botan.botan_mp_div(d.handle_(), q.handle_(), self.__obj, other.handle_()) + return (d, q) + + def __mod__(self, other): + d = MPI() + q = MPI() + botan.botan_mp_div(d.handle_(), q.handle_(), self.__obj, other.handle_()) + return q + + def __lshift__(self, shift): + shift = c_size_t(shift) + r = MPI() + botan.botan_mp_lshift(r.handle_(), self.__obj, shift) + return r + + def __ilshift__(self, shift): + shift = c_size_t(shift) + botan.botan_mp_lshift(self.__obj, self.__obj, shift) + return self + + def __rshift__(self, shift): + shift = c_size_t(shift) + r = MPI() + botan.botan_mp_rshift(r.handle_(), self.__obj, shift) + return r + + def __irshift__(self, shift): + shift = c_size_t(shift) + botan.botan_mp_rshift(self.__obj, self.__obj, shift) + return self + + def pow_mod(self, exponent, modulus): + r = MPI() + botan.botan_mp_powmod(r.handle_(), self.__obj, exponent.handle_(), modulus.handle_()) + return r + + def is_prime(self, rng_obj, prob=128): + return botan.botan_mp_is_prime(self.__obj, rng_obj.handle_(), c_size_t(prob)) == 1 + + def inverse_mod(self, modulus): + r = MPI() + botan.botan_mp_mod_inverse(r.handle_(), self.__obj, modulus.handle_()) + return r + + def bit_count(self): + b = c_size_t(0) + botan.botan_mp_num_bits(self.__obj, byref(b)) + return b.value + + def byte_count(self): + b = c_size_t(0) + botan.botan_mp_num_bytes(self.__obj, byref(b)) + return b.value + + def get_bit(self, bit): + return botan.botan_mp_get_bit(self.__obj, c_size_t(bit)) == 1 + + def clear_bit(self, bit): + botan.botan_mp_clear_bit(self.__obj, c_size_t(bit)) + + def set_bit(self, bit): + botan.botan_mp_set_bit(self.__obj, c_size_t(bit)) + +class FormatPreservingEncryptionFE1(object): + + def __init__(self, modulus, key, rounds=5, compat_mode=False): + flags = c_uint32(1 if compat_mode else 0) + self.__obj = c_void_p(0) + botan.botan_fpe_fe1_init(byref(self.__obj), modulus.handle_(), key, len(key), c_size_t(rounds), flags) + + def __del__(self): + botan.botan_fpe_destroy(self.__obj) + + def encrypt(self, msg, tweak): + r = MPI(msg) + botan.botan_fpe_encrypt(self.__obj, r.handle_(), _ctype_bits(tweak), len(tweak)) + return r + + def decrypt(self, msg, tweak): + r = MPI(msg) + botan.botan_fpe_decrypt(self.__obj, r.handle_(), _ctype_bits(tweak), len(tweak)) + return r + +# Typedefs for compat with older versions +cipher = SymmetricCipher # pylint: disable=invalid-name +rng = RandomNumberGenerator # pylint: disable=invalid-name +hash_function = HashFunction # pylint: disable=invalid-name +message_authentication_code = MsgAuthCode # pylint: disable=invalid-name + +x509_cert = X509Cert # pylint: disable=invalid-name +public_key = PublicKey # pylint: disable=invalid-name +private_key = PrivateKey # pylint: disable=invalid-name + +pk_op_encrypt = PKEncrypt # pylint: disable=invalid-name +pk_op_decrypt = PKDecrypt # pylint: disable=invalid-name +pk_op_sign = PKSign # pylint: disable=invalid-name +pk_op_verify = PKVerify # pylint: disable=invalid-name +pk_op_key_agreement = PKKeyAgreement # pylint: disable=invalid-name diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 4bd0085ab..371831a14 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -68,10 +68,12 @@ class BotanPythonTests(unittest.TestCase): self.assertTrue(botan2.check_bcrypt('test', '$2a$04$wjen1fAA.UW6UxthpKK.huyOoxvCR7ATRCVC4CBIEGVDOCtr8Oj1C')) - def test_hmac(self): + def test_mac(self): hmac = botan2.message_authentication_code('HMAC(SHA-256)') self.assertEqual(hmac.algo_name(), 'HMAC(SHA-256)') + self.assertEqual(hmac.minimum_keylength(), 0) + self.assertEqual(hmac.maximum_keylength(), 4096) hmac.set_key(hex_decode('0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20')) hmac.update(hex_decode('616263')) @@ -93,6 +95,12 @@ class BotanPythonTests(unittest.TestCase): output3 = user_rng.get(1021) self.assertEqual(len(output3), 1021) + system_rng = botan2.rng('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') self.assertEqual(h.algo_name(), 'SHA-256') @@ -240,5 +248,97 @@ class BotanPythonTests(unittest.TestCase): self.assertTrue(cert.to_string().startswith("Version: 3")) + def test_mpi(self): + # pylint: disable=too-many-statements + z = botan2.MPI() + self.assertEqual(z.bit_count(), 0) + five = botan2.MPI('5') + self.assertEqual(five.bit_count(), 3) + big = botan2.MPI('0x85839682368923476892367235') + self.assertEqual(big.bit_count(), 104) + small = botan2.MPI(0xDEADBEEF) + + self.assertEqual(int(small), 0xDEADBEEF) + + self.assertEqual(int(small >> 16), 0xDEAD) + + small >>= 15 + + self.assertEqual(int(small), 0x1BD5B) + + small <<= 15 + + self.assertEqual(int(small), 0xDEAD8000) + + ten = botan2.MPI(10) + + self.assertEqual(ten, five + five) + self.assertNotEqual(ten, five) + self.assertTrue(five < ten) + self.assertTrue(five <= ten) + + x = botan2.MPI(five) + + self.assertEqual(x, five) + + x += botan2.MPI(1) + self.assertNotEqual(x, five) + + self.assertEqual(int(x * five), 30) + + x *= five + x *= five + self.assertEqual(int(x), 150) + + self.assertTrue(not x.is_negative()) + + x.flip_sign() + self.assertTrue(x.is_negative()) + self.assertEqual(int(x), -150) + + x.flip_sign() + + x.set_bit(0) + self.assertTrue(int(x), 151) + self.assertTrue(x.get_bit(0)) + self.assertTrue(x.get_bit(4)) + self.assertFalse(x.get_bit(6)) + + x.clear_bit(4) + self.assertEqual(int(x), 135) + + rng = botan2.RandomNumberGenerator() + self.assertFalse(x.is_prime(rng)) + + two = botan2.MPI(2) + + x += two + self.assertTrue(x.is_prime(rng)) + + mod = x + two + + inv = x.inverse_mod(mod) + self.assertEqual(int(inv), 69) + self.assertEqual(int((inv * x) % mod), 1) + + p = inv.pow_mod(botan2.MPI(46), mod) + self.assertEqual(int(p), 42) + + def test_fpe(self): + + modulus = botan2.MPI('1000000000') + key = b'001122334455' + + fpe = botan2.FormatPreservingEncryptionFE1(modulus, key) + + value = botan2.MPI('392910392') + tweak = 'tweak value' + + ctext = fpe.encrypt(value, tweak) + + ptext = fpe.decrypt(ctext, tweak) + + self.assertEqual(value, ptext) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3