1use std::sync::RwLock;
12
13use bitwarden_api_api::models::{AccountKeysRequestModel, SecurityStateModel};
14use bitwarden_crypto::{
15 CoseSerializable, CryptoError, EncString, KeyStore, KeyStoreContext,
16 PublicKeyEncryptionAlgorithm, SignatureAlgorithm, SignedPublicKey, SymmetricKeyAlgorithm,
17};
18use bitwarden_encoding::B64;
19use bitwarden_error::bitwarden_error;
20use serde::{Deserialize, Serialize};
21use thiserror::Error;
22use tracing::{info, instrument};
23#[cfg(feature = "wasm")]
24use tsify::Tsify;
25
26use crate::{
27 UserId,
28 key_management::{
29 KeyIds, PrivateKeyId, SecurityState, SignedSecurityState, SigningKeyId, SymmetricKeyId,
30 },
31};
32
33#[derive(Debug, Error)]
35#[bitwarden_error(flat)]
36pub enum AccountCryptographyInitializationError {
37 #[error("The encryption type of the user key does not match the account cryptographic state")]
40 WrongUserKeyType,
41 #[error("Wrong user key")]
44 WrongUserKey,
45 #[error("Decryption succeeded but produced corrupt data")]
47 CorruptData,
48 #[error("Signature or mac verification failed, the data may have been tampered with")]
50 TamperedData,
51 #[error("Key store is already initialized")]
54 KeyStoreAlreadyInitialized,
55 #[error("A generic cryptographic error occurred: {0}")]
57 GenericCrypto(CryptoError),
58}
59
60impl From<CryptoError> for AccountCryptographyInitializationError {
61 fn from(err: CryptoError) -> Self {
62 AccountCryptographyInitializationError::GenericCrypto(err)
63 }
64}
65
66#[derive(Debug, Error)]
68#[bitwarden_error(flat)]
69pub enum RotateCryptographyStateError {
70 #[error("The provided key is missing from the key store")]
72 KeyMissing,
73 #[error("The provided data was invalid")]
75 InvalidData,
76}
77
78#[derive(Clone, Serialize, Deserialize)]
81#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
82#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
83#[allow(clippy::large_enum_variant)]
84pub enum WrappedAccountCryptographicState {
85 V1 {
87 private_key: EncString,
89 },
90 V2 {
95 private_key: EncString,
97 signed_public_key: Option<SignedPublicKey>,
101 signing_key: EncString,
103 security_state: SignedSecurityState,
105 },
106}
107
108impl std::fmt::Debug for WrappedAccountCryptographicState {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 match self {
111 WrappedAccountCryptographicState::V1 { .. } => f
112 .debug_struct("WrappedAccountCryptographicState::V1")
113 .finish(),
114 WrappedAccountCryptographicState::V2 { .. } => f
115 .debug_struct("WrappedAccountCryptographicState::V2")
116 .finish(),
117 }
118 }
119}
120
121impl WrappedAccountCryptographicState {
122 pub fn to_request_model(
126 &self,
127 user_key: &SymmetricKeyId,
128 ctx: &mut KeyStoreContext<KeyIds>,
129 ) -> Result<AccountKeysRequestModel, AccountCryptographyInitializationError> {
130 let private_key = match self {
131 WrappedAccountCryptographicState::V1 { private_key }
132 | WrappedAccountCryptographicState::V2 { private_key, .. } => private_key.clone(),
133 };
134 let private_key_tmp_id = ctx.unwrap_private_key(*user_key, &private_key)?;
135 let public_key = ctx.get_public_key(private_key_tmp_id)?;
136
137 let signature_keypair = match self {
138 WrappedAccountCryptographicState::V1 { .. } => None,
139 WrappedAccountCryptographicState::V2 { signing_key, .. } => {
140 let signing_key_tmp_id = ctx.unwrap_signing_key(*user_key, signing_key)?;
141 let verifying_key = ctx.get_verifying_key(signing_key_tmp_id)?;
142 Some((signing_key.clone(), verifying_key))
143 }
144 };
145
146 Ok(AccountKeysRequestModel {
147 user_key_encrypted_account_private_key: Some(private_key.to_string()),
149 account_public_key: Some(B64::from(public_key.to_der()?).to_string()),
151 signature_key_pair: signature_keypair
152 .as_ref()
153 .map(|(signing_key, verifying_key)| {
154 Box::new(bitwarden_api_api::models::SignatureKeyPairRequestModel {
155 wrapped_signing_key: Some(signing_key.to_string()),
156 verifying_key: Some(B64::from(verifying_key.to_cose()).to_string()),
157 signature_algorithm: Some(match verifying_key.algorithm() {
158 SignatureAlgorithm::Ed25519 => "ed25519".to_string(),
159 }),
160 })
161 }),
162 public_key_encryption_key_pair: Some(Box::new(
163 bitwarden_api_api::models::PublicKeyEncryptionKeyPairRequestModel {
164 wrapped_private_key: match self {
165 WrappedAccountCryptographicState::V1 { private_key }
166 | WrappedAccountCryptographicState::V2 { private_key, .. } => {
167 Some(private_key.to_string())
168 }
169 },
170 public_key: Some(B64::from(public_key.to_der()?).to_string()),
171 signed_public_key: match self.signed_public_key() {
172 Ok(Some(spk)) => Some(spk.clone().into()),
173 _ => None,
174 },
175 },
176 )),
177 security_state: match (self, signature_keypair.as_ref()) {
178 (_, None) | (WrappedAccountCryptographicState::V1 { .. }, Some(_)) => None,
179 (
180 WrappedAccountCryptographicState::V2 { security_state, .. },
181 Some((_, verifying_key)),
182 ) => {
183 Some(Box::new(SecurityStateModel {
185 security_state: Some(security_state.into()),
186 security_version: security_state
187 .to_owned()
188 .verify_and_unwrap(verifying_key)
189 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?
190 .version() as i32,
191 }))
192 }
193 },
194 })
195 }
196
197 pub fn make(
200 ctx: &mut KeyStoreContext<KeyIds>,
201 user_id: UserId,
202 ) -> Result<(SymmetricKeyId, Self), AccountCryptographyInitializationError> {
203 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
204 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
205 let signing_key = ctx.make_signing_key(SignatureAlgorithm::Ed25519);
206 let signed_public_key = ctx.make_signed_public_key(private_key, signing_key)?;
207
208 let security_state = SecurityState::initialize_for_user(user_id);
209 let signed_security_state = security_state.sign(signing_key, ctx)?;
210
211 Ok((
212 user_key,
213 WrappedAccountCryptographicState::V2 {
214 private_key: ctx.wrap_private_key(user_key, private_key)?,
215 signed_public_key: Some(signed_public_key),
216 signing_key: ctx.wrap_signing_key(user_key, signing_key)?,
217 security_state: signed_security_state,
218 },
219 ))
220 }
221
222 #[cfg(test)]
223 fn make_v1(
224 ctx: &mut KeyStoreContext<KeyIds>,
225 ) -> Result<(SymmetricKeyId, Self), AccountCryptographyInitializationError> {
226 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
227 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
228
229 Ok((
230 user_key,
231 WrappedAccountCryptographicState::V1 {
232 private_key: ctx.wrap_private_key(user_key, private_key)?,
233 },
234 ))
235 }
236
237 #[instrument(skip(self, ctx), err)]
240 pub fn rotate(
241 &self,
242 current_user_key: &SymmetricKeyId,
243 new_user_key: &SymmetricKeyId,
244 user_id: UserId,
245 ctx: &mut KeyStoreContext<KeyIds>,
246 ) -> Result<Self, RotateCryptographyStateError> {
247 match self {
248 WrappedAccountCryptographicState::V1 { private_key } => {
249 let private_key_id = ctx
257 .unwrap_private_key(*current_user_key, private_key)
258 .map_err(|_| RotateCryptographyStateError::InvalidData)?;
259 let new_private_key = ctx
260 .wrap_private_key(*new_user_key, private_key_id)
261 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
262
263 let signing_key_id = ctx.make_signing_key(SignatureAlgorithm::Ed25519);
265 let new_signing_key = ctx
266 .wrap_signing_key(*new_user_key, signing_key_id)
267 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
268
269 let signed_public_key = ctx
271 .make_signed_public_key(private_key_id, signing_key_id)
272 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
273
274 let security_state = SecurityState::initialize_for_user(user_id);
276 let signed_security_state = security_state
277 .sign(signing_key_id, ctx)
278 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
279
280 Ok(WrappedAccountCryptographicState::V2 {
281 private_key: new_private_key,
282 signed_public_key: Some(signed_public_key),
283 signing_key: new_signing_key,
284 security_state: signed_security_state,
285 })
286 }
287 WrappedAccountCryptographicState::V2 {
288 private_key,
289 signed_public_key,
290 signing_key,
291 security_state,
292 } => {
293 let private_key_id = ctx
297 .unwrap_private_key(*current_user_key, private_key)
298 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
299 let new_private_key = ctx
300 .wrap_private_key(*new_user_key, private_key_id)
301 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
302
303 let signing_key_id = ctx
305 .unwrap_signing_key(*current_user_key, signing_key)
306 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
307 let new_signing_key = ctx
308 .wrap_signing_key(*new_user_key, signing_key_id)
309 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
310
311 Ok(WrappedAccountCryptographicState::V2 {
312 private_key: new_private_key,
313 signed_public_key: signed_public_key.clone(),
314 signing_key: new_signing_key,
315 security_state: security_state.clone(),
316 })
317 }
318 }
319 }
320
321 pub(crate) fn set_to_context(
326 &self,
327 security_state_rwlock: &RwLock<Option<SecurityState>>,
328 user_key: SymmetricKeyId,
329 store: &KeyStore<KeyIds>,
330 mut ctx: KeyStoreContext<KeyIds>,
331 ) -> Result<(), AccountCryptographyInitializationError> {
332 if ctx.has_symmetric_key(SymmetricKeyId::User)
333 || ctx.has_private_key(PrivateKeyId::UserPrivateKey)
334 || ctx.has_signing_key(SigningKeyId::UserSigningKey)
335 {
336 return Err(AccountCryptographyInitializationError::KeyStoreAlreadyInitialized);
337 }
338
339 match self {
340 WrappedAccountCryptographicState::V1 { private_key } => {
341 info!("Initializing V1 account cryptographic state");
342 if ctx.get_symmetric_key_algorithm(user_key)?
343 != SymmetricKeyAlgorithm::Aes256CbcHmac
344 {
345 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
346 }
347
348 if let Ok(private_key_id) = ctx.unwrap_private_key(user_key, private_key) {
351 ctx.persist_private_key(private_key_id, PrivateKeyId::UserPrivateKey)?;
352 } else {
353 tracing::warn!(
354 "V1 private key could not be unwrapped, skipping setting private key"
355 );
356 }
357
358 ctx.persist_symmetric_key(user_key, SymmetricKeyId::User)?;
359 }
360 WrappedAccountCryptographicState::V2 {
361 private_key,
362 signed_public_key,
363 signing_key,
364 security_state,
365 } => {
366 info!("Initializing V2 account cryptographic state");
367 if ctx.get_symmetric_key_algorithm(user_key)?
368 != SymmetricKeyAlgorithm::XChaCha20Poly1305
369 {
370 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
371 }
372
373 let private_key_id = ctx
374 .unwrap_private_key(user_key, private_key)
375 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
376 let signing_key_id = ctx
377 .unwrap_signing_key(user_key, signing_key)
378 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
379
380 if let Some(signed_public_key) = signed_public_key {
381 signed_public_key
382 .to_owned()
383 .verify_and_unwrap(&ctx.get_verifying_key(signing_key_id)?)
384 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
385 }
386
387 let security_state: SecurityState = security_state
388 .to_owned()
389 .verify_and_unwrap(&ctx.get_verifying_key(signing_key_id)?)
390 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
391 ctx.persist_private_key(private_key_id, PrivateKeyId::UserPrivateKey)?;
392 ctx.persist_signing_key(signing_key_id, SigningKeyId::UserSigningKey)?;
393 ctx.persist_symmetric_key(user_key, SymmetricKeyId::User)?;
394 drop(ctx);
397 store.set_security_state_version(security_state.version());
398 *security_state_rwlock.write().expect("RwLock not poisoned") = Some(security_state);
399 }
400 }
401
402 Ok(())
403 }
404
405 fn signed_public_key(
407 &self,
408 ) -> Result<Option<&SignedPublicKey>, AccountCryptographyInitializationError> {
409 match self {
410 WrappedAccountCryptographicState::V1 { .. } => Ok(None),
411 WrappedAccountCryptographicState::V2 {
412 signed_public_key, ..
413 } => Ok(signed_public_key.as_ref()),
414 }
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use std::{str::FromStr, sync::RwLock};
421
422 use bitwarden_crypto::{KeyStore, PrimitiveEncryptable};
423
424 use super::*;
425 use crate::key_management::{PrivateKeyId, SigningKeyId, SymmetricKeyId};
426
427 #[test]
428 fn test_set_to_context_v1() {
429 let temp_store: KeyStore<KeyIds> = KeyStore::default();
431 let mut temp_ctx = temp_store.context_mut();
432
433 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
435
436 let private_key_id = temp_ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
438 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
439
440 let wrapped = WrappedAccountCryptographicState::V1 {
442 private_key: wrapped_private,
443 };
444 #[allow(deprecated)]
445 let user_key = temp_ctx
446 .dangerous_get_symmetric_key(user_key)
447 .unwrap()
448 .to_owned();
449 drop(temp_ctx);
450 drop(temp_store);
451
452 let store: KeyStore<KeyIds> = KeyStore::default();
454 let mut ctx = store.context_mut();
455 let user_key = ctx.add_local_symmetric_key(user_key);
456 let security_state = RwLock::new(None);
457
458 wrapped
460 .set_to_context(&security_state, user_key, &store, ctx)
461 .unwrap();
462 let ctx = store.context();
463
464 assert!(ctx.has_private_key(PrivateKeyId::UserPrivateKey));
466 assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
467 }
468
469 #[test]
470 fn test_set_to_context_v2() {
471 let temp_store: KeyStore<KeyIds> = KeyStore::default();
473 let mut temp_ctx = temp_store.context_mut();
474
475 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
477
478 let private_key_id = temp_ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
480 let signing_key_id = temp_ctx.make_signing_key(SignatureAlgorithm::Ed25519);
481 let signed_public_key = temp_ctx
482 .make_signed_public_key(private_key_id, signing_key_id)
483 .unwrap();
484
485 let user_id = UserId::new_v4();
487 let security_state = SecurityState::initialize_for_user(user_id);
488 let signed_security_state = security_state.sign(signing_key_id, &mut temp_ctx).unwrap();
489
490 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
492 let wrapped_signing = temp_ctx.wrap_signing_key(user_key, signing_key_id).unwrap();
493
494 let wrapped = WrappedAccountCryptographicState::V2 {
495 private_key: wrapped_private,
496 signed_public_key: Some(signed_public_key),
497 signing_key: wrapped_signing,
498 security_state: signed_security_state,
499 };
500 #[allow(deprecated)]
501 let user_key = temp_ctx
502 .dangerous_get_symmetric_key(user_key)
503 .unwrap()
504 .to_owned();
505 drop(temp_ctx);
506 drop(temp_store);
507
508 let store: KeyStore<KeyIds> = KeyStore::default();
510 let mut ctx = store.context_mut();
511 let user_key = ctx.add_local_symmetric_key(user_key);
512 let security_state = RwLock::new(None);
513
514 wrapped
515 .set_to_context(&security_state, user_key, &store, ctx)
516 .unwrap();
517
518 assert!(store.context().has_symmetric_key(SymmetricKeyId::User));
519 assert!(
521 store
522 .context()
523 .has_private_key(PrivateKeyId::UserPrivateKey)
524 );
525 assert!(
526 store
527 .context()
528 .has_signing_key(SigningKeyId::UserSigningKey)
529 );
530 assert!(security_state.read().unwrap().is_some());
532 }
533
534 #[test]
535 fn test_to_private_keys_request_model_v2() {
536 let temp_store: KeyStore<KeyIds> = KeyStore::default();
537 let mut temp_ctx = temp_store.context_mut();
538 let user_id = UserId::new_v4();
539 let (user_key, wrapped_account_cryptography_state) =
540 WrappedAccountCryptographicState::make(&mut temp_ctx, user_id).unwrap();
541
542 wrapped_account_cryptography_state
543 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
544 .unwrap();
545
546 let mut ctx = temp_store.context_mut();
547 let model = wrapped_account_cryptography_state
548 .to_request_model(&SymmetricKeyId::User, &mut ctx)
549 .expect("to_private_keys_request_model should succeed");
550 drop(ctx);
551
552 let ctx = temp_store.context();
553
554 let sig_pair = model
555 .signature_key_pair
556 .expect("signature_key_pair present");
557 assert_eq!(
558 sig_pair.verifying_key.unwrap(),
559 B64::from(
560 ctx.get_verifying_key(SigningKeyId::UserSigningKey)
561 .unwrap()
562 .to_cose()
563 )
564 .to_string()
565 );
566
567 let pk_pair = model.public_key_encryption_key_pair.unwrap();
568 assert_eq!(
569 pk_pair.public_key.unwrap(),
570 B64::from(
571 ctx.get_public_key(PrivateKeyId::UserPrivateKey)
572 .unwrap()
573 .to_der()
574 .unwrap()
575 )
576 .to_string()
577 );
578
579 let signed_security_state = model
580 .security_state
581 .clone()
582 .expect("security_state present");
583 let security_state =
584 SignedSecurityState::from_str(signed_security_state.security_state.unwrap().as_str())
585 .unwrap()
586 .verify_and_unwrap(&ctx.get_verifying_key(SigningKeyId::UserSigningKey).unwrap())
587 .expect("security state should verify");
588 assert_eq!(
589 security_state.version(),
590 model.security_state.unwrap().security_version as u64
591 );
592 }
593
594 #[test]
595 fn test_set_to_context_v1_corrupt_private_key() {
596 let temp_store: KeyStore<KeyIds> = KeyStore::default();
599 let mut temp_ctx = temp_store.context_mut();
600
601 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
602 let corrupt_private_key = "not a private key"
603 .encrypt(&mut temp_ctx, user_key)
604 .unwrap();
605
606 let wrapped = WrappedAccountCryptographicState::V1 {
608 private_key: corrupt_private_key,
609 };
610
611 #[expect(deprecated)]
612 let user_key_material = temp_ctx
613 .dangerous_get_symmetric_key(user_key)
614 .unwrap()
615 .to_owned();
616 drop(temp_ctx);
617 drop(temp_store);
618
619 let store: KeyStore<KeyIds> = KeyStore::default();
621 let mut ctx = store.context_mut();
622 let user_key = ctx.add_local_symmetric_key(user_key_material);
623 let security_state = RwLock::new(None);
624
625 wrapped
626 .set_to_context(&security_state, user_key, &store, ctx)
627 .unwrap();
628
629 let ctx = store.context();
630
631 assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
633 assert!(!ctx.has_private_key(PrivateKeyId::UserPrivateKey));
635 }
636
637 #[test]
638 fn test_rotate_v1_to_v2() {
639 let store: KeyStore<KeyIds> = KeyStore::default();
641 let mut ctx = store.context_mut();
642
643 let user_id = UserId::new_v4();
645 let (old_user_key_id, wrapped_state) =
646 WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
647 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
648 #[allow(deprecated)]
649 let new_user_key_owned = ctx
650 .dangerous_get_symmetric_key(new_user_key_id)
651 .unwrap()
652 .to_owned();
653 wrapped_state
654 .set_to_context(&RwLock::new(None), old_user_key_id, &store, ctx)
655 .unwrap();
656
657 let mut ctx = store.context_mut();
660 let new_user_key_id = ctx.add_local_symmetric_key(new_user_key_owned.clone());
661
662 let rotated_state = wrapped_state
664 .rotate(&SymmetricKeyId::User, &new_user_key_id, user_id, &mut ctx)
665 .unwrap();
666
667 match rotated_state {
673 WrappedAccountCryptographicState::V2 { .. } => {}
674 _ => panic!("Expected V2 after rotation from V1"),
675 }
676 let store_2 = KeyStore::<KeyIds>::default();
677 let mut ctx_2 = store_2.context_mut();
678 let user_key_id = ctx_2.add_local_symmetric_key(new_user_key_owned.clone());
679 rotated_state
680 .set_to_context(&RwLock::new(None), user_key_id, &store_2, ctx_2)
681 .unwrap();
682 let ctx_2 = store_2.context();
684
685 let public_key_before_rotation = ctx
687 .get_public_key(PrivateKeyId::UserPrivateKey)
688 .expect("Private key should be present in context before rotation");
689 let public_key_after_rotation = ctx_2
690 .get_public_key(PrivateKeyId::UserPrivateKey)
691 .expect("Private key should be present in context after rotation");
692 assert_eq!(
693 public_key_before_rotation.to_der().unwrap(),
694 public_key_after_rotation.to_der().unwrap(),
695 "Private key should be preserved during rotation from V2 to V2"
696 );
697 }
698
699 #[test]
700 fn test_rotate_v2() {
701 let store: KeyStore<KeyIds> = KeyStore::default();
703 let mut ctx = store.context_mut();
704
705 let user_id = UserId::new_v4();
707 let (old_user_key_id, wrapped_state) =
708 WrappedAccountCryptographicState::make(&mut ctx, user_id).unwrap();
709 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
710 #[allow(deprecated)]
711 let new_user_key_owned = ctx
712 .dangerous_get_symmetric_key(new_user_key_id)
713 .unwrap()
714 .to_owned();
715 wrapped_state
716 .set_to_context(&RwLock::new(None), old_user_key_id, &store, ctx)
717 .unwrap();
718
719 let mut ctx = store.context_mut();
722 let new_user_key_id = ctx.add_local_symmetric_key(new_user_key_owned.clone());
723
724 let rotated_state = wrapped_state
726 .rotate(&SymmetricKeyId::User, &new_user_key_id, user_id, &mut ctx)
727 .unwrap();
728
729 match rotated_state {
735 WrappedAccountCryptographicState::V2 { .. } => {}
736 _ => panic!("Expected V2 after rotation from V2"),
737 }
738 let store_2 = KeyStore::<KeyIds>::default();
739 let mut ctx_2 = store_2.context_mut();
740 let user_key_id = ctx_2.add_local_symmetric_key(new_user_key_owned.clone());
741 rotated_state
742 .set_to_context(&RwLock::new(None), user_key_id, &store_2, ctx_2)
743 .unwrap();
744 let ctx_2 = store_2.context();
746
747 let verifying_key_before_rotation = ctx
749 .get_verifying_key(SigningKeyId::UserSigningKey)
750 .expect("Signing key should be present in context before rotation");
751 let verifying_key_after_rotation = ctx_2
752 .get_verifying_key(SigningKeyId::UserSigningKey)
753 .expect("Signing key should be present in context after rotation");
754 assert_eq!(
755 verifying_key_before_rotation.to_cose(),
756 verifying_key_after_rotation.to_cose(),
757 "Signing key should be preserved during rotation from V2 to V2"
758 );
759
760 let public_key_before_rotation = ctx
761 .get_public_key(PrivateKeyId::UserPrivateKey)
762 .expect("Private key should be present in context before rotation");
763 let public_key_after_rotation = ctx_2
764 .get_public_key(PrivateKeyId::UserPrivateKey)
765 .expect("Private key should be present in context after rotation");
766 assert_eq!(
767 public_key_before_rotation.to_der().unwrap(),
768 public_key_after_rotation.to_der().unwrap(),
769 "Private key should be preserved during rotation from V2 to V2"
770 );
771 }
772}