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,
194 wait_time_days: 0,
195 id: ea.id,
196 key_encrypted: reencrypted_key.to_string().into(),
197 }),
198 Err(_) => Err(ReencryptError::KeySharingError),
199 }
200 })
201 .collect()
202}
203
204fn reencrypt_organization_memberships(
206 trusted_organization_keys: Vec<V1OrganizationMembership>,
207 new_user_key_id: SymmetricKeyId,
208 ctx: &mut KeyStoreContext<KeyIds>,
209) -> Result<Vec<ResetPasswordWithOrgIdRequestModel>, ReencryptError> {
210 trusted_organization_keys
211 .into_iter()
212 .map(|org_membership| {
213 let _span =
214 debug_span!("reencrypt_organization_key", organization = ?org_membership.organization_id)
215 .entered();
216 match UnsignedSharedKey::encapsulate(new_user_key_id, &org_membership.public_key, ctx) {
219 Ok(reencrypted_key) => Ok(ResetPasswordWithOrgIdRequestModel {
220 reset_password_key: Some(reencrypted_key.to_string()),
221 master_password_hash: None,
222 organization_id: org_membership.organization_id,
223 }),
224 Err(_) => Err(ReencryptError::KeySharingError),
225 }
226 })
227 .collect()
228}
229
230fn reencrypt_userkey_for_masterpassword_unlock(
231 password: String,
232 hint: Option<String>,
233 kdf: Kdf,
234 salt: String,
235 new_user_key_id: SymmetricKeyId,
236 ctx: &mut KeyStoreContext<KeyIds>,
237) -> Result<MasterPasswordUnlockAndAuthenticationDataModel, ReencryptError> {
238 let _span = debug_span!("derive_master_password_unlock_data").entered();
239 let unlock_data =
240 MasterPasswordUnlockData::derive(&password, &kdf, &salt, new_user_key_id, ctx)
241 .map_err(|_| ReencryptError::MasterPasswordDerivation)?;
242 let authentication_data = MasterPasswordAuthenticationData::derive(&password, &kdf, &salt)
243 .map_err(|_| ReencryptError::MasterPasswordDerivation)?;
244 to_authentication_and_unlock_data(unlock_data, authentication_data, hint)
245 .map_err(|_| ReencryptError::MasterPasswordDerivation)
246}
247
248#[derive(Debug)]
249struct ParsingError;
250
251fn to_authentication_and_unlock_data(
252 master_password_unlock_data: MasterPasswordUnlockData,
253 master_password_authentication_data: MasterPasswordAuthenticationData,
254 hint: Option<String>,
255) -> Result<MasterPasswordUnlockAndAuthenticationDataModel, ParsingError> {
256 let (kdf_type, kdf_iterations, kdf_memory, kdf_parallelism) =
257 match master_password_unlock_data.kdf {
258 bitwarden_crypto::Kdf::PBKDF2 { iterations } => {
259 (models::KdfType::PBKDF2_SHA256, iterations, None, None)
260 }
261 bitwarden_crypto::Kdf::Argon2id {
262 iterations,
263 memory,
264 parallelism,
265 } => (
266 models::KdfType::Argon2id,
267 iterations,
268 Some(memory),
269 Some(parallelism),
270 ),
271 };
272 Ok(MasterPasswordUnlockAndAuthenticationDataModel {
273 kdf_type,
274 kdf_iterations: kdf_iterations.get().try_into().map_err(|_| ParsingError)?,
275 kdf_memory: kdf_memory
276 .map(|m| m.get().try_into().map_err(|_| ParsingError))
277 .transpose()?,
278 kdf_parallelism: kdf_parallelism
279 .map(|p| p.get().try_into().map_err(|_| ParsingError))
280 .transpose()?,
281 email: Some(master_password_unlock_data.salt.clone()),
282 master_key_authentication_hash: Some(
283 master_password_authentication_data
284 .master_password_authentication_hash
285 .to_string(),
286 ),
287 master_key_encrypted_user_key: Some(
288 master_password_unlock_data
289 .master_key_wrapped_user_key
290 .to_string(),
291 ),
292 master_password_hint: hint,
293 })
294}
295
296#[cfg(test)]
297mod tests {
298 use std::num::NonZeroU32;
299
300 use bitwarden_api_api::models::KdfType;
301 use bitwarden_core::key_management::KeyIds;
302 use bitwarden_crypto::{Kdf, KeyStore, PublicKeyEncryptionAlgorithm, UnsignedSharedKey};
303 use uuid::Uuid;
304
305 use super::*;
306 use crate::key_rotation::partial_rotateable_keyset::PartialRotateableKeyset;
307
308 fn create_test_kdf_pbkdf2() -> Kdf {
309 Kdf::PBKDF2 {
310 iterations: NonZeroU32::new(600000).expect("valid iterations"),
311 }
312 }
313
314 fn create_test_kdf_argon2id() -> Kdf {
315 Kdf::Argon2id {
316 iterations: NonZeroU32::new(3).expect("valid iterations"),
317 memory: NonZeroU32::new(64).expect("valid memory"),
318 parallelism: NonZeroU32::new(4).expect("valid parallelism"),
319 }
320 }
321
322 fn create_test_unlock_data() -> MasterkeyUnlockMethod {
323 let kdf = create_test_kdf_argon2id();
324 let salt = "[email protected]".to_string();
325 let password = "test_password".to_string();
326 MasterkeyUnlockMethod::Password {
327 password,
328 hint: None,
329 kdf,
330 salt,
331 }
332 }
333
334 fn assert_symmetric_keys_equal(
335 key_id_1: SymmetricKeyId,
336 key_id_2: SymmetricKeyId,
337 ctx: &mut KeyStoreContext<KeyIds>,
338 ) {
339 #[allow(deprecated)]
340 let key_1 = ctx
341 .dangerous_get_symmetric_key(key_id_1)
342 .expect("key 1 should exist");
343 #[allow(deprecated)]
344 let key_2 = ctx
345 .dangerous_get_symmetric_key(key_id_2)
346 .expect("key 2 should exist");
347 assert_eq!(key_1, key_2, "symmetric keys should be equal");
348 }
349
350 #[test]
351 fn test_to_authentication_and_unlock_data_pbkdf2() {
352 let store: KeyStore<KeyIds> = KeyStore::default();
353 let mut ctx = store.context_mut();
354
355 let kdf = create_test_kdf_pbkdf2();
356 let salt = "[email protected]";
357 let password = "test_password";
358
359 let user_key_id = ctx.generate_symmetric_key();
360 let unlock_data = MasterPasswordUnlockData::derive(password, &kdf, salt, user_key_id, &ctx)
361 .expect("derive should succeed");
362 let auth_data = MasterPasswordAuthenticationData::derive(password, &kdf, salt)
363 .expect("derive should succeed");
364
365 let result = to_authentication_and_unlock_data(unlock_data, auth_data, None);
366 assert!(result.is_ok());
367
368 let model = result.expect("should be ok");
369 assert_eq!(model.kdf_type, KdfType::PBKDF2_SHA256);
370 assert_eq!(model.kdf_iterations, 600000);
371 assert!(model.kdf_memory.is_none());
372 assert!(model.kdf_parallelism.is_none());
373 assert_eq!(model.email, Some(salt.to_string()));
374 assert!(model.master_key_authentication_hash.is_some());
375 assert!(model.master_key_encrypted_user_key.is_some());
376 assert!(model.master_password_hint.is_none());
377
378 let master_password_unlock_data = MasterPasswordUnlockData {
380 master_key_wrapped_user_key: model
381 .master_key_encrypted_user_key
382 .expect("should be present")
383 .parse()
384 .expect("should parse"),
385 kdf: kdf.clone(),
386 salt: salt.to_string(),
387 };
388 let decrypted_user_key = master_password_unlock_data
389 .unwrap_to_context(password, &mut ctx)
390 .expect("unwrap should succeed");
391 assert_symmetric_keys_equal(user_key_id, decrypted_user_key, &mut ctx);
392 }
393
394 #[test]
395 fn test_to_authentication_and_unlock_data_argon2id() {
396 let store: KeyStore<KeyIds> = KeyStore::default();
397 let mut ctx = store.context_mut();
398
399 let kdf = create_test_kdf_argon2id();
400 let salt = "[email protected]";
401 let password = "test_password";
402
403 let user_key_id = ctx.generate_symmetric_key();
404 let unlock_data = MasterPasswordUnlockData::derive(password, &kdf, salt, user_key_id, &ctx)
405 .expect("derive should succeed");
406 let auth_data = MasterPasswordAuthenticationData::derive(password, &kdf, salt)
407 .expect("derive should succeed");
408
409 let result = to_authentication_and_unlock_data(unlock_data, auth_data, None);
410 assert!(result.is_ok());
411
412 let model = result.expect("should be ok");
413 assert_eq!(model.kdf_type, KdfType::Argon2id);
414 assert_eq!(model.kdf_iterations, 3);
415 assert_eq!(model.kdf_memory, Some(64));
416 assert_eq!(model.kdf_parallelism, Some(4));
417 assert_eq!(model.email, Some(salt.to_string()));
418 assert!(model.master_key_authentication_hash.is_some());
419 assert!(model.master_key_encrypted_user_key.is_some());
420
421 let master_password_unlock_data = MasterPasswordUnlockData {
423 master_key_wrapped_user_key: model
424 .master_key_encrypted_user_key
425 .expect("should be present")
426 .parse()
427 .expect("should parse"),
428 kdf: kdf.clone(),
429 salt: salt.to_string(),
430 };
431 let decrypted_user_key = master_password_unlock_data
432 .unwrap_to_context(password, &mut ctx)
433 .expect("unwrap should succeed");
434 assert_symmetric_keys_equal(user_key_id, decrypted_user_key, &mut ctx);
435 }
436
437 #[test]
438 fn test_reencrypt_unlock_device_key_data() {
439 let store: KeyStore<KeyIds> = KeyStore::default();
440 let mut ctx = store.context_mut();
441
442 let current_user_key_id = ctx.generate_symmetric_key();
443 let new_user_key_id = ctx.generate_symmetric_key();
444 let master_key_unlock_method = create_test_unlock_data();
445
446 let (device_keyset, device_private_key) =
447 PartialRotateableKeyset::make_test_keyset(current_user_key_id, &mut ctx);
448
449 let result = reencrypt_unlock(
450 ReencryptUnlockInput {
451 master_key_unlock_method,
452 trusted_devices: vec![device_keyset],
453 webauthn_credentials: vec![],
454 trusted_organization_keys: vec![],
455 trusted_emergency_access_keys: vec![],
456 },
457 current_user_key_id,
458 new_user_key_id,
459 &mut ctx,
460 );
461
462 let unlock_data = result.expect("should be ok");
463
464 let device_unlock = unlock_data
465 .device_key_unlock_data
466 .as_ref()
467 .expect("should be present")
468 .first()
469 .expect("should have at least one");
470 let decrypted_user_key = device_unlock
471 .encrypted_user_key
472 .parse::<UnsignedSharedKey>()
473 .expect("should parse")
474 .decapsulate(device_private_key, &mut ctx)
475 .expect("unwrap should succeed");
476 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
477 }
478
479 #[test]
480 fn test_reencrypt_unlock_webauthn_prf_credential_data() {
481 let store: KeyStore<KeyIds> = KeyStore::default();
482 let mut ctx = store.context_mut();
483
484 let current_user_key_id = ctx.generate_symmetric_key();
485 let new_user_key_id = ctx.generate_symmetric_key();
486 let master_key_unlock_method = create_test_unlock_data();
487
488 let (credential_keyset, credential_private_key) =
489 PartialRotateableKeyset::make_test_keyset(current_user_key_id, &mut ctx);
490
491 let result = reencrypt_unlock(
492 ReencryptUnlockInput {
493 master_key_unlock_method,
494 trusted_devices: vec![],
495 webauthn_credentials: vec![credential_keyset],
496 trusted_organization_keys: vec![],
497 trusted_emergency_access_keys: vec![],
498 },
499 current_user_key_id,
500 new_user_key_id,
501 &mut ctx,
502 );
503
504 let unlock_data = result.expect("should be ok");
505
506 let credential_unlock = unlock_data
508 .passkey_unlock_data
509 .as_ref()
510 .expect("should be present")
511 .first()
512 .expect("should have at least one");
513 let decrypted_user_key = credential_unlock
514 .encrypted_user_key
515 .parse::<UnsignedSharedKey>()
516 .expect("should parse")
517 .decapsulate(credential_private_key, &mut ctx)
518 .expect("unwrap should succeed");
519 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
520 }
521
522 #[test]
523 fn test_reencrypt_unlock_emergency_access_data() {
524 let store: KeyStore<KeyIds> = KeyStore::default();
525 let mut ctx = store.context_mut();
526
527 let current_user_key_id = ctx.generate_symmetric_key();
528 let new_user_key_id = ctx.generate_symmetric_key();
529 let master_key_unlock_method = create_test_unlock_data();
530
531 let organization_private_key =
532 ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
533 let emergency_access = V1EmergencyAccessMembership {
534 id: Uuid::new_v4(),
535 name: "Test User".to_string(),
536 public_key: ctx
537 .get_public_key(organization_private_key)
538 .expect("key exists"),
539 };
540
541 let result = reencrypt_unlock(
542 ReencryptUnlockInput {
543 master_key_unlock_method,
544 trusted_devices: vec![],
545 webauthn_credentials: vec![],
546 trusted_organization_keys: vec![],
547 trusted_emergency_access_keys: vec![emergency_access],
548 },
549 current_user_key_id,
550 new_user_key_id,
551 &mut ctx,
552 );
553
554 let unlock_data = result.expect("should be ok");
555
556 let emergency_access_unlock = unlock_data
558 .emergency_access_unlock_data
559 .as_ref()
560 .expect("should be present")
561 .first()
562 .expect("should have at least one");
563 let decrypted_user_key = emergency_access_unlock
564 .key_encrypted
565 .as_ref()
566 .map(|k| k.parse::<UnsignedSharedKey>())
567 .expect("should be present")
568 .expect("should parse")
569 .decapsulate(organization_private_key, &mut ctx)
570 .expect("unwrap should succeed");
571 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
572 }
573
574 #[test]
575 fn test_reencrypt_unlock_organization_membership_data() {
576 let store: KeyStore<KeyIds> = KeyStore::default();
577 let mut ctx = store.context_mut();
578
579 let kdf = create_test_kdf_argon2id();
580 let salt = "[email protected]".to_string();
581 let password = "test_password".to_string();
582
583 let current_user_key_id = ctx.generate_symmetric_key();
584 let new_user_key_id = ctx.generate_symmetric_key();
585
586 let org_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
587 let org_membership = V1OrganizationMembership {
588 organization_id: Uuid::new_v4(),
589 name: "Test Org".to_string(),
590 public_key: ctx.get_public_key(org_key).expect("key exists"),
591 };
592
593 let master_key_unlock_method = MasterkeyUnlockMethod::Password {
595 password: password.clone(),
596 hint: None,
597 kdf: kdf.clone(),
598 salt: salt.clone(),
599 };
600
601 let result = reencrypt_unlock(
602 ReencryptUnlockInput {
603 master_key_unlock_method,
604 trusted_devices: vec![],
605 webauthn_credentials: vec![],
606 trusted_organization_keys: vec![org_membership],
607 trusted_emergency_access_keys: vec![],
608 },
609 current_user_key_id,
610 new_user_key_id,
611 &mut ctx,
612 );
613
614 let unlock_data = result.expect("should be ok");
615
616 let org_membership_unlock = unlock_data
617 .organization_account_recovery_unlock_data
618 .as_ref()
619 .expect("should be present")
620 .first()
621 .expect("should have at least one");
622 let decrypted_user_key = org_membership_unlock
623 .reset_password_key
624 .as_ref()
625 .map(|k| k.parse::<UnsignedSharedKey>())
626 .expect("should be present")
627 .expect("should parse")
628 .decapsulate(org_key, &mut ctx)
629 .expect("unwrap should succeed");
630 assert_symmetric_keys_equal(new_user_key_id, decrypted_user_key, &mut ctx);
631 }
632}