aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/credits.rst5
-rw-r--r--src/lib/ffi/ffi.h93
-rw-r--r--src/lib/ffi/ffi_srp6.cpp125
-rwxr-xr-xsrc/python/botan2.py106
-rw-r--r--src/scripts/test_python.py40
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
+ 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()