Skip to content

Commit b761b51

Browse files
author
Rostyslav Khudolii
committed
Add SRP-6 support
Use the newly introduced C99 FFI API to implement both server- and client- side functionalities. Enable SRP-6 support only when botan version >= 3.0 is used ("botan3" cargo feature).
1 parent bdf1de5 commit b761b51

File tree

6 files changed

+343
-0
lines changed

6 files changed

+343
-0
lines changed

botan-sys/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ mod rng;
2020
mod utils;
2121
mod version;
2222
mod x509;
23+
#[cfg(feature="botan3")]
24+
mod srp6;
2325

2426
pub use block::*;
2527
pub use cipher::*;
@@ -38,3 +40,5 @@ pub use rng::*;
3840
pub use utils::*;
3941
pub use version::*;
4042
pub use x509::*;
43+
#[cfg(feature="botan3")]
44+
pub use srp6::*;

botan-sys/src/srp6.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#![allow(non_camel_case_types)]
2+
3+
use cty::{c_char, c_int};
4+
use rng::botan_rng_t;
5+
6+
pub enum botan_srp6_server_session_struct {}
7+
pub type botan_srp6_server_session_t = *mut botan_srp6_server_session_struct;
8+
9+
extern "C" {
10+
pub fn botan_srp6_server_session_init(srp6: *mut botan_srp6_server_session_t) -> c_int;
11+
pub fn botan_srp6_server_session_destroy(srp6: botan_srp6_server_session_t) -> c_int;
12+
pub fn botan_srp6_server_session_step1(
13+
srp6: botan_srp6_server_session_t,
14+
v: *const u8,
15+
v_len: usize,
16+
group_id: *const c_char,
17+
hash_id: *const c_char,
18+
rng_obj: botan_rng_t,
19+
b_pub: *mut u8,
20+
b_pub_len: *mut usize
21+
) -> c_int;
22+
pub fn botan_srp6_server_session_step2(
23+
srp6: botan_srp6_server_session_t,
24+
a: *const u8,
25+
a_len: usize,
26+
key: *mut u8,
27+
key_len: *mut usize,
28+
) -> c_int;
29+
pub fn botan_generate_srp6_verifier(
30+
identifier: *const c_char,
31+
password: *const c_char,
32+
salt: *const u8,
33+
salt_len: usize,
34+
group_id: *const c_char,
35+
hash_id: *const c_char,
36+
verifier: *mut u8,
37+
verifier_len: *mut usize
38+
) -> c_int;
39+
pub fn botan_srp6_client_agree(
40+
username: *const c_char,
41+
password: *const c_char,
42+
group_id: *const c_char,
43+
hash_id: *const c_char,
44+
salt: *const u8,
45+
salt_len: usize,
46+
b: *const u8,
47+
b_len: usize,
48+
rng_obj: botan_rng_t,
49+
a: *mut u8,
50+
a_len: *mut usize,
51+
key: *mut u8,
52+
key_len: *mut usize,
53+
) -> c_int;
54+
}

botan/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ mod rng;
117117
mod utils;
118118
mod version;
119119
mod x509;
120+
#[cfg(feature="botan3")]
121+
mod srp6;
120122

121123
pub use crate::mp::*;
122124
pub use crate::rng::*;
@@ -136,3 +138,5 @@ pub use pk_ops::*;
136138
pub use pubkey::*;
137139
pub use version::*;
138140
pub use x509::*;
141+
#[cfg(feature="botan3")]
142+
pub use srp6::*;

botan/src/srp6.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//! SRP-6a (RFC 5054 compatatible)
2+
//!
3+
//! This module contains the [`ServerSession`] type and the client side functions.
4+
//!
5+
//! # Examples
6+
//!
7+
//! ```
8+
//! use botan::RandomNumberGenerator;
9+
//! use botan::{ServerSession, generate_srp6_verifier, srp6_client_agree};
10+
//!
11+
//! let mut rng = RandomNumberGenerator::new_system().expect("Failed to create a random number generator");
12+
//! let mut server = ServerSession::new().expect("Failed to create a SRP6 server session");
13+
//! let salt = rng.read(24).expect("Failed to generate salt");
14+
//! let verifier = generate_srp6_verifier("alice", "password123", &salt, "modp/srp/1024", "SHA-512").expect("Failed to generate SRP6 verifier");
15+
//! let b_pub = server.step1(&verifier, "modp/srp/1024", "SHA-512", &rng).expect("Failed to calculate server B value");
16+
//! let (a_pub, client_key) = srp6_client_agree("alice", "password123", "modp/srp/1024", "SHA-512", &salt, &b_pub, &rng).expect("Failed to generate client key");
17+
//! let server_key = server.step2(&a_pub).expect("Failed to generate server key");
18+
//! assert_eq!(client_key, server_key);
19+
//! ```
20+
21+
use crate::{utils::*, RandomNumberGenerator};
22+
use botan_sys::*;
23+
24+
/// An SRP-6 server session
25+
#[derive(Debug)]
26+
pub struct ServerSession {
27+
obj: botan_srp6_server_session_t,
28+
}
29+
30+
botan_impl_drop!(ServerSession, botan_srp6_server_session_destroy);
31+
32+
impl ServerSession {
33+
/// Returns a new server session object.
34+
///
35+
/// # Errors
36+
///
37+
/// Returns [`ErrorType::OutOfMemory`] if memory is exhausted
38+
pub fn new() -> Result<Self> {
39+
Ok(Self {
40+
obj: botan_init!(botan_srp6_server_session_init)?,
41+
})
42+
}
43+
44+
/// Server side step 1. Returns SRP-6 B value.
45+
///
46+
/// # Arguments
47+
///
48+
/// `verifier`: the verification value saved from client registration
49+
/// `group_id`: the SRP group id
50+
/// `hash_id`: the SRP hash in use
51+
/// `rng`: a random number generator
52+
///
53+
/// # Errors
54+
///
55+
/// Returns [`ErrorType::BadParameter`] if SRP group/hash id is invalid.
56+
pub fn step1(
57+
&mut self,
58+
verifier: &[u8],
59+
group_id: &str,
60+
hash_id: &str,
61+
rng: &RandomNumberGenerator,
62+
) -> Result<Vec<u8>> {
63+
let group_id = make_cstr(group_id)?;
64+
let hash_id = make_cstr(hash_id)?;
65+
call_botan_ffi_returning_vec_u8(128, &|b_pub, b_pub_len| unsafe {
66+
botan_srp6_server_session_step1(
67+
self.obj,
68+
verifier.as_ptr(),
69+
verifier.len(),
70+
group_id.as_ptr(),
71+
hash_id.as_ptr(),
72+
rng.handle(),
73+
b_pub,
74+
b_pub_len,
75+
)
76+
})
77+
}
78+
79+
/// Server side step 2. Returns shared symmetric key.
80+
///
81+
/// # Arguments
82+
///
83+
/// `a_pub`: the client's value
84+
///
85+
/// # Errors
86+
///
87+
/// Returns [`ErrorType::BadParameter`] if the A value is invalid.
88+
pub fn step2(&self, a_pub: &[u8]) -> Result<Vec<u8>> {
89+
call_botan_ffi_returning_vec_u8(128, &|key, key_len| unsafe {
90+
botan_srp6_server_session_step2(self.obj, a_pub.as_ptr(), a_pub.len(), key, key_len)
91+
})
92+
}
93+
}
94+
95+
/// Returns a new SRP-6 verifier.
96+
///
97+
/// `identifier`: a username or other client identifier
98+
/// `password`: the secret used to authenticate user
99+
/// `salt`: a randomly chosen value, at least 128 bits long
100+
/// `group_id`: the SRP group id
101+
/// `hash_id`: the SRP hash in use
102+
///
103+
/// # Error
104+
///
105+
/// Returns [`ErrorType::BadParameter`] if SRP group/hash id is invalid.
106+
/// Returns [`ErrorType::BadParameter`] if salt is too short.
107+
pub fn generate_srp6_verifier(
108+
identifier: &str,
109+
password: &str,
110+
salt: &[u8],
111+
group_id: &str,
112+
hash_id: &str,
113+
) -> Result<Vec<u8>> {
114+
if salt.len() * 8 < 128 {
115+
return Err(Error::with_message(
116+
ErrorType::BadParameter,
117+
"Salt is too short".to_string(),
118+
));
119+
}
120+
121+
let identifier = make_cstr(identifier)?;
122+
let password = make_cstr(password)?;
123+
let group_id = make_cstr(group_id)?;
124+
let hash_id = make_cstr(hash_id)?;
125+
126+
call_botan_ffi_returning_vec_u8(128, &|verifier, verifier_len| unsafe {
127+
botan_generate_srp6_verifier(
128+
identifier.as_ptr(),
129+
password.as_ptr(),
130+
salt.as_ptr(),
131+
salt.len(),
132+
group_id.as_ptr(),
133+
hash_id.as_ptr(),
134+
verifier,
135+
verifier_len,
136+
)
137+
})
138+
}
139+
140+
/// SRP6a Client side. Returns the client public key and the shared secret key.
141+
///
142+
/// `username`: the username we are attempting login for
143+
/// `password`: the password we are attempting to use
144+
/// `salt`: the salt value sent by the server
145+
/// `group_id`: specifies the shared SRP group
146+
/// `hash_id`: specifies a secure hash function
147+
/// `b_pub`: is the server's public value
148+
/// `rng`: rng is a random number generator
149+
///
150+
/// # Error
151+
///
152+
/// Returns [`ErrorType::BadParameter`] if SRP group/hash id is invalid.
153+
/// Returns [`ErrorType::BadParameter`] if the B value is invalid.
154+
pub fn srp6_client_agree(
155+
username: &str,
156+
password: &str,
157+
group_id: &str,
158+
hash_id: &str,
159+
salt: &[u8],
160+
b_pub: &[u8],
161+
rng: &RandomNumberGenerator,
162+
) -> Result<(Vec<u8>, Vec<u8>)> {
163+
let username = make_cstr(username)?;
164+
let password = make_cstr(password)?;
165+
let group_id = make_cstr(group_id)?;
166+
let hash_id = make_cstr(hash_id)?;
167+
168+
call_botan_ffi_returning_vec_u8_pair(128, 128, &|a, a_len, key, key_len| unsafe {
169+
botan_srp6_client_agree(
170+
username.as_ptr(),
171+
password.as_ptr(),
172+
group_id.as_ptr(),
173+
hash_id.as_ptr(),
174+
salt.as_ptr(),
175+
salt.len(),
176+
b_pub.as_ptr(),
177+
b_pub.len(),
178+
rng.handle(),
179+
a,
180+
a_len,
181+
key,
182+
key_len,
183+
)
184+
})
185+
}

botan/src/utils.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,43 @@ pub(crate) fn call_botan_ffi_returning_vec_u8(
4949
Ok(output)
5050
}
5151

52+
#[cfg(feature = "botan3")]
53+
pub(crate) fn call_botan_ffi_returning_vec_u8_pair(
54+
mut initial_size1: usize,
55+
mut initial_size2: usize,
56+
cb: &dyn Fn(*mut u8, *mut usize, *mut u8, *mut usize) -> c_int,
57+
) -> Result<(Vec<u8>, Vec<u8>)> {
58+
let mut out1 = vec![0; initial_size1];
59+
let mut out1_len = out1.len();
60+
let mut out2 = vec![0; initial_size2];
61+
let mut out2_len = out2.len();
62+
let rc = cb(
63+
out1.as_mut_ptr(),
64+
&mut out1_len,
65+
out2.as_mut_ptr(),
66+
&mut out2_len,
67+
);
68+
match rc {
69+
0 => {
70+
assert!(out1_len <= out1.len());
71+
assert!(out2_len <= out2.len());
72+
out1.resize(out1_len, 0);
73+
out2.resize(out2_len, 0);
74+
Ok((out1, out2))
75+
}
76+
BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE => {
77+
if out1_len > out1.len() {
78+
initial_size1 = out1_len;
79+
}
80+
if out2_len > out2.len() {
81+
initial_size2 = out2_len;
82+
}
83+
call_botan_ffi_returning_vec_u8_pair(initial_size1, initial_size2, cb)
84+
}
85+
_ => Err(Error::from_rc(rc)),
86+
}
87+
}
88+
5289
fn cstr_slice_to_str(raw_cstr: &[u8]) -> Result<String> {
5390
let cstr = CStr::from_bytes_with_nul(raw_cstr).map_err(Error::conversion_error)?;
5491
Ok(cstr.to_str().map_err(Error::conversion_error)?.to_owned())

botan/tests/tests.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,3 +913,62 @@ fn test_totp() -> Result<(), botan::Error> {
913913
assert!(!totp.check(90693936, 59 + 31, 1)?);
914914
Ok(())
915915
}
916+
917+
#[cfg(feature = "botan3")]
918+
#[test]
919+
fn test_srp6() -> Result<(), botan::Error> {
920+
const IDENTITY: &str = "alice";
921+
const PASSWORD: &str = "password123";
922+
let mut rng = botan::RandomNumberGenerator::new_system()?;
923+
924+
// Test successful authentication
925+
let mut server = botan::ServerSession::new()?;
926+
let salt = rng.read(24)?;
927+
let verifier =
928+
botan::generate_srp6_verifier(IDENTITY, PASSWORD, &salt, "modp/srp/1024", "SHA-512")?;
929+
let b_pub = server.step1(&verifier, "modp/srp/1024", "SHA-512", &rng)?;
930+
let (a_pub, client_key) = botan::srp6_client_agree(
931+
IDENTITY,
932+
PASSWORD,
933+
"modp/srp/1024",
934+
"SHA-512",
935+
&salt,
936+
&b_pub,
937+
&rng,
938+
)?;
939+
let server_key = server.step2(&a_pub)?;
940+
assert_eq!(client_key, server_key);
941+
942+
// Test wrong server's B value
943+
let salt = rng.read(24)?;
944+
let b = b"BD0C6151 2C692C0C B6D041FA 01BB152D 4916A1E7 7AF46AE1 05393011 \
945+
BAF38964 DC46A067 0DD125B9 5A981652 236F99D9 B681CBF8 7837EC99 \
946+
6C6DA044 53728610 D0C6DDB5 8B318885 D7D82C7F 8DEB75CE 7BD4FBAA \
947+
37089E6F 9C6059F3 88838E7A 00030B33 1EB76840 910440B1 B27AAEAE \
948+
EB4012B7 D7665238 A8E3FB00 4B117B58";
949+
let result = botan::srp6_client_agree(
950+
IDENTITY,
951+
PASSWORD,
952+
"modp/srp/1024",
953+
"SHA-512",
954+
&salt,
955+
b,
956+
&rng,
957+
);
958+
assert!(result.is_err());
959+
960+
// Test wrong client's A value
961+
let salt = rng.read(24)?;
962+
let verifier =
963+
botan::generate_srp6_verifier(IDENTITY, PASSWORD, &salt, "modp/srp/1024", "SHA-512")?;
964+
let _ = server.step1(&verifier, "modp/srp/1024", "SHA-512", &rng)?;
965+
let a_pub = b"61D5E490 F6F1B795 47B0704C 436F523D D0E560F0 C64115BB 72557EC4 \
966+
4352E890 3211C046 92272D8B 2D1A5358 A2CF1B6E 0BFCF99F 921530EC \
967+
8E393561 79EAE45E 42BA92AE ACED8251 71E1E8B9 AF6D9C03 E1327F44 \
968+
BE087EF0 6530E69F 66615261 EEF54073 CA11CF58 58F0EDFD FE15EFEA \
969+
B349EF5D 76988A36 72FAC47B 0769447B";
970+
let result = server.step2(a_pub);
971+
assert!(result.is_err());
972+
973+
Ok(())
974+
}

0 commit comments

Comments
 (0)