1use std::sync::RwLock;
12
13use bitwarden_api_api::models::{
14 AccountKeysRequestModel, PrivateKeysResponseModel, SecurityStateModel,
15};
16use bitwarden_crypto::{
17 CoseSerializable, CryptoError, EncString, KeyStore, KeyStoreContext,
18 PublicKeyEncryptionAlgorithm, SignatureAlgorithm, SignedPublicKey, SymmetricKeyAlgorithm,
19};
20use bitwarden_encoding::B64;
21use bitwarden_error::bitwarden_error;
22use serde::{Deserialize, Serialize};
23use thiserror::Error;
24use tracing::{info, instrument};
25#[cfg(feature = "wasm")]
26use tsify::Tsify;
27
28use crate::{
29 MissingFieldError,
30 key_management::{
31 KeyIds, PrivateKeyId, SecurityState, SignedSecurityState, SigningKeyId, SymmetricKeyId,
32 },
33 require,
34};
35
36#[derive(Debug, Error)]
38#[bitwarden_error(flat)]
39pub enum AccountCryptographyInitializationError {
40 #[error("The encryption type of the user key does not match the account cryptographic state")]
43 WrongUserKeyType,
44 #[error("Wrong user key")]
47 WrongUserKey,
48 #[error("Decryption succeeded but produced corrupt data")]
50 CorruptData,
51 #[error("Signature or mac verification failed, the data may have been tampered with")]
53 TamperedData,
54 #[error("Key store is already initialized")]
57 KeyStoreAlreadyInitialized,
58 #[error("A generic cryptographic error occurred: {0}")]
60 GenericCrypto(CryptoError),
61}
62
63impl From<CryptoError> for AccountCryptographyInitializationError {
64 fn from(err: CryptoError) -> Self {
65 AccountCryptographyInitializationError::GenericCrypto(err)
66 }
67}
68
69#[derive(Debug, Error)]
71#[bitwarden_error(flat)]
72pub enum RotateCryptographyStateError {
73 #[error("The provided key is missing from the key store")]
75 KeyMissing,
76 #[error("The provided data was invalid")]
78 InvalidData,
79}
80
81#[derive(Debug, Error)]
84pub enum AccountKeysResponseParseError {
85 #[error(transparent)]
87 MissingField(#[from] MissingFieldError),
88 #[error("Malformed field value in API response")]
90 MalformedField,
91 #[error("Inconsistent account cryptographic state in API response")]
93 InconsistentState,
94}
95
96#[derive(Clone, Serialize, Deserialize, PartialEq)]
99#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
100#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
101#[allow(clippy::large_enum_variant)]
102pub enum WrappedAccountCryptographicState {
103 V1 {
105 private_key: EncString,
107 },
108 V2 {
113 private_key: EncString,
115 signed_public_key: Option<SignedPublicKey>,
119 signing_key: EncString,
121 security_state: SignedSecurityState,
123 },
124}
125
126impl std::fmt::Debug for WrappedAccountCryptographicState {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 match self {
129 WrappedAccountCryptographicState::V1 { .. } => f
130 .debug_struct("WrappedAccountCryptographicState::V1")
131 .finish(),
132 WrappedAccountCryptographicState::V2 { security_state, .. } => f
133 .debug_struct("WrappedAccountCryptographicState::V2")
134 .field("security_state", security_state)
135 .finish(),
136 }
137 }
138}
139
140impl TryFrom<&PrivateKeysResponseModel> for WrappedAccountCryptographicState {
141 type Error = AccountKeysResponseParseError;
142
143 fn try_from(response: &PrivateKeysResponseModel) -> Result<Self, Self::Error> {
144 let private_key: EncString =
145 require!(&response.public_key_encryption_key_pair.wrapped_private_key)
146 .parse()
147 .map_err(|_| AccountKeysResponseParseError::MalformedField)?;
148
149 let is_v2_encryption = matches!(private_key, EncString::Cose_Encrypt0_B64 { .. });
150
151 if is_v2_encryption {
152 let signature_key_pair = response
153 .signature_key_pair
154 .as_ref()
155 .ok_or(AccountKeysResponseParseError::InconsistentState)?;
156
157 let signing_key: EncString = require!(&signature_key_pair.wrapped_signing_key)
158 .parse()
159 .map_err(|_| AccountKeysResponseParseError::MalformedField)?;
160
161 let signed_public_key: Option<SignedPublicKey> = response
162 .public_key_encryption_key_pair
163 .signed_public_key
164 .as_ref()
165 .map(|spk| spk.parse())
166 .transpose()
167 .map_err(|_| AccountKeysResponseParseError::MalformedField)?;
168
169 let security_state_model = response
170 .security_state
171 .as_ref()
172 .ok_or(AccountKeysResponseParseError::InconsistentState)?;
173 let security_state: SignedSecurityState =
174 require!(&security_state_model.security_state)
175 .parse()
176 .map_err(|_| AccountKeysResponseParseError::MalformedField)?;
177
178 Ok(WrappedAccountCryptographicState::V2 {
179 private_key,
180 signed_public_key,
181 signing_key,
182 security_state,
183 })
184 } else {
185 if response.signature_key_pair.is_some() || response.security_state.is_some() {
186 return Err(AccountKeysResponseParseError::InconsistentState);
187 }
188
189 Ok(WrappedAccountCryptographicState::V1 { private_key })
190 }
191 }
192}
193
194impl WrappedAccountCryptographicState {
195 #[instrument(skip_all, err)]
199 pub fn to_request_model(
200 &self,
201 user_key: &SymmetricKeyId,
202 ctx: &mut KeyStoreContext<KeyIds>,
203 ) -> Result<AccountKeysRequestModel, AccountCryptographyInitializationError> {
204 let private_key = match self {
205 WrappedAccountCryptographicState::V1 { private_key }
206 | WrappedAccountCryptographicState::V2 { private_key, .. } => private_key.clone(),
207 };
208 let private_key_tmp_id = ctx.unwrap_private_key(*user_key, &private_key)?;
209 let public_key = ctx.get_public_key(private_key_tmp_id)?;
210
211 let signature_keypair = match self {
212 WrappedAccountCryptographicState::V1 { .. } => None,
213 WrappedAccountCryptographicState::V2 { signing_key, .. } => {
214 let signing_key_tmp_id = ctx.unwrap_signing_key(*user_key, signing_key)?;
215 let verifying_key = ctx.get_verifying_key(signing_key_tmp_id)?;
216 Some((signing_key.clone(), verifying_key))
217 }
218 };
219
220 Ok(AccountKeysRequestModel {
221 user_key_encrypted_account_private_key: Some(private_key.to_string()),
223 account_public_key: Some(B64::from(public_key.to_der()?).to_string()),
225 signature_key_pair: signature_keypair
226 .as_ref()
227 .map(|(signing_key, verifying_key)| {
228 Box::new(bitwarden_api_api::models::SignatureKeyPairRequestModel {
229 wrapped_signing_key: Some(signing_key.to_string()),
230 verifying_key: Some(B64::from(verifying_key.to_cose()).to_string()),
231 signature_algorithm: Some(match verifying_key.algorithm() {
232 SignatureAlgorithm::Ed25519 => "ed25519".to_string(),
233 }),
234 })
235 }),
236 public_key_encryption_key_pair: Some(Box::new(
237 bitwarden_api_api::models::PublicKeyEncryptionKeyPairRequestModel {
238 wrapped_private_key: match self {
239 WrappedAccountCryptographicState::V1 { private_key }
240 | WrappedAccountCryptographicState::V2 { private_key, .. } => {
241 Some(private_key.to_string())
242 }
243 },
244 public_key: Some(B64::from(public_key.to_der()?).to_string()),
245 signed_public_key: match self.signed_public_key() {
246 Ok(Some(spk)) => Some(spk.clone().into()),
247 _ => None,
248 },
249 },
250 )),
251 security_state: match (self, signature_keypair.as_ref()) {
252 (_, None) | (WrappedAccountCryptographicState::V1 { .. }, Some(_)) => None,
253 (
254 WrappedAccountCryptographicState::V2 { security_state, .. },
255 Some((_, verifying_key)),
256 ) => {
257 Some(Box::new(SecurityStateModel {
259 security_state: Some(security_state.into()),
260 security_version: security_state
261 .to_owned()
262 .verify_and_unwrap(verifying_key)
263 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?
264 .version() as i32,
265 }))
266 }
267 },
268 })
269 }
270
271 pub fn make(
274 ctx: &mut KeyStoreContext<KeyIds>,
275 ) -> Result<(SymmetricKeyId, Self), AccountCryptographyInitializationError> {
276 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
277 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
278 let signing_key = ctx.make_signing_key(SignatureAlgorithm::Ed25519);
279 let signed_public_key = ctx.make_signed_public_key(private_key, signing_key)?;
280
281 let security_state = SecurityState::new();
282 let signed_security_state = security_state.sign(signing_key, ctx)?;
283
284 Ok((
285 user_key,
286 WrappedAccountCryptographicState::V2 {
287 private_key: ctx.wrap_private_key(user_key, private_key)?,
288 signed_public_key: Some(signed_public_key),
289 signing_key: ctx.wrap_signing_key(user_key, signing_key)?,
290 security_state: signed_security_state,
291 },
292 ))
293 }
294
295 #[cfg(test)]
296 fn make_v1(
297 ctx: &mut KeyStoreContext<KeyIds>,
298 ) -> Result<(SymmetricKeyId, Self), AccountCryptographyInitializationError> {
299 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
300 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
301
302 Ok((
303 user_key,
304 WrappedAccountCryptographicState::V1 {
305 private_key: ctx.wrap_private_key(user_key, private_key)?,
306 },
307 ))
308 }
309
310 #[instrument(skip(self, ctx), err)]
313 pub fn rotate(
314 &self,
315 current_user_key: &SymmetricKeyId,
316 new_user_key: &SymmetricKeyId,
317 ctx: &mut KeyStoreContext<KeyIds>,
318 ) -> Result<Self, RotateCryptographyStateError> {
319 match self {
320 WrappedAccountCryptographicState::V1 { private_key } => {
321 let private_key_id = ctx
329 .unwrap_private_key(*current_user_key, private_key)
330 .map_err(|_| RotateCryptographyStateError::InvalidData)?;
331 let new_private_key = ctx
332 .wrap_private_key(*new_user_key, private_key_id)
333 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
334
335 let signing_key_id = ctx.make_signing_key(SignatureAlgorithm::Ed25519);
337 let new_signing_key = ctx
338 .wrap_signing_key(*new_user_key, signing_key_id)
339 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
340
341 let signed_public_key = ctx
343 .make_signed_public_key(private_key_id, signing_key_id)
344 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
345
346 let security_state = SecurityState::new();
348 let signed_security_state = security_state
349 .sign(signing_key_id, ctx)
350 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
351
352 Ok(WrappedAccountCryptographicState::V2 {
353 private_key: new_private_key,
354 signed_public_key: Some(signed_public_key),
355 signing_key: new_signing_key,
356 security_state: signed_security_state,
357 })
358 }
359 WrappedAccountCryptographicState::V2 {
360 private_key,
361 signed_public_key,
362 signing_key,
363 security_state,
364 } => {
365 let private_key_id = ctx
369 .unwrap_private_key(*current_user_key, private_key)
370 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
371 let new_private_key = ctx
372 .wrap_private_key(*new_user_key, private_key_id)
373 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
374
375 let signing_key_id = ctx
377 .unwrap_signing_key(*current_user_key, signing_key)
378 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
379 let new_signing_key = ctx
380 .wrap_signing_key(*new_user_key, signing_key_id)
381 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
382
383 Ok(WrappedAccountCryptographicState::V2 {
384 private_key: new_private_key,
385 signed_public_key: signed_public_key.clone(),
386 signing_key: new_signing_key,
387 security_state: security_state.clone(),
388 })
389 }
390 }
391 }
392
393 pub(crate) fn set_to_context(
398 &self,
399 security_state_rwlock: &RwLock<Option<SecurityState>>,
400 user_key: SymmetricKeyId,
401 store: &KeyStore<KeyIds>,
402 mut ctx: KeyStoreContext<KeyIds>,
403 ) -> Result<(), AccountCryptographyInitializationError> {
404 if ctx.has_symmetric_key(SymmetricKeyId::User)
405 || ctx.has_private_key(PrivateKeyId::UserPrivateKey)
406 || ctx.has_signing_key(SigningKeyId::UserSigningKey)
407 {
408 return Err(AccountCryptographyInitializationError::KeyStoreAlreadyInitialized);
409 }
410
411 match self {
412 WrappedAccountCryptographicState::V1 { private_key } => {
413 info!(state = ?self, "Initializing V1 account cryptographic state");
414 if ctx.get_symmetric_key_algorithm(user_key)?
415 != SymmetricKeyAlgorithm::Aes256CbcHmac
416 {
417 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
418 }
419
420 if let Ok(private_key_id) = ctx.unwrap_private_key(user_key, private_key) {
423 ctx.persist_private_key(private_key_id, PrivateKeyId::UserPrivateKey)?;
424 } else {
425 tracing::warn!(
426 "V1 private key could not be unwrapped, skipping setting private key"
427 );
428 }
429
430 ctx.persist_symmetric_key(user_key, SymmetricKeyId::User)?;
431 #[cfg(feature = "dangerous-crypto-debug")]
432 #[allow(deprecated)]
433 {
434 let user_key = ctx
435 .dangerous_get_symmetric_key(SymmetricKeyId::User)
436 .expect("User key should be set");
437 let private_key = ctx
438 .dangerous_get_private_key(PrivateKeyId::UserPrivateKey)
439 .ok();
440 let public_key = ctx.get_public_key(PrivateKeyId::UserPrivateKey).ok();
441 info!(
442 ?user_key,
443 ?private_key,
444 ?public_key,
445 "V1 account cryptographic state set to context"
446 );
447 }
448 }
449 WrappedAccountCryptographicState::V2 {
450 private_key,
451 signed_public_key,
452 signing_key,
453 security_state,
454 } => {
455 info!(state = ?self, "Initializing V2 account cryptographic state");
456 if ctx.get_symmetric_key_algorithm(user_key)?
457 != SymmetricKeyAlgorithm::XChaCha20Poly1305
458 {
459 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
460 }
461
462 let private_key_id = ctx
463 .unwrap_private_key(user_key, private_key)
464 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
465 let signing_key_id = ctx
466 .unwrap_signing_key(user_key, signing_key)
467 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
468
469 if let Some(signed_public_key) = signed_public_key {
470 signed_public_key
471 .to_owned()
472 .verify_and_unwrap(&ctx.get_verifying_key(signing_key_id)?)
473 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
474 }
475
476 let verifying_key = ctx.get_verifying_key(signing_key_id)?;
477 let security_state: SecurityState = security_state
478 .to_owned()
479 .verify_and_unwrap(&verifying_key)
480 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
481 info!(
482 security_state_version = security_state.version(),
483 verifying_key = ?verifying_key,
484 "V2 account cryptographic state verified"
485 );
486 ctx.persist_private_key(private_key_id, PrivateKeyId::UserPrivateKey)?;
487 ctx.persist_signing_key(signing_key_id, SigningKeyId::UserSigningKey)?;
488 ctx.persist_symmetric_key(user_key, SymmetricKeyId::User)?;
489
490 #[cfg(feature = "dangerous-crypto-debug")]
491 #[allow(deprecated)]
492 {
493 let user_key = ctx
494 .dangerous_get_symmetric_key(SymmetricKeyId::User)
495 .expect("User key should be set");
496 let private_key = ctx
497 .dangerous_get_private_key(PrivateKeyId::UserPrivateKey)
498 .ok();
499 let signing_key = ctx
500 .dangerous_get_signing_key(SigningKeyId::UserSigningKey)
501 .ok();
502 let verifying_key = ctx.get_verifying_key(SigningKeyId::UserSigningKey).ok();
503 let public_key = ctx.get_public_key(PrivateKeyId::UserPrivateKey).ok();
504 info!(
505 ?user_key,
506 ?private_key,
507 ?signing_key,
508 ?verifying_key,
509 ?public_key,
510 ?signed_public_key,
511 ?security_state,
512 "V2 account cryptographic state set to context."
513 );
514 }
515
516 drop(ctx);
519 store.set_security_state_version(security_state.version());
520 *security_state_rwlock.write().expect("RwLock not poisoned") = Some(security_state);
521 }
522 }
523
524 Ok(())
525 }
526
527 fn signed_public_key(
529 &self,
530 ) -> Result<Option<&SignedPublicKey>, AccountCryptographyInitializationError> {
531 match self {
532 WrappedAccountCryptographicState::V1 { .. } => Ok(None),
533 WrappedAccountCryptographicState::V2 {
534 signed_public_key, ..
535 } => Ok(signed_public_key.as_ref()),
536 }
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use std::{str::FromStr, sync::RwLock};
543
544 use bitwarden_crypto::{KeyStore, PrimitiveEncryptable};
545
546 use super::*;
547 use crate::key_management::{PrivateKeyId, SigningKeyId, SymmetricKeyId};
548
549 #[test]
550 #[ignore = "Manual test to verify debug format"]
551 fn test_debug() {
552 let store: KeyStore<KeyIds> = KeyStore::default();
553 let mut ctx = store.context_mut();
554
555 let (_, v1) = WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
556 println!("{:?}", v1);
557
558 let v1 = format!("{v1:?}");
559 assert!(!v1.contains("private_key"));
560
561 let (_, v2) = WrappedAccountCryptographicState::make(&mut ctx).unwrap();
562 println!("{:?}", v2);
563
564 let v2 = format!("{v2:?}");
565 assert!(!v2.contains("private_key"));
566 assert!(!v2.contains("signed_public_key"));
567 assert!(!v2.contains("signing_key"));
568 }
569
570 #[test]
571 fn test_set_to_context_v1() {
572 let temp_store: KeyStore<KeyIds> = KeyStore::default();
574 let mut temp_ctx = temp_store.context_mut();
575
576 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
578
579 let private_key_id = temp_ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
581 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
582
583 let wrapped = WrappedAccountCryptographicState::V1 {
585 private_key: wrapped_private,
586 };
587 #[allow(deprecated)]
588 let user_key = temp_ctx
589 .dangerous_get_symmetric_key(user_key)
590 .unwrap()
591 .to_owned();
592 drop(temp_ctx);
593 drop(temp_store);
594
595 let store: KeyStore<KeyIds> = KeyStore::default();
597 let mut ctx = store.context_mut();
598 let user_key = ctx.add_local_symmetric_key(user_key);
599 let security_state = RwLock::new(None);
600
601 wrapped
603 .set_to_context(&security_state, user_key, &store, ctx)
604 .unwrap();
605 let ctx = store.context();
606
607 assert!(ctx.has_private_key(PrivateKeyId::UserPrivateKey));
609 assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
610 }
611
612 #[test]
613 fn test_set_to_context_v2() {
614 let temp_store: KeyStore<KeyIds> = KeyStore::default();
616 let mut temp_ctx = temp_store.context_mut();
617
618 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
620
621 let private_key_id = temp_ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
623 let signing_key_id = temp_ctx.make_signing_key(SignatureAlgorithm::Ed25519);
624 let signed_public_key = temp_ctx
625 .make_signed_public_key(private_key_id, signing_key_id)
626 .unwrap();
627
628 let security_state = SecurityState::new();
630 let signed_security_state = security_state.sign(signing_key_id, &mut temp_ctx).unwrap();
631
632 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
634 let wrapped_signing = temp_ctx.wrap_signing_key(user_key, signing_key_id).unwrap();
635
636 let wrapped = WrappedAccountCryptographicState::V2 {
637 private_key: wrapped_private,
638 signed_public_key: Some(signed_public_key),
639 signing_key: wrapped_signing,
640 security_state: signed_security_state,
641 };
642 #[allow(deprecated)]
643 let user_key = temp_ctx
644 .dangerous_get_symmetric_key(user_key)
645 .unwrap()
646 .to_owned();
647 drop(temp_ctx);
648 drop(temp_store);
649
650 let store: KeyStore<KeyIds> = KeyStore::default();
652 let mut ctx = store.context_mut();
653 let user_key = ctx.add_local_symmetric_key(user_key);
654 let security_state = RwLock::new(None);
655
656 wrapped
657 .set_to_context(&security_state, user_key, &store, ctx)
658 .unwrap();
659
660 assert!(store.context().has_symmetric_key(SymmetricKeyId::User));
661 assert!(
663 store
664 .context()
665 .has_private_key(PrivateKeyId::UserPrivateKey)
666 );
667 assert!(
668 store
669 .context()
670 .has_signing_key(SigningKeyId::UserSigningKey)
671 );
672 assert!(security_state.read().unwrap().is_some());
674 }
675
676 #[test]
677 fn test_to_private_keys_request_model_v2() {
678 let temp_store: KeyStore<KeyIds> = KeyStore::default();
679 let mut temp_ctx = temp_store.context_mut();
680 let (user_key, wrapped_account_cryptography_state) =
681 WrappedAccountCryptographicState::make(&mut temp_ctx).unwrap();
682
683 wrapped_account_cryptography_state
684 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
685 .unwrap();
686
687 let mut ctx = temp_store.context_mut();
688 let model = wrapped_account_cryptography_state
689 .to_request_model(&SymmetricKeyId::User, &mut ctx)
690 .expect("to_private_keys_request_model should succeed");
691 drop(ctx);
692
693 let ctx = temp_store.context();
694
695 let sig_pair = model
696 .signature_key_pair
697 .expect("signature_key_pair present");
698 assert_eq!(
699 sig_pair.verifying_key.unwrap(),
700 B64::from(
701 ctx.get_verifying_key(SigningKeyId::UserSigningKey)
702 .unwrap()
703 .to_cose()
704 )
705 .to_string()
706 );
707
708 let pk_pair = model.public_key_encryption_key_pair.unwrap();
709 assert_eq!(
710 pk_pair.public_key.unwrap(),
711 B64::from(
712 ctx.get_public_key(PrivateKeyId::UserPrivateKey)
713 .unwrap()
714 .to_der()
715 .unwrap()
716 )
717 .to_string()
718 );
719
720 let signed_security_state = model
721 .security_state
722 .clone()
723 .expect("security_state present");
724 let security_state =
725 SignedSecurityState::from_str(signed_security_state.security_state.unwrap().as_str())
726 .unwrap()
727 .verify_and_unwrap(&ctx.get_verifying_key(SigningKeyId::UserSigningKey).unwrap())
728 .expect("security state should verify");
729 assert_eq!(
730 security_state.version(),
731 model.security_state.unwrap().security_version as u64
732 );
733 }
734
735 #[test]
736 fn test_set_to_context_v1_corrupt_private_key() {
737 let temp_store: KeyStore<KeyIds> = KeyStore::default();
740 let mut temp_ctx = temp_store.context_mut();
741
742 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
743 let corrupt_private_key = "not a private key"
744 .encrypt(&mut temp_ctx, user_key)
745 .unwrap();
746
747 let wrapped = WrappedAccountCryptographicState::V1 {
749 private_key: corrupt_private_key,
750 };
751
752 #[expect(deprecated)]
753 let user_key_material = temp_ctx
754 .dangerous_get_symmetric_key(user_key)
755 .unwrap()
756 .to_owned();
757 drop(temp_ctx);
758 drop(temp_store);
759
760 let store: KeyStore<KeyIds> = KeyStore::default();
762 let mut ctx = store.context_mut();
763 let user_key = ctx.add_local_symmetric_key(user_key_material);
764 let security_state = RwLock::new(None);
765
766 wrapped
767 .set_to_context(&security_state, user_key, &store, ctx)
768 .unwrap();
769
770 let ctx = store.context();
771
772 assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
774 assert!(!ctx.has_private_key(PrivateKeyId::UserPrivateKey));
776 }
777
778 #[test]
779 fn test_try_from_response_v2_roundtrip() {
780 use bitwarden_api_api::models::{
781 PublicKeyEncryptionKeyPairResponseModel, SecurityStateModel,
782 SignatureKeyPairResponseModel,
783 };
784
785 let temp_store: KeyStore<KeyIds> = KeyStore::default();
786 let mut temp_ctx = temp_store.context_mut();
787 let (user_key, wrapped_state) =
788 WrappedAccountCryptographicState::make(&mut temp_ctx).unwrap();
789
790 wrapped_state
791 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
792 .unwrap();
793
794 let mut ctx = temp_store.context_mut();
795 let request_model = wrapped_state
796 .to_request_model(&SymmetricKeyId::User, &mut ctx)
797 .unwrap();
798 drop(ctx);
799
800 let pk_pair = request_model.public_key_encryption_key_pair.unwrap();
801 let sig_pair = request_model.signature_key_pair.unwrap();
802 let sec_state = request_model.security_state.unwrap();
803
804 let response = PrivateKeysResponseModel {
805 object: None,
806 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
807 object: None,
808 wrapped_private_key: pk_pair.wrapped_private_key,
809 public_key: pk_pair.public_key,
810 signed_public_key: pk_pair.signed_public_key,
811 }),
812 signature_key_pair: Some(Box::new(SignatureKeyPairResponseModel {
813 object: None,
814 wrapped_signing_key: sig_pair.wrapped_signing_key,
815 verifying_key: sig_pair.verifying_key,
816 })),
817 security_state: Some(Box::new(SecurityStateModel {
818 security_state: sec_state.security_state,
819 security_version: sec_state.security_version,
820 })),
821 };
822
823 let parsed = WrappedAccountCryptographicState::try_from(&response)
824 .expect("V2 response should parse successfully");
825
826 assert_eq!(parsed, wrapped_state);
827 }
828
829 #[test]
830 fn test_try_from_response_v1() {
831 use bitwarden_api_api::models::PublicKeyEncryptionKeyPairResponseModel;
832
833 let temp_store: KeyStore<KeyIds> = KeyStore::default();
834 let mut temp_ctx = temp_store.context_mut();
835 let (_user_key, wrapped_state) =
836 WrappedAccountCryptographicState::make_v1(&mut temp_ctx).unwrap();
837
838 let wrapped_private_key = match &wrapped_state {
839 WrappedAccountCryptographicState::V1 { private_key } => private_key.to_string(),
840 _ => panic!("Expected V1"),
841 };
842 drop(temp_ctx);
843
844 let response = PrivateKeysResponseModel {
845 object: None,
846 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
847 object: None,
848 wrapped_private_key: Some(wrapped_private_key),
849 public_key: None,
850 signed_public_key: None,
851 }),
852 signature_key_pair: None,
853 security_state: None,
854 };
855
856 let parsed = WrappedAccountCryptographicState::try_from(&response)
857 .expect("V1 response should parse successfully");
858
859 assert_eq!(parsed, wrapped_state);
860 }
861
862 #[test]
863 fn test_try_from_response_missing_private_key() {
864 use bitwarden_api_api::models::PublicKeyEncryptionKeyPairResponseModel;
865
866 let response = PrivateKeysResponseModel {
867 object: None,
868 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
869 object: None,
870 wrapped_private_key: None,
871 public_key: None,
872 signed_public_key: None,
873 }),
874 signature_key_pair: None,
875 security_state: None,
876 };
877
878 let result = WrappedAccountCryptographicState::try_from(&response);
879 assert!(result.is_err());
880 assert!(
881 matches!(
882 result.unwrap_err(),
883 AccountKeysResponseParseError::MissingField(_)
884 ),
885 "Should return MissingField error"
886 );
887 }
888
889 #[test]
890 fn test_try_from_response_v2_encryption_missing_signature_key_pair() {
891 use bitwarden_api_api::models::PublicKeyEncryptionKeyPairResponseModel;
892
893 let temp_store: KeyStore<KeyIds> = KeyStore::default();
895 let mut temp_ctx = temp_store.context_mut();
896 let (user_key, wrapped_state) =
897 WrappedAccountCryptographicState::make(&mut temp_ctx).unwrap();
898
899 wrapped_state
900 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
901 .unwrap();
902
903 let mut ctx = temp_store.context_mut();
904 let request_model = wrapped_state
905 .to_request_model(&SymmetricKeyId::User, &mut ctx)
906 .unwrap();
907 drop(ctx);
908
909 let pk_pair = request_model.public_key_encryption_key_pair.unwrap();
910
911 let response = PrivateKeysResponseModel {
913 object: None,
914 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
915 object: None,
916 wrapped_private_key: pk_pair.wrapped_private_key,
917 public_key: pk_pair.public_key,
918 signed_public_key: None,
919 }),
920 signature_key_pair: None,
921 security_state: None,
922 };
923
924 let result = WrappedAccountCryptographicState::try_from(&response);
925 assert!(matches!(
926 result.unwrap_err(),
927 AccountKeysResponseParseError::InconsistentState
928 ));
929 }
930
931 #[test]
932 fn test_try_from_response_v1_encryption_with_unexpected_v2_fields() {
933 use bitwarden_api_api::models::{
934 PublicKeyEncryptionKeyPairResponseModel, SignatureKeyPairResponseModel,
935 };
936
937 let temp_store: KeyStore<KeyIds> = KeyStore::default();
939 let mut temp_ctx = temp_store.context_mut();
940 let (_user_key, wrapped_state) =
941 WrappedAccountCryptographicState::make_v1(&mut temp_ctx).unwrap();
942
943 let wrapped_private_key = match &wrapped_state {
944 WrappedAccountCryptographicState::V1 { private_key } => private_key.to_string(),
945 _ => panic!("Expected V1"),
946 };
947 drop(temp_ctx);
948
949 let response = PrivateKeysResponseModel {
951 object: None,
952 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
953 object: None,
954 wrapped_private_key: Some(wrapped_private_key),
955 public_key: None,
956 signed_public_key: None,
957 }),
958 signature_key_pair: Some(Box::new(SignatureKeyPairResponseModel {
959 object: None,
960 wrapped_signing_key: Some("bogus".to_string()),
961 verifying_key: None,
962 })),
963 security_state: None,
964 };
965
966 let result = WrappedAccountCryptographicState::try_from(&response);
967 assert!(matches!(
968 result.unwrap_err(),
969 AccountKeysResponseParseError::InconsistentState
970 ));
971 }
972
973 #[test]
974 fn test_rotate_v1_to_v2() {
975 let store: KeyStore<KeyIds> = KeyStore::default();
977 let mut ctx = store.context_mut();
978
979 let (old_user_key_id, wrapped_state) =
981 WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
982 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
983 #[allow(deprecated)]
984 let new_user_key_owned = ctx
985 .dangerous_get_symmetric_key(new_user_key_id)
986 .unwrap()
987 .to_owned();
988 wrapped_state
989 .set_to_context(&RwLock::new(None), old_user_key_id, &store, ctx)
990 .unwrap();
991
992 let mut ctx = store.context_mut();
995 let new_user_key_id = ctx.add_local_symmetric_key(new_user_key_owned.clone());
996
997 let rotated_state = wrapped_state
999 .rotate(&SymmetricKeyId::User, &new_user_key_id, &mut ctx)
1000 .unwrap();
1001
1002 match rotated_state {
1008 WrappedAccountCryptographicState::V2 { .. } => {}
1009 _ => panic!("Expected V2 after rotation from V1"),
1010 }
1011 let store_2 = KeyStore::<KeyIds>::default();
1012 let mut ctx_2 = store_2.context_mut();
1013 let user_key_id = ctx_2.add_local_symmetric_key(new_user_key_owned.clone());
1014 rotated_state
1015 .set_to_context(&RwLock::new(None), user_key_id, &store_2, ctx_2)
1016 .unwrap();
1017 let ctx_2 = store_2.context();
1019
1020 let public_key_before_rotation = ctx
1022 .get_public_key(PrivateKeyId::UserPrivateKey)
1023 .expect("Private key should be present in context before rotation");
1024 let public_key_after_rotation = ctx_2
1025 .get_public_key(PrivateKeyId::UserPrivateKey)
1026 .expect("Private key should be present in context after rotation");
1027 assert_eq!(
1028 public_key_before_rotation.to_der().unwrap(),
1029 public_key_after_rotation.to_der().unwrap(),
1030 "Private key should be preserved during rotation from V2 to V2"
1031 );
1032 }
1033
1034 #[test]
1035 fn test_rotate_v2() {
1036 let store: KeyStore<KeyIds> = KeyStore::default();
1038 let mut ctx = store.context_mut();
1039
1040 let (old_user_key_id, wrapped_state) =
1042 WrappedAccountCryptographicState::make(&mut ctx).unwrap();
1043 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1044 #[allow(deprecated)]
1045 let new_user_key_owned = ctx
1046 .dangerous_get_symmetric_key(new_user_key_id)
1047 .unwrap()
1048 .to_owned();
1049 wrapped_state
1050 .set_to_context(&RwLock::new(None), old_user_key_id, &store, ctx)
1051 .unwrap();
1052
1053 let mut ctx = store.context_mut();
1056 let new_user_key_id = ctx.add_local_symmetric_key(new_user_key_owned.clone());
1057
1058 let rotated_state = wrapped_state
1060 .rotate(&SymmetricKeyId::User, &new_user_key_id, &mut ctx)
1061 .unwrap();
1062
1063 match rotated_state {
1069 WrappedAccountCryptographicState::V2 { .. } => {}
1070 _ => panic!("Expected V2 after rotation from V2"),
1071 }
1072 let store_2 = KeyStore::<KeyIds>::default();
1073 let mut ctx_2 = store_2.context_mut();
1074 let user_key_id = ctx_2.add_local_symmetric_key(new_user_key_owned.clone());
1075 rotated_state
1076 .set_to_context(&RwLock::new(None), user_key_id, &store_2, ctx_2)
1077 .unwrap();
1078 let ctx_2 = store_2.context();
1080
1081 let verifying_key_before_rotation = ctx
1083 .get_verifying_key(SigningKeyId::UserSigningKey)
1084 .expect("Signing key should be present in context before rotation");
1085 let verifying_key_after_rotation = ctx_2
1086 .get_verifying_key(SigningKeyId::UserSigningKey)
1087 .expect("Signing key should be present in context after rotation");
1088 assert_eq!(
1089 verifying_key_before_rotation.to_cose(),
1090 verifying_key_after_rotation.to_cose(),
1091 "Signing key should be preserved during rotation from V2 to V2"
1092 );
1093
1094 let public_key_before_rotation = ctx
1095 .get_public_key(PrivateKeyId::UserPrivateKey)
1096 .expect("Private key should be present in context before rotation");
1097 let public_key_after_rotation = ctx_2
1098 .get_public_key(PrivateKeyId::UserPrivateKey)
1099 .expect("Private key should be present in context after rotation");
1100 assert_eq!(
1101 public_key_before_rotation.to_der().unwrap(),
1102 public_key_after_rotation.to_der().unwrap(),
1103 "Private key should be preserved during rotation from V2 to V2"
1104 );
1105 }
1106}