Skip to main content

bitwarden_crypto/
util.rs

1use std::pin::Pin;
2
3use hmac::digest::OutputSizeUser;
4use hybrid_array::{Array, ArraySize};
5use rand::{
6    RngExt,
7    distr::{Alphanumeric, Distribution, SampleString, StandardUniform},
8};
9use typenum::Unsigned;
10use zeroize::{Zeroize, Zeroizing};
11
12use crate::Result;
13
14pub(crate) type PbkdfSha256Hmac = hmac::Hmac<sha2::Sha256>;
15
16/// Size of the stack region [`clear_stack`] overwrites.
17const CLEARED_STACK_SIZE: usize = 16 * 1024;
18
19/// Overwrites the stack region below the caller with zeros.
20///
21/// KDF and hash implementations (Argon2, PBKDF2, HMAC, SHA-256) leave copies of their state and
22/// output in stack frames that are popped but not wiped. Call this right after such an operation
23/// returns, so the next stack user does not have to happen to overwrite them.
24#[inline(never)]
25pub(crate) fn clear_stack() {
26    std::hint::black_box([0u8; CLEARED_STACK_SIZE]);
27}
28pub(crate) const PBKDF_SHA256_HMAC_OUT_SIZE: usize =
29    <<PbkdfSha256Hmac as OutputSizeUser>::OutputSize as Unsigned>::USIZE;
30
31#[derive(Debug)]
32pub(crate) enum HkdfExpandError {
33    InvalidInputLegth,
34    InvalidOutputLength,
35}
36
37/// [RFC5869](https://datatracker.ietf.org/doc/html/rfc5869) HKDF-Expand operation
38pub(crate) fn hkdf_expand<T: ArraySize>(
39    prk: &[u8],
40    info: Option<&str>,
41) -> Result<Pin<Box<Array<u8, T>>>, HkdfExpandError> {
42    let hkdf = hkdf::Hkdf::<sha2::Sha256>::from_prk(prk)
43        .map_err(|_| HkdfExpandError::InvalidInputLegth)?;
44    let mut key = Box::<Array<u8, T>>::default();
45
46    let i = info.map(|i| i.as_bytes()).unwrap_or(&[]);
47    hkdf.expand(i, key.as_mut_slice())
48        .map_err(|_| HkdfExpandError::InvalidOutputLength)?;
49
50    Ok(Box::into_pin(key))
51}
52
53/// Generate random bytes that are cryptographically secure
54pub fn generate_random_bytes<T>() -> Zeroizing<T>
55where
56    StandardUniform: Distribution<T>,
57    T: Zeroize,
58{
59    Zeroizing::new(bitwarden_random::rng().random::<T>())
60}
61
62/// Generate a random alphanumeric string of a given length
63///
64/// Note: Do not use this generating user facing passwords. Use the `bitwarden-generator` crate for
65/// that.
66pub fn generate_random_alphanumeric(len: usize) -> String {
67    Alphanumeric.sample_string(&mut bitwarden_random::rng(), len)
68}
69
70/// Derive pbkdf2 of a given password and salt
71pub fn pbkdf2(password: &[u8], salt: &[u8], rounds: u32) -> [u8; PBKDF_SHA256_HMAC_OUT_SIZE] {
72    pbkdf2::pbkdf2_array::<PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE>(password, salt, rounds)
73        .expect("hash is a valid fixed size")
74}
75
76#[cfg(test)]
77mod tests {
78    use typenum::U64;
79
80    use super::*;
81
82    #[test]
83    fn test_hkdf_expand() {
84        let prk = &[
85            23, 152, 120, 41, 214, 16, 156, 133, 71, 226, 178, 135, 208, 255, 66, 101, 189, 70,
86            173, 30, 39, 215, 175, 236, 38, 180, 180, 62, 196, 4, 159, 70,
87        ];
88        let info = Some("info");
89
90        let result: Pin<Box<Array<u8, U64>>> = hkdf_expand(prk, info).unwrap();
91
92        let expected_output: [u8; 64] = [
93            6, 114, 42, 38, 87, 231, 30, 109, 30, 255, 104, 129, 255, 94, 92, 108, 124, 145, 215,
94            208, 17, 60, 135, 22, 70, 158, 40, 53, 45, 182, 8, 63, 65, 87, 239, 234, 185, 227, 153,
95            122, 115, 205, 144, 56, 102, 149, 92, 139, 217, 102, 119, 57, 37, 57, 251, 178, 18, 52,
96            94, 77, 132, 215, 239, 100,
97        ];
98
99        assert_eq!(result.as_slice(), expected_output);
100    }
101
102    #[test]
103    fn test_hkdf_expand_invalid_input_length() {
104        // PRK must be at least HashLen (32 bytes for SHA-256), using a too-short key
105        let prk = &[1, 2, 3, 4, 5];
106        let info = Some("info");
107
108        let result: Result<Pin<Box<Array<u8, U64>>>, HkdfExpandError> = hkdf_expand(prk, info);
109
110        assert!(matches!(result, Err(HkdfExpandError::InvalidInputLegth)));
111    }
112
113    #[test]
114    fn test_hkdf_expand_invalid_output_length() {
115        let prk = &[
116            23, 152, 120, 41, 214, 16, 156, 133, 71, 226, 178, 135, 208, 255, 66, 101, 189, 70,
117            173, 30, 39, 215, 175, 236, 38, 180, 180, 62, 196, 4, 159, 70,
118        ];
119        let info = Some("info");
120
121        // HKDF-SHA256 can produce at most 255 * 32 = 8160 bytes, requesting more should fail
122        type TooLarge = typenum::U8192;
123        let result: Result<Pin<Box<Array<u8, TooLarge>>>, HkdfExpandError> = hkdf_expand(prk, info);
124
125        assert!(matches!(result, Err(HkdfExpandError::InvalidOutputLength)));
126    }
127}