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 { .. } => {
150            let legacy_key = SymmetricCryptoKey::Aes256CbcKey(super::Aes256CbcKey {
151                enc_key: key.clone(),
152            });
153            user_key.decrypt_with_key(&legacy_key)?
154        }
155        EncString::Aes256Cbc_HmacSha256_B64 { .. } => {
156            let stretched_key = SymmetricCryptoKey::Aes256CbcHmacKey(stretch_key(key));
157            user_key.decrypt_with_key(&stretched_key)?
158        }
159        EncString::Cose_Encrypt0_B64 { .. } => {
160            return Err(CryptoError::OperationNotSupported(
161                crate::error::UnsupportedOperationError::EncryptionNotImplementedForKey,
162            ));
163        }
164    };
165
166    SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(dec))
167}
168
169/// Generate a new random user key and encrypt it with the master key.
170///
171/// WARNING: This function should only be used with a proper cryptographic random number generator.
172/// If you do not have a good reason for using this, use [MasterKey::make_user_key] instead.
173///
174/// This function is only split out from [MasterKey::make_user_key], to make it unit testable.
175fn make_user_key(
176    rng: impl rand::CryptoRng,
177    master_key: &MasterKey,
178) -> Result<(UserKey, EncString)> {
179    let user_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key_internal(rng);
180    let protected = master_key.encrypt_user_key(&user_key)?;
181    Ok((UserKey::new(user_key), protected))
182}
183
184#[cfg(test)]
185mod tests {
186    use std::num::NonZeroU32;
187
188    use rand::SeedableRng;
189
190    use super::{HashPurpose, Kdf, MasterKey, make_user_key};
191    use crate::{
192        EncString, SymmetricCryptoKey,
193        keys::{master_key::KdfDerivedKeyMaterial, symmetric_crypto_key::derive_symmetric_key},
194    };
195
196    #[test]
197    fn test_password_hash_pbkdf2() {
198        let password = "asdfasdf";
199        let salts = [
200            "[email protected]",
201            "[email protected]",
202            " [email protected]",
203        ];
204        let kdf = Kdf::PBKDF2 {
205            iterations: NonZeroU32::new(100_000).unwrap(),
206        };
207
208        for salt in salts.iter() {
209            let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
210                .unwrap()
211                .into();
212
213            assert_eq!(
214                "wmyadRMyBZOH7P/a/ucTCbSghKgdzDpPqUnu/DAVtSw=",
215                String::from(
216                    master_key.derive_master_key_hash(
217                        password.as_bytes(),
218                        HashPurpose::ServerAuthorization
219                    )
220                ),
221            );
222        }
223    }
224
225    #[test]
226    fn test_password_hash_argon2id() {
227        let password = "asdfasdf";
228        let salt = "test_salt";
229        let kdf = Kdf::Argon2id {
230            iterations: NonZeroU32::new(4).unwrap(),
231            memory: NonZeroU32::new(32).unwrap(),
232            parallelism: NonZeroU32::new(2).unwrap(),
233        };
234
235        let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
236            .unwrap()
237            .into();
238
239        assert_eq!(
240            "PR6UjYmjmppTYcdyTiNbAhPJuQQOmynKbdEl1oyi/iQ=",
241            master_key
242                .derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization)
243                .to_string(),
244        );
245    }
246
247    #[test]
248    fn test_make_user_key() {
249        let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]);
250
251        let master_key: MasterKey = KdfDerivedKeyMaterial(Box::pin(
252            [
253                31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167,
254                69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75,
255            ]
256            .into(),
257        ))
258        .into();
259
260        let (user_key, protected) = make_user_key(&mut rng, &master_key).unwrap();
261        let SymmetricCryptoKey::Aes256CbcHmacKey(user_key_unwrapped) = &user_key.0 else {
262            panic!("User key is not an Aes256CbcHmacKey");
263        };
264
265        assert_eq!(
266            user_key_unwrapped.enc_key.as_slice(),
267            [
268                62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161, 44, 132, 14,
269                195, 206, 154, 127, 59, 24, 27, 225, 136, 239, 113, 26, 30
270            ]
271        );
272        assert_eq!(
273            user_key_unwrapped.mac_key.as_slice(),
274            [
275                152, 76, 225, 114, 185, 33, 111, 65, 159, 68, 83, 103, 69, 109, 86, 25, 49, 74, 66,
276                163, 218, 134, 176, 1, 56, 123, 253, 184, 14, 12, 254, 66
277            ]
278        );
279
280        // Ensure we can decrypt the key and get back the same key
281        let decrypted = master_key.decrypt_user_key(protected).unwrap();
282
283        assert_eq!(
284            decrypted, user_key.0,
285            "Decrypted key doesn't match user key"
286        );
287    }
288
289    #[test]
290    fn test_make_user_key2() {
291        let kdf_material = KdfDerivedKeyMaterial(derive_symmetric_key("test1").enc_key.clone());
292        let master_key = MasterKey::KdfKey(kdf_material);
293
294        let user_key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test2"));
295
296        let encrypted = master_key.encrypt_user_key(&user_key).unwrap();
297        let decrypted = master_key.decrypt_user_key(encrypted).unwrap();
298
299        assert_eq!(decrypted, user_key, "Decrypted key doesn't match user key");
300    }
301
302    #[test]
303    fn test_decrypt_user_key_aes_cbc256_b64() {
304        let password = "asdfasdfasdf";
305        let salt = "[email protected]";
306        let kdf = Kdf::PBKDF2 {
307            iterations: NonZeroU32::new(600_000).unwrap(),
308        };
309
310        let master_key: MasterKey = KdfDerivedKeyMaterial::derive(password, salt, &kdf)
311            .unwrap()
312            .into();
313
314        let user_key: EncString = "0.8UClLa8IPE1iZT7chy5wzQ==|6PVfHnVk5S3XqEtQemnM5yb4JodxmPkkWzmDRdfyHtjORmvxqlLX40tBJZ+CKxQWmS8tpEB5w39rbgHg/gqs0haGdZG4cPbywsgGzxZ7uNI=".parse().unwrap();
315
316        let decrypted = master_key.decrypt_user_key(user_key).unwrap();
317        let SymmetricCryptoKey::Aes256CbcHmacKey(decrypted) = &decrypted else {
318            panic!("Decrypted key is not an Aes256CbcHmacKey");
319        };
320
321        assert_eq!(
322            decrypted.enc_key.as_slice(),
323            [
324                12, 95, 151, 203, 37, 4, 236, 67, 137, 97, 90, 58, 6, 127, 242, 28, 209, 168, 125,
325                29, 118, 24, 213, 44, 117, 202, 2, 115, 132, 165, 125, 148
326            ]
327        );
328        assert_eq!(
329            decrypted.mac_key.as_slice(),
330            [
331                186, 215, 234, 137, 24, 169, 227, 29, 218, 57, 180, 237, 73, 91, 189, 51, 253, 26,
332                17, 52, 226, 4, 134, 75, 194, 208, 178, 133, 128, 224, 140, 167
333            ]
334        );
335    }
336}