bitwarden_crypto/keys/
master_key.rs

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