embedded_mbedtls/
rng.rs

1// Copyright Open Logistics Foundation
2//
3// Licensed under the Open Logistics Foundation License 1.3.
4// For details on the licensing terms, see the LICENSE file.
5// SPDX-License-Identifier: OLFL-1.3
6
7//! Random number generation module which defines the [`CtrDrbg`] RNG type
8
9// Additionally, it defines the [`rng_try_fill_bytes_callback_fn`] used as callback function to
10// `mbedtls_ssl_conf_rng`.
11
12#[cfg(feature = "alloc")]
13use alloc::boxed::Box;
14
15use core::{marker::PhantomData, ops::DerefMut};
16
17use core::ffi;
18use embedded_mbedtls_sys::{
19    mbedtls_ctr_drbg_context, mbedtls_ctr_drbg_init, mbedtls_ctr_drbg_seed,
20    MBEDTLS_ERR_CTR_DRBG_REQUEST_TOO_BIG, MBEDTLS_ERR_ENTROPY_SOURCE_FAILED,
21};
22use rand_core::{CryptoRng, RngCore};
23
24use crate::error::Error;
25
26/// This function is used as callback function to `mbedtls_ssl_conf_rng`. It can be used for
27/// generic RNGs as well as for the [`CtrDrbg`] wrapper defined here.
28pub(crate) unsafe extern "C" fn rng_try_fill_bytes_callback_fn<RNG: RngCore>(
29    entropy_context: *mut ffi::c_void,
30    buf: *mut ffi::c_uchar,
31    buf_len: usize,
32) -> ffi::c_int {
33    let rng: &mut RNG = &mut *(entropy_context as *mut RNG);
34    let bytes = core::slice::from_raw_parts_mut(buf, buf_len);
35    if rng.try_fill_bytes(bytes).is_err() {
36        return embedded_mbedtls_sys::MBEDTLS_ERR_SSL_NO_RNG;
37    }
38    0
39}
40
41/// CTR_DRBG pseudorandom generator
42///
43/// Cryptographically secure pseudorandom number generator (CSPRNG).
44/// The Mbed TLS implementation of CTR_DRBG which uses AES-256 (default) or AES-128.
45///
46/// Use this if:
47/// - your RNG source isn't suitable for generating sufficient amounts of cryptographically secure
48/// random numbers (e.g. your RNG isn't fast enough)
49/// - if you do not have a true hardware RNG and want to use it as an RNG initialized with a
50/// user-supplied (fixed) seed (use the `personalization_string` then); it should be obvious that
51/// a non-random seed does not yield true random numbers and that reusing the same seed undermines
52/// security
53/// - if you want to augment your RNG with another source of entropy (again, use the
54/// `personalization_string`)
55///
56/// This type is a wrapper around the `mbedtls_ctr_drbg_context`
57pub struct CtrDrbg<'a, RNG: RngCore + CryptoRng, D: DerefMut<Target = RNG>> {
58    context: mbedtls_ctr_drbg_context,
59    entropy_source: D,
60    _custom: PhantomData<&'a [u8]>,
61}
62
63impl<'a, RNG: RngCore + CryptoRng> CtrDrbg<'a, RNG, &'a mut RNG> {
64    /// Initialize and seed a CtrDrbg context
65    ///
66    /// Might fail when no entropy can be collected.
67    ///
68    /// When the `alloc` feature is activated, the `new_with_heap_rng` constructor can be used to
69    /// pass an owned entropy source which will be moved to the heap.
70    /// This enables you to freely move the `CtrDrbg` together with the context.
71    ///
72    /// # Example
73    /// ```
74    /// use embedded_mbedtls::rng::CtrDrbg;
75    /// use rand_core::RngCore;
76    ///
77    /// # let rng = rand::thread_rng();
78    /// let mut entropy_src = rng;
79    ///
80    /// let mut ctr_drbg = CtrDrbg::new(&mut entropy_src, None).unwrap();
81    ///
82    /// let _random = ctr_drbg.next_u32();
83    /// ```
84    pub fn new(
85        entropy_source: &'a mut RNG,
86        personalization_string: Option<&'a [u8]>,
87    ) -> Result<Self, Error> {
88        Self::new_generic(entropy_source, personalization_string)
89    }
90}
91
92#[cfg(feature = "alloc")]
93impl<'a, RNG: RngCore + CryptoRng> CtrDrbg<'a, RNG, Box<RNG>> {
94    /// Initialize and seed a CtrDrbg context, moving the entropy source into a `Box`
95    ///
96    /// This allows to move the [`CtrDrbg`] instance freely. Especially, it allows to return from
97    /// an initializer function in which the entropy source was set up.
98    ///
99    /// Might fail when no entropy can be collected.
100    ///
101    /// Example:
102    /// ```
103    /// use embedded_mbedtls::rng::CtrDrbg;
104    /// use rand_core::RngCore;
105    ///
106    /// # let rng = rand::thread_rng();
107    /// let mut ctr_drbg = CtrDrbg::new_with_heap_rng(rng, None).unwrap();
108    /// // The ctr_drbg can now be moved around freely
109    ///
110    /// let _random = ctr_drbg.next_u32();
111    /// ```
112    pub fn new_with_heap_rng(
113        entropy_source: RNG,
114        personalization_string: Option<&'a [u8]>,
115    ) -> Result<Self, Error> {
116        Self::new_generic(Box::new(entropy_source), personalization_string)
117    }
118}
119
120impl<'a, RNG: RngCore + CryptoRng, D: DerefMut<Target = RNG>> CtrDrbg<'a, RNG, D> {
121    /// Initialize and seed a CtrDrbg context
122    ///
123    /// _Note_: Internal function which should __not__ be made public.
124    /// With this function the `entropy_source` is only constrained by `DerefMut<Target = RNG>`,
125    /// which makes it possible to pass a type which implements DerefMut itself and won't guarantee that the `entropy_source` is never moved.
126    /// This is crucial since the C context of the CTR_DRBG holds a raw pointer to it.
127    fn new_generic(
128        entropy_source: D,
129        personalization_string: Option<&'a [u8]>,
130    ) -> Result<Self, Error> {
131        let context = mbedtls_ctr_drbg_context::default();
132        let mut this = Self {
133            context,
134            entropy_source,
135            _custom: PhantomData,
136        };
137        unsafe { mbedtls_ctr_drbg_init(&mut this.context) };
138        if let Some(custom) = personalization_string {
139            let ret = unsafe {
140                mbedtls_ctr_drbg_seed(
141                    &mut this.context,
142                    Some(rng_try_fill_bytes_callback_fn::<RNG>),
143                    this.entropy_source.deref_mut() as *mut RNG as *mut ffi::c_void,
144                    custom.as_ptr(),
145                    custom.len(),
146                )
147            };
148            if ret != 0 {
149                return Err(ret.into());
150            }
151        } else {
152            let ret = unsafe {
153                mbedtls_ctr_drbg_seed(
154                    &mut this.context,
155                    Some(rng_try_fill_bytes_callback_fn::<RNG>),
156                    this.entropy_source.deref_mut() as *mut RNG as *mut ffi::c_void,
157                    core::ptr::null(),
158                    0,
159                )
160            };
161            if ret != 0 {
162                return Err(ret.into());
163            }
164        }
165
166        Ok(this)
167    }
168}
169
170impl<R: RngCore + CryptoRng, D: DerefMut<Target = R>> Drop for CtrDrbg<'_, R, D> {
171    fn drop(&mut self) {
172        unsafe {
173            embedded_mbedtls_sys::mbedtls_ctr_drbg_free(&mut self.context);
174        }
175    }
176}
177
178impl<RNG: RngCore + CryptoRng, D: DerefMut<Target = RNG>> CryptoRng for CtrDrbg<'_, RNG, D> {}
179
180impl<RNG: RngCore + CryptoRng, D: DerefMut<Target = RNG>> RngCore for CtrDrbg<'_, RNG, D> {
181    fn next_u32(&mut self) -> u32 {
182        rand_core::impls::next_u32_via_fill(self)
183    }
184
185    fn next_u64(&mut self) -> u64 {
186        rand_core::impls::next_u64_via_fill(self)
187    }
188
189    fn fill_bytes(&mut self, dest: &mut [u8]) {
190        if dest.len() > embedded_mbedtls_sys::MBEDTLS_CTR_DRBG_MAX_REQUEST as usize {
191            log::error!("Failed to generate random data: Request too big!");
192            panic!("Failed to generate random data: Request too big!");
193        }
194        if self.try_fill_bytes(dest).is_err() {
195            panic!("Failed to generate random data: Entropy source failed!");
196        }
197    }
198
199    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
200        let ret = unsafe {
201            embedded_mbedtls_sys::mbedtls_ctr_drbg_random(
202                &mut self.context as *mut mbedtls_ctr_drbg_context as *mut ffi::c_void,
203                dest.as_mut_ptr(),
204                dest.len(),
205            )
206        };
207
208        if ret == MBEDTLS_ERR_CTR_DRBG_REQUEST_TOO_BIG {
209            log::error!("Failed to generate random data: Request to too big!");
210            use core::num::NonZeroU32;
211            use rand_core::Error;
212            return Err(Error::from(unsafe {
213                NonZeroU32::new_unchecked(Error::CUSTOM_START)
214            }));
215        }
216        if ret == MBEDTLS_ERR_ENTROPY_SOURCE_FAILED {
217            log::error!("Failed to generate random data: Entropy source failed!");
218            use core::num::NonZeroU32;
219            use rand_core::Error;
220            return Err(Error::from(unsafe {
221                NonZeroU32::new_unchecked(Error::CUSTOM_START + 1)
222            }));
223        }
224        if ret < 0 {
225            log::error!("Failed to generate random data: mbedtls error {ret}");
226            use core::num::NonZeroU32;
227            use rand_core::Error;
228            return Err(Error::from(unsafe {
229                NonZeroU32::new_unchecked(Error::CUSTOM_START + 2)
230            }));
231        }
232        Ok(())
233    }
234}
235
236#[cfg(test)]
237mod test {
238    use rand_core::RngCore;
239
240    use super::CtrDrbg;
241
242    #[test]
243    fn stack_entropy_drbg() {
244        let mut entropy_source = rand::thread_rng();
245        let mut ctr_drbg = CtrDrbg::new(&mut entropy_source, None).unwrap();
246
247        let _random = ctr_drbg.next_u32();
248    }
249
250    #[cfg(feature = "alloc")]
251    #[test]
252    fn boxed_entropy_drbg() {
253        let mut ctr_drbg = CtrDrbg::new_with_heap_rng(Box::new(rand::thread_rng()), None).unwrap();
254
255        let _random = ctr_drbg.next_u32();
256    }
257}