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#[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 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 #[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 #[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 pub fn make_user_key(&self) -> Result<(UserKey, EncString)> {
78 make_user_key(rand::thread_rng(), self)
79 }
80
81 #[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 #[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
136pub(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
146pub(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 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
175fn 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 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}