bitwarden_crypto/keys/
master_key.rs

1use std::pin::Pin;
2
3use bitwarden_encoding::B64;
4use generic_array::GenericArray;
5use rand::Rng;
6use typenum::U32;
7use zeroize::Zeroize;
8
9use super::{
10    kdf::{Kdf, KdfDerivedKeyMaterial},
11    utils::stretch_key,
12};
13use crate::{
14    util::{self},
15    BitwardenLegacyKeyBytes, CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey,
16    UserKey,
17};
18
19#[allow(missing_docs)]
20#[derive(Copy, Clone)]
21#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
22pub enum HashPurpose {
23    ServerAuthorization = 1,
24    LocalAuthorization = 2,
25}
26
27/// Master Key.
28///
29/// Derived from the users master password, used to protect the [UserKey].
30/// TODO: <https://bitwarden.atlassian.net/browse/PM-18366> split KeyConnectorKey into a separate file
31#[allow(missing_docs)]
32pub enum MasterKey {
33    KdfKey(KdfDerivedKeyMaterial),
34    KeyConnectorKey(Pin<Box<GenericArray<u8, U32>>>),
35}
36
37impl MasterKey {
38    pub(crate) fn new(key: KdfDerivedKeyMaterial) -> Self {
39        Self::KdfKey(key)
40    }
41
42    /// Generate a new random master key for KeyConnector.
43    pub fn generate(mut rng: impl rand::RngCore) -> Self {
44        let mut key = Box::pin(GenericArray::<u8, U32>::default());
45
46        rng.fill(key.as_mut_slice());
47        Self::KeyConnectorKey(key)
48    }
49
50    fn inner_bytes(&self) -> &Pin<Box<GenericArray<u8, U32>>> {
51        match self {
52            Self::KdfKey(key) => &key.0,
53            Self::KeyConnectorKey(key) => key,
54        }
55    }
56
57    /// Derives a users master key from their password, email and KDF.
58    ///
59    /// Note: the email is trimmed and converted to lowercase before being used.
60    pub fn derive(password: &str, email: &str, kdf: &Kdf) -> Result<Self, CryptoError> {
61        Ok(KdfDerivedKeyMaterial::derive(password, email, kdf)?.into())
62    }
63
64    /// Derive the master key hash, used for local and remote password validation.
65    pub fn derive_master_key_hash(&self, password: &[u8], purpose: HashPurpose) -> B64 {
66        let hash = util::pbkdf2(self.inner_bytes().as_slice(), password, purpose as u32);
67
68        hash.as_slice().into()
69    }
70
71    /// Generate a new random user key and encrypt it with the master key.
72    pub fn make_user_key(&self) -> Result<(UserKey, EncString)> {
73        make_user_key(rand::thread_rng(), self)
74    }
75
76    /// Encrypt the users user key
77    pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result<EncString> {
78        encrypt_user_key(self.inner_bytes(), user_key)
79    }
80
81    /// Decrypt the users user key
82    pub fn decrypt_user_key(&self, user_key: EncString) -> Result<SymmetricCryptoKey> {
83        decrypt_user_key(self.inner_bytes(), user_key)
84    }
85
86    #[allow(missing_docs)]
87    pub fn to_base64(&self) -> B64 {
88        B64::from(self.inner_bytes().as_slice())
89    }
90}
91
92impl TryFrom<&mut [u8]> for MasterKey {
93    type Error = CryptoError;
94
95    fn try_from(value: &mut [u8]) -> Result<Self> {
96        if value.len() != 32 {
97            value.zeroize();
98            return Err(CryptoError::InvalidKey);
99        }
100
101        let material =
102            KdfDerivedKeyMaterial(Box::pin(GenericArray::<u8, U32>::clone_from_slice(value)));
103        value.zeroize();
104        Ok(Self::new(material))
105    }
106}
107
108impl From<KdfDerivedKeyMaterial> for MasterKey {
109    fn from(key: KdfDerivedKeyMaterial) -> Self {
110        Self::new(key)
111    }
112}
113
114impl TryFrom<&SymmetricCryptoKey> for MasterKey {
115    type Error = CryptoError;
116
117    fn try_from(value: &SymmetricCryptoKey) -> Result<Self> {
118        match value {
119            SymmetricCryptoKey::Aes256CbcKey(key) => {
120                Ok(Self::KdfKey(KdfDerivedKeyMaterial(key.enc_key.clone())))
121            }
122            _ => Err(CryptoError::InvalidKey),
123        }
124    }
125}
126
127/// Helper function to encrypt a user key with a master or pin key.
128pub(super) fn encrypt_user_key(
129    master_key: &Pin<Box<GenericArray<u8, U32>>>,
130    user_key: &SymmetricCryptoKey,
131) -> Result<EncString> {
132    let stretched_master_key = stretch_key(master_key)?;
133    let user_key_bytes = user_key.to_encoded();
134    EncString::encrypt_aes256_hmac(user_key_bytes.as_ref(), &stretched_master_key)
135}
136
137/// Helper function to decrypt a user key with a master or pin key or key-connector-key.
138pub(super) fn decrypt_user_key(
139    key: &Pin<Box<GenericArray<u8, U32>>>,
140    user_key: EncString,
141) -> Result<SymmetricCryptoKey> {
142    let dec: Vec<u8> = match user_key {
143        // Legacy. user_keys were encrypted using `Aes256Cbc_B64` a long time ago. We've since
144        // moved to using `Aes256Cbc_HmacSha256_B64`. However, we still need to support
145        // decrypting these old keys.
146        EncString::Aes256Cbc_B64 { .. } => {
147            let legacy_key = SymmetricCryptoKey::Aes256CbcKey(super::Aes256CbcKey {
148                enc_key: Box::pin(GenericArray::clone_from_slice(key)),
149            });
150            user_key.decrypt_with_key(&legacy_key)?
151        }
152        EncString::Aes256Cbc_HmacSha256_B64 { .. } => {
153            let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key)?);
154            user_key.decrypt_with_key(&stretched_key)?
155        }
156        EncString::Cose_Encrypt0_B64 { .. } => {
157            return Err(CryptoError::OperationNotSupported(
158                crate::error::UnsupportedOperation::EncryptionNotImplementedForKey,
159            ));
160        }
161    };
162
163    SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(dec))
164}
165
166/// Generate a new random user key and encrypt it with the master key.
167///
168/// WARNING: This function should only be used with a proper cryptographic random number generator.
169/// If you do not have a good reason for using this, use [MasterKey::make_user_key] instead.
170///
171/// This function is only split out from [MasterKey::make_user_key], to make it unit testable.
172fn make_user_key(
173    rng: impl rand::RngCore + rand::CryptoRng,
174    master_key: &MasterKey,
175) -> Result<(UserKey, EncString)> {
176    let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key_internal(rng);
177    let protected = master_key.encrypt_user_key(&user_key)?;
178    Ok((UserKey::new(user_key), protected))
179}
180
181#[cfg(test)]
182mod tests {
183    use std::num::NonZeroU32;
184
185    use rand::SeedableRng;
186
187    use super::{make_user_key, HashPurpose, Kdf, MasterKey};
188    use crate::{
189        keys::{master_key::KdfDerivedKeyMaterial, symmetric_crypto_key::derive_symmetric_key},
190        EncString, SymmetricCryptoKey,
191    };
192
193    #[test]
194    fn test_password_hash_pbkdf2() {
195        let password = "asdfasdf";
196        let salts = [
197            "[email protected]",
198            "[email protected]",
199            " [email protected]",
200        ];
201        let kdf = Kdf::PBKDF2 {
202            iterations: NonZeroU32::new(100_000).unwrap(),
203        };
204
205        for salt in salts.iter() {
206            let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
207                .unwrap()
208                .into();
209
210            assert_eq!(
211                "wmyadRMyBZOH7P/a/ucTCbSghKgdzDpPqUnu/DAVtSw=",
212                String::from(
213                    master_key.derive_master_key_hash(
214                        password.as_bytes(),
215                        HashPurpose::ServerAuthorization
216                    )
217                ),
218            );
219        }
220    }
221
222    #[test]
223    fn test_password_hash_argon2id() {
224        let password = "asdfasdf";
225        let salt = "test_salt";
226        let kdf = Kdf::Argon2id {
227            iterations: NonZeroU32::new(4).unwrap(),
228            memory: NonZeroU32::new(32).unwrap(),
229            parallelism: NonZeroU32::new(2).unwrap(),
230        };
231
232        let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
233            .unwrap()
234            .into();
235
236        assert_eq!(
237            "PR6UjYmjmppTYcdyTiNbAhPJuQQOmynKbdEl1oyi/iQ=",
238            master_key
239                .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)
240                .to_string(),
241        );
242    }
243
244    #[test]
245    fn test_make_user_key() {
246        let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]);
247
248        let master_key: MasterKey = KdfDerivedKeyMaterial(Box::pin(
249            [
250                31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167,
251                69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75,
252            ]
253            .into(),
254        ))
255        .into();
256
257        let (user_key, protected) = make_user_key(&mut rng, &master_key).unwrap();
258        let SymmetricCryptoKey::Aes256CbcHmacKey(user_key_unwrapped) = &user_key.0 else {
259            panic!("User key is not an Aes256CbcHmacKey");
260        };
261
262        assert_eq!(
263            user_key_unwrapped.enc_key.as_slice(),
264            [
265                62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161, 44, 132, 14,
266                195, 206, 154, 127, 59, 24, 27, 225, 136, 239, 113, 26, 30
267            ]
268        );
269        assert_eq!(
270            user_key_unwrapped.mac_key.as_slice(),
271            [
272                152, 76, 225, 114, 185, 33, 111, 65, 159, 68, 83, 103, 69, 109, 86, 25, 49, 74, 66,
273                163, 218, 134, 176, 1, 56, 123, 253, 184, 14, 12, 254, 66
274            ]
275        );
276
277        // Ensure we can decrypt the key and get back the same key
278        let decrypted = master_key.decrypt_user_key(protected).unwrap();
279
280        assert_eq!(
281            decrypted, user_key.0,
282            "Decrypted key doesn't match user key"
283        );
284    }
285
286    #[test]
287    fn test_make_user_key2() {
288        let kdf_material = KdfDerivedKeyMaterial(derive_symmetric_key("test1").enc_key.clone());
289        let master_key = MasterKey::KdfKey(kdf_material);
290
291        let user_key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test2"));
292
293        let encrypted = master_key.encrypt_user_key(&user_key).unwrap();
294        let decrypted = master_key.decrypt_user_key(encrypted).unwrap();
295
296        assert_eq!(decrypted, user_key, "Decrypted key doesn't match user key");
297    }
298
299    #[test]
300    fn test_decrypt_user_key_aes_cbc256_b64() {
301        let password = "asdfasdfasdf";
302        let salt = "[email protected]";
303        let kdf = Kdf::PBKDF2 {
304            iterations: NonZeroU32::new(600_000).unwrap(),
305        };
306
307        let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
308            .unwrap()
309            .into();
310
311        let user_key: EncString = "0.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=".parse().unwrap();
312
313        let decrypted = master_key.decrypt_user_key(user_key).unwrap();
314        let SymmetricCryptoKey::Aes256CbcHmacKey(decrypted) = &decrypted else {
315            panic!("Decrypted key is not an Aes256CbcHmacKey");
316        };
317
318        assert_eq!(
319            decrypted.enc_key.as_slice(),
320            [
321                12, 95, 151, 203, 37, 4, 236, 67, 137, 97, 90, 58, 6, 127, 242, 28, 209, 168, 125,
322                29, 118, 24, 213, 44, 117, 202, 2, 115, 132, 165, 125, 148
323            ]
324        );
325        assert_eq!(
326            decrypted.mac_key.as_slice(),
327            [
328                186, 215, 234, 137, 24, 169, 227, 29, 218, 57, 180, 237, 73, 91, 189, 51, 253, 26,
329                17, 52, 226, 4, 134, 75, 194, 208, 178, 133, 128, 224, 140, 167
330            ]
331        );
332    }
333}