bitwarden_crypto/keys/
utils.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use std::pin::Pin;

use generic_array::{typenum::U32, GenericArray};
use sha2::Digest;

use crate::{util::hkdf_expand, CryptoError, Kdf, Result, SymmetricCryptoKey};

const PBKDF2_MIN_ITERATIONS: u32 = 5000;

const ARGON2ID_MIN_MEMORY: u32 = 16 * 1024;
const ARGON2ID_MIN_ITERATIONS: u32 = 2;
const ARGON2ID_MIN_PARALLELISM: u32 = 1;

/// Derive a generic key from a secret and salt using the provided KDF.
pub(super) fn derive_kdf_key(secret: &[u8], salt: &[u8], kdf: &Kdf) -> Result<SymmetricCryptoKey> {
    let mut hash = match kdf {
        Kdf::PBKDF2 { iterations } => {
            let iterations = iterations.get();
            if iterations < PBKDF2_MIN_ITERATIONS {
                return Err(CryptoError::InsufficientKdfParameters);
            }

            crate::util::pbkdf2(secret, salt, iterations)
        }
        Kdf::Argon2id {
            iterations,
            memory,
            parallelism,
        } => {
            let memory = memory.get() * 1024; // Convert MiB to KiB;
            let iterations = iterations.get();
            let parallelism = parallelism.get();

            if memory < ARGON2ID_MIN_MEMORY
                || iterations < ARGON2ID_MIN_ITERATIONS
                || parallelism < ARGON2ID_MIN_PARALLELISM
            {
                return Err(CryptoError::InsufficientKdfParameters);
            }

            use argon2::*;

            let params = Params::new(memory, iterations, parallelism, Some(32))?;
            let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);

            let salt_sha = sha2::Sha256::new().chain_update(salt).finalize();

            let mut hash = [0u8; 32];
            argon.hash_password_into(secret, &salt_sha, &mut hash)?;

            // Argon2 is using some stack memory that is not zeroed. Eventually some function will
            // overwrite the stack, but we use this trick to force the used stack to be zeroed.
            #[inline(never)]
            fn clear_stack() {
                std::hint::black_box([0u8; 4096]);
            }
            clear_stack();

            hash
        }
    };
    SymmetricCryptoKey::try_from(hash.as_mut_slice())
}

/// Stretch the given key using HKDF.
pub(super) fn stretch_kdf_key(k: &SymmetricCryptoKey) -> Result<SymmetricCryptoKey> {
    let key: Pin<Box<GenericArray<u8, U32>>> = hkdf_expand(&k.key, Some("enc"))?;
    let mac_key: Pin<Box<GenericArray<u8, U32>>> = hkdf_expand(&k.key, Some("mac"))?;

    Ok(SymmetricCryptoKey::new(key, Some(mac_key)))
}

#[cfg(test)]
mod tests {
    use std::num::NonZero;

    use super::*;

    #[test]
    fn test_stretch_kdf_key() {
        let key = SymmetricCryptoKey::new(
            Box::pin(
                [
                    31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138,
                    167, 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75,
                ]
                .into(),
            ),
            None,
        );

        let stretched = stretch_kdf_key(&key).unwrap();

        assert_eq!(
            [
                111, 31, 178, 45, 238, 152, 37, 114, 143, 215, 124, 83, 135, 173, 195, 23, 142,
                134, 120, 249, 61, 132, 163, 182, 113, 197, 189, 204, 188, 21, 237, 96
            ],
            stretched.key.as_slice()
        );
        assert_eq!(
            [
                221, 127, 206, 234, 101, 27, 202, 38, 86, 52, 34, 28, 78, 28, 185, 16, 48, 61, 127,
                166, 209, 247, 194, 87, 232, 26, 48, 85, 193, 249, 179, 155
            ],
            stretched.mac_key.as_ref().unwrap().as_slice()
        );
    }

    #[test]
    fn test_derive_kdf_minimums() {
        fn nz(n: u32) -> NonZero<u32> {
            NonZero::new(n).unwrap()
        }

        let secret = [0u8; 32];
        let salt = [0u8; 32];

        for kdf in [
            Kdf::PBKDF2 {
                iterations: nz(4999),
            },
            Kdf::Argon2id {
                iterations: nz(1),
                memory: nz(16),
                parallelism: nz(1),
            },
            Kdf::Argon2id {
                iterations: nz(2),
                memory: nz(15),
                parallelism: nz(1),
            },
            Kdf::Argon2id {
                iterations: nz(1),
                memory: nz(15),
                parallelism: nz(1),
            },
        ] {
            assert_eq!(
                derive_kdf_key(&secret, &salt, &kdf)
                    .unwrap_err()
                    .to_string(),
                "Insufficient KDF parameters"
            );
        }
    }
}