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