Skip to content

Commit

Permalink
Add SRP-6 support
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
Rostyslav Khudolii committed Mar 27, 2022
1 parent bdf1de5 commit b761b51
Show file tree
Hide file tree
Showing 6 changed files with 343 additions and 0 deletions.
4 changes: 4 additions & 0 deletions botan-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ mod rng;
mod utils;
mod version;
mod x509;
#[cfg(feature="botan3")]
mod srp6;

pub use block::*;
pub use cipher::*;
Expand All @@ -38,3 +40,5 @@ pub use rng::*;
pub use utils::*;
pub use version::*;
pub use x509::*;
#[cfg(feature="botan3")]
pub use srp6::*;
54 changes: 54 additions & 0 deletions botan-sys/src/srp6.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#![allow(non_camel_case_types)]

use cty::{c_char, c_int};
use rng::botan_rng_t;

pub enum botan_srp6_server_session_struct {}
pub type botan_srp6_server_session_t = *mut botan_srp6_server_session_struct;

extern "C" {
pub fn botan_srp6_server_session_init(srp6: *mut botan_srp6_server_session_t) -> c_int;
pub fn botan_srp6_server_session_destroy(srp6: botan_srp6_server_session_t) -> c_int;
pub fn botan_srp6_server_session_step1(
srp6: botan_srp6_server_session_t,
v: *const u8,
v_len: usize,
group_id: *const c_char,
hash_id: *const c_char,
rng_obj: botan_rng_t,
b_pub: *mut u8,
b_pub_len: *mut usize
) -> c_int;
pub fn botan_srp6_server_session_step2(
srp6: botan_srp6_server_session_t,
a: *const u8,
a_len: usize,
key: *mut u8,
key_len: *mut usize,
) -> c_int;
pub fn botan_generate_srp6_verifier(
identifier: *const c_char,
password: *const c_char,
salt: *const u8,
salt_len: usize,
group_id: *const c_char,
hash_id: *const c_char,
verifier: *mut u8,
verifier_len: *mut usize
) -> c_int;
pub fn botan_srp6_client_agree(
username: *const c_char,
password: *const c_char,
group_id: *const c_char,
hash_id: *const c_char,
salt: *const u8,
salt_len: usize,
b: *const u8,
b_len: usize,
rng_obj: botan_rng_t,
a: *mut u8,
a_len: *mut usize,
key: *mut u8,
key_len: *mut usize,
) -> c_int;
}
4 changes: 4 additions & 0 deletions botan/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ mod rng;
mod utils;
mod version;
mod x509;
#[cfg(feature="botan3")]
mod srp6;

pub use crate::mp::*;
pub use crate::rng::*;
Expand All @@ -136,3 +138,5 @@ pub use pk_ops::*;
pub use pubkey::*;
pub use version::*;
pub use x509::*;
#[cfg(feature="botan3")]
pub use srp6::*;
185 changes: 185 additions & 0 deletions botan/src/srp6.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
//! SRP-6a (RFC 5054 compatatible)
//!
//! This module contains the [`ServerSession`] type and the client side functions.
//!
//! # Examples
//!
//! ```
//! use botan::RandomNumberGenerator;
//! use botan::{ServerSession, generate_srp6_verifier, srp6_client_agree};
//!
//! let mut rng = RandomNumberGenerator::new_system().expect("Failed to create a random number generator");
//! let mut server = ServerSession::new().expect("Failed to create a SRP6 server session");
//! let salt = rng.read(24).expect("Failed to generate salt");
//! let verifier = generate_srp6_verifier("alice", "password123", &salt, "modp/srp/1024", "SHA-512").expect("Failed to generate SRP6 verifier");
//! let b_pub = server.step1(&verifier, "modp/srp/1024", "SHA-512", &rng).expect("Failed to calculate server B value");
//! 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");
//! let server_key = server.step2(&a_pub).expect("Failed to generate server key");
//! assert_eq!(client_key, server_key);
//! ```

use crate::{utils::*, RandomNumberGenerator};
use botan_sys::*;

/// An SRP-6 server session
#[derive(Debug)]
pub struct ServerSession {
obj: botan_srp6_server_session_t,
}

