1use std::sync::RwLock;
12
13use bitwarden_api_api::models::{
14 AccountKeysRequestModel, PrivateKeysResponseModel, SecurityStateModel,
15 WrappedAccountCryptographicStateRequestModel,
16};
17use bitwarden_crypto::{
18 CoseSerializable, CryptoError, EncString, KeyStore, KeyStoreContext,
19 PublicKeyEncryptionAlgorithm, SignatureAlgorithm, SignedPublicKey, SymmetricKeyAlgorithm,
20};
21use bitwarden_encoding::B64;
22use bitwarden_error::bitwarden_error;
23use serde::{Deserialize, Serialize};
24use thiserror::Error;
25use tracing::info;
26#[cfg(feature = "wasm")]
27use tsify::Tsify;
28
29use crate::{
30 MissingFieldError,
31 key_management::{
32 KeySlotIds, PrivateKeySlotId, SecurityState, SignedSecurityState, SigningKeySlotId,
33 SymmetricKeySlotId,
34 },
35 require,
36};
37
38#[derive(Debug, Error)]
40#[bitwarden_error(flat)]
41pub enum AccountCryptographyInitializationError {
42 #[error("The encryption type of the user key does not match the account cryptographic state")]
45 WrongUserKeyType,
46 #[error("Wrong user key")]
49 WrongUserKey,
50 #[error("Decryption succeeded but produced corrupt data")]
52 CorruptData,
53 #[error("Signature or mac verification failed, the data may have been tampered with")]
55 TamperedData,
56 #[error("A generic cryptographic error occurred: {0}")]
58 GenericCrypto(CryptoError),
59}
60
61impl From<CryptoError> for AccountCryptographyInitializationError {
62 fn from(err: CryptoError) -> Self {
63 AccountCryptographyInitializationError::GenericCrypto(err)
64 }
65}
66
67#[derive(Debug, Error)]
69#[bitwarden_error(flat)]
70pub enum RotateCryptographyStateError {
71 #[error("The provided key is missing from the key store")]
73 KeyMissing,
74 #[error("The provided data was invalid")]
76 InvalidData,
77}
78
79#[derive(Debug, Error)]
82pub enum AccountKeysResponseParseError {
83 #[error(transparent)]
85 MissingField(#[from] MissingFieldError),
86 #[error("Malformed field value in API response")]
88 MalformedField,
89 #[error("Inconsistent account cryptographic state in API response")]
91 InconsistentState,
92}
93
94#[derive(Clone, Serialize, Deserialize, PartialEq)]
97#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
98#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
99#[allow(clippy::large_enum_variant)]
100pub enum WrappedAccountCryptographicState {
101 V1 {
103 private_key: EncString,
105 },
106 V2 {
111 private_key: EncString,
113 signed_public_key: Option<SignedPublicKey>,
117 signing_key: EncString,
119 security_state: SignedSecurityState,
121 },
122}
123
124#[cfg(feature = "wasm")]
125impl TryFrom<wasm_bindgen::JsValue> for WrappedAccountCryptographicState {
126 type Error = serde_wasm_bindgen::Error;
127
128 fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
129 serde_wasm_bindgen::from_value(value)
130 }
131}
132
133impl std::fmt::Debug for WrappedAccountCryptographicState {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 WrappedAccountCryptographicState::V1 { .. } => f
137 .debug_struct("WrappedAccountCryptographicState::V1")
138 .finish(),
139 WrappedAccountCryptographicState::V2 { security_state, .. } => f
140 .debug_struct("WrappedAccountCryptographicState::V2")
141 .field("security_state", security_state)
142 .finish(),
143 }
144 }
145}
146
147impl TryFrom<&PrivateKeysResponseModel> for WrappedAccountCryptographicState {
148 type Error = AccountKeysResponseParseError;
149
150 fn try_from(response: &PrivateKeysResponseModel) -> Result<Self, Self::Error> {
151 let private_key: EncString =
152 require!(&response.public_key_encryption_key_pair.wrapped_private_key)
153 .parse()
154 .map_err(|_| AccountKeysResponseParseError::MalformedField)?;
155
156 let is_v2_encryption = matches!(private_key, EncString::Cose_Encrypt0_B64 { .. });
157
158 if is_v2_encryption {
159 let signature_key_pair = response
160 .signature_key_pair
161 .as_ref()
162 .ok_or(AccountKeysResponseParseError::InconsistentState)?;
163
164 let signing_key: EncString = require!(&signature_key_pair.wrapped_signing_key)
165 .parse()
166 .map_err(|_| AccountKeysResponseParseError::MalformedField)?;
167
168 let signed_public_key: Option<SignedPublicKey> = response
169 .public_key_encryption_key_pair
170 .signed_public_key
171 .as_ref()
172 .map(|spk| spk.parse())
173 .transpose()
174 .map_err(|_| AccountKeysResponseParseError::MalformedField)?;
175
176 let security_state_model = response
177 .security_state
178 .as_ref()
179 .ok_or(AccountKeysResponseParseError::InconsistentState)?;
180 let security_state: SignedSecurityState =
181 require!(&security_state_model.security_state)
182 .parse()
183 .map_err(|_| AccountKeysResponseParseError::MalformedField)?;
184
185 Ok(WrappedAccountCryptographicState::V2 {
186 private_key,
187 signed_public_key,
188 signing_key,
189 security_state,
190 })
191 } else {
192 if response.signature_key_pair.is_some() || response.security_state.is_some() {
193 return Err(AccountKeysResponseParseError::InconsistentState);
194 }
195
196 Ok(WrappedAccountCryptographicState::V1 { private_key })
197 }
198 }
199}
200
201impl WrappedAccountCryptographicState {
202 pub fn to_wrapped_request_model(
207 &self,
208 user_key: &SymmetricKeySlotId,
209 ctx: &mut KeyStoreContext<KeySlotIds>,
210 ) -> Result<WrappedAccountCryptographicStateRequestModel, AccountCryptographyInitializationError>
211 {
212 match self {
213 WrappedAccountCryptographicState::V1 { .. } => {
214 Err(AccountCryptographyInitializationError::WrongUserKeyType)
215 }
216 WrappedAccountCryptographicState::V2 {
217 private_key,
218 signing_key,
219 security_state,
220 signed_public_key,
221 ..
222 } => {
223 let private_key = private_key.clone();
224 let private_key_tmp_id = ctx.unwrap_private_key(*user_key, &private_key)?;
225 let public_key = ctx.get_public_key(private_key_tmp_id)?;
226
227 let signing_key_tmp_id = ctx.unwrap_signing_key(*user_key, signing_key)?;
228 let verifying_key = ctx.get_verifying_key(signing_key_tmp_id)?;
229
230 Ok(WrappedAccountCryptographicStateRequestModel {
231 signature_key_pair: Box::new(
232 bitwarden_api_api::models::SignatureKeyPairRequestModel {
233 wrapped_signing_key: Some(signing_key.to_string()),
234 verifying_key: Some(B64::from(verifying_key.to_cose()).to_string()),
235 signature_algorithm: Some(verifying_key.algorithm().to_string()),
236 },
237 ),
238 public_key_encryption_key_pair: Box::new(
239 bitwarden_api_api::models::PublicKeyEncryptionKeyPairRequestModel {
240 wrapped_private_key: Some(private_key.to_string()),
241 public_key: Some(B64::from(public_key.to_der()?).to_string()),
242 signed_public_key: signed_public_key.clone().map(|spk| spk.into()),
243 },
244 ),
245 security_state: Box::new(SecurityStateModel {
247 security_state: Some(security_state.into()),
248 security_version: security_state
249 .to_owned()
250 .verify_and_unwrap(&verifying_key)
251 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?
252 .version() as i32,
253 }),
254 })
255 }
256 }
257 }
258
259 #[bitwarden_logging::instrument(err)]
263 pub fn to_request_model(
264 &self,
265 user_key: &SymmetricKeySlotId,
266 ctx: &mut KeyStoreContext<KeySlotIds>,
267 ) -> Result<AccountKeysRequestModel, AccountCryptographyInitializationError> {
268 let private_key = match self {
269 WrappedAccountCryptographicState::V1 { private_key }
270 | WrappedAccountCryptographicState::V2 { private_key, .. } => private_key.clone(),
271 };
272 let private_key_tmp_id = ctx.unwrap_private_key(*user_key, &private_key)?;
273 let public_key = ctx.get_public_key(private_key_tmp_id)?;
274
275 let signature_keypair = match self {
276 WrappedAccountCryptographicState::V1 { .. } => None,
277 WrappedAccountCryptographicState::V2 { signing_key, .. } => {
278 let signing_key_tmp_id = ctx.unwrap_signing_key(*user_key, signing_key)?;
279 let verifying_key = ctx.get_verifying_key(signing_key_tmp_id)?;
280 Some((signing_key.clone(), verifying_key))
281 }
282 };
283
284 Ok(AccountKeysRequestModel {
285 user_key_encrypted_account_private_key: Some(private_key.to_string()),
287 account_public_key: Some(B64::from(public_key.to_der()?).to_string()),
289 signature_key_pair: signature_keypair
290 .as_ref()
291 .map(|(signing_key, verifying_key)| {
292 Box::new(bitwarden_api_api::models::SignatureKeyPairRequestModel {
293 wrapped_signing_key: Some(signing_key.to_string()),
294 verifying_key: Some(B64::from(verifying_key.to_cose()).to_string()),
295 signature_algorithm: Some(verifying_key.algorithm().to_string()),
296 })
297 }),
298 public_key_encryption_key_pair: Some(Box::new(
299 bitwarden_api_api::models::PublicKeyEncryptionKeyPairRequestModel {
300 wrapped_private_key: match self {
301 WrappedAccountCryptographicState::V1 { private_key }
302 | WrappedAccountCryptographicState::V2 { private_key, .. } => {
303 Some(private_key.to_string())
304 }
305 },
306 public_key: Some(B64::from(public_key.to_der()?).to_string()),
307 signed_public_key: match self.signed_public_key() {
308 Ok(Some(spk)) => Some(spk.clone().into()),
309 _ => None,
310 },
311 },
312 )),
313 security_state: match (self, signature_keypair.as_ref()) {
314 (_, None) | (WrappedAccountCryptographicState::V1 { .. }, Some(_)) => None,
315 (
316 WrappedAccountCryptographicState::V2 { security_state, .. },
317 Some((_, verifying_key)),
318 ) => {
319 Some(Box::new(SecurityStateModel {
321 security_state: Some(security_state.into()),
322 security_version: security_state
323 .to_owned()
324 .verify_and_unwrap(verifying_key)
325 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?
326 .version() as i32,
327 }))
328 }
329 },
330 })
331 }
332
333 pub fn make(
336 ctx: &mut KeyStoreContext<KeySlotIds>,
337 ) -> Result<(SymmetricKeySlotId, Self), AccountCryptographyInitializationError> {
338 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
339 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
340 let signing_key = ctx.make_signing_key(SignatureAlgorithm::MlDsa44);
341 let signed_public_key = ctx.make_signed_public_key(private_key, signing_key)?;
342
343 let security_state = SecurityState::new();
344 let signed_security_state = security_state.sign(signing_key, ctx)?;
345
346 Ok((
347 user_key,
348 WrappedAccountCryptographicState::V2 {
349 private_key: ctx.wrap_private_key(user_key, private_key)?,
350 signed_public_key: Some(signed_public_key),
351 signing_key: ctx.wrap_signing_key(user_key, signing_key)?,
352 security_state: signed_security_state,
353 },
354 ))
355 }
356
357 #[cfg(test)]
358 pub(crate) fn make_v1(
359 ctx: &mut KeyStoreContext<KeySlotIds>,
360 ) -> Result<(SymmetricKeySlotId, Self), AccountCryptographyInitializationError> {
361 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
362 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
363
364 Ok((
365 user_key,
366 WrappedAccountCryptographicState::V1 {
367 private_key: ctx.wrap_private_key(user_key, private_key)?,
368 },
369 ))
370 }
371
372 #[bitwarden_logging::instrument(err)]
379 pub fn get_from_key_store(
380 ctx: &KeyStoreContext<KeySlotIds>,
381 ) -> Result<Self, RotateCryptographyStateError> {
382 if !ctx
383 .is_v1_symmetric_key(SymmetricKeySlotId::User)
384 .map_err(|_| RotateCryptographyStateError::KeyMissing)?
385 {
386 return Err(RotateCryptographyStateError::InvalidData);
387 }
388
389 let private_key = ctx
390 .wrap_private_key(SymmetricKeySlotId::User, PrivateKeySlotId::UserPrivateKey)
391 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
392
393 Ok(WrappedAccountCryptographicState::V1 { private_key })
394 }
395
396 #[bitwarden_logging::instrument(err, fields(current_user_key = ?current_user_key, new_user_key = ?new_user_key))]
399 pub fn rotate(
400 &self,
401 current_user_key: &SymmetricKeySlotId,
402 new_user_key: &SymmetricKeySlotId,
403 ctx: &mut KeyStoreContext<KeySlotIds>,
404 ) -> Result<Self, RotateCryptographyStateError> {
405 match self {
406 WrappedAccountCryptographicState::V1 { private_key } => {
407 let private_key_id = ctx
415 .unwrap_private_key(*current_user_key, private_key)
416 .map_err(|_| RotateCryptographyStateError::InvalidData)?;
417 let new_private_key = ctx
418 .wrap_private_key(*new_user_key, private_key_id)
419 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
420
421 let signing_key_id = ctx.make_signing_key(SignatureAlgorithm::MlDsa44);
423 let new_signing_key = ctx
424 .wrap_signing_key(*new_user_key, signing_key_id)
425 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
426
427 let signed_public_key = ctx
429 .make_signed_public_key(private_key_id, signing_key_id)
430 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
431
432 let security_state = SecurityState::new();
434 let signed_security_state = security_state
435 .sign(signing_key_id, ctx)
436 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
437
438 Ok(WrappedAccountCryptographicState::V2 {
439 private_key: new_private_key,
440 signed_public_key: Some(signed_public_key),
441 signing_key: new_signing_key,
442 security_state: signed_security_state,
443 })
444 }
445 WrappedAccountCryptographicState::V2 {
446 private_key,
447 signed_public_key,
448 signing_key,
449 security_state,
450 } => {
451 let private_key_id = ctx
455 .unwrap_private_key(*current_user_key, private_key)
456 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
457 let new_private_key = ctx
458 .wrap_private_key(*new_user_key, private_key_id)
459 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
460
461 let signing_key_id = ctx
463 .unwrap_signing_key(*current_user_key, signing_key)
464 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
465 let new_signing_key = ctx
466 .wrap_signing_key(*new_user_key, signing_key_id)
467 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
468
469 Ok(WrappedAccountCryptographicState::V2 {
470 private_key: new_private_key,
471 signed_public_key: signed_public_key.clone(),
472 signing_key: new_signing_key,
473 security_state: security_state.clone(),
474 })
475 }
476 }
477 }
478
479 pub(crate) fn set_to_context(
484 &self,
485 security_state_rwlock: &RwLock<Option<SecurityState>>,
486 user_key: SymmetricKeySlotId,
487 store: &KeyStore<KeySlotIds>,
488 mut ctx: KeyStoreContext<KeySlotIds>,
489 ) -> Result<(), AccountCryptographyInitializationError> {
490 match self {
491 WrappedAccountCryptographicState::V1 { private_key } => {
492 info!(state = ?self, "Initializing V1 account cryptographic state");
493 if ctx.get_symmetric_key_algorithm(user_key)?
494 != SymmetricKeyAlgorithm::Aes256CbcHmac
495 {
496 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
497 }
498
499 if let Ok(private_key_id) = ctx.unwrap_private_key(user_key, private_key) {
502 ctx.persist_private_key(private_key_id, PrivateKeySlotId::UserPrivateKey)?;
503 } else {
504 tracing::warn!(
505 "V1 private key could not be unwrapped, skipping setting private key"
506 );
507 }
508
509 ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)?;
510 #[cfg(feature = "dangerous-crypto-debug")]
511 #[allow(deprecated)]
512 {
513 let user_key = ctx
514 .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
515 .expect("User key should be set");
516 let private_key = ctx
517 .dangerous_get_private_key(PrivateKeySlotId::UserPrivateKey)
518 .ok();
519 let public_key = ctx.get_public_key(PrivateKeySlotId::UserPrivateKey).ok();
520 info!(
521 ?user_key,
522 ?private_key,
523 ?public_key,
524 "V1 account cryptographic state set to context"
525 );
526 }
527 }
528 WrappedAccountCryptographicState::V2 {
529 private_key,
530 signed_public_key,
531 signing_key,
532 security_state,
533 } => {
534 info!(state = ?self, "Initializing V2 account cryptographic state");
535 if !matches!(
536 ctx.get_symmetric_key_algorithm(user_key)?,
537 SymmetricKeyAlgorithm::XAes256Gcm
538 ) {
539 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
540 }
541
542 let private_key_id = ctx
543 .unwrap_private_key(user_key, private_key)
544 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
545 let signing_key_id = ctx
546 .unwrap_signing_key(user_key, signing_key)
547 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
548
549 if let Some(signed_public_key) = signed_public_key {
550 signed_public_key
551 .to_owned()
552 .verify_and_unwrap(&ctx.get_verifying_key(signing_key_id)?)
553 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
554 }
555
556 let verifying_key = ctx.get_verifying_key(signing_key_id)?;
557 let security_state: SecurityState = security_state
558 .to_owned()
559 .verify_and_unwrap(&verifying_key)
560 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
561 info!(
562 security_state_version = security_state.version(),
563 verifying_key = ?verifying_key,
564 "V2 account cryptographic state verified"
565 );
566 ctx.persist_private_key(private_key_id, PrivateKeySlotId::UserPrivateKey)?;
567 ctx.persist_signing_key(signing_key_id, SigningKeySlotId::UserSigningKey)?;
568 ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)?;
569
570 #[cfg(feature = "dangerous-crypto-debug")]
571 #[allow(deprecated)]
572 {
573 let user_key = ctx
574 .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
575 .expect("User key should be set");
576 let private_key = ctx
577 .dangerous_get_private_key(PrivateKeySlotId::UserPrivateKey)
578 .ok();
579 let signing_key = ctx
580 .dangerous_get_signing_key(SigningKeySlotId::UserSigningKey)
581 .ok();
582 let verifying_key =
583 ctx.get_verifying_key(SigningKeySlotId::UserSigningKey).ok();
584 let public_key = ctx.get_public_key(PrivateKeySlotId::UserPrivateKey).ok();
585 info!(
586 ?user_key,
587 ?private_key,
588 ?signing_key,
589 ?verifying_key,
590 ?public_key,
591 ?signed_public_key,
592 ?security_state,
593 "V2 account cryptographic state set to context."
594 );
595 }
596
597 drop(ctx);
600 store.set_security_state_version(security_state.version());
601 *security_state_rwlock.write().expect("RwLock not poisoned") = Some(security_state);
602 }
603 }
604
605 Ok(())
606 }
607
608 fn signed_public_key(
610 &self,
611 ) -> Result<Option<&SignedPublicKey>, AccountCryptographyInitializationError> {
612 match self {
613 WrappedAccountCryptographicState::V1 { .. } => Ok(None),
614 WrappedAccountCryptographicState::V2 {
615 signed_public_key, ..
616 } => Ok(signed_public_key.as_ref()),
617 }
618 }
619}
620
621#[cfg(test)]
622mod tests {
623 use std::{str::FromStr, sync::RwLock};
624
625 use bitwarden_crypto::{KeyStore, PrimitiveEncryptable};
626
627 use super::*;
628 use crate::key_management::{PrivateKeySlotId, SigningKeySlotId, SymmetricKeySlotId};
629
630 #[test]
631 #[ignore = "Manual test to verify debug format"]
632 fn test_debug() {
633 let store: KeyStore<KeySlotIds> = KeyStore::default();
634 let mut ctx = store.context_mut();
635
636 let (_, v1) = WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
637 println!("{:?}", v1);
638
639 let v1 = format!("{v1:?}");
640 assert!(!v1.contains("private_key"));
641
642 let (_, v2) = WrappedAccountCryptographicState::make(&mut ctx).unwrap();
643 println!("{:?}", v2);
644
645 let v2 = format!("{v2:?}");
646 assert!(!v2.contains("private_key"));
647 assert!(!v2.contains("signed_public_key"));
648 assert!(!v2.contains("signing_key"));
649 }
650
651 #[test]
652 fn test_set_to_context_v1() {
653 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
655 let mut temp_ctx = temp_store.context_mut();
656
657 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
659
660 let private_key_id = temp_ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
662 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
663
664 let wrapped = WrappedAccountCryptographicState::V1 {
666 private_key: wrapped_private,
667 };
668 #[allow(deprecated)]
669 let user_key = temp_ctx
670 .dangerous_get_symmetric_key(user_key)
671 .unwrap()
672 .to_owned();
673 drop(temp_ctx);
674 drop(temp_store);
675
676 let store: KeyStore<KeySlotIds> = KeyStore::default();
678 let mut ctx = store.context_mut();
679 let user_key = ctx.add_local_symmetric_key(user_key);
680 let security_state = RwLock::new(None);
681
682 wrapped
684 .set_to_context(&security_state, user_key, &store, ctx)
685 .unwrap();
686 let ctx = store.context();
687
688 assert!(ctx.has_private_key(PrivateKeySlotId::UserPrivateKey));
690 assert!(ctx.has_symmetric_key(SymmetricKeySlotId::User));
691 }
692
693 #[test]
694 fn test_set_to_context_v2() {
695 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
697 let mut temp_ctx = temp_store.context_mut();
698
699 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
701
702 let private_key_id = temp_ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
704 let signing_key_id = temp_ctx.make_signing_key(SignatureAlgorithm::Ed25519);
705 let signed_public_key = temp_ctx
706 .make_signed_public_key(private_key_id, signing_key_id)
707 .unwrap();
708
709 let security_state = SecurityState::new();
711 let signed_security_state = security_state.sign(signing_key_id, &mut temp_ctx).unwrap();
712
713 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
715 let wrapped_signing = temp_ctx.wrap_signing_key(user_key, signing_key_id).unwrap();
716
717 let wrapped = WrappedAccountCryptographicState::V2 {
718 private_key: wrapped_private,
719 signed_public_key: Some(signed_public_key),
720 signing_key: wrapped_signing,
721 security_state: signed_security_state,
722 };
723 #[allow(deprecated)]
724 let user_key = temp_ctx
725 .dangerous_get_symmetric_key(user_key)
726 .unwrap()
727 .to_owned();
728 drop(temp_ctx);
729 drop(temp_store);
730
731 let store: KeyStore<KeySlotIds> = KeyStore::default();
733 let mut ctx = store.context_mut();
734 let user_key = ctx.add_local_symmetric_key(user_key);
735 let security_state = RwLock::new(None);
736
737 wrapped
738 .set_to_context(&security_state, user_key, &store, ctx)
739 .unwrap();
740
741 assert!(store.context().has_symmetric_key(SymmetricKeySlotId::User));
742 assert!(
744 store
745 .context()
746 .has_private_key(PrivateKeySlotId::UserPrivateKey)
747 );
748 assert!(
749 store
750 .context()
751 .has_signing_key(SigningKeySlotId::UserSigningKey)
752 );
753 assert!(security_state.read().unwrap().is_some());
755 }
756
757 #[test]
758 fn test_to_private_keys_request_model_v2() {
759 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
760 let mut temp_ctx = temp_store.context_mut();
761 let (user_key, wrapped_account_cryptography_state) =
762 WrappedAccountCryptographicState::make(&mut temp_ctx).unwrap();
763
764 wrapped_account_cryptography_state
765 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
766 .unwrap();
767
768 let mut ctx = temp_store.context_mut();
769 let model = wrapped_account_cryptography_state
770 .to_request_model(&SymmetricKeySlotId::User, &mut ctx)
771 .expect("to_private_keys_request_model should succeed");
772 drop(ctx);
773
774 let ctx = temp_store.context();
775
776 let sig_pair = model
777 .signature_key_pair
778 .expect("signature_key_pair present");
779 assert_eq!(
780 sig_pair.verifying_key.unwrap(),
781 B64::from(
782 ctx.get_verifying_key(SigningKeySlotId::UserSigningKey)
783 .unwrap()
784 .to_cose()
785 )
786 .to_string()
787 );
788
789 let pk_pair = model.public_key_encryption_key_pair.unwrap();
790 assert_eq!(
791 pk_pair.public_key.unwrap(),
792 B64::from(
793 ctx.get_public_key(PrivateKeySlotId::UserPrivateKey)
794 .unwrap()
795 .to_der()
796 .unwrap()
797 )
798 .to_string()
799 );
800
801 let signed_security_state = model
802 .security_state
803 .clone()
804 .expect("security_state present");
805 let security_state =
806 SignedSecurityState::from_str(signed_security_state.security_state.unwrap().as_str())
807 .unwrap()
808 .verify_and_unwrap(
809 &ctx.get_verifying_key(SigningKeySlotId::UserSigningKey)
810 .unwrap(),
811 )
812 .expect("security state should verify");
813 assert_eq!(
814 security_state.version(),
815 model.security_state.unwrap().security_version as u64
816 );
817 }
818
819 #[test]
820 fn test_set_to_context_v1_corrupt_private_key() {
821 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
824 let mut temp_ctx = temp_store.context_mut();
825
826 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
827 let corrupt_private_key = "not a private key"
828 .encrypt(&mut temp_ctx, user_key)
829 .unwrap();
830
831 let wrapped = WrappedAccountCryptographicState::V1 {
833 private_key: corrupt_private_key,
834 };
835
836 #[expect(deprecated)]
837 let user_key_material = temp_ctx
838 .dangerous_get_symmetric_key(user_key)
839 .unwrap()
840 .to_owned();
841 drop(temp_ctx);
842 drop(temp_store);
843
844 let store: KeyStore<KeySlotIds> = KeyStore::default();
846 let mut ctx = store.context_mut();
847 let user_key = ctx.add_local_symmetric_key(user_key_material);
848 let security_state = RwLock::new(None);
849
850 wrapped
851 .set_to_context(&security_state, user_key, &store, ctx)
852 .unwrap();
853
854 let ctx = store.context();
855
856 assert!(ctx.has_symmetric_key(SymmetricKeySlotId::User));
858 assert!(!ctx.has_private_key(PrivateKeySlotId::UserPrivateKey));
860 }
861
862 #[test]
863 fn test_try_from_response_v2_roundtrip() {
864 use bitwarden_api_api::models::{
865 PublicKeyEncryptionKeyPairResponseModel, SecurityStateModel,
866 SignatureKeyPairResponseModel,
867 };
868
869 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
870 let mut temp_ctx = temp_store.context_mut();
871 let (user_key, wrapped_state) =
872 WrappedAccountCryptographicState::make(&mut temp_ctx).unwrap();
873
874 wrapped_state
875 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
876 .unwrap();
877
878 let mut ctx = temp_store.context_mut();
879 let request_model = wrapped_state
880 .to_request_model(&SymmetricKeySlotId::User, &mut ctx)
881 .unwrap();
882 drop(ctx);
883
884 let pk_pair = request_model.public_key_encryption_key_pair.unwrap();
885 let sig_pair = request_model.signature_key_pair.unwrap();
886 let sec_state = request_model.security_state.unwrap();
887
888 let response = PrivateKeysResponseModel {
889 object: None,
890 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
891 object: None,
892 wrapped_private_key: pk_pair.wrapped_private_key,
893 public_key: pk_pair.public_key,
894 signed_public_key: pk_pair.signed_public_key,
895 }),
896 signature_key_pair: Some(Box::new(SignatureKeyPairResponseModel {
897 object: None,
898 wrapped_signing_key: sig_pair.wrapped_signing_key,
899 verifying_key: sig_pair.verifying_key,
900 })),
901 security_state: Some(Box::new(SecurityStateModel {
902 security_state: sec_state.security_state,
903 security_version: sec_state.security_version,
904 })),
905 };
906
907 let parsed = WrappedAccountCryptographicState::try_from(&response)
908 .expect("V2 response should parse successfully");
909
910 assert_eq!(parsed, wrapped_state);
911 }
912
913 #[test]
914 fn test_try_from_response_v1() {
915 use bitwarden_api_api::models::PublicKeyEncryptionKeyPairResponseModel;
916
917 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
918 let mut temp_ctx = temp_store.context_mut();
919 let (_user_key, wrapped_state) =
920 WrappedAccountCryptographicState::make_v1(&mut temp_ctx).unwrap();
921
922 let wrapped_private_key = match &wrapped_state {
923 WrappedAccountCryptographicState::V1 { private_key } => private_key.to_string(),
924 _ => panic!("Expected V1"),
925 };
926 drop(temp_ctx);
927
928 let response = PrivateKeysResponseModel {
929 object: None,
930 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
931 object: None,
932 wrapped_private_key: Some(wrapped_private_key),
933 public_key: None,
934 signed_public_key: None,
935 }),
936 signature_key_pair: None,
937 security_state: None,
938 };
939
940 let parsed = WrappedAccountCryptographicState::try_from(&response)
941 .expect("V1 response should parse successfully");
942
943 assert_eq!(parsed, wrapped_state);
944 }
945
946 #[test]
947 fn test_try_from_response_missing_private_key() {
948 use bitwarden_api_api::models::PublicKeyEncryptionKeyPairResponseModel;
949
950 let response = PrivateKeysResponseModel {
951 object: None,
952 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
953 object: None,
954 wrapped_private_key: None,
955 public_key: None,
956 signed_public_key: None,
957 }),
958 signature_key_pair: None,
959 security_state: None,
960 };
961
962 let result = WrappedAccountCryptographicState::try_from(&response);
963 assert!(result.is_err());
964 assert!(
965 matches!(
966 result.unwrap_err(),
967 AccountKeysResponseParseError::MissingField(_)
968 ),
969 "Should return MissingField error"
970 );
971 }
972
973 #[test]
974 fn test_try_from_response_v2_encryption_missing_signature_key_pair() {
975 use bitwarden_api_api::models::PublicKeyEncryptionKeyPairResponseModel;
976
977 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
979 let mut temp_ctx = temp_store.context_mut();
980 let (user_key, wrapped_state) =
981 WrappedAccountCryptographicState::make(&mut temp_ctx).unwrap();
982
983 wrapped_state
984 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
985 .unwrap();
986
987 let mut ctx = temp_store.context_mut();
988 let request_model = wrapped_state
989 .to_request_model(&SymmetricKeySlotId::User, &mut ctx)
990 .unwrap();
991 drop(ctx);
992
993 let pk_pair = request_model.public_key_encryption_key_pair.unwrap();
994
995 let response = PrivateKeysResponseModel {
997 object: None,
998 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
999 object: None,
1000 wrapped_private_key: pk_pair.wrapped_private_key,
1001 public_key: pk_pair.public_key,
1002 signed_public_key: None,
1003 }),
1004 signature_key_pair: None,
1005 security_state: None,
1006 };
1007
1008 let result = WrappedAccountCryptographicState::try_from(&response);
1009 assert!(matches!(
1010 result.unwrap_err(),
1011 AccountKeysResponseParseError::InconsistentState
1012 ));
1013 }
1014
1015 #[test]
1016 fn test_try_from_response_v1_encryption_with_unexpected_v2_fields() {
1017 use bitwarden_api_api::models::{
1018 PublicKeyEncryptionKeyPairResponseModel, SignatureKeyPairResponseModel,
1019 };
1020
1021 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
1023 let mut temp_ctx = temp_store.context_mut();
1024 let (_user_key, wrapped_state) =
1025 WrappedAccountCryptographicState::make_v1(&mut temp_ctx).unwrap();
1026
1027 let wrapped_private_key = match &wrapped_state {
1028 WrappedAccountCryptographicState::V1 { private_key } => private_key.to_string(),
1029 _ => panic!("Expected V1"),
1030 };
1031 drop(temp_ctx);
1032
1033 let response = PrivateKeysResponseModel {
1035 object: None,
1036 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
1037 object: None,
1038 wrapped_private_key: Some(wrapped_private_key),
1039 public_key: None,
1040 signed_public_key: None,
1041 }),
1042 signature_key_pair: Some(Box::new(SignatureKeyPairResponseModel {
1043 object: None,
1044 wrapped_signing_key: Some("bogus".to_string()),
1045 verifying_key: None,
1046 })),
1047 security_state: None,
1048 };
1049
1050 let result = WrappedAccountCryptographicState::try_from(&response);
1051 assert!(matches!(
1052 result.unwrap_err(),
1053 AccountKeysResponseParseError::InconsistentState
1054 ));
1055 }
1056
1057 #[test]
1058 fn test_rotate_v1_to_v2() {
1059 let store: KeyStore<KeySlotIds> = KeyStore::default();
1061 let mut ctx = store.context_mut();
1062
1063 let (old_user_key_id, wrapped_state) =
1065 WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
1066 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
1067 #[allow(deprecated)]
1068 let new_user_key_owned = ctx
1069 .dangerous_get_symmetric_key(new_user_key_id)
1070 .unwrap()
1071 .to_owned();
1072 wrapped_state
1073 .set_to_context(&RwLock::new(None), old_user_key_id, &store, ctx)
1074 .unwrap();
1075
1076 let mut ctx = store.context_mut();
1079 let new_user_key_id = ctx.add_local_symmetric_key(new_user_key_owned.clone());
1080
1081 let rotated_state = wrapped_state
1083 .rotate(&SymmetricKeySlotId::User, &new_user_key_id, &mut ctx)
1084 .unwrap();
1085
1086 match rotated_state {
1092 WrappedAccountCryptographicState::V2 { .. } => {}
1093 _ => panic!("Expected V2 after rotation from V1"),
1094 }
1095 let store_2 = KeyStore::<KeySlotIds>::default();
1096 let mut ctx_2 = store_2.context_mut();
1097 let user_key_id = ctx_2.add_local_symmetric_key(new_user_key_owned.clone());
1098 rotated_state
1099 .set_to_context(&RwLock::new(None), user_key_id, &store_2, ctx_2)
1100 .unwrap();
1101 let ctx_2 = store_2.context();
1103
1104 let public_key_before_rotation = ctx
1106 .get_public_key(PrivateKeySlotId::UserPrivateKey)
1107 .expect("Private key should be present in context before rotation");
1108 let public_key_after_rotation = ctx_2
1109 .get_public_key(PrivateKeySlotId::UserPrivateKey)
1110 .expect("Private key should be present in context after rotation");
1111 assert_eq!(
1112 public_key_before_rotation.to_der().unwrap(),
1113 public_key_after_rotation.to_der().unwrap(),
1114 "Private key should be preserved during rotation from V2 to V2"
1115 );
1116 }
1117
1118 #[test]
1119 fn test_rotate_v2() {
1120 let store: KeyStore<KeySlotIds> = KeyStore::default();
1122 let mut ctx = store.context_mut();
1123
1124 let (old_user_key_id, wrapped_state) =
1126 WrappedAccountCryptographicState::make(&mut ctx).unwrap();
1127 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
1128 #[allow(deprecated)]
1129 let new_user_key_owned = ctx
1130 .dangerous_get_symmetric_key(new_user_key_id)
1131 .unwrap()
1132 .to_owned();
1133 wrapped_state
1134 .set_to_context(&RwLock::new(None), old_user_key_id, &store, ctx)
1135 .unwrap();
1136
1137 let mut ctx = store.context_mut();
1140 let new_user_key_id = ctx.add_local_symmetric_key(new_user_key_owned.clone());
1141
1142 let rotated_state = wrapped_state
1144 .rotate(&SymmetricKeySlotId::User, &new_user_key_id, &mut ctx)
1145 .unwrap();
1146
1147 match rotated_state {
1153 WrappedAccountCryptographicState::V2 { .. } => {}
1154 _ => panic!("Expected V2 after rotation from V2"),
1155 }
1156 let store_2 = KeyStore::<KeySlotIds>::default();
1157 let mut ctx_2 = store_2.context_mut();
1158 let user_key_id = ctx_2.add_local_symmetric_key(new_user_key_owned.clone());
1159 rotated_state
1160 .set_to_context(&RwLock::new(None), user_key_id, &store_2, ctx_2)
1161 .unwrap();
1162 let ctx_2 = store_2.context();
1164
1165 let verifying_key_before_rotation = ctx
1167 .get_verifying_key(SigningKeySlotId::UserSigningKey)
1168 .expect("Signing key should be present in context before rotation");
1169 let verifying_key_after_rotation = ctx_2
1170 .get_verifying_key(SigningKeySlotId::UserSigningKey)
1171 .expect("Signing key should be present in context after rotation");
1172 assert_eq!(
1173 verifying_key_before_rotation.to_cose(),
1174 verifying_key_after_rotation.to_cose(),
1175 "Signing key should be preserved during rotation from V2 to V2"
1176 );
1177
1178 let public_key_before_rotation = ctx
1179 .get_public_key(PrivateKeySlotId::UserPrivateKey)
1180 .expect("Private key should be present in context before rotation");
1181 let public_key_after_rotation = ctx_2
1182 .get_public_key(PrivateKeySlotId::UserPrivateKey)
1183 .expect("Private key should be present in context after rotation");
1184 assert_eq!(
1185 public_key_before_rotation.to_der().unwrap(),
1186 public_key_after_rotation.to_der().unwrap(),
1187 "Private key should be preserved during rotation from V2 to V2"
1188 );
1189 }
1190
1191 #[test]
1192 fn test_to_wrapped_request_model_v1_returns_wrong_user_key_type() {
1193 let store: KeyStore<KeySlotIds> = KeyStore::default();
1194 let mut ctx = store.context_mut();
1195 let (user_key_id, wrapped) = WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
1196 let result = wrapped.to_wrapped_request_model(&user_key_id, &mut ctx);
1197 assert!(matches!(
1198 result.unwrap_err(),
1199 AccountCryptographyInitializationError::WrongUserKeyType
1200 ));
1201 }
1202
1203 #[test]
1204 fn test_to_wrapped_request_model_v2() {
1205 let store: KeyStore<KeySlotIds> = KeyStore::default();
1206 let mut ctx = store.context_mut();
1207 let (user_key_id, wrapped) = WrappedAccountCryptographicState::make(&mut ctx).unwrap();
1208 let result = wrapped
1209 .to_wrapped_request_model(&user_key_id, &mut ctx)
1210 .unwrap();
1211
1212 let wrapped_signing_key_str = result
1213 .signature_key_pair
1214 .wrapped_signing_key
1215 .as_ref()
1216 .unwrap();
1217 assert!(!wrapped_signing_key_str.is_empty());
1218
1219 let enc_signing_key: EncString = wrapped_signing_key_str.parse().unwrap();
1220 let signing_key_tmp = ctx
1221 .unwrap_signing_key(user_key_id, &enc_signing_key)
1222 .unwrap();
1223 let verifying_key = ctx.get_verifying_key(signing_key_tmp).unwrap();
1224 let expected = B64::from(verifying_key.to_cose()).to_string();
1225 assert!(
1226 result
1227 .signature_key_pair
1228 .verifying_key
1229 .as_ref()
1230 .is_some_and(|s| s == &expected),
1231 "verifying_key should match expected value"
1232 );
1233
1234 assert_eq!(
1235 result.signature_key_pair.signature_algorithm.as_deref(),
1236 Some("mldsa44")
1237 );
1238
1239 assert!(
1240 result
1241 .public_key_encryption_key_pair
1242 .wrapped_private_key
1243 .as_ref()
1244 .is_some_and(|s| !s.is_empty()),
1245 "wrapped_private_key should be non-empty"
1246 );
1247 let wrapped_private_key_str = result
1248 .public_key_encryption_key_pair
1249 .wrapped_private_key
1250 .as_ref()
1251 .unwrap();
1252 let enc_private_key: EncString = wrapped_private_key_str.parse().unwrap();
1253 let private_key_tmp = ctx
1254 .unwrap_private_key(user_key_id, &enc_private_key)
1255 .unwrap();
1256 let public_key = ctx.get_public_key(private_key_tmp).unwrap();
1257
1258 let expected = B64::from(public_key.to_der().unwrap()).to_string();
1259 assert!(
1260 result
1261 .public_key_encryption_key_pair
1262 .public_key
1263 .as_ref()
1264 .is_some_and(|s| s == &expected),
1265 "public_key should match expected value"
1266 );
1267 assert!(
1268 result
1269 .public_key_encryption_key_pair
1270 .signed_public_key
1271 .is_some(),
1272 "signed_public_key should be present"
1273 );
1274 assert!(
1275 result
1276 .security_state
1277 .security_state
1278 .as_ref()
1279 .is_some_and(|s| !s.is_empty()),
1280 "security_state string should be non-empty"
1281 );
1282 assert!(result.security_state.security_version == 2);
1283 }
1284
1285 #[test]
1286 fn test_to_wrapped_request_model_wrong_user_key_returns_error() {
1287 let store: KeyStore<KeySlotIds> = KeyStore::default();
1288 let mut ctx = store.context_mut();
1289 let (_user_key_id, wrapped) = WrappedAccountCryptographicState::make(&mut ctx).unwrap();
1290
1291 let wrong_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
1293
1294 let result = wrapped.to_wrapped_request_model(&wrong_user_key_id, &mut ctx);
1295 assert!(result.is_err());
1296 assert!(!matches!(
1298 result.unwrap_err(),
1299 AccountCryptographyInitializationError::WrongUserKeyType
1300 ));
1301 }
1302
1303 #[test]
1304 fn test_get_from_key_store_v1() {
1305 let store: KeyStore<KeySlotIds> = KeyStore::default();
1306 let mut ctx = store.context_mut();
1307 let (user_key, state) = WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
1308 state
1309 .set_to_context(&RwLock::new(None), user_key, &store, ctx)
1310 .unwrap();
1311
1312 let ctx = store.context();
1313 let result = WrappedAccountCryptographicState::get_from_key_store(&ctx);
1314 assert!(result.is_ok());
1315 assert!(matches!(
1316 result.unwrap(),
1317 WrappedAccountCryptographicState::V1 { .. }
1318 ));
1319 }
1320
1321 #[test]
1322 fn test_get_from_key_store_v2_returns_error() {
1323 let store: KeyStore<KeySlotIds> = KeyStore::default();
1324 let mut ctx = store.context_mut();
1325 let (user_key, state) = WrappedAccountCryptographicState::make(&mut ctx).unwrap();
1326 state
1327 .set_to_context(&RwLock::new(None), user_key, &store, ctx)
1328 .unwrap();
1329
1330 let ctx = store.context();
1331 let result = WrappedAccountCryptographicState::get_from_key_store(&ctx);
1332 assert!(matches!(
1333 result,
1334 Err(RotateCryptographyStateError::InvalidData)
1335 ));
1336 }
1337
1338 #[test]
1339 fn test_get_from_key_store_no_user_key() {
1340 let store: KeyStore<KeySlotIds> = KeyStore::default();
1341 let ctx = store.context();
1342 let result = WrappedAccountCryptographicState::get_from_key_store(&ctx);
1343 assert!(matches!(
1344 result,
1345 Err(RotateCryptographyStateError::KeyMissing)
1346 ));
1347 }
1348}