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    util::{self},
16};
17
18#[allow(missing_docs)]
19#[derive(Copy, Clone)]
20#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
21#[cfg_attr(feature = "dangerous-crypto-debug", derive(Debug))]
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)]
32#[cfg_attr(feature = "dangerous-crypto-debug", derive(Debug))]
33pub enum MasterKey {
34    KdfKey(KdfDerivedKeyMaterial),
35    KeyConnectorKey(Pin<Box<Array<u8, U32>>>),
36}
37
38impl MasterKey {
39    pub(crate) fn new(key: KdfDerivedKeyMaterial) -> Self {
40        Self::KdfKey(key)
41    }
42
43    /// Generate a new random master key for KeyConnector.
44    pub fn generate(mut rng: impl rand::Rng) -> Self {
45        let mut key = Box::pin(Array::<u8, U32>::default());
46
47        rng.fill(key.as_mut_slice());
48        Self::KeyConnectorKey(key)
49    }
50
51    fn inner_bytes(&self) -> &Pin<Box<Array<u8, U32>>> {
52        match self {
53            Self::KdfKey(key) => &key.0,
54            Self::KeyConnectorKey(key) => key,
55        }
56    }
57
58    /// Derives a users master key from their password, email and KDF.
59    ///
60    /// Note: the email is trimmed and converted to lowercase before being used.
61    // Only instrumented under `dangerous-crypto-debug`, where it intentionally logs key
62    // material, so it uses `tracing::instrument` directly (the `bitwarden_logging` wrapper
63    // enforces `skip_all`). There is no production instrumentation. The `allow` only applies
64    // when the dangerous arm is active.
65    #[cfg_attr(
66        feature = "dangerous-crypto-debug",
67        allow(unknown_lints, tracing_instrument)
68    )]
69    #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument)]
70    pub fn derive(password: &str, email: &str, kdf: &Kdf) -> Result<Self, CryptoError> {
71        Ok(KdfDerivedKeyMaterial::derive(password, email, kdf)?.into())
72    }
73
74    /// Derive the master key hash, used for local and remote password validation.
75    #[cfg_attr(
76        feature = "dangerous-crypto-debug",
77        allow(unknown_lints, tracing_instrument)
78    )]
79    #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument)]
80    pub fn derive_master_key_hash(&self, password: &[u8], purpose: HashPurpose) -> B64 {
81        let hash = util::pbkdf2(self.inner_bytes().as_slice(), password, purpose as u32);
82
83        hash.as_slice().into()
84    }
85
86    /// Generate a new random user key and encrypt it with the master key.
87    pub fn make_user_key(&self) -> Result<(UserKey, EncString)> {
88        make_user_key(bitwarden_random::rng(), self)
89    }
90
91    /// Encrypt the users user key
92    #[cfg_attr(
93        feature = "dangerous-crypto-debug",
94        allow(unknown_lints, tracing_instrument)
95    )]
96    #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument(err))]
97    #[cfg_attr(
98        not(feature = "dangerous-crypto-debug"),
99        bitwarden_logging::instrument(err)
100    )]
101    pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result<EncString> {
102        encrypt_user_key(self.inner_bytes(), user_key)
103    }
104
105    /// Decrypt the users user key
106    #[cfg_attr(
107        feature = "dangerous-crypto-debug",
108        allow(unknown_lints, tracing_instrument)
109    )]
110    #[cfg_attr(feature = "dangerous-crypto-debug", tracing::instrument(err))]
111    #[cfg_attr(
112        not(feature = "dangerous-crypto-debug"),
113        bitwarden_logging::instrument(err)
114    )]
115    pub fn decrypt_user_key(&self, user_key: EncString) -> Result<SymmetricCryptoKey> {
116        decrypt_user_key(self.inner_bytes(), user_key)
117    }
118
119    #[allow(missing_docs)]
120    pub fn to_base64(&self) -> B64 {
121        B64::from(self.inner_bytes().as_slice())
122    }
123}
124
125impl TryFrom<Vec<u8>> for MasterKey {
126    type Error = CryptoError;
127
128    fn try_from(value: Vec<u8>) -> Result<Self> {
129        let material = KdfDerivedKeyMaterial(Box::pin(
130            Array::<u8, U32>::try_from(value).map_err(|_| CryptoError::InvalidKey)?,
131        ));
132        Ok(Self::new(material))
133    }
134}
135
136impl From<KdfDerivedKeyMaterial> for MasterKey {
137    fn from(key: KdfDerivedKeyMaterial) -> Self {
138        Self::new(key)
139    }
140}
141
142impl TryFrom<&SymmetricCryptoKey> for MasterKey {
143    type Error = CryptoError;
144
145    fn try_from(value: &SymmetricCryptoKey) -> Result<Self> {
146        match value {
147            SymmetricCryptoKey::Aes256CbcKey(key) => {
148                Ok(Self::KdfKey(KdfDerivedKeyMaterial(key.enc_key.clone())))
149            }
150            _ => Err(CryptoError::InvalidKey),
151        }
152    }
153}
154
155/// Helper function to encrypt a user key with a master or pin key.
156pub(super) fn encrypt_user_key(
157    master_key: &Pin<Box<Array<u8, U32>>>,
158    user_key: &SymmetricCryptoKey,
159) -> Result<EncString> {
160    let stretched_master_key = stretch_key(master_key);
161    let user_key_bytes = user_key.to_encoded();
162    EncString::encrypt_aes256_hmac(user_key_bytes.as_ref(), &stretched_master_key)
163}
164
165/// Helper function to decrypt a user key with a master or pin key or key-connector-key.
166pub(super) fn decrypt_user_key(
167    key: &Pin<Box<Array<u8, U32>>>,
168    user_key: EncString,
169) -> Result<SymmetricCryptoKey> {
170    let dec: Vec<u8> = match user_key {
171        // Legacy. user_keys were encrypted using `Aes256Cbc_B64` a long time ago. We've since
172        // moved to using `Aes256Cbc_HmacSha256_B64`. However, we still need to support
173        // decrypting these old keys.
174        EncString::Aes256Cbc_B64 { iv, ref data } => {
175            crate::aes::decrypt_aes256(&iv, data.clone(), &key.clone())
176                .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 = KdfDerivedKeyMaterial(derive_symmetric_key("test1").enc_key.clone());
315        let master_key = MasterKey::KdfKey(kdf_material);
316
317        let user_key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test2"));
318
319        let encrypted = master_key.encrypt_user_key(&user_key).unwrap();
320        let decrypted = master_key.decrypt_user_key(encrypted).unwrap();
321
322        assert_eq!(decrypted, user_key, "Decrypted key doesn't match user key");
323    }
324
325    #[test]
326    fn test_decrypt_user_key_aes_cbc256_b64() {
327        let password = "asdfasdfasdf";
328        let salt = "[email protected]";
329        let kdf = Kdf::PBKDF2 {
330            iterations: NonZeroU32::new(600_000).unwrap(),
331        };
332
333        let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
334            .unwrap()
335            .into();
336
337        let user_key: EncString = "0.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=".parse().unwrap();
338
339        let decrypted = master_key.decrypt_user_key(user_key).unwrap();
340        let SymmetricCryptoKey::Aes256CbcHmacKey(decrypted) = &decrypted else {
341            panic!("Decrypted key is not an Aes256CbcHmacKey");
342        };
343
344        assert_eq!(
345            decrypted.enc_key.as_slice(),
346            [
347                12, 95, 151, 203, 37, 4, 236, 67, 137, 97, 90, 58, 6, 127, 242, 28, 209, 168, 125,
348                29, 118, 24, 213, 44, 117, 202, 2, 115, 132, 165, 125, 148
349            ]
350        );
351        assert_eq!(
352            decrypted.mac_key.as_slice(),
353            [
354                186, 215, 234, 137, 24, 169, 227, 29, 218, 57, 180, 237, 73, 91, 189, 51, 253, 26,
355                17, 52, 226, 4, 134, 75, 194, 208, 178, 133, 128, 224, 140, 167
356            ]
357        );
358    }
359
360    #[test]
361    fn test_decrypt_cbc256() {
362        let master_key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe08=".to_string();
363        let master_key = SymmetricCryptoKey::try_from(master_key).unwrap();
364        let SymmetricCryptoKey::Aes256CbcKey(master_key) = &master_key else {
365            panic!("Incorrect key type for test used");
366        };
367
368        let master_key = MasterKey::KdfKey(KdfDerivedKeyMaterial(master_key.enc_key.clone()));
369
370        let enc_str = "0.tn/heK4HLbbEe+yEkC+kvw==|8QM94f7aVTtjm/bmvRdVxOxiLiiZtHYYO7+oBdjFCkilncesx0iVrXPl+tMKqW+Jo7+FtZdPNsTrL6RdoG7i5QbCRVwK+9010+xm7MTQY8s=";
371        let enc_string: EncString = enc_str.parse().unwrap();
372
373        let decrypted = master_key.decrypt_user_key(enc_string).unwrap();
374        let SymmetricCryptoKey::Aes256CbcHmacKey(decrypted) = &decrypted else {
375            panic!("Failed to decrypt Aes256CbcHmacKey from Aes256CbcKey encrypted EncString")
376        };
377
378        assert_eq!(
379            decrypted.enc_key.as_slice(),
380            [
381                116, 170, 187, 43, 80, 212, 193, 202, 234, 181, 57, 66, 151, 249, 59, 47, 70, 16,
382                57, 4, 170, 78, 85, 241, 152, 232, 91, 57, 9, 87, 209, 245,
383            ]
384        );
385        assert_eq!(
386            decrypted.mac_key.as_slice(),
387            [
388                40, 245, 106, 140, 2, 225, 138, 213, 98, 223, 92, 168, 135, 208, 22, 194, 31, 21,
389                178, 252, 203, 198, 35, 174, 53, 218, 254, 151, 235, 57, 7, 98,
390            ]
391        );
392    }
393}