Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
63 changes: 63 additions & 0 deletions botan/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,3 +913,66 @@ 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 = hex::decode(
"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 = hex::decode(
"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(())
}