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