Skip to main content

coven_crypto/
randomness.rs

1//! The only source of random key material, salts and nonces in this crate.
2
3use crate::CryptoError;
4
5#[cfg(any(test, feature = "test-utils"))]
6thread_local! {
7    // Clippy asks for a `const` initializer this already has where the
8    // target has no native thread locals, as Android: std builds those
9    // from a function, which the lint reads as the initializer.
10    #[cfg_attr(target_os = "android", allow(clippy::missing_const_for_thread_local))]
11    static REFUSALS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
12}
13
14/// Refuse the next `count` requests for randomness on this thread, as a
15/// system whose random source fails would. Randomness comes from the system
16/// with no handle to arm, and a thread's count leaves other tests alone.
17#[cfg(any(test, feature = "test-utils"))]
18pub fn refuse_randomness(count: usize) {
19    REFUSALS.set(count);
20}
21
22pub(crate) fn fill(bytes: &mut [u8]) -> Result<(), CryptoError> {
23    #[cfg(any(test, feature = "test-utils"))]
24    if let Some(left) = REFUSALS.get().checked_sub(1) {
25        REFUSALS.set(left);
26        return fill_with(bytes, |_| Err(getrandom::Error::UNSUPPORTED));
27    }
28    fill_with(bytes, getrandom::fill)
29}
30
31/// 32 fresh random bytes, erased on drop: the material of every key,
32/// secret and seed coven makes.
33pub(crate) fn secret_bytes() -> Result<zeroize::Zeroizing<[u8; 32]>, CryptoError> {
34    let mut bytes = zeroize::Zeroizing::new([0; 32]);
35    fill(bytes.as_mut())?;
36    Ok(bytes)
37}
38
39/// A fresh random secret of 32 bytes as lowercase hex, for a credential coven
40/// sets but nobody keeps, such as a service's parent user (Appendix E.12).
41pub fn random_secret() -> Result<crate::SecretText, CryptoError> {
42    Ok(crate::SecretText::new(hex::encode(secret_bytes()?.as_ref())))
43}
44
45fn fill_with(
46    bytes: &mut [u8],
47    source: impl FnOnce(&mut [u8]) -> Result<(), getrandom::Error>,
48) -> Result<(), CryptoError> {
49    source(bytes).map_err(|error| CryptoError::Unavailable(Box::new(error)))
50}
51
52#[cfg(test)]
53#[path = "randomness_tests.rs"]
54mod tests;