bitwarden_crypto/keys/
master_key.rs

1use std::pin::Pin;
2
3use base64::{engine::general_purpose::STANDARD, Engine};
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) -> Result<String> {
66        let hash = util::pbkdf2(self.inner_bytes().as_slice(), password, purpose as u32);
67
68        Ok(STANDARD.encode(hash))
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) -> String {
88        STANDARD.encode(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                master_key
213                    .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)
214                    .unwrap(),
215            );
216        }
217    }
218
219    #[test]
220    fn test_password_hash_argon2id() {
221        let password = "asdfasdf";
222        let salt = "test_salt";
223        let kdf = Kdf::Argon2id {
224            iterations: NonZeroU32::new(4).unwrap(),
225            memory: NonZeroU32::new(32).unwrap(),
226            parallelism: NonZeroU32::new(2).unwrap(),
227        };
228
229        let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
230            .unwrap()
231            .into();
232
233        assert_eq!(
234            "PR6UjYmjmppTYcdyTiNbAhPJuQQOmynKbdEl1oyi/iQ=",
235            master_key
236                .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)
237                .unwrap(),
238        );
239    }
240
241    #[test]
242    fn test_make_user_key() {
243        let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]);
244
245        let master_key: MasterKey = KdfDerivedKeyMaterial(Box::pin(
246            [
247                31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167,
248                69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75,
249            ]
250            .into(),
251        ))
252        .into();
253
254        let (user_key, protected) = make_user_key(&mut rng, &master_key).unwrap();
255        let SymmetricCryptoKey::Aes256CbcHmacKey(user_key_unwrapped) = &user_key.0 else {
256            panic!("User key is not an Aes256CbcHmacKey");
257        };
258
259        assert_eq!(
260            user_key_unwrapped.enc_key.as_slice(),
261            [
262                62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161, 44, 132, 14,
263                195, 206, 154, 127, 59, 24, 27, 225, 136, 239, 113, 26, 30
264            ]
265        );
266        assert_eq!(
267            user_key_unwrapped.mac_key.as_slice(),
268            [
269                152, 76, 225, 114, 185, 33, 111, 65, 159, 68, 83, 103, 69, 109, 86, 25, 49, 74, 66,
270                163, 218, 134, 176, 1, 56, 123, 253, 184, 14, 12, 254, 66
271            ]
272        );
273
274        // Ensure we can decrypt the key and get back the same key
275        let decrypted = master_key.decrypt_user_key(protected).unwrap();
276
277        assert_eq!(
278            decrypted, user_key.0,
279            "Decrypted key doesn't match user key"
280        );
281    }
282
283    #[test]
284    fn test_make_user_key2() {
285        let kdf_material = KdfDerivedKeyMaterial(derive_symmetric_key("test1").enc_key.clone());
286        let master_key = MasterKey::KdfKey(kdf_material);
287
288        let user_key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test2"));
289
290        let encrypted = master_key.encrypt_user_key(&user_key).unwrap();
291        let decrypted = master_key.decrypt_user_key(encrypted).unwrap();
292
293        assert_eq!(decrypted, user_key, "Decrypted key doesn't match user key");
294    }
295
296    #[test]
297    fn test_decrypt_user_key_aes_cbc256_b64() {
298        let password = "asdfasdfasdf";
299        let salt = "[email protected]";
300        let kdf = Kdf::PBKDF2 {
301            iterations: NonZeroU32::new(600_000).unwrap(),
302        };
303
304        let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
305            .unwrap()
306            .into();
307
308        let user_key: EncString = "0.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=".parse().unwrap();
309
310        let decrypted = master_key.decrypt_user_key(user_key).unwrap();
311        let SymmetricCryptoKey::Aes256CbcHmacKey(decrypted) = &decrypted else {
312            panic!("Decrypted key is not an Aes256CbcHmacKey");
313        };
314
315        assert_eq!(
316            decrypted.enc_key.as_slice(),
317            [
318                12, 95, 151, 203, 37, 4, 236, 67, 137, 97, 90, 58, 6, 127, 242, 28, 209, 168, 125,
319                29, 118, 24, 213, 44, 117, 202, 2, 115, 132, 165, 125, 148
320            ]
321        );
322        assert_eq!(
323            decrypted.mac_key.as_slice(),
324            [
325                186, 215, 234, 137, 24, 169, 227, 29, 218, 57, 180, 237, 73, 91, 189, 51, 253, 26,
326                17, 52, 226, 4, 134, 75, 194, 208, 178, 133, 128, 224, 140, 167
327            ]
328        );
329    }
330}