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