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