1use core::panic;
6
7use bitwarden_api_api::models::{
8 self, EmergencyAccessWithIdRequestModel, MasterPasswordUnlockAndAuthenticationDataModel,
9 OtherDeviceKeysUpdateRequestModel, ResetPasswordWithOrgIdRequestModel, UnlockDataRequestModel,
10 WebAuthnLoginRotateKeyRequestModel,
11};
12use bitwarden_core::key_management::{
13 KeyIds, MasterPasswordAuthenticationData, MasterPasswordUnlockData, SymmetricKeyId,
14};
15use bitwarden_crypto::{Kdf, KeyStoreContext, PublicKey, UnsignedSharedKey};
16use serde::{Deserialize, Serialize};
17use tracing::debug_span;
18#[cfg(feature = "wasm")]
19use tsify::Tsify;
20
21use crate::key_rotation::partial_rotateable_keyset::PartialRotateableKeyset;
22
23pub(super) enum MasterkeyUnlockMethod {
27 Password {
29 password: String,
30 hint: Option<String>,
31 kdf: Kdf,
32 salt: String,
33 },
34 KeyConnector,
37 None,
40}
41
42#[derive(Serialize, Deserialize, Clone)]
45#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
46pub struct V1EmergencyAccessMembership {
47 pub id: uuid::Uuid,
48 pub name: String,
49 pub public_key: PublicKey,
50}
51
52#[derive(Serialize, Deserialize, Clone)]
55#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
56pub struct V1OrganizationMembership {
57 pub organization_id: uuid::Uuid,
58 pub name: String,
59 pub public_key: PublicKey,
60}
61
62#[derive(Debug)]
63pub(super) enum ReencryptError {
64 MasterPasswordDerivation,
66 KeysetUnlockDataReencryption,
68 KeySharingError,
70}
71
72pub(super) struct ReencryptUnlockInput {
74 pub(super) master_key_unlock_method: MasterkeyUnlockMethod,
76 pub(super) trusted_devices: Vec<PartialRotateableKeyset>,
78 pub(super) webauthn_credentials: Vec<PartialRotateableKeyset>,
80 pub(super) trusted_organization_keys: Vec<V1OrganizationMembership>,
82 pub(super) trusted_emergency_access_keys: Vec<V1EmergencyAccessMembership>,
84}
85
86pub(super) fn reencrypt_unlock(
88 input: ReencryptUnlockInput,
89 current_user_key_id: SymmetricKeyId,
90 new_user_key_id: SymmetricKeyId,
91 ctx: &mut KeyStoreContext<KeyIds>,
92) -> Result<UnlockDataRequestModel, ReencryptError> {
93 let master_password_unlock_data = match input.master_key_unlock_method {
94 MasterkeyUnlockMethod::Password {
95 password,
96 hint,
97 kdf,
98 salt,
99 } => reencrypt_userkey_for_masterpassword_unlock(
100 password,
101 hint,
102 kdf,
103 salt,
104 new_user_key_id,
105 ctx,
106 )?,
107 MasterkeyUnlockMethod::KeyConnector => {
108 panic!("KeyConnector based masterkey unlock method is not supported yet")
109 }
110 MasterkeyUnlockMethod::None => panic!("None masterkey unlock method is not supported yet"),
111 };
112
113 let tde_device_unlock_data = reencrypt_tde_devices(
114 &input.trusted_devices,
115 current_user_key_id,
116 new_user_key_id,
117 ctx,
118 )?;
119 let prf_passkey_unlock_data = reencrypt_passkey_credentials(
120 &input.webauthn_credentials,
121 current_user_key_id,
122 new_user_key_id,
123 ctx,
124 )?;
125 let emergency_accesses =
126 reencrypt_emergency_access_keys(input.trusted_emergency_access_keys, new_user_key_id, ctx)?;
127 let organizations_memberships =
128 reencrypt_organization_memberships(input.trusted_organization_keys, new_user_key_id, ctx)?;
129
130 Ok(UnlockDataRequestModel {
131 master_password_unlock_data: Box::new(master_password_unlock_data),
132 emergency_access_unlock_data: Some(emergency_accesses),
133 organization_account_recovery_unlock_data: Some(organizations_memberships),
134 passkey_unlock_data: Some(prf_passkey_unlock_data),
135 device_key_unlock_data: Some(tde_device_unlock_data),
136 v2_upgrade_token: None,
137 })
138}
139
140fn reencrypt_tde_devices(
142 trusted_devices: &[PartialRotateableKeyset],
143 current_user_key_id: SymmetricKeyId,
144 new_user_key_id: SymmetricKeyId,
145 ctx: &mut KeyStoreContext<KeyIds>,
146) -> Result<Vec<OtherDeviceKeysUpdateRequestModel>, ReencryptError> {
147 trusted_devices
148 .iter()
149 .map(|device| {
150 let _span = debug_span!("reencrypt_device_key", device_id = ?device.id).entered();
151 device
152 .rotate_userkey(current_user_key_id, new_user_key_id, ctx)
153 .map_err(|_| ReencryptError::KeysetUnlockDataReencryption)
154 .map(Into::into)
155 })
156 .collect()
157}
158
159fn reencrypt_passkey_credentials(
161 webauthn_credentials: &[PartialRotateableKeyset],
162 current_user_key_id: SymmetricKeyId,
163 new_user_key_id: SymmetricKeyId,
164 ctx: &mut KeyStoreContext<KeyIds>,
165) -> Result<Vec<WebAuthnLoginRotateKeyRequestModel>, ReencryptError> {
166 webauthn_credentials
167 .iter()
168 .map(|cred| {
169 let _span =
170 debug_span!("reencrypt_webauthn_credential", credential_id = ?cred.id).entered();
171 cred.rotate_userkey(current_user_key_id, new_user_key_id, ctx)
172 .map_err(|_| ReencryptError::KeysetUnlockDataReencryption)
173 .map(Into::into)
174 })
175 .collect()
176}
177
178fn reencrypt_emergency_access_keys(
180 trusted_emergency_access_keys: Vec<V1EmergencyAccessMembership>,
181 new_user_key_id: SymmetricKeyId,
182 ctx: &mut KeyStoreContext<KeyIds>,
183) -> Result<Vec<EmergencyAccessWithIdRequestModel>, ReencryptError> {
184 trusted_emergency_access_keys
185 .into_iter()
186 .map(|ea| {
187 let _span =
188 debug_span!("reencrypt_emergency_access_key", grantee_id = ?ea.id).entered();
189 match UnsignedSharedKey::encapsulate(new_user_key_id, &ea.public_key, ctx) {
192 Ok(reencrypted_key) => Ok(EmergencyAccessWithIdRequestModel {
193 r#type: models::EmergencyAccessType::Takeover,
195 wait_time_days: 1,
197 id: ea.id,
198 key_encrypted: reencrypted_key.to_string().into(),
199 }),
200 Err(_) => Err(ReencryptError::KeySharingError),
201 }
202 })
203 .collect()
204}
205
206fn reencrypt_organization_memberships(
208 trusted_organization_keys: Vec<V1OrganizationMembership>,
209 new_user_key_id: SymmetricKeyId,
210 ctx: &mut KeyStoreContext<KeyIds>,
211) -> Result<Vec<ResetPasswordWithOrgIdRequestModel>, ReencryptError> {
212 trusted_organization_keys
213 .into_iter()
214 .map(|org_membership| {
215 let _span =
216 debug_span!("reencrypt_organization_key", organization = ?org_membership.organization_id)
217 .entered();
218 match UnsignedSharedKey::encapsulate(new_user_key_id, &org_membership.public_key, ctx) {
221 Ok(reencrypted_key) => Ok(ResetPasswordWithOrgIdRequestModel {
222 reset_password_key: Some(reencrypted_key.to_string()),
223 master_password_hash: None,
224 organization_id: org_membership.organization_id,
225 }),
226 Err(_) => Err(ReencryptError::KeySharingError),
227 }
228 })
229 .collect()
230}
231
232fn reencrypt_userkey_for_masterpassword_unlock(
233 password: String,
234 hint: Option<String>,
235 kdf: Kdf,
236 salt: String,
237 new_user_key_id: SymmetricKeyId,
238 ctx: &mut KeyStoreContext<KeyIds>,
239) -> Result<MasterPasswordUnlockAndAuthenticationDataModel, ReencryptError> {
240 let _span = debug_span!("derive_master_password_unlock_data").entered();
241 let unlock_data =
242 MasterPasswordUnlockData::derive(&password, &kdf, &salt, new_user_key_id, ctx)
243 .map_err(|_| ReencryptError::MasterPasswordDerivation)?;
244 let authentication_data = MasterPasswordAuthenticationData::derive(&password, &kdf, &salt)
245 .map_err(|_| ReencryptError::MasterPasswordDerivation)?;
246 to_authentication_and_unlock_data(unlock_data, authentication_data, hint)
247 .map_err(|_| ReencryptError::MasterPasswordDerivation)
248}
249
250#[derive(Debug)]
251struct ParsingError;
252
253fn to_authentication_and_unlock_data(
254 master_password_unlock_data: MasterPasswordUnlockData,
255 master_password_authentication_data: MasterPasswordAuthenticationData,
256 hint: Option<String>,
257) -> Result<MasterPasswordUnlockAndAuthenticationDataModel, ParsingError> {
258 let (kdf_type, kdf_iterations, kdf_memory, kdf_parallelism) =
259 match master_password_unlock_data.kdf {
260 bitwarden_crypto::Kdf::PBKDF2 { iterations } => {
261 (models::KdfType::PBKDF2_SHA256, iterations, None, None)
262 }
263 bitwarden_crypto::Kdf::Argon2id {
264 iterations,
265 memory,
266 parallelism,
267 } => (
268 models::KdfType::Argon2id,
269 iterations,
270 Some(memory),
271 Some(parallelism),
272 ),
273 };
274 Ok(MasterPasswordUnlockAndAuthenticationDataModel {
275 kdf_type,
276 kdf_iterations: kdf_iterations.get().try_into().map_err(|_| ParsingError)?,
277 kdf_memory: kdf_memory
278 .map(|m| m.get().try_into().map_err(|_| ParsingError))
279 .transpose()?,
280 kdf_parallelism: kdf_parallelism
281 .map(|p| p.get().try_into().map_err(|_| ParsingError))
282 .transpose()?,
283 email: Some(master_password_unlock_data.salt.clone()),
284 master_key_authentication_hash: Some(
285 master_password_authentication_data
286 .master_password_authentication_hash
287 .to_string(),
288 ),
289 master_key_encrypted_user_key: Some(
290 master_password_unlock_data
291 .master_key_wrapped_user_key
292 .to_string(),
293 ),
294 master_password_hint: hint,
295 master_password_salt: Some(master_password_unlock_data.salt.clone()),
296 })
297}
298
299#[cfg(test)]
300mod tests {
301 use std::num::NonZeroU32;
302
303 use bitwarden_api_api::models::KdfType;
304 use bitwarden_core::key_management::KeyIds;
305 use bitwarden_crypto::{Kdf, KeyStore, PublicKeyEncryptionAlgorithm, UnsignedSharedKey};
306 use uuid::Uuid;
307
308 use super::*;
309 use crate::key_rotation::partial_rotateable_keyset::PartialRotateableKeyset;
310
311 fn create_test_kdf_pbkdf2() -> Kdf {
312 Kdf::PBKDF2 {
313 iterations: NonZeroU32::new(600000).expect("valid iterations"),
314 }
315 }
316
317 fn create_test_kdf_argon2id() -> Kdf {
318 Kdf::Argon2id {
319 iterations: NonZeroU32::new(3).expect("valid iterations"),
320 memory: NonZeroU32::new(64).expect("valid memory"),
321 parallelism: NonZeroU32::new(4).expect("valid parallelism"),
322 }
323 }
324
325 fn create_test_unlock_data() -> MasterkeyUnlockMethod {
326 let kdf = create_test_kdf_argon2id();
327 let salt = "[email protected]".to_string();
328 let password = "test_password".to_string();
329 MasterkeyUnlockMethod::Password {
330 password,
331 hint: None,
332 kdf,
333 salt,
334 }
335 }
336
337 fn assert_symmetric_keys_equal(
338 key_id_1: SymmetricKeyId,
339 key_id_2: SymmetricKeyId,
340 ctx: &mut KeyStoreContext<KeyIds>,
341 ) {
342 #[allow(deprecated)]
343 let key_1 = ctx
344 .dangerous_get_symmetric_key(key_id_1)
345 .expect("key 1 should exist");
346 #[allow(deprecated)]
347 let key_2 = ctx
348 .dangerous_get_symmetric_key(key_id_2)
349 .expect("key 2 should exist");
350 assert_eq!(key_1, key_2, "symmetric keys should be equal");
351 }
352
353 #[test]
354 fn test_to_authentication_and_unlock_data_pbkdf2() {
355 let store: KeyStore<KeyIds> = KeyStore::default();
356 let mut ctx = store.context_mut();
357
358 let kdf = create_test_kdf_pbkdf2();
359 let salt = "[email protected]";
360 let password = "test_password";
361
362 let user_key_id = ctx.generate_symmetric_key();
363 let unlock_data = MasterPasswordUnlockData::derive(password, &kdf, salt, user_key_id, &ctx)
364 .expect("derive should succeed");
365 let auth_data = MasterPasswordAuthenticationData::derive(password, &kdf, salt)
366 .expect("derive should succeed");
367
368 let result = to_authentication_and_unlock_data(unlock_data, auth_data, None);
369 assert!(result.is_ok());
370
371 let model = result.expect("should be ok");
372 assert_eq!(model.kdf_type, KdfType::PBKDF2_SHA256);
373 assert_eq!(model.kdf_iterations, 600000);
374 assert!(model.kdf_memory.is_none());
375 assert!(model.kdf_parallelism.is_none());
376 assert_eq!(model.email, Some(salt.to_string()));
377 assert!(model.master_key_authentication_hash.is_some());
378 assert!(model.master_key_encrypted_user_key.is_some());
379 assert!(model.master_password_hint.is_none());
380
381 let master_password_unlock_data = MasterPasswordUnlockData {
383 master_key_wrapped_user_key: model
384 .master_key_encrypted_user_key
385 .expect("should be present")
386 .parse()
387 .expect("should parse"),
388 kdf: kdf.clone(),
389 salt: salt.to_string(),
390 };
391 let decrypted_user_key = master_password_unlock_data
392 .unwrap_to_context(password, &mut ctx)
393 .expect("unwrap should succeed");
394 assert_symmetric_keys_equal(user_key_id, decrypted_user_key, &mut ctx);
395 }
396
397 #[test]
398 fn test_to_authentication_and_unlock_data_argon2id() {
399 let store: KeyStore<KeyIds> = KeyStore::default();
400 let mut ctx = store.context_mut();
401
402 let kdf = create_test_kdf_argon2id();
403 let salt = "[email protected]";
404 let password = "test_password";
405
406 let user_key_id = ctx.generate_symmetric_key();
407 let unlock_data = MasterPasswordUnlockData::derive(password, &kdf, salt, user_key_id, &ctx)
408 .expect("derive should succeed");
409 let auth_data = MasterPasswordAuthenticationData::derive(password, &kdf, salt)
410 .expect("derive should succeed");
411
412 let result = to_authentication_and_unlock_data(unlock_data, auth_data, None);
413 assert!(result.is_ok());
414
415 let model = result.expect("should be ok");
416 assert_eq!(model.kdf_type, KdfType::Argon2id);
417 assert_eq!(model.kdf_iterations, 3);
418 assert_eq!(model.kdf_memory, Some(64));
419 assert_eq!(model.kdf_parallelism, Some(4));
420 assert_eq!(model.email, Some(salt.to_string()));
421 assert!(model.master_key_authentication_hash.is_some());
422 assert!(model.master_key_encrypted_user_key.is_some());
423
424 let master_password_unlock_data = MasterPasswordUnlockData {
426 master_key_wrapped_user_key: model
427 .master_key_encrypted_user_key
428 .expect("should be present")
429 .parse()
430 .expect("should parse"),
431 kdf: kdf.clone(),
432 salt: salt.to_string(),
433 };
434 let decrypted_user_key = master_password_unlock_data
435 .unwrap_to_context(password, &mut ctx)
436 .expect("unwrap should succeed");
437 assert_symmetric_keys_equal(user_key_id, decrypted_user_key, &mut ctx);
438 }
439
440 #[test]
441 fn test_reencrypt_unlock_device_key_data() {
442 let store: KeyStore<KeyIds> = KeyStore::default();
443 let mut ctx = store.context_mut();
444
445 let current_user_key_id = ctx.generate_symmetric_key();
446 let new_user_key_id = ctx.generate_symmetric_key();
447 let master_key_unlock_method = create_test_unlock_data();
448
449 let (device_keyset, device_private_key) =
450 PartialRotateableKeyset::make_test_keyset(current_user_key_id, &mut ctx);
451
452 let result = reencrypt_unlock(
453 ReencryptUnlockInput {
454 master_key_unlock_method,
455 trusted_devices: vec![device_keyset],
456 webauthn_credentials: vec![],
457 trusted_organization_keys: vec![],
458 trusted_emergency_access_keys: vec![],
459 },
460 current_user_key_id,
461 new_user_key_id,
462 &mut ctx,
463 );
464
465 let unlock_data = result.expect("should be ok");
466
467 let device_unlock = unlock_data
468 .device_key_unlock_data
469 .as_ref()
470 .expect("should be present")
471 .first()
472 .expect("should have at least one");
473 let decrypted_user_key = device_unlock
474 .encrypted_user_key
475 .parse::<UnsignedSharedKey>()
476 .expect("should parse")
477 .decapsulate(device_private_key, &mut ctx)
478 .expect("unwrap should succeed");
479 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
480 }
481
482 #[test]
483 fn test_reencrypt_unlock_webauthn_prf_credential_data() {
484 let store: KeyStore<KeyIds> = KeyStore::default();
485 let mut ctx = store.context_mut();
486
487 let current_user_key_id = ctx.generate_symmetric_key();
488 let new_user_key_id = ctx.generate_symmetric_key();
489 let master_key_unlock_method = create_test_unlock_data();
490
491 let (credential_keyset, credential_private_key) =
492 PartialRotateableKeyset::make_test_keyset(current_user_key_id, &mut ctx);
493
494 let result = reencrypt_unlock(
495 ReencryptUnlockInput {
496 master_key_unlock_method,
497 trusted_devices: vec![],
498 webauthn_credentials: vec![credential_keyset],
499 trusted_organization_keys: vec![],
500 trusted_emergency_access_keys: vec![],
501 },
502 current_user_key_id,
503 new_user_key_id,
504 &mut ctx,
505 );
506
507 let unlock_data = result.expect("should be ok");
508
509 let credential_unlock = unlock_data
511 .passkey_unlock_data
512 .as_ref()
513 .expect("should be present")
514 .first()
515 .expect("should have at least one");
516 let decrypted_user_key = credential_unlock
517 .encrypted_user_key
518 .parse::<UnsignedSharedKey>()
519 .expect("should parse")
520 .decapsulate(credential_private_key, &mut ctx)
521 .expect("unwrap should succeed");
522 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
523 }
524
525 #[test]
526 fn test_reencrypt_unlock_emergency_access_data() {
527 let store: KeyStore<KeyIds> = KeyStore::default();
528 let mut ctx = store.context_mut();
529
530 let current_user_key_id = ctx.generate_symmetric_key();
531 let new_user_key_id = ctx.generate_symmetric_key();
532 let master_key_unlock_method = create_test_unlock_data();
533
534 let organization_private_key =
535 ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
536 let emergency_access = V1EmergencyAccessMembership {
537 id: Uuid::new_v4(),
538 name: "Test User".to_string(),
539 public_key: ctx
540 .get_public_key(organization_private_key)
541 .expect("key exists"),
542 };
543
544 let result = reencrypt_unlock(
545 ReencryptUnlockInput {
546 master_key_unlock_method,
547 trusted_devices: vec![],
548 webauthn_credentials: vec![],
549 trusted_organization_keys: vec![],
550 trusted_emergency_access_keys: vec![emergency_access],
551 },
552 current_user_key_id,
553 new_user_key_id,
554 &mut ctx,
555 );
556
557 let unlock_data = result.expect("should be ok");
558
559 let emergency_access_unlock = unlock_data
561 .emergency_access_unlock_data
562 .as_ref()
563 .expect("should be present")
564 .first()
565 .expect("should have at least one");
566 let decrypted_user_key = emergency_access_unlock
567 .key_encrypted
568 .as_ref()
569 .map(|k| k.parse::<UnsignedSharedKey>())
570 .expect("should be present")
571 .expect("should parse")
572 .decapsulate(organization_private_key, &mut ctx)
573 .expect("unwrap should succeed");
574 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
575 }
576
577 #[test]
578 fn test_reencrypt_unlock_organization_membership_data() {
579 let store: KeyStore<KeyIds> = KeyStore::default();
580 let mut ctx = store.context_mut();
581
582 let kdf = create_test_kdf_argon2id();
583 let salt = "[email protected]".to_string();
584 let password = "test_password".to_string();
585
586 let current_user_key_id = ctx.generate_symmetric_key();
587 let new_user_key_id = ctx.generate_symmetric_key();
588
589 let org_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
590 let org_membership = V1OrganizationMembership {
591 organization_id: Uuid::new_v4(),
592 name: "Test Org".to_string(),
593 public_key: ctx.get_public_key(org_key).expect("key exists"),
594 };
595
596 let master_key_unlock_method = MasterkeyUnlockMethod::Password {
598 password: password.clone(),
599 hint: None,
600 kdf: kdf.clone(),
601 salt: salt.clone(),
602 };
603
604 let result = reencrypt_unlock(
605 ReencryptUnlockInput {
606 master_key_unlock_method,
607 trusted_devices: vec![],
608 webauthn_credentials: vec![],
609 trusted_organization_keys: vec![org_membership],
610 trusted_emergency_access_keys: vec![],
611 },
612 current_user_key_id,
613 new_user_key_id,
614 &mut ctx,
615 );
616
617 let unlock_data = result.expect("should be ok");
618
619 let org_membership_unlock = unlock_data
620 .organization_account_recovery_unlock_data
621 .as_ref()
622 .expect("should be present")
623 .first()
624 .expect("should have at least one");
625 let decrypted_user_key = org_membership_unlock
626 .reset_password_key
627 .as_ref()
628 .map(|k| k.parse::<UnsignedSharedKey>())
629 .expect("should be present")
630 .expect("should parse")
631 .decapsulate(org_key, &mut ctx)
632 .expect("unwrap should succeed");
633 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
634 }
635}