diff options
-rw-r--r-- | doc/manual/contents.rst | 3 | ||||
-rw-r--r-- | doc/manual/python.rst | 222 | ||||
-rw-r--r-- | src/lib/asn1/oid_lookup/default.cpp | 2 | ||||
-rw-r--r-- | src/lib/ffi/ffi.cpp | 196 | ||||
-rw-r--r-- | src/lib/ffi/ffi.h | 13 | ||||
-rwxr-xr-x | src/python/botan.py | 101 |
6 files changed, 452 insertions, 85 deletions
diff --git a/doc/manual/contents.rst b/doc/manual/contents.rst index 83c36e71f..df1a665ed 100644 --- a/doc/manual/contents.rst +++ b/doc/manual/contents.rst @@ -6,6 +6,7 @@ Contents index building + python firststep secmem rng @@ -26,4 +27,4 @@ Contents fpe versions ffi - python + diff --git a/doc/manual/python.rst b/doc/manual/python.rst index f851cbaca..a1239253b 100644 --- a/doc/manual/python.rst +++ b/doc/manual/python.rst @@ -6,5 +6,227 @@ Python Binding .. highlight:: python +.. py:module:: botan + The Python binding is based on the `ffi` module of botan and the `ctypes` module of the Python standard library. + +Versioning +---------------------------------------- +.. py:function:: version_major() + + Returns the major number of the library version (currently, 1) +.. py:function:: version_minor() + + Returns the minor number of the library version (currently, 11) +.. py:function:: version_patch() + + Returns the patch number of the library version (currently, 14) + +.. py:function:: version_string() + + Returns a free form version string for the library + +Random Number Generators +---------------------------------------- +.. py:class:: rng(rng_type = 'system') + + Type 'user' also allowed (userspace HKDF RNG seeded from system + rng). The system RNG is very cheap to create, as just a single file + handle or CSP handle is kept open, from first use until shutdown, + no matter how many 'system' rng instances are created. Thus it is + easy to use the RNG in a one-off way, with `botan.rng().get(32)`. + + .. py:method:: get(length) + + Return some bits + + .. py:method:: reseed(bits = 256) + + Meaningless on system RNG, on userspace RNG causes a reseed/rekey + + +Hash Functions +---------------------------------------- +.. py:class:: hash_function(algo) + + Algo is a string (eg 'SHA-1', 'SHA-384', 'Skein-512') + + .. py:method:: clear() + + Clear state + + .. py:method:: output_length() + + .. py:method:: update(x) + + Add some input + + .. py:method:: final() + + Returns the hash of all input provided, resets + for another message. + +Message Authentication Codes +---------------------------------------- +.. py:class:: message_authentication_code(algo) + + Algo is a string (eg 'HMAC(SHA-256)', 'Poly1305', 'CMAC(AES-256)') + + .. py:method:: clear() + + .. py:method:: output_length() + + .. py:method:: set_key(key) + + Set the key + + .. py:method:: update(x) + + Add some input + + .. py:method:: final() + + Returns the MAC of all input provided, resets + for another message with the same key. + +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'). + + Set the second param to False for decryption + + .. py:method:: tag_length() + + Returns the tag length (0 for unauthenticated modes) + + .. py:method:: default_nonce_length() + + Returns default nonce length + + .. py:method:: update_granularity() + + Returns update block size + + .. py:method:: is_authenticated() + + Returns True if this is an AEAD mode + + .. py:method:: valid_nonce_length(nonce_len) + + Returns True if nonce_len is a valid nonce len for + this mode + + .. py:method:: clear() + + Resets all state + + .. py:method:: set_key(key) + + Set the key + + .. py:method:: start(nonce) + + Start processing a message using nonce + + .. py:method:: update(txt) + + Consumes input text and returns output + + .. py:method:: finish(txt = None) + + Finish processing (with an optional final input). May throw if + message authentication checks fail, in which case all plaintext + previously processed must be discarded. Alternately, always + call finish with the entire message, avoiding calls to update + entirely. + +Bcrypt +---------------------------------------- +.. py:function:: bcrypt(passwd, rng, work_factor = 10) + + Provided the password and an RNG object, returns a bcrypt string + +.. py:function:: check_bcrypt(passwd, bcrypt) + + Check a bcrypt hash against the provided password, returning True + iff the password matches. + +PBKDF +---------------------------------------- +.. py:function:: pbkdf(algo, password, out_len, iterations = 100000, salt = rng().get(12)) + + Runs a PBKDF2 algo specified as a string (eg 'PBKDF2(SHA-256)', 'PBKDF2(CMAC(Blowfish))'). + Runs with n iterations with meaning depending on the algorithm. + The salt can be provided or otherwise is randomly chosen. In any case it is returned + from the call. + + Returns out_len bytes of output (or potentially less depending on + the algorithm and the size of the request). + + Returns tuple of salt, iterations, and psk + +.. py:function:: pbkdf_timed(algo, password, out_len, ms_to_run = 300, salt = rng().get(12)) + + Runs for as many iterations as needed to consumed ms_to_run + milliseconds on whatever we're running on. Returns tuple of salt, + iterations, and psk + +KDF +---------------------------------------- +.. py:function:: kdf(algo, secret, out_len, salt) + +Public Key +---------------------------------------- +.. py:class:: public_key(object) + + .. py:method:: fingerprint(hash = 'SHA-256') + +.. py:class:: private_key(algo, param, rng) + + Constructor creates a new private key. The paramater 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:method:: get_public_key() + + Return a public_key object + + .. py:method:: export() + +Public Key Operations +---------------------------------------- +.. py:class:: pk_op_encrypt(pubkey, padding, rng) + + .. py:method:: encrypt(msg, rng) + +.. py:class:: pk_op_decrypt(privkey, padding) + + .. py:method:: decrypt(msg) + +.. py:class:: pk_op_sign(privkey, hash_w_padding) + + .. py:method:: update(msg) + .. py:method:: finish(rng) + +.. py:class:: pk_op_verify(pubkey, hash_w_padding) + + .. py:method:: update(msg) + .. py:method:: check_signature(signature) + +.. py:class:: pk_op_key_agreement(privkey, kdf) + + .. py:method:: public_value() + + Returns the public value to be passed to the other party + + .. py:method:: agree(other, key_len, salt) + + Returns a key derived by the KDF. + diff --git a/src/lib/asn1/oid_lookup/default.cpp b/src/lib/asn1/oid_lookup/default.cpp index de04f542a..0e23b18a2 100644 --- a/src/lib/asn1/oid_lookup/default.cpp +++ b/src/lib/asn1/oid_lookup/default.cpp @@ -28,7 +28,7 @@ const char* default_oid_list() // X9.62 ecPublicKey, valid for ECDSA and ECDH (RFC 3279 sec 2.3.5) "1.2.840.10045.2.1 = ECDSA" "\n" - //"1.3.132.1.12 = ECDH" "\n" + "1.3.132.1.12 = ECDH" "\n" "1.2.643.2.2.19 = GOST-34.10" "\n" diff --git a/src/lib/ffi/ffi.cpp b/src/lib/ffi/ffi.cpp index 937423c69..94fc546fe 100644 --- a/src/lib/ffi/ffi.cpp +++ b/src/lib/ffi/ffi.cpp @@ -31,6 +31,10 @@ #include <botan/ecdh.h> #endif +#if defined(BOTAN_HAS_CURVE_25519) + #include <botan/curve25519.h> +#endif + #if defined(BOTAN_HAS_BCRYPT) #include <botan/bcrypt.h> #endif @@ -49,7 +53,9 @@ struct botan_struct T* get() const { - BOTAN_ASSERT_EQUAL(m_magic, MAGIC, "Bad magic value - memory corruption?"); + if(m_magic != MAGIC) + throw std::runtime_error("Bad magic " + std::to_string(m_magic) + + " in ffi object expected" + std::to_string(MAGIC)); return m_obj.get(); } private: @@ -59,7 +65,7 @@ struct botan_struct void log_exception(const char* func_name, const char* what) { - printf("botan ffi %s: %s\n", func_name, what); + fprintf(stderr, "%s: %s\n", func_name, what); } template<typename T, uint32_t M> @@ -96,20 +102,32 @@ int apply_fn(botan_struct<T, M>* o, const char* func_name, F func) return -1; } -template<typename Alloc> -int write_output(uint8_t out[], size_t* out_len, const std::vector<uint8_t, Alloc>& buf) +int write_output(uint8_t out[], size_t* out_len, const uint8_t buf[], size_t buf_len) { Botan::clear_mem(out, *out_len); const size_t avail = *out_len; - *out_len = buf.size(); - if(avail >= buf.size()) + *out_len = buf_len; + if(avail >= buf_len) { - Botan::copy_mem(out, &buf[0], buf.size()); + Botan::copy_mem(out, &buf[0], buf_len); return 0; } return -1; } +template<typename Alloc> +int write_vec_output(uint8_t out[], size_t* out_len, const std::vector<uint8_t, Alloc>& buf) + { + return write_output(out, out_len, &buf[0], buf.size()); + } + +int write_str_output(uint8_t out[], size_t* out_len, const std::string& str) + { + return write_output(out, out_len, + reinterpret_cast<const uint8_t*>(str.c_str()), + str.size() + 1); + } + #define BOTAN_FFI_DO(T, obj, block) apply_fn(obj, BOTAN_CURRENT_FUNCTION, [=](T& obj) { do { block } while(0); return 0; }) } @@ -261,8 +279,10 @@ int botan_hash_init(botan_hash_t* hash, const char* hash_name, uint32_t flags) { try { - if(!hash || !hash_name || flags != 0) - return -1; + if(hash == nullptr || hash_name == nullptr || *hash_name == 0) + return BOTAN_FFI_ERROR_NULL_POINTER; + if(flags != 0) + return BOTAN_FFI_ERROR_BAD_FLAG; if(auto h = Botan::get_hash_function(hash_name)) { @@ -279,7 +299,7 @@ int botan_hash_init(botan_hash_t* hash, const char* hash_name, uint32_t flags) log_exception(BOTAN_CURRENT_FUNCTION, "unknown"); } - return -2; + return BOTAN_FFI_ERROR_EXCEPTION_THROWN; } int botan_hash_destroy(botan_hash_t hash) @@ -634,25 +654,26 @@ int botan_kdf(const char* kdf_algo, } #if defined(BOTAN_HAS_BCRYPT) -int botan_bcrypt_generate(char* out, size_t out_len, const char* pass, - botan_rng_t rng_obj, size_t wf) +int botan_bcrypt_generate(uint8_t* out, size_t* out_len, + const char* pass, + botan_rng_t rng_obj, size_t wf, + uint32_t flags) { try { BOTAN_ASSERT_ARG_NON_NULL(out); + BOTAN_ASSERT_ARG_NON_NULL(out_len); BOTAN_ASSERT_ARG_NON_NULL(pass); + if(flags != 0) + return BOTAN_FFI_ERROR_BAD_FLAG; + if(wf < 2 || wf > 30) throw std::runtime_error("Bad bcrypt work factor " + std::to_string(wf)); Botan::RandomNumberGenerator& rng = safe_get(rng_obj); - - std::memset(out, 0, out_len); - const std::string c = Botan::generate_bcrypt(pass, rng, wf); - if(out_len <= c.size()) - return ENOMEM; - std::memcpy(out, c.c_str(), c.size()); - return 0; + const std::string bcrypt = Botan::generate_bcrypt(pass, rng, wf); + return write_str_output(out, out_len, bcrypt); } catch(std::exception& e) { @@ -663,7 +684,7 @@ int botan_bcrypt_generate(char* out, size_t out_len, const char* pass, log_exception(BOTAN_CURRENT_FUNCTION, "unknown"); } - return -1; + return BOTAN_FFI_ERROR_EXCEPTION_THROWN; } int botan_bcrypt_is_valid(const char* pass, const char* hash) @@ -683,21 +704,26 @@ int botan_bcrypt_is_valid(const char* pass, const char* hash) log_exception(BOTAN_CURRENT_FUNCTION, "unknown"); } - return -1; + return BOTAN_FFI_ERROR_EXCEPTION_THROWN; } #endif -int botan_privkey_create_rsa(botan_privkey_t* key, botan_rng_t rng_obj, size_t n_bits) +int botan_privkey_create_rsa(botan_privkey_t* key_obj, botan_rng_t rng_obj, size_t n_bits) { try { - *key = nullptr; + if(key_obj == nullptr || rng_obj == nullptr) + return -1; + if(n_bits < 1024 || n_bits > 16*1024) + return -2; + + *key_obj = nullptr; #if defined(BOTAN_HAS_RSA) Botan::RandomNumberGenerator& rng = safe_get(rng_obj); - std::unique_ptr<Botan::RSA_PrivateKey> rsa(new Botan::RSA_PrivateKey(rng, n_bits)); - *key = new botan_privkey_struct(rsa.release()); + std::unique_ptr<Botan::Private_Key> key(new Botan::RSA_PrivateKey(rng, n_bits)); + *key_obj = new botan_privkey_struct(key.release()); return 0; #endif } @@ -706,19 +732,24 @@ int botan_privkey_create_rsa(botan_privkey_t* key, botan_rng_t rng_obj, size_t n log_exception(BOTAN_CURRENT_FUNCTION, e.what()); } - return -1; + return BOTAN_FFI_ERROR_EXCEPTION_THROWN; } -int botan_privkey_create_ecdsa(botan_privkey_t* key, botan_rng_t rng_obj, const char* params) +int botan_privkey_create_ecdsa(botan_privkey_t* key_obj, botan_rng_t rng_obj, const char* param_str) { try { + if(key_obj == nullptr || rng_obj == nullptr || param_str == nullptr || *param_str == 0) + return -1; + + *key_obj = nullptr; + #if defined(BOTAN_HAS_ECDSA) Botan::RandomNumberGenerator& rng = safe_get(rng_obj); - Botan::EC_Group grp(params); - std::unique_ptr<Botan::ECDSA_PrivateKey> ecdsa(new Botan::ECDSA_PrivateKey(rng, grp)); - *key = new botan_privkey_struct(ecdsa.release()); + Botan::EC_Group grp(param_str); + std::unique_ptr<Botan::Private_Key> key(new Botan::ECDSA_PrivateKey(rng, grp)); + *key_obj = new botan_privkey_struct(key.release()); return 0; #endif } @@ -727,18 +758,33 @@ int botan_privkey_create_ecdsa(botan_privkey_t* key, botan_rng_t rng_obj, const log_exception(BOTAN_CURRENT_FUNCTION, e.what()); } - return -1; + return BOTAN_FFI_ERROR_EXCEPTION_THROWN; } -int botan_privkey_create_ecdh(botan_privkey_t* key, botan_rng_t rng_obj, const char* params) +int botan_privkey_create_ecdh(botan_privkey_t* key_obj, botan_rng_t rng_obj, const char* param_str) { try { + if(key_obj == nullptr || rng_obj == nullptr || param_str == nullptr || *param_str == 0) + return -1; + + *key_obj = nullptr; + + const std::string params(param_str); + +#if defined(BOTAN_HAS_CURVE_25519) + if(params == "curve25519") + { + std::unique_ptr<Botan::Private_Key> key(new Botan::Curve25519_PrivateKey(safe_get(rng_obj))); + *key_obj = new botan_privkey_struct(key.release()); + return 0; + } +#endif + #if defined(BOTAN_HAS_ECDH) - Botan::RandomNumberGenerator& rng = safe_get(rng_obj); Botan::EC_Group grp(params); - std::unique_ptr<Botan::ECDH_PrivateKey> ecdh(new Botan::ECDH_PrivateKey(rng, grp)); - *key = new botan_privkey_struct(ecdh.release()); + std::unique_ptr<Botan::Private_Key> key(new Botan::ECDH_PrivateKey(safe_get(rng_obj), grp)); + *key_obj = new botan_privkey_struct(key.release()); return 0; #endif } @@ -747,7 +793,7 @@ int botan_privkey_create_ecdh(botan_privkey_t* key, botan_rng_t rng_obj, const c log_exception(BOTAN_CURRENT_FUNCTION, e.what()); } - return -1; + return BOTAN_FFI_ERROR_EXCEPTION_THROWN; } int botan_privkey_load(botan_privkey_t* key, botan_rng_t rng_obj, @@ -779,7 +825,7 @@ int botan_privkey_load(botan_privkey_t* key, botan_rng_t rng_obj, return -1; *key = nullptr; - return -1; + return BOTAN_FFI_ERROR_EXCEPTION_THROWN; } int botan_privkey_destroy(botan_privkey_t key) @@ -809,7 +855,7 @@ int botan_privkey_export_pubkey(botan_pubkey_t* pubout, botan_privkey_t key_obj) log_exception(BOTAN_CURRENT_FUNCTION, e.what()); } - return -1; + return BOTAN_FFI_ERROR_EXCEPTION_THROWN; } int botan_pubkey_algo_name(botan_pubkey_t key, char out[], size_t* out_len) @@ -833,29 +879,44 @@ int botan_pubkey_algo_name(botan_pubkey_t key, char out[], size_t* out_len) int botan_pubkey_export(botan_pubkey_t key, uint8_t out[], size_t* out_len, uint32_t flags) { return BOTAN_FFI_DO(Botan::Public_Key, key, { - return write_output(out, out_len, Botan::X509::BER_encode(key)); + if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_DER) + return write_vec_output(out, out_len, Botan::X509::BER_encode(key)); + else if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_PEM) + return write_str_output(out, out_len, Botan::X509::PEM_encode(key)); + else + return -2; }); } int botan_privkey_export(botan_privkey_t key, uint8_t out[], size_t* out_len, uint32_t flags) { return BOTAN_FFI_DO(Botan::Private_Key, key, { - return write_output(out, out_len, Botan::PKCS8::BER_encode(key)); + if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_DER) + return write_vec_output(out, out_len, Botan::PKCS8::BER_encode(key)); + else if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_PEM) + return write_str_output(out, out_len, Botan::PKCS8::PEM_encode(key)); + else + return -2; }); } int botan_privkey_export_encrypted(botan_privkey_t key, uint8_t out[], size_t* out_len, botan_rng_t rng_obj, - const char* passphrase, - const char* encryption_algo, + const char* pass, + const char* pbe, uint32_t flags) { return BOTAN_FFI_DO(Botan::Private_Key, key, { - auto ber = Botan::PKCS8::BER_encode(key, safe_get(rng_obj), passphrase, - std::chrono::milliseconds(300), - encryption_algo); - return write_output(out, out_len, ber); + auto pbkdf_time = std::chrono::milliseconds(300); + Botan::RandomNumberGenerator& rng = safe_get(rng_obj); + + if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_DER) + return write_vec_output(out, out_len, Botan::PKCS8::BER_encode(key, rng, pass, pbkdf_time, pbe)); + else if(flags == BOTAN_PRIVKEY_EXPORT_FLAG_PEM) + return write_str_output(out, out_len, Botan::PKCS8::PEM_encode(key, rng, pass, pbkdf_time, pbe)); + else + return -2; }); } @@ -876,8 +937,6 @@ int botan_pubkey_fingerprint(botan_pubkey_t key, const char* hash_fn, Botan::copy_mem(out, &z[0], *out_len); return 0; }); - *out_len = 0; - return -1; } int botan_pk_op_encrypt_create(botan_pk_op_encrypt_t* op, @@ -890,7 +949,7 @@ int botan_pk_op_encrypt_create(botan_pk_op_encrypt_t* op, BOTAN_ASSERT_NONNULL(op); if(flags != 0) - return -2; + return BOTAN_FFI_ERROR_BAD_FLAG; std::unique_ptr<Botan::PK_Encryptor> pk(new Botan::PK_Encryptor_EME(safe_get(key_obj), padding)); *op = new botan_pk_op_encrypt_struct(pk.release()); @@ -916,7 +975,7 @@ int botan_pk_op_encrypt(botan_pk_op_encrypt_t op, const uint8_t plaintext[], size_t plaintext_len) { return BOTAN_FFI_DO(Botan::PK_Encryptor, op, { - return write_output(out, out_len, op.encrypt(plaintext, plaintext_len, safe_get(rng_obj))); + return write_vec_output(out, out_len, op.encrypt(plaintext, plaintext_len, safe_get(rng_obj))); }); } @@ -933,7 +992,7 @@ int botan_pk_op_decrypt_create(botan_pk_op_decrypt_t* op, BOTAN_ASSERT_NONNULL(op); if(flags != 0) - return -2; + return BOTAN_FFI_ERROR_BAD_FLAG; std::unique_ptr<Botan::PK_Decryptor> pk(new Botan::PK_Decryptor_EME(safe_get(key_obj), padding)); *op = new botan_pk_op_decrypt_struct(pk.release()); @@ -958,7 +1017,7 @@ int botan_pk_op_decrypt(botan_pk_op_decrypt_t op, uint8_t ciphertext[], size_t ciphertext_len) { return BOTAN_FFI_DO(Botan::PK_Decryptor, op, { - return write_output(out, out_len, op.decrypt(ciphertext, ciphertext_len)); + return write_vec_output(out, out_len, op.decrypt(ciphertext, ciphertext_len)); }); } @@ -975,7 +1034,7 @@ int botan_pk_op_sign_create(botan_pk_op_sign_t* op, BOTAN_ASSERT_NONNULL(op); if(flags != 0) - return -2; + return BOTAN_FFI_ERROR_BAD_FLAG; std::unique_ptr<Botan::PK_Signer> pk(new Botan::PK_Signer(safe_get(key_obj), hash)); *op = new botan_pk_op_sign_struct(pk.release()); @@ -986,7 +1045,7 @@ int botan_pk_op_sign_create(botan_pk_op_sign_t* op, log_exception(BOTAN_CURRENT_FUNCTION, e.what()); } - return -1; + return BOTAN_FFI_ERROR_EXCEPTION_THROWN; } int botan_pk_op_sign_destroy(botan_pk_op_sign_t op) @@ -1003,7 +1062,7 @@ int botan_pk_op_sign_update(botan_pk_op_sign_t op, const uint8_t in[], size_t in int botan_pk_op_sign_finish(botan_pk_op_sign_t op, botan_rng_t rng_obj, uint8_t out[], size_t* out_len) { return BOTAN_FFI_DO(Botan::PK_Signer, op, { - return write_output(out, out_len, op.signature(safe_get(rng_obj))); + return write_vec_output(out, out_len, op.signature(safe_get(rng_obj))); }); } @@ -1017,7 +1076,7 @@ int botan_pk_op_verify_create(botan_pk_op_verify_t* op, BOTAN_ASSERT_NONNULL(op); if(flags != 0) - return -2; + return BOTAN_FFI_ERROR_BAD_FLAG; std::unique_ptr<Botan::PK_Verifier> pk(new Botan::PK_Verifier(safe_get(key_obj), hash)); *op = new botan_pk_op_verify_struct(pk.release()); @@ -1064,7 +1123,7 @@ int botan_pk_op_key_agreement_create(botan_pk_op_ka_t* op, BOTAN_ASSERT_NONNULL(op); if(flags != 0) - return -2; + return BOTAN_FFI_ERROR_BAD_FLAG; std::unique_ptr<Botan::PK_Key_Agreement> pk(new Botan::PK_Key_Agreement(safe_get(key_obj), kdf)); *op = new botan_pk_op_ka_struct(pk.release()); @@ -1084,6 +1143,27 @@ int botan_pk_op_key_agreement_destroy(botan_pk_op_ka_t op) return 0; } +int botan_pk_op_key_agreement_export_public(botan_privkey_t key, + uint8_t out[], size_t* out_len) + { + return BOTAN_FFI_DO(Botan::Private_Key, key, { + auto kak = dynamic_cast<const Botan::PK_Key_Agreement_Key*>(&key); + if(!kak) + return -2; + const std::vector<uint8_t> pv = kak->public_value(); + const size_t avail = *out_len; + *out_len = pv.size(); + + if(pv.size() <= avail) + { + Botan::copy_mem(out, &pv[0], pv.size()); + return 0; + } + + return -1; + }); + } + int botan_pk_op_key_agreement(botan_pk_op_ka_t op, uint8_t out[], size_t* out_len, const uint8_t other_key[], size_t other_key_len, @@ -1091,7 +1171,7 @@ int botan_pk_op_key_agreement(botan_pk_op_ka_t op, { return BOTAN_FFI_DO(Botan::PK_Key_Agreement, op, { auto k = op.derive_key(*out_len, other_key, other_key_len, salt, salt_len).bits_of(); - return write_output(out, out_len, k); + return write_vec_output(out, out_len, k); }); } diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index cda2c1480..34e706e2a 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -38,6 +38,11 @@ BOTAN_DLL uint32_t botan_version_datestamp(); /* * Error handling */ +#define BOTAN_FFI_ERROR_EXCEPTION_THROWN (-20) +#define BOTAN_FFI_ERROR_BAD_FLAG (-30) +#define BOTAN_FFI_ERROR_NULL_POINTER (-31) +#define BOTAN_FFI_ERROR_NULL_POINTER (-31) + //const char* botan_error_description(int err); /* @@ -146,10 +151,11 @@ BOTAN_DLL int botan_kdf(const char* kdf_algo, */ #if defined(BOTAN_HAS_BCRYPT) -BOTAN_DLL int botan_bcrypt_generate(char* out, size_t out_len, +BOTAN_DLL int botan_bcrypt_generate(uint8_t* out, size_t* out_len, const char* pass, botan_rng_t rng, - size_t work_factor); + size_t work_factor, + uint32_t flags); /** * Returns 0 if if this password/hash combination is valid @@ -290,6 +296,9 @@ BOTAN_DLL int botan_pk_op_key_agreement_create(botan_pk_op_ka_t* op, uint32_t flags); BOTAN_DLL int botan_pk_op_key_agreement_destroy(botan_pk_op_ka_t op); +BOTAN_DLL int botan_pk_op_key_agreement_export_public(botan_privkey_t key, + uint8_t out[], size_t* out_len); + BOTAN_DLL int botan_pk_op_key_agreement(botan_pk_op_ka_t op, uint8_t out[], size_t* out_len, const uint8_t other_key[], size_t other_key_len, diff --git a/src/python/botan.py b/src/python/botan.py index 8414308ec..2957dce29 100755 --- a/src/python/botan.py +++ b/src/python/botan.py @@ -44,7 +44,7 @@ RNG """ class rng(object): # Can also use type "system" - def __init__(self, rng_type = 'user'): + 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), rng_type) @@ -155,12 +155,6 @@ class cipher(object): botan.botan_cipher_destroy.argtypes = [c_void_p] botan.botan_cipher_destroy(self.cipher) - def tag_length(self): - botan.botan_cipher_tag_length.argtypes = [c_void_p,POINTER(c_size_t)] - l = c_size_t(0) - botan.botan_cipher_tag_size(self.cipher, byref(l)) - return l.value - def default_nonce_length(self): botan.botan_cipher_default_nonce_length.argtypes = [c_void_p, POINTER(c_size_t)] l = c_size_t(0) @@ -234,14 +228,19 @@ class cipher(object): Bcrypt TODO: might not be enabled - handle that gracefully! """ -def generate_bcrypt(passwd, rng, work_factor = 10): - botan.botan_bcrypt_generate.argtypes = [POINTER(c_char), c_size_t, c_char_p, c_void_p, c_size_t] - out = create_string_buffer(61) - rc = botan.botan_bcrypt_generate(out, sizeof(out), passwd, rng.rng, c_size_t(work_factor)) - +def bcrypt(passwd, rng, work_factor = 10): + 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), passwd, rng.rng, c_size_t(work_factor), flags) if rc != 0: raise Exception('botan bcrypt failed, error %s' % (rc)) - return str(out.raw) + b = out.raw[0:out_len.value] + if b[-1] == '\x00': + b = b[:-1] + return b def check_bcrypt(passwd, bcrypt): rc = botan.botan_bcrypt_is_valid(passwd, bcrypt) @@ -250,17 +249,16 @@ def check_bcrypt(passwd, bcrypt): """ PBKDF """ -def pbkdf(algo, password, out_len, iterations, salt): +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(algo, out_buf, out_len, password, salt, len(salt), iterations) - return out_buf.raw + return (salt,iterations,out_buf.raw) -def pbkdf_timed(algo, password, out_len, rng, ms_to_run, salt_len = 12): +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) - salt = rng.get(salt_len) iterations = c_size_t(0) botan.botan_pbkdf_timed(algo, out_buf, out_len, password, salt, len(salt), ms_to_run, byref(iterations)) return (salt,iterations.value,out_buf.raw) @@ -434,10 +432,48 @@ class pk_op_verify(object): return True return False +class pk_op_key_agreement(object): + def __init__(self, key, kdf): + 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 = 0 # always zero in this ABI + botan.botan_pk_op_key_agreement_create(byref(self.op), key.privkey, kdf, flags) + if not self.op: + raise Exception("No key agreement for you") + + pub = create_string_buffer(4096) + pub_len = c_size_t(len(pub)) + botan.botan_pk_op_key_agreement_export_public(key.privkey, pub, byref(pub_len)) + self.m_public_value = pub.raw[0:pub_len.value] + + 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] + + outbuf_sz = c_size_t(key_len) + outbuf = create_string_buffer(outbuf_sz.value) + rc = botan.botan_pk_op_key_agreement(self.op, outbuf, byref(outbuf_sz), other, len(other), salt, len(salt)) + + if rc == -1 and outbuf_sz.value > len(outbuf): + outbuf = create_string_buffer(outbuf_sz.value) + botan.botan_pk_op_key_agreement(self.op, outbuf, byref(outbuf_sz), other, len(other), salt, len(salt)) + return outbuf.raw[0:outbuf_sz.value] + """ Tests and examples """ def test(): + r = rng("user") + + print version_string() print version_major(), version_minor(), version_patch() @@ -447,7 +483,6 @@ def test(): print pbkdf('PBKDF2(SHA-1)', '', 32, 10000, '0001020304050607'.decode('hex')).encode('hex').upper() print '59B2B1143B4CB1059EC58D9722FB1C72471E0D85C6F7543BA5228526375B0127' - r = rng("user") (salt,iterations,psk) = pbkdf_timed('PBKDF2(SHA-256)', 'xyz', 32, r, 200, 12) print salt.encode('hex'), iterations print 'x', psk.encode('hex') @@ -483,8 +518,8 @@ def test(): signer = pk_op_sign(rsapriv, 'EMSA4(SHA-384)') - signer.update('mess') - signer.update('age') + signer.update('messa') + signer.update('ge') sig = signer.finish(r) r.reseed(200) @@ -494,14 +529,34 @@ def test(): verify.update('mess') verify.update('age') - print "correct sig accepted?", verify.check_signature(sig) + print "good sig accepted?", verify.check_signature(sig) verify.update('mess of things') verify.update('age') print "bad sig accepted?", verify.check_signature(sig) - key = private_key('ecdsa', 'secp256r1', r) - blob = key.export() + verify.update('message') + print "good sig accepted?", verify.check_signature(sig) + + dh_grp = 'secp256r1' + #dh_grp = 'curve25519' + dh_kdf = 'KDF2(SHA-384)' + a_dh_priv = private_key('ecdh', dh_grp, r) + a_dh_pub = a_dh_priv.get_public_key() + + b_dh_priv = private_key('ecdh', dh_grp, r) + b_dh_pub = b_dh_priv.get_public_key() + + a_dh = pk_op_key_agreement(a_dh_priv, dh_kdf) + b_dh = pk_op_key_agreement(b_dh_priv, dh_kdf) + + print "dh pubs", a_dh.public_value().encode('hex'), b_dh.public_value().encode('hex') + + a_key = a_dh.agree(b_dh.public_value(), 20, 'salt') + b_key = b_dh.agree(a_dh.public_value(), 20, 'salt') + + print "dh shared", a_key.encode('hex'), b_key.encode('hex') + #f = open('key.ber','wb') #f.write(blob) |