Skip to main content

bitwarden_crypto/keys/
master_key.rs

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