#[cfg(feature = "alloc")]
use alloc::boxed::Box;
use core::{marker::PhantomData, ops::DerefMut};
use core::ffi;
use embedded_mbedtls_sys::{
mbedtls_ctr_drbg_context, mbedtls_ctr_drbg_init, mbedtls_ctr_drbg_seed,
MBEDTLS_ERR_CTR_DRBG_REQUEST_TOO_BIG, MBEDTLS_ERR_ENTROPY_SOURCE_FAILED,
};
use rand_core::{CryptoRng, RngCore};
use crate::error::Error;
pub(crate) unsafe extern "C" fn rng_try_fill_bytes_callback_fn<RNG: RngCore>(
entropy_context: *mut ffi::c_void,
buf: *mut ffi::c_uchar,
buf_len: usize,
) -> ffi::c_int {
let rng: &mut RNG = &mut *(entropy_context as *mut RNG);
let bytes = core::slice::from_raw_parts_mut(buf, buf_len);
if rng.try_fill_bytes(bytes).is_err() {
return embedded_mbedtls_sys::MBEDTLS_ERR_SSL_NO_RNG;
}
0
}
pub struct CtrDrbg<'a, RNG: RngCore + CryptoRng, D: DerefMut<Target = RNG>> {
context: mbedtls_ctr_drbg_context,
entropy_source: D,
_custom: PhantomData<&'a [u8]>,
}
impl<'a, RNG: RngCore + CryptoRng> CtrDrbg<'a, RNG, &'a mut RNG> {
pub fn new(
entropy_source: &'a mut RNG,
personalization_string: Option<&'a [u8]>,
) -> Result<Self, Error> {
Self::new_generic(entropy_source, personalization_string)
}
}
#[cfg(feature = "alloc")]
impl<'a, RNG: RngCore + CryptoRng> CtrDrbg<'a, RNG, Box<RNG>> {
pub fn new_with_heap_rng(
entropy_source: RNG,
personalization_string: Option<&'a [u8]>,
) -> Result<Self, Error> {
Self::new_generic(Box::new(entropy_source), personalization_string)
}
}
impl<'a, RNG: RngCore + CryptoRng, D: DerefMut<Target = RNG>> CtrDrbg<'a, RNG, D> {
fn new_generic(
entropy_source: D,
personalization_string: Option<&'a [u8]>,
) -> Result<Self, Error> {
let context = mbedtls_ctr_drbg_context::default();
let mut this = Self {
context,
entropy_source,
_custom: PhantomData,
};
unsafe { mbedtls_ctr_drbg_init(&mut this.context) };
if let Some(custom) = personalization_string {
let ret = unsafe {
mbedtls_ctr_drbg_seed(
&mut this.context,
Some(rng_try_fill_bytes_callback_fn::<RNG>),
this.entropy_source.deref_mut() as *mut RNG as *mut ffi::c_void,
custom.as_ptr(),
custom.len(),
)
};
if ret != 0 {
return Err(ret.into());
}
} else {
let ret = unsafe {
mbedtls_ctr_drbg_seed(
&mut this.context,
Some(rng_try_fill_bytes_callback_fn::<RNG>),
this.entropy_source.deref_mut() as *mut RNG as *mut ffi::c_void,
core::ptr::null(),
0,
)
};
if ret != 0 {
return Err(ret.into());
}
}
Ok(this)
}
}
impl<R: RngCore + CryptoRng, D: DerefMut<Target = R>> Drop for CtrDrbg<'_, R, D> {
fn drop(&mut self) {
unsafe {
embedded_mbedtls_sys::mbedtls_ctr_drbg_free(&mut self.context);
}
}
}
impl<RNG: RngCore + CryptoRng, D: DerefMut<Target = RNG>> CryptoRng for CtrDrbg<'_, RNG, D> {}
impl<RNG: RngCore + CryptoRng, D: DerefMut<Target = RNG>> RngCore for CtrDrbg<'_, RNG, D> {
fn next_u32(&mut self) -> u32 {
rand_core::impls::next_u32_via_fill(self)
}
fn next_u64(&mut self) -> u64 {
rand_core::impls::next_u64_via_fill(self)
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
if dest.len() > embedded_mbedtls_sys::MBEDTLS_CTR_DRBG_MAX_REQUEST as usize {
log::error!("Failed to generate random data: Request too big!");
panic!("Failed to generate random data: Request too big!");
}
if self.try_fill_bytes(dest).is_err() {
panic!("Failed to generate random data: Entropy source failed!");
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
let ret = unsafe {
embedded_mbedtls_sys::mbedtls_ctr_drbg_random(
&mut self.context as *mut mbedtls_ctr_drbg_context as *mut ffi::c_void,
dest.as_mut_ptr(),
dest.len(),
)
};
if ret == MBEDTLS_ERR_CTR_DRBG_REQUEST_TOO_BIG {
log::error!("Failed to generate random data: Request to too big!");
use core::num::NonZeroU32;
use rand_core::Error;
return Err(Error::from(unsafe {
NonZeroU32::new_unchecked(Error::CUSTOM_START)
}));
}
if ret == MBEDTLS_ERR_ENTROPY_SOURCE_FAILED {
log::error!("Failed to generate random data: Entropy source failed!");
use core::num::NonZeroU32;
use rand_core::Error;
return Err(Error::from(unsafe {
NonZeroU32::new_unchecked(Error::CUSTOM_START + 1)
}));
}
if ret < 0 {
log::error!("Failed to generate random data: mbedtls error {ret}");
use core::num::NonZeroU32;
use rand_core::Error;
return Err(Error::from(unsafe {
NonZeroU32::new_unchecked(Error::CUSTOM_START + 2)
}));
}
Ok(())
}
}
#[cfg(test)]
mod test {
use rand_core::RngCore;
use super::CtrDrbg;
#[test]
fn stack_entropy_drbg() {
let mut entropy_source = rand::thread_rng();
let mut ctr_drbg = CtrDrbg::new(&mut entropy_source, None).unwrap();
let _random = ctr_drbg.next_u32();
}
#[cfg(feature = "alloc")]
#[test]
fn boxed_entropy_drbg() {
let mut ctr_drbg = CtrDrbg::new_with_heap_rng(Box::new(rand::thread_rng()), None).unwrap();
let _random = ctr_drbg.next_u32();
}
}