botan_impl_drop!(ServerSession, botan_srp6_server_session_destroy);

impl ServerSession {
/// Returns a new server session object.
///
/// # Errors
///
/// Returns [`ErrorType::OutOfMemory`] if memory is exhausted
pub fn new() -> Result<Self> {
Ok(Self {
obj: botan_init!(botan_srp6_server_session_init)?,
})
}

/// Server side step 1. Returns SRP-6 B value.
///
/// # Arguments
///
/// `verifier`: the verification value saved from client registration
/// `group_id`: the SRP group id
/// `hash_id`: the SRP hash in use
/// `rng`: a random number generator
///
/// # Errors
///
/// Returns [`ErrorType::BadParameter`] if SRP group/hash id is invalid.
pub fn step1(
&mut self,
verifier: &[u8],
group_id: &str,
hash_id: &str,
rng: &RandomNumberGenerator,
) -> Result<Vec<u8>> {
let group_id = make_cstr(group_id)?;
let hash_id = make_cstr(hash_id)?;
call_botan_ffi_returning_vec_u8(128, &|b_pub, b_pub_len| unsafe {
botan_srp6_server_session_step1(
self.obj,
verifier.as_ptr(),
verifier.len(),
group_id.as_ptr(),
hash_id.as_ptr(),
rng.handle(),
b_pub,
b_pub_len,
)
})
}

/// Server side step 2. Returns shared symmetric key.
///
/// # Arguments
///
/// `a_pub`: the client's value
///
/// # Errors
///
/// Returns [`ErrorType::BadParameter`] if the A value is invalid.
pub fn step2(&self, a_pub: &[u8]) -> Result<Vec<u8>> {
call_botan_ffi_returning_vec_u8(128, &|key, key_len| unsafe {
botan_srp6_server_session_step2(self.obj, a_pub.as_ptr(), a_pub.len(), key, key_len)
})
}
}

/// Returns a new SRP-6 verifier.
///
/// `identifier`: a username or other client identifier
/// `password`: the secret used to authenticate user
/// `salt`: a randomly chosen value, at least 128 bits long
/// `group_id`: the SRP group id
/// `hash_id`: the SRP hash in use
///
/// # Error
///
/// Returns [`ErrorType::BadParameter`] if SRP group/hash id is invalid.
/// Returns [`ErrorType::BadParameter`] if salt is too short.
pub fn generate_srp6_verifier(
identifier: &str,
password: &str,
salt: &[u8],
group_id: &str,
hash_id: &str,
) -> Result<Vec<u8>> {
if salt.len() * 8 < 128 {
return Err(Error::with_message(
ErrorType::BadParameter,
"Salt is too short".to_string(),
));
}

let identifier = make_cstr(identifier)?;
let password = make_cstr(password)?;
let group_id = make_cstr(group_id)?;
let hash_id = make_cstr(hash_id)?;

call_botan_ffi_returning_vec_u8(128, &|verifier, verifier_len| unsafe {
botan_generate_srp6_verifier(
identifier.as_ptr(),
password.as_ptr(),
salt.as_ptr(),
salt.len(),
group_id.as_ptr(),
hash_id.as_ptr(),
verifier,
verifier_len,
)
})
}

/// SRP6a Client side. Returns the client public key and the shared secret key.
///
/// `username`: the username we are attempting login for
/// `password`: the password we are attempting to use
/// `salt`: the salt value sent by the server
/// `group_id`: specifies the shared SRP group
/// `hash_id`: specifies a secure hash function
/// `b_pub`: is the server's public value
/// `rng`: rng is a random number generator
///
/// # Error
///
/// Returns [`ErrorType::BadParameter`] if SRP group/hash id is invalid.
/// Returns [`ErrorType::BadParameter`] if the B value is invalid.
pub fn srp6_client_agree(
username: &str,
password: &str,
group_id: &str,
hash_id: &str,
salt: &[u8],
b_pub: &[u8],
rng: &RandomNumberGenerator,
) -> Result<(Vec<u8>, Vec<u8>)> {
let username = make_cstr(username)?;
let password = make_cstr(password)?;
let group_id = make_cstr(group_id)?;
let hash_id = make_cstr(hash_id)?;

call_botan_ffi_returning_vec_u8_pair(128, 128, &|a, a_len, key, key_len| unsafe {
botan_srp6_client_agree(
username.as_ptr(),
password.as_ptr(),
group_id.as_ptr(),
hash_id.as_ptr(),
salt.as_ptr(),
salt.len(),
b_pub.as_ptr(),
b_pub.len(),
rng.handle(),
a,
a_len,
key,
key_len,
)
})
}
37 changes: 37 additions & 0 deletions botan/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,43 @@ pub(crate) fn call_botan_ffi_returning_vec_u8(
Ok(output)
}

#[cfg(feature = "botan3")]
pub(crate) fn call_botan_ffi_returning_vec_u8_pair(
mut initial_size1: usize,
mut initial_size2: usize,
cb: &dyn Fn(*mut u8, *mut usize, *mut u8, *mut usize) -> c_int,
) -> Result<(Vec<u8>, Vec<u8>)> {
let mut out1 = vec![0; initial_size1];
let mut out1_len = out1.len();
let mut out2 = vec![0; initial_size2];
let mut out2_len = out2.len();
let rc = cb(
out1.as_mut_ptr(),
&mut out1_len,
out2.as_mut_ptr(),
&mut out2_len,
);
match rc {
0 => {
assert!(out1_len <= out1.len());
assert!(out2_len <= out2.len());
out1.resize(out1_len, 0);
out2.resize(out2_len, 0);
Ok((out1, out2))
}
BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE => {
if out1_len > out1.len() {
initial_size1 = out1_len;
}
if out2_len > out2.len() {
initial_size2 = out2_len;
}
call_botan_ffi_returning_vec_u8_pair(initial_size1, initial_size2, cb)
}
_ => Err(Error::from_rc(rc)),
}
}

fn cstr_slice_to_str(raw_cstr: &[u8]) -> Result<String> {
let cstr = CStr::from_bytes_with_nul(raw_cstr).map_err(Error::conversion_error)?;
Ok(cstr.to_str().map_err(Error::conversion_error)?.to_owned())
Expand Down
59 changes: 59 additions & 0 deletions botan/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,3 +913,62 @@ fn test_totp() -> Result<(), botan::Error> {
assert!(!totp.check(90693936, 59 + 31, 1)?);
Ok(())
}

#[cfg(feature = "botan3")]
#[test]
fn test_srp6() -> Result<(), botan::Error> {
const IDENTITY: &str = "alice";
const PASSWORD: &str = "password123";
let mut rng = botan::RandomNumberGenerator::new_system()?;

// Test successful authentication
let mut server = botan::ServerSession::new()?;
let salt = rng.read(24)?;
let verifier =
botan::generate_srp6_verifier(IDENTITY, PASSWORD, &salt, "modp/srp/1024", "SHA-512")?;
let b_pub = server.step1(&verifier, "modp/srp/1024", "SHA-512", &rng)?;
let (a_pub, client_key) = botan::srp6_client_agree(
IDENTITY,
PASSWORD,
"modp/srp/1024",
"SHA-512",
&salt,
&b_pub,
&rng,
)?;
let server_key = server.step2(&a_pub)?;
assert_eq!(client_key, server_key);

// Test wrong server's B value
let salt = rng.read(24)?;
let 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";
let result = botan::srp6_client_agree(
IDENTITY,
PASSWORD,
"modp/srp/1024",
"SHA-512",
&salt,
b,
&rng,
);
assert!(result.is_err());

// Test wrong client's A value
let salt = rng.read(24)?;
let verifier =
botan::generate_srp6_verifier(IDENTITY, PASSWORD, &salt, "modp/srp/1024", "SHA-512")?;
let _ = server.step1(&verifier, "modp/srp/1024", "SHA-512", &rng)?;
let a_pub = 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";
let result = server.step2(a_pub);
assert!(result.is_err());

Ok(())
}

0 comments on commit b761b51

Please sign in to comment.