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