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#[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 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 #[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 #[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 pub fn make_user_key(&self) -> Result<(UserKey, EncString)> {
77 make_user_key(rand::rng(), self)
78 }
79
80 #[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 #[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
130pub(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
140pub(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 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
169fn 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 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}