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 })
296}
297
298#[cfg(test)]
299mod tests {
300 use std::num::NonZeroU32;
301
302 use bitwarden_api_api::models::KdfType;
303 use bitwarden_core::key_management::KeyIds;
304 use bitwarden_crypto::{Kdf, KeyStore, PublicKeyEncryptionAlgorithm, UnsignedSharedKey};
305 use uuid::Uuid;
306
307 use super::*;
308 use crate::key_rotation::partial_rotateable_keyset::PartialRotateableKeyset;
309
310 fn create_test_kdf_pbkdf2() -> Kdf {
311 Kdf::PBKDF2 {
312 iterations: NonZeroU32::new(600000).expect("valid iterations"),
313 }
314 }
315
316 fn create_test_kdf_argon2id() -> Kdf {
317 Kdf::Argon2id {
318 iterations: NonZeroU32::new(3).expect("valid iterations"),
319 memory: NonZeroU32::new(64).expect("valid memory"),
320 parallelism: NonZeroU32::new(4).expect("valid parallelism"),
321 }
322 }
323
324 fn create_test_unlock_data() -> MasterkeyUnlockMethod {
325 let kdf = create_test_kdf_argon2id();
326 let salt = "[email protected]".to_string();
327 let password = "test_password".to_string();
328 MasterkeyUnlockMethod::Password {
329 password,
330 hint: None,
331 kdf,
332 salt,
333 }
334 }
335
336 fn assert_symmetric_keys_equal(
337 key_id_1: SymmetricKeyId,
338 key_id_2: SymmetricKeyId,
339 ctx: &mut KeyStoreContext<KeyIds>,
340 ) {
341 #[allow(deprecated)]
342 let key_1 = ctx
343 .dangerous_get_symmetric_key(key_id_1)
344 .expect("key 1 should exist");
345 #[allow(deprecated)]
346 let key_2 = ctx
347 .dangerous_get_symmetric_key(key_id_2)
348 .expect("key 2 should exist");
349 assert_eq!(key_1, key_2, "symmetric keys should be equal");
350 }
351
352 #[test]
353 fn test_to_authentication_and_unlock_data_pbkdf2() {
354 let store: KeyStore<KeyIds> = KeyStore::default();
355 let mut ctx = store.context_mut();
356
357 let kdf = create_test_kdf_pbkdf2();
358 let salt = "[email protected]";
359 let password = "test_password";
360
361 let user_key_id = ctx.generate_symmetric_key();
362 let unlock_data = MasterPasswordUnlockData::derive(password, &kdf, salt, user_key_id, &ctx)
363 .expect("derive should succeed");
364 let auth_data = MasterPasswordAuthenticationData::derive(password, &kdf, salt)
365 .expect("derive should succeed");
366
367 let result = to_authentication_and_unlock_data(unlock_data, auth_data, None);
368 assert!(result.is_ok());
369
370 let model = result.expect("should be ok");
371 assert_eq!(model.kdf_type, KdfType::PBKDF2_SHA256);
372 assert_eq!(model.kdf_iterations, 600000);
373 assert!(model.kdf_memory.is_none());
374 assert!(model.kdf_parallelism.is_none());
375 assert_eq!(model.email, Some(salt.to_string()));
376 assert!(model.master_key_authentication_hash.is_some());
377 assert!(model.master_key_encrypted_user_key.is_some());
378 assert!(model.master_password_hint.is_none());
379
380 let master_password_unlock_data = MasterPasswordUnlockData {
382 master_key_wrapped_user_key: model
383 .master_key_encrypted_user_key
384 .expect("should be present")
385 .parse()
386 .expect("should parse"),
387 kdf: kdf.clone(),
388 salt: salt.to_string(),
389 };
390 let decrypted_user_key = master_password_unlock_data
391 .unwrap_to_context(password, &mut ctx)
392 .expect("unwrap should succeed");
393 assert_symmetric_keys_equal(user_key_id, decrypted_user_key, &mut ctx);
394 }
395
396 #[test]
397 fn test_to_authentication_and_unlock_data_argon2id() {
398 let store: KeyStore<KeyIds> = KeyStore::default();
399 let mut ctx = store.context_mut();
400
401 let kdf = create_test_kdf_argon2id();
402 let salt = "[email protected]";
403 let password = "test_password";
404
405 let user_key_id = ctx.generate_symmetric_key();
406 let unlock_data = MasterPasswordUnlockData::derive(password, &kdf, salt, user_key_id, &ctx)
407 .expect("derive should succeed");
408 let auth_data = MasterPasswordAuthenticationData::derive(password, &kdf, salt)
409 .expect("derive should succeed");
410
411 let result = to_authentication_and_unlock_data(unlock_data, auth_data, None);
412 assert!(result.is_ok());
413
414 let model = result.expect("should be ok");
415 assert_eq!(model.kdf_type, KdfType::Argon2id);
416 assert_eq!(model.kdf_iterations, 3);
417 assert_eq!(model.kdf_memory, Some(64));
418 assert_eq!(model.kdf_parallelism, Some(4));
419 assert_eq!(model.email, Some(salt.to_string()));
420 assert!(model.master_key_authentication_hash.is_some());
421 assert!(model.master_key_encrypted_user_key.is_some());
422
423 let master_password_unlock_data = MasterPasswordUnlockData {
425 master_key_wrapped_user_key: model
426 .master_key_encrypted_user_key
427 .expect("should be present")
428 .parse()
429 .expect("should parse"),
430 kdf: kdf.clone(),
431 salt: salt.to_string(),
432 };
433 let decrypted_user_key = master_password_unlock_data
434 .unwrap_to_context(password, &mut ctx)
435 .expect("unwrap should succeed");
436 assert_symmetric_keys_equal(user_key_id, decrypted_user_key, &mut ctx);
437 }
438
439 #[test]
440 fn test_reencrypt_unlock_device_key_data() {
441 let store: KeyStore<KeyIds> = KeyStore::default();
442 let mut ctx = store.context_mut();
443
444 let current_user_key_id = ctx.generate_symmetric_key();
445 let new_user_key_id = ctx.generate_symmetric_key();
446 let master_key_unlock_method = create_test_unlock_data();
447
448 let (device_keyset, device_private_key) =
449 PartialRotateableKeyset::make_test_keyset(current_user_key_id, &mut ctx);
450
451 let result = reencrypt_unlock(
452 ReencryptUnlockInput {
453 master_key_unlock_method,
454 trusted_devices: vec![device_keyset],
455 webauthn_credentials: vec![],
456 trusted_organization_keys: vec![],
457 trusted_emergency_access_keys: vec![],
458 },
459 current_user_key_id,
460 new_user_key_id,
461 &mut ctx,
462 );
463
464 let unlock_data = result.expect("should be ok");
465
466 let device_unlock = unlock_data
467 .device_key_unlock_data
468 .as_ref()
469 .expect("should be present")
470 .first()
471 .expect("should have at least one");
472 let decrypted_user_key = device_unlock
473 .encrypted_user_key
474 .parse::<UnsignedSharedKey>()
475 .expect("should parse")
476 .decapsulate(device_private_key, &mut ctx)
477 .expect("unwrap should succeed");
478 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
479 }
480
481 #[test]
482 fn test_reencrypt_unlock_webauthn_prf_credential_data() {
483 let store: KeyStore<KeyIds> = KeyStore::default();
484 let mut ctx = store.context_mut();
485
486 let current_user_key_id = ctx.generate_symmetric_key();
487 let new_user_key_id = ctx.generate_symmetric_key();
488 let master_key_unlock_method = create_test_unlock_data();
489
490 let (credential_keyset, credential_private_key) =
491 PartialRotateableKeyset::make_test_keyset(current_user_key_id, &mut ctx);
492
493 let result = reencrypt_unlock(
494 ReencryptUnlockInput {
495 master_key_unlock_method,
496 trusted_devices: vec![],
497 webauthn_credentials: vec![credential_keyset],
498 trusted_organization_keys: vec![],
499 trusted_emergency_access_keys: vec![],
500 },
501 current_user_key_id,
502 new_user_key_id,
503 &mut ctx,
504 );
505
506 let unlock_data = result.expect("should be ok");
507
508 let credential_unlock = unlock_data
510 .passkey_unlock_data
511 .as_ref()
512 .expect("should be present")
513 .first()
514 .expect("should have at least one");
515 let decrypted_user_key = credential_unlock
516 .encrypted_user_key
517 .parse::<UnsignedSharedKey>()
518 .expect("should parse")
519 .decapsulate(credential_private_key, &mut ctx)
520 .expect("unwrap should succeed");
521 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
522 }
523
524 #[test]
525 fn test_reencrypt_unlock_emergency_access_data() {
526 let store: KeyStore<KeyIds> = KeyStore::default();
527 let mut ctx = store.context_mut();
528
529 let current_user_key_id = ctx.generate_symmetric_key();
530 let new_user_key_id = ctx.generate_symmetric_key();
531 let master_key_unlock_method = create_test_unlock_data();
532
533 let organization_private_key =
534 ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
535 let emergency_access = V1EmergencyAccessMembership {
536 id: Uuid::new_v4(),
537 name: "Test User".to_string(),
538 public_key: ctx
539 .get_public_key(organization_private_key)
540 .expect("key exists"),
541 };
542
543 let result = reencrypt_unlock(
544 ReencryptUnlockInput {
545 master_key_unlock_method,
546 trusted_devices: vec![],
547 webauthn_credentials: vec![],
548 trusted_organization_keys: vec![],
549 trusted_emergency_access_keys: vec![emergency_access],
550 },
551 current_user_key_id,
552 new_user_key_id,
553 &mut ctx,
554 );
555
556 let unlock_data = result.expect("should be ok");
557
558 let emergency_access_unlock = unlock_data
560 .emergency_access_unlock_data
561 .as_ref()
562 .expect("should be present")
563 .first()
564 .expect("should have at least one");
565 let decrypted_user_key = emergency_access_unlock
566 .key_encrypted
567 .as_ref()
568 .map(|k| k.parse::<UnsignedSharedKey>())
569 .expect("should be present")
570 .expect("should parse")
571 .decapsulate(organization_private_key, &mut ctx)
572 .expect("unwrap should succeed");
573 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
574 }
575
576 #[test]
577 fn test_reencrypt_unlock_organization_membership_data() {
578 let store: KeyStore<KeyIds> = KeyStore::default();
579 let mut ctx = store.context_mut();
580
581 let kdf = create_test_kdf_argon2id();
582 let salt = "[email protected]".to_string();
583 let password = "test_password".to_string();
584
585 let current_user_key_id = ctx.generate_symmetric_key();
586 let new_user_key_id = ctx.generate_symmetric_key();
587
588 let org_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
589 let org_membership = V1OrganizationMembership {
590 organization_id: Uuid::new_v4(),
591 name: "Test Org".to_string(),
592 public_key: ctx.get_public_key(org_key).expect("key exists"),
593 };
594
595 let master_key_unlock_method = MasterkeyUnlockMethod::Password {
597 password: password.clone(),
598 hint: None,
599 kdf: kdf.clone(),
600 salt: salt.clone(),
601 };
602
603 let result = reencrypt_unlock(
604 ReencryptUnlockInput {
605 master_key_unlock_method,
606 trusted_devices: vec![],
607 webauthn_credentials: vec![],
608 trusted_organization_keys: vec![org_membership],
609 trusted_emergency_access_keys: vec![],
610 },
611 current_user_key_id,
612 new_user_key_id,
613 &mut ctx,
614 );
615
616 let unlock_data = result.expect("should be ok");
617
618 let org_membership_unlock = unlock_data
619 .organization_account_recovery_unlock_data
620 .as_ref()
621 .expect("should be present")
622 .first()
623 .expect("should have at least one");
624 let decrypted_user_key = org_membership_unlock
625 .reset_password_key
626 .as_ref()
627 .map(|k| k.parse::<UnsignedSharedKey>())
628 .expect("should be present")
629 .expect("should parse")
630 .decapsulate(org_key, &mut ctx)
631 .expect("unwrap should succeed");
632 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
633 }
634}