diff options
-rw-r--r-- | doc/credits.rst | 5 | ||||
-rw-r--r-- | src/lib/ffi/ffi.h | 93 | ||||
-rw-r--r-- | src/lib/ffi/ffi_srp6.cpp | 125 | ||||
-rwxr-xr-x | src/python/botan2.py | 106 | ||||
-rw-r--r-- | src/scripts/test_python.py | 40 |
5 files changed, 355 insertions, 14 deletions
diff --git a/doc/credits.rst b/doc/credits.rst index a2b48bf24..912780c7e 100644 --- a/doc/credits.rst +++ b/doc/credits.rst @@ -154,3 +154,8 @@ snail-mail address (S), and Bitcoin address (B). W: https://www.ribose.com/ D: SM3, Streebog, various minor contributions + + N: Rostyslav Khudolii + E: [email protected] + D: SRP6 FFI + S: Ukraine/Denmark diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 4dbc218c6..730ae4cfe 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -1782,6 +1782,99 @@ int botan_fpe_encrypt(botan_fpe_t fpe, botan_mp_t x, const uint8_t tweak[], size BOTAN_PUBLIC_API(2,8) int botan_fpe_decrypt(botan_fpe_t fpe, botan_mp_t x, const uint8_t tweak[], size_t tweak_len); +/** +* SRP-6 Server Session type +*/ +typedef struct botan_srp6_server_session_struct* botan_srp6_server_session_t; + +/** +* Initialize an SRP-6 server session object +* @param srp6 SRP-6 server session object +*/ +BOTAN_PUBLIC_API(3, 0) +int botan_srp6_server_session_init(botan_srp6_server_session_t *srp6); + +/** +* Frees all resources of the SRP-6 server session object +* @param srp6 SRP-6 server session object +* @return 0 if success, error if invalid object handle +*/ +BOTAN_PUBLIC_API(3, 0) +int botan_srp6_server_session_destroy(botan_srp6_server_session_t srp6); + +/** +* SRP-6 Server side step 1 +* @param srp6 SRP-6 server session object +* @param verifier the verification value saved from client registration +* @param group_id the SRP group id +* @param hash_id the SRP hash in use +* @param rng_obj a random number generator object +* @param B_pub out buffer to store the SRP-6 B value +* @param B_pub_len SRP-6 B value length +* @return 0 on success, negative on failure +*/ +BOTAN_PUBLIC_API(3, 0) +int botan_srp6_server_session_step1(botan_srp6_server_session_t srp6, + const uint8_t verifier[], + size_t verifier_len, const char *group_id, + const char *hash_id, botan_rng_t rng_obj, + uint8_t B_pub[], size_t *B_pub_len); + +/** +* SRP-6 Server side step 2 +* @param srp6 SRP-6 server session object +* @param A the client's value +* @param A_len the client's value length +* @param key out buffer to store the symmetric key value +* @param key_len symmetric key length +* @return 0 on success, negative on failure +*/ +BOTAN_PUBLIC_API(3, 0) +int botan_srp6_server_session_step2(botan_srp6_server_session_t srp6, + const uint8_t A[], size_t A_len, + uint8_t key[], size_t *key_len); + +/** +* Generate a new SRP-6 verifier +* @param identifier a username or other client identifier +* @param password the secret used to authenticate user +* @param salt a randomly chosen value, at least 128 bits long +* @param group_id specifies the shared SRP group +* @param hash_id specifies a secure hash function +* @param verifier out buffer to store the SRP-6 verifier value +* @param verifier_len SRP-6 verifier value length +* @return 0 on success, negative on failure +*/ +BOTAN_PUBLIC_API(3, 0) +int botan_generate_srp6_verifier(const char *identifier, const char *password, + const uint8_t salt[], size_t salt_len, + const char *group_id, const char *hash_id, + uint8_t verifier[], size_t *verifier_len); + +/** +* SRP6a Client side +* @param username the username we are attempting login for +* @param password the password we are attempting to use +* @param group_id specifies the shared SRP group +* @param hash_id specifies a secure hash function +* @param salt is the salt value sent by the server +* @param B is the server's public value +* @param B_len is the server's public value length +* @param rng_obj is a random number generator object +* @param A out buffer to store the SRP-6 A value +* @param A_len SRP-6 A verifier value length +* @param K out buffer to store the symmetric value +* @param K_len symmetric key length +* @return 0 on success, negative on failure +*/ +BOTAN_PUBLIC_API(3, 0) +int botan_srp6_client_agree(const char *username, const char *password, + const char *group_id, const char *hash_id, + const uint8_t salt[], size_t salt_len, + const uint8_t B[], size_t B_len, botan_rng_t rng_obj, + uint8_t A[], size_t *A_len, uint8_t K[], + size_t *K_len); + #ifdef __cplusplus } #endif diff --git a/src/lib/ffi/ffi_srp6.cpp b/src/lib/ffi/ffi_srp6.cpp new file mode 100644 index 000000000..7ae4d2204 --- /dev/null +++ b/src/lib/ffi/ffi_srp6.cpp @@ -0,0 +1,125 @@ +/* +* (C) 2022 Rostyslav Khudolii +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "botan/bigint.h" +#include "botan/ffi.h" +#include "botan/internal/ffi_rng.h" +#include "botan/internal/ffi_util.h" +#include "botan/rng.h" +#include "botan/srp6.h" +#include "botan/symkey.h" + +extern "C" { + +using namespace Botan_FFI; + +BOTAN_FFI_DECLARE_STRUCT(botan_srp6_server_session_struct, + Botan::SRP6_Server_Session, 0x44F7425F); + +int botan_srp6_server_session_init(botan_srp6_server_session_t *srp6) { +return ffi_guard_thunk(__func__, [=]() -> int { +*srp6 = new botan_srp6_server_session_struct( + std::make_unique<Botan::SRP6_Server_Session>()); +return BOTAN_FFI_SUCCESS; +}); +} + +int botan_srp6_server_session_destroy(botan_srp6_server_session_t srp6) { +return BOTAN_FFI_CHECKED_DELETE(srp6); +} + +int botan_srp6_server_session_step1(botan_srp6_server_session_t srp6, + const uint8_t *verifier, + size_t verifier_len, const char *group_id, + const char *hash_id, botan_rng_t rng_obj, + uint8_t b_pub[], size_t *b_pub_len) { +return BOTAN_FFI_DO(Botan::SRP6_Server_Session, srp6, s, { + if (!verifier || !group_id || !hash_id || !rng_obj) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + try { + Botan::RandomNumberGenerator &rng = safe_get(rng_obj); + auto v_bn = Botan::BigInt::decode(verifier, verifier_len); + auto b_pub_bn = s.step1(v_bn, group_id, hash_id, rng); + return write_vec_output(b_pub, b_pub_len, + Botan::BigInt::encode(b_pub_bn)); + } catch (Botan::Decoding_Error &) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } catch (Botan::Lookup_Error &) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + }); +} + +int botan_srp6_server_session_step2(botan_srp6_server_session_t srp6, + const uint8_t a[], size_t a_len, + uint8_t key[], size_t *key_len) { +return BOTAN_FFI_DO(Botan::SRP6_Server_Session, srp6, s, { + if (!a) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + try { + Botan::BigInt a_bn = Botan::BigInt::decode(a, a_len); + auto key_sk = s.step2(a_bn); + return write_vec_output(key, key_len, key_sk.bits_of()); + } catch (Botan::Decoding_Error &) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + }); +} + +int botan_generate_srp6_verifier(const char *username, const char *password, + const uint8_t salt[], size_t salt_len, + const char *group_id, const char *hash_id, + uint8_t verifier[], size_t *verifier_len) { +return ffi_guard_thunk(__func__, [=]() -> int { +if (!username || !password || !salt || !group_id || !hash_id) { +return BOTAN_FFI_ERROR_NULL_POINTER; +} +try { +std::vector<uint8_t> salt_vec(salt, salt + salt_len); +auto verifier_bn = Botan::generate_srp6_verifier( + username, password, salt_vec, group_id, hash_id); +return write_vec_output(verifier, verifier_len, + Botan::BigInt::encode(verifier_bn)); +} catch (Botan::Lookup_Error &) { +return BOTAN_FFI_ERROR_BAD_PARAMETER; +} +}); +} + +int botan_srp6_client_agree(const char *identity, const char *password, + const char *group_id, const char *hash_id, + const uint8_t salt[], size_t salt_len, + const uint8_t b[], size_t b_len, botan_rng_t rng_obj, + uint8_t A[], size_t *A_len, uint8_t K[], + size_t *K_len) { +return ffi_guard_thunk(__func__, [=]() -> int { +if (!identity || !password || !salt || !group_id || !hash_id || !b || !rng_obj) { +return BOTAN_FFI_ERROR_NULL_POINTER; +} +try { +std::vector<uint8_t> saltv(salt, salt + salt_len); +Botan::RandomNumberGenerator &rng = safe_get(rng_obj); +auto b_bn = Botan::BigInt::decode(b, b_len); +auto [A_bn, K_sk] = Botan::srp6_client_agree( + identity, password, group_id, hash_id, saltv, b_bn, rng); +auto ret_a = write_vec_output(A, A_len, Botan::BigInt::encode(A_bn)); +auto ret_k = write_vec_output(K, K_len, K_sk.bits_of()); +if (ret_a != BOTAN_FFI_SUCCESS) { +return ret_a; +} +if (ret_k != BOTAN_FFI_SUCCESS) { +return ret_k; +} +return BOTAN_FFI_SUCCESS; +} catch (Botan::Lookup_Error &) { +return BOTAN_FFI_ERROR_BAD_PARAMETER; +} +}); +} + +} diff --git a/src/python/botan2.py b/src/python/botan2.py index 3a868db46..656800824 100755 --- a/src/python/botan2.py +++ b/src/python/botan2.py @@ -101,10 +101,10 @@ def _set_prototypes(dll): def ffi_api(fn, args, allowed_errors=None): if allowed_errors is None: allowed_errors = [-10] - fn.argtypes = args - fn.restype = c_int - fn.errcheck = _errcheck - fn.allowed_errors = allowed_errors + fn.argtypes = args + fn.restype = c_int + fn.errcheck = _errcheck + fn.allowed_errors = allowed_errors dll.botan_version_string.argtypes = [] dll.botan_version_string.restype = c_char_p @@ -434,6 +434,19 @@ def _set_prototypes(dll): ffi_api(dll.botan_fpe_encrypt, [c_void_p, c_void_p, c_char_p, c_size_t]) ffi_api(dll.botan_fpe_decrypt, [c_void_p, c_void_p, c_char_p, c_size_t]) + # SRP6-a + ffi_api(dll.botan_srp6_server_session_init, [c_void_p]) + ffi_api(dll.botan_srp6_server_session_destroy, [c_void_p]) + ffi_api(dll.botan_srp6_server_session_step1, + [c_void_p, c_char_p, c_size_t, c_char_p, c_char_p, c_void_p, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_srp6_server_session_step2, + [c_void_p, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_generate_srp6_verifier, + [c_char_p, c_char_p, c_char_p, c_size_t, c_char_p, c_char_p, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_srp6_client_agree, + [c_char_p, c_char_p, c_char_p, c_char_p, c_char_p, c_size_t, c_char_p, c_size_t, c_void_p, + c_char_p, POINTER(c_size_t), c_char_p, POINTER(c_size_t)]) + return dll # @@ -461,6 +474,26 @@ def _call_fn_returning_vec(guess, fn): assert buf_len.value <= len(buf) return buf.raw[0:int(buf_len.value)] +def _call_fn_returning_vec_pair(guess1, guess2, fn): + + buf1 = create_string_buffer(guess1) + buf1_len = c_size_t(len(buf1)) + + buf2 = create_string_buffer(guess2) + buf2_len = c_size_t(len(buf2)) + + rc = fn(buf1, byref(buf1_len), buf2, byref(buf2_len)) + if rc == -10: + if buf1_len.value > len(buf1): + guess1 = buf1_len.value + if buf2_len.value > len(buf2): + guess2 = buf2_len.value + return _call_fn_returning_vec_pair(guess1, guess2, fn) + + assert buf1_len.value <= len(buf1) + assert buf2_len.value <= len(buf2) + return (buf1.raw[0:int(buf1_len.value)], buf2.raw[0:int(buf2_len.value)]) + def _call_fn_returning_str(guess, fn): # Assumes that anything called with this is returning plain ASCII strings # (base64 data, algorithm names, etc) @@ -806,10 +839,10 @@ class SymmetricCipher(object): elif self._is_cbc: # Hack: the largest block size currently supported extra_bytes = 64 - out = create_string_buffer(inp_sz.value + extra_bytes) - out_sz = c_size_t(len(out)) - out_written = c_size_t(0) - flags = c_uint32(1 if final else 0) + out = create_string_buffer(inp_sz.value + extra_bytes) + out_sz = c_size_t(len(out)) + out_written = c_size_t(0) + flags = c_uint32(1 if final else 0) _DLL.botan_cipher_update(self.__obj, flags, out, out_sz, byref(out_written), @@ -1406,7 +1439,7 @@ class X509Cert(object): # pylint: disable=invalid-name arr_intermediates = c_intermediates() for i, ca in enumerate(intermediates): arr_intermediates[i] = ca.handle_() - len_intermediates = c_size_t(len(intermediates)) + len_intermediates = c_size_t(len(intermediates)) else: arr_intermediates = c_void_p(0) len_intermediates = c_size_t(0) @@ -1416,7 +1449,7 @@ class X509Cert(object): # pylint: disable=invalid-name arr_trusted = c_trusted() for i, ca in enumerate(trusted): arr_trusted[i] = ca.handle_() - len_trusted = c_size_t(len(trusted)) + len_trusted = c_size_t(len(trusted)) else: arr_trusted = c_void_p(0) len_trusted = c_size_t(0) @@ -1426,7 +1459,7 @@ class X509Cert(object): # pylint: disable=invalid-name arr_crls = c_crls() for i, crl in enumerate(crls): arr_crls[i] = crl.handle_() - len_crls = c_size_t(len(crls)) + len_crls = c_size_t(len(crls)) else: arr_crls = c_void_p(0) len_crls = c_size_t(0) @@ -1739,14 +1772,14 @@ class TOTP(object): def generate(self, timestamp=None): if timestamp is None: timestamp = int(system_time()) - code = c_uint32(0) - _DLL.botan_totp_generate(self.__obj, byref(code), timestamp) + code = c_uint32(0) + _DLL.botan_totp_generate(self.__obj, byref(code), timestamp) return code.value def check(self, code, timestamp=None, acceptable_drift=0): if timestamp is None: timestamp = int(system_time()) - rc = _DLL.botan_totp_check(self.__obj, code, timestamp, acceptable_drift) + rc = _DLL.botan_totp_check(self.__obj, code, timestamp, acceptable_drift) if rc == 0: return True return False @@ -1762,3 +1795,48 @@ def nist_key_unwrap(kek, wrapped): out_len = c_size_t(len(output)) _DLL.botan_key_unwrap3394(wrapped, len(wrapped), kek, len(kek), output, byref(out_len)) return output[0:int(out_len.value)] + +class Srp6ServerSession(object): + __obj = c_void_p(0) + + def __init__(self): + _DLL.botan_srp6_server_session_init(byref(self.__obj)) + + def __del__(self): + _DLL.botan_srp6_server_session_destroy(self.__obj) + + def step1(self, verifier, group, hash, rng): + return _call_fn_returning_vec(128, + lambda b, bl: + _DLL.botan_srp6_server_session_step1(self.__obj, + verifier, len(verifier), + _ctype_str(group), _ctype_str(hash), + rng.handle_(), + b, bl)) + + def step2(self, A): + return _call_fn_returning_vec(128, lambda k, kl: + _DLL.botan_srp6_server_session_step2(self.__obj, + A, len(A), + k, kl)) + +def generate_srp6_verifier(identifier, password, salt, group, hash): + return _call_fn_returning_vec(128, lambda v, vl: + _DLL.botan_generate_srp6_verifier(_ctype_str(identifier), + _ctype_str(password), + salt, len(salt), + _ctype_str(group), + _ctype_str(hash), + v, vl)) + +def srp6_client_agree(username, password, group, hash, salt, B, rng): + return _call_fn_returning_vec_pair(128, 128, lambda a, al, k, kl: + _DLL.botan_srp6_client_agree(_ctype_str(username), + _ctype_str(password), + _ctype_str(group), + _ctype_str(hash), + salt, len(salt), + B, len(B), + rng.handle_(), + a, al, + k, kl)) diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 6a2b3b1b8..84a7480fb 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -683,5 +683,45 @@ ofvkP1EDmpx50fHLawIDAQAB self.assertFalse(totp.check(7081804, 1111111109 + 1)) self.assertTrue(totp.check(7081804, 1111111109 + 30, 1)) + def test_srp6(self): + identity = 'alice' + password = 'password123' + rng = botan2.RandomNumberGenerator() + + # Test successful authentication + server = botan2.Srp6ServerSession() + salt = rng.get(24) + verifier = botan2.generate_srp6_verifier(identity, password, salt, 'modp/srp/1024', 'SHA-512') + B = server.step1(verifier, 'modp/srp/1024', 'SHA-512', rng) + (A, key_c) = botan2.srp6_client_agree(identity, password, 'modp/srp/1024', 'SHA-512', salt, B, rng) + key_s = server.step2(A) + self.assertEqual(key_c, key_s) + + # Test wrong server ephemeral + try: + salt = rng.get(24) + B = b'BD0C6151 2C692C0C B6D041FA 01BB152D 4916A1E7 7AF46AE1 05393011 \ + BAF38964 DC46A067 0DD125B9 5A981652 236F99D9 B681CBF8 7837EC99 \ + 6C6DA044 53728610 D0C6DDB5 8B318885 D7D82C7F 8DEB75CE 7BD4FBAA \ + 37089E6F 9C6059F3 88838E7A 00030B33 1EB76840 910440B1 B27AAEAE \ + EB4012B7 D7665238 A8E3FB00 4B117B58' + botan2.srp6_client_agree(identity, password, 'modp/srp/1024', 'SHA-512', salt, B, rng) + except botan2.BotanException as e: + self.assertEqual(str(e), "botan_srp6_client_agree failed: -1 (Invalid input): Invalid SRP parameter from server") + + # Test wrong client ephemeral + try: + salt = rng.get(24) + verifier = botan2.generate_srp6_verifier(identity, password, salt, 'modp/srp/1024', 'SHA-512') + server.step1(verifier, 'modp/srp/1024', 'SHA-512', rng) + A = b'61D5E490 F6F1B795 47B0704C 436F523D D0E560F0 C64115BB 72557EC4 \ + 4352E890 3211C046 92272D8B 2D1A5358 A2CF1B6E 0BFCF99F 921530EC \ + 8E393561 79EAE45E 42BA92AE ACED8251 71E1E8B9 AF6D9C03 E1327F44 \ + BE087EF0 6530E69F 66615261 EEF54073 CA11CF58 58F0EDFD FE15EFEA \ + B349EF5D 76988A36 72FAC47B 0769447B' + server.step2(A) + except botan2.BotanException as e: + self.assertEqual(str(e), "botan_srp6_server_session_step2 failed: -32 (Bad parameter)") + if __name__ == '__main__': unittest.main() |