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 #[instrument(skip_all, err)]
126 pub fn to_request_model(
127 &self,
128 user_key: &SymmetricKeyId,
129 ctx: &mut KeyStoreContext<KeyIds>,
130 ) -> Result<AccountKeysRequestModel, AccountCryptographyInitializationError> {
131 let private_key = match self {
132 WrappedAccountCryptographicState::V1 { private_key }
133 | WrappedAccountCryptographicState::V2 { private_key, .. } => private_key.clone(),
134 };
135 let private_key_tmp_id = ctx.unwrap_private_key(*user_key, &private_key)?;
136 let public_key = ctx.get_public_key(private_key_tmp_id)?;
137
138 let signature_keypair = match self {
139 WrappedAccountCryptographicState::V1 { .. } => None,
140 WrappedAccountCryptographicState::V2 { signing_key, .. } => {
141 let signing_key_tmp_id = ctx.unwrap_signing_key(*user_key, signing_key)?;
142 let verifying_key = ctx.get_verifying_key(signing_key_tmp_id)?;
143 Some((signing_key.clone(), verifying_key))
144 }
145 };
146
147 Ok(AccountKeysRequestModel {
148 user_key_encrypted_account_private_key: Some(private_key.to_string()),
150 account_public_key: Some(B64::from(public_key.to_der()?).to_string()),
152 signature_key_pair: signature_keypair
153 .as_ref()
154 .map(|(signing_key, verifying_key)| {
155 Box::new(bitwarden_api_api::models::SignatureKeyPairRequestModel {
156 wrapped_signing_key: Some(signing_key.to_string()),
157 verifying_key: Some(B64::from(verifying_key.to_cose()).to_string()),
158 signature_algorithm: Some(match verifying_key.algorithm() {
159 SignatureAlgorithm::Ed25519 => "ed25519".to_string(),
160 }),
161 })
162 }),
163 public_key_encryption_key_pair: Some(Box::new(
164 bitwarden_api_api::models::PublicKeyEncryptionKeyPairRequestModel {
165 wrapped_private_key: match self {
166 WrappedAccountCryptographicState::V1 { private_key }
167 | WrappedAccountCryptographicState::V2 { private_key, .. } => {
168 Some(private_key.to_string())
169 }
170 },
171 public_key: Some(B64::from(public_key.to_der()?).to_string()),
172 signed_public_key: match self.signed_public_key() {
173 Ok(Some(spk)) => Some(spk.clone().into()),
174 _ => None,
175 },
176 },
177 )),
178 security_state: match (self, signature_keypair.as_ref()) {
179 (_, None) | (WrappedAccountCryptographicState::V1 { .. }, Some(_)) => None,
180 (
181 WrappedAccountCryptographicState::V2 { security_state, .. },
182 Some((_, verifying_key)),
183 ) => {
184 Some(Box::new(SecurityStateModel {
186 security_state: Some(security_state.into()),
187 security_version: security_state
188 .to_owned()
189 .verify_and_unwrap(verifying_key)
190 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?
191 .version() as i32,
192 }))
193 }
194 },
195 })
196 }
197
198 pub fn make(
201 ctx: &mut KeyStoreContext<KeyIds>,
202 user_id: UserId,
203 ) -> Result<(SymmetricKeyId, Self), AccountCryptographyInitializationError> {
204 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
205 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
206 let signing_key = ctx.make_signing_key(SignatureAlgorithm::Ed25519);
207 let signed_public_key = ctx.make_signed_public_key(private_key, signing_key)?;
208
209 let security_state = SecurityState::initialize_for_user(user_id);
210 let signed_security_state = security_state.sign(signing_key, ctx)?;
211
212 Ok((
213 user_key,
214 WrappedAccountCryptographicState::V2 {
215 private_key: ctx.wrap_private_key(user_key, private_key)?,
216 signed_public_key: Some(signed_public_key),
217 signing_key: ctx.wrap_signing_key(user_key, signing_key)?,
218 security_state: signed_security_state,
219 },
220 ))
221 }
222
223 #[cfg(test)]
224 fn make_v1(
225 ctx: &mut KeyStoreContext<KeyIds>,
226 ) -> Result<(SymmetricKeyId, Self), AccountCryptographyInitializationError> {
227 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
228 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
229
230 Ok((
231 user_key,
232 WrappedAccountCryptographicState::V1 {
233 private_key: ctx.wrap_private_key(user_key, private_key)?,
234 },
235 ))
236 }
237
238 #[instrument(skip(self, ctx), err)]
241 pub fn rotate(
242 &self,
243 current_user_key: &SymmetricKeyId,
244 new_user_key: &SymmetricKeyId,
245 user_id: UserId,
246 ctx: &mut KeyStoreContext<KeyIds>,
247 ) -> Result<Self, RotateCryptographyStateError> {
248 match self {
249 WrappedAccountCryptographicState::V1 { private_key } => {
250 let private_key_id = ctx
258 .unwrap_private_key(*current_user_key, private_key)
259 .map_err(|_| RotateCryptographyStateError::InvalidData)?;
260 let new_private_key = ctx
261 .wrap_private_key(*new_user_key, private_key_id)
262 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
263
264 let signing_key_id = ctx.make_signing_key(SignatureAlgorithm::Ed25519);
266 let new_signing_key = ctx
267 .wrap_signing_key(*new_user_key, signing_key_id)
268 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
269
270 let signed_public_key = ctx
272 .make_signed_public_key(private_key_id, signing_key_id)
273 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
274
275 let security_state = SecurityState::initialize_for_user(user_id);
277 let signed_security_state = security_state
278 .sign(signing_key_id, ctx)
279 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
280
281 Ok(WrappedAccountCryptographicState::V2 {
282 private_key: new_private_key,
283 signed_public_key: Some(signed_public_key),
284 signing_key: new_signing_key,
285 security_state: signed_security_state,
286 })
287 }
288 WrappedAccountCryptographicState::V2 {
289 private_key,
290 signed_public_key,
291 signing_key,
292 security_state,
293 } => {
294 let private_key_id = ctx
298 .unwrap_private_key(*current_user_key, private_key)
299 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
300 let new_private_key = ctx
301 .wrap_private_key(*new_user_key, private_key_id)
302 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
303
304 let signing_key_id = ctx
306 .unwrap_signing_key(*current_user_key, signing_key)
307 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
308 let new_signing_key = ctx
309 .wrap_signing_key(*new_user_key, signing_key_id)
310 .map_err(|_| RotateCryptographyStateError::KeyMissing)?;
311
312 Ok(WrappedAccountCryptographicState::V2 {
313 private_key: new_private_key,
314 signed_public_key: signed_public_key.clone(),
315 signing_key: new_signing_key,
316 security_state: security_state.clone(),
317 })
318 }
319 }
320 }
321
322 pub(crate) fn set_to_context(
327 &self,
328 security_state_rwlock: &RwLock<Option<SecurityState>>,
329 user_key: SymmetricKeyId,
330 store: &KeyStore<KeyIds>,
331 mut ctx: KeyStoreContext<KeyIds>,
332 ) -> Result<(), AccountCryptographyInitializationError> {
333 if ctx.has_symmetric_key(SymmetricKeyId::User)
334 || ctx.has_private_key(PrivateKeyId::UserPrivateKey)
335 || ctx.has_signing_key(SigningKeyId::UserSigningKey)
336 {
337 return Err(AccountCryptographyInitializationError::KeyStoreAlreadyInitialized);
338 }
339
340 match self {
341 WrappedAccountCryptographicState::V1 { private_key } => {
342 info!("Initializing V1 account cryptographic state");
343 if ctx.get_symmetric_key_algorithm(user_key)?
344 != SymmetricKeyAlgorithm::Aes256CbcHmac
345 {
346 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
347 }
348
349 if let Ok(private_key_id) = ctx.unwrap_private_key(user_key, private_key) {
352 ctx.persist_private_key(private_key_id, PrivateKeyId::UserPrivateKey)?;
353 } else {
354 tracing::warn!(
355 "V1 private key could not be unwrapped, skipping setting private key"
356 );
357 }
358
359 ctx.persist_symmetric_key(user_key, SymmetricKeyId::User)?;
360 }
361 WrappedAccountCryptographicState::V2 {
362 private_key,
363 signed_public_key,
364 signing_key,
365 security_state,
366 } => {
367 info!("Initializing V2 account cryptographic state");
368 if ctx.get_symmetric_key_algorithm(user_key)?
369 != SymmetricKeyAlgorithm::XChaCha20Poly1305
370 {
371 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
372 }
373
374 let private_key_id = ctx
375 .unwrap_private_key(user_key, private_key)
376 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
377 let signing_key_id = ctx
378 .unwrap_signing_key(user_key, signing_key)
379 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
380
381 if let Some(signed_public_key) = signed_public_key {
382 signed_public_key
383 .to_owned()
384 .verify_and_unwrap(&ctx.get_verifying_key(signing_key_id)?)
385 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
386 }
387
388 let security_state: SecurityState = security_state
389 .to_owned()
390 .verify_and_unwrap(&ctx.get_verifying_key(signing_key_id)?)
391 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
392 ctx.persist_private_key(private_key_id, PrivateKeyId::UserPrivateKey)?;
393 ctx.persist_signing_key(signing_key_id, SigningKeyId::UserSigningKey)?;
394 ctx.persist_symmetric_key(user_key, SymmetricKeyId::User)?;
395 drop(ctx);
398 store.set_security_state_version(security_state.version());
399 *security_state_rwlock.write().expect("RwLock not poisoned") = Some(security_state);
400 }
401 }
402
403 Ok(())
404 }
405
406 fn signed_public_key(
408 &self,
409 ) -> Result<Option<&SignedPublicKey>, AccountCryptographyInitializationError> {
410 match self {
411 WrappedAccountCryptographicState::V1 { .. } => Ok(None),
412 WrappedAccountCryptographicState::V2 {
413 signed_public_key, ..
414 } => Ok(signed_public_key.as_ref()),
415 }
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use std::{str::FromStr, sync::RwLock};
422
423 use bitwarden_crypto::{KeyStore, PrimitiveEncryptable};
424
425 use super::*;
426 use crate::key_management::{PrivateKeyId, SigningKeyId, SymmetricKeyId};
427
428 #[test]
429 fn test_set_to_context_v1() {
430 let temp_store: KeyStore<KeyIds> = KeyStore::default();
432 let mut temp_ctx = temp_store.context_mut();
433
434 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
436
437 let private_key_id = temp_ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
439 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
440
441 let wrapped = WrappedAccountCryptographicState::V1 {
443 private_key: wrapped_private,
444 };
445 #[allow(deprecated)]
446 let user_key = temp_ctx
447 .dangerous_get_symmetric_key(user_key)
448 .unwrap()
449 .to_owned();
450 drop(temp_ctx);
451 drop(temp_store);
452
453 let store: KeyStore<KeyIds> = KeyStore::default();
455 let mut ctx = store.context_mut();
456 let user_key = ctx.add_local_symmetric_key(user_key);
457 let security_state = RwLock::new(None);
458
459 wrapped
461 .set_to_context(&security_state, user_key, &store, ctx)
462 .unwrap();
463 let ctx = store.context();
464
465 assert!(ctx.has_private_key(PrivateKeyId::UserPrivateKey));
467 assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
468 }
469
470 #[test]
471 fn test_set_to_context_v2() {
472 let temp_store: KeyStore<KeyIds> = KeyStore::default();
474 let mut temp_ctx = temp_store.context_mut();
475
476 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
478
479 let private_key_id = temp_ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
481 let signing_key_id = temp_ctx.make_signing_key(SignatureAlgorithm::Ed25519);
482 let signed_public_key = temp_ctx
483 .make_signed_public_key(private_key_id, signing_key_id)
484 .unwrap();
485
486 let user_id = UserId::new_v4();
488 let security_state = SecurityState::initialize_for_user(user_id);
489 let signed_security_state = security_state.sign(signing_key_id, &mut temp_ctx).unwrap();
490
491 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
493 let wrapped_signing = temp_ctx.wrap_signing_key(user_key, signing_key_id).unwrap();
494
495 let wrapped = WrappedAccountCryptographicState::V2 {
496 private_key: wrapped_private,
497 signed_public_key: Some(signed_public_key),
498 signing_key: wrapped_signing,
499 security_state: signed_security_state,
500 };
501 #[allow(deprecated)]
502 let user_key = temp_ctx
503 .dangerous_get_symmetric_key(user_key)
504 .unwrap()
505 .to_owned();
506 drop(temp_ctx);
507 drop(temp_store);
508
509 let store: KeyStore<KeyIds> = KeyStore::default();
511 let mut ctx = store.context_mut();
512 let user_key = ctx.add_local_symmetric_key(user_key);
513 let security_state = RwLock::new(None);
514
515 wrapped
516 .set_to_context(&security_state, user_key, &store, ctx)
517 .unwrap();
518
519 assert!(store.context().has_symmetric_key(SymmetricKeyId::User));
520 assert!(
522 store
523 .context()
524 .has_private_key(PrivateKeyId::UserPrivateKey)
525 );
526 assert!(
527 store
528 .context()
529 .has_signing_key(SigningKeyId::UserSigningKey)
530 );
531 assert!(security_state.read().unwrap().is_some());
533 }
534
535 #[test]
536 fn test_to_private_keys_request_model_v2() {
537 let temp_store: KeyStore<KeyIds> = KeyStore::default();
538 let mut temp_ctx = temp_store.context_mut();
539 let user_id = UserId::new_v4();
540 let (user_key, wrapped_account_cryptography_state) =
541 WrappedAccountCryptographicState::make(&mut temp_ctx, user_id).unwrap();
542
543 wrapped_account_cryptography_state
544 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
545 .unwrap();
546
547 let mut ctx = temp_store.context_mut();
548 let model = wrapped_account_cryptography_state
549 .to_request_model(&SymmetricKeyId::User, &mut ctx)
550 .expect("to_private_keys_request_model should succeed");
551 drop(ctx);
552
553 let ctx = temp_store.context();
554
555 let sig_pair = model
556 .signature_key_pair
557 .expect("signature_key_pair present");
558 assert_eq!(
559 sig_pair.verifying_key.unwrap(),
560 B64::from(
561 ctx.get_verifying_key(SigningKeyId::UserSigningKey)
562 .unwrap()
563 .to_cose()
564 )
565 .to_string()
566 );
567
568 let pk_pair = model.public_key_encryption_key_pair.unwrap();
569 assert_eq!(
570 pk_pair.public_key.unwrap(),
571 B64::from(
572 ctx.get_public_key(PrivateKeyId::UserPrivateKey)
573 .unwrap()
574 .to_der()
575 .unwrap()
576 )
577 .to_string()
578 );
579
580 let signed_security_state = model
581 .security_state
582 .clone()
583 .expect("security_state present");
584 let security_state =
585 SignedSecurityState::from_str(signed_security_state.security_state.unwrap().as_str())
586 .unwrap()
587 .verify_and_unwrap(&ctx.get_verifying_key(SigningKeyId::UserSigningKey).unwrap())
588 .expect("security state should verify");
589 assert_eq!(
590 security_state.version(),
591 model.security_state.unwrap().security_version as u64
592 );
593 }
594
595 #[test]
596 fn test_set_to_context_v1_corrupt_private_key() {
597 let temp_store: KeyStore<KeyIds> = KeyStore::default();
600 let mut temp_ctx = temp_store.context_mut();
601
602 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
603 let corrupt_private_key = "not a private key"
604 .encrypt(&mut temp_ctx, user_key)
605 .unwrap();
606
607 let wrapped = WrappedAccountCryptographicState::V1 {
609 private_key: corrupt_private_key,
610 };
611
612 #[expect(deprecated)]
613 let user_key_material = temp_ctx
614 .dangerous_get_symmetric_key(user_key)
615 .unwrap()
616 .to_owned();
617 drop(temp_ctx);
618 drop(temp_store);
619
620 let store: KeyStore<KeyIds> = KeyStore::default();
622 let mut ctx = store.context_mut();
623 let user_key = ctx.add_local_symmetric_key(user_key_material);
624 let security_state = RwLock::new(None);
625
626 wrapped
627 .set_to_context(&security_state, user_key, &store, ctx)
628 .unwrap();
629
630 let ctx = store.context();
631
632 assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
634 assert!(!ctx.has_private_key(PrivateKeyId::UserPrivateKey));
636 }
637
638 #[test]
639 fn test_rotate_v1_to_v2() {
640 let store: KeyStore<KeyIds> = KeyStore::default();
642 let mut ctx = store.context_mut();
643
644 let user_id = UserId::new_v4();
646 let (old_user_key_id, wrapped_state) =
647 WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
648 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
649 #[allow(deprecated)]
650 let new_user_key_owned = ctx
651 .dangerous_get_symmetric_key(new_user_key_id)
652 .unwrap()
653 .to_owned();
654 wrapped_state
655 .set_to_context(&RwLock::new(None), old_user_key_id, &store, ctx)
656 .unwrap();
657
658 let mut ctx = store.context_mut();
661 let new_user_key_id = ctx.add_local_symmetric_key(new_user_key_owned.clone());
662
663 let rotated_state = wrapped_state
665 .rotate(&SymmetricKeyId::User, &new_user_key_id, user_id, &mut ctx)
666 .unwrap();
667
668 match rotated_state {
674 WrappedAccountCryptographicState::V2 { .. } => {}
675 _ => panic!("Expected V2 after rotation from V1"),
676 }
677 let store_2 = KeyStore::<KeyIds>::default();
678 let mut ctx_2 = store_2.context_mut();
679 let user_key_id = ctx_2.add_local_symmetric_key(new_user_key_owned.clone());
680 rotated_state
681 .set_to_context(&RwLock::new(None), user_key_id, &store_2, ctx_2)
682 .unwrap();
683 let ctx_2 = store_2.context();
685
686 let public_key_before_rotation = ctx
688 .get_public_key(PrivateKeyId::UserPrivateKey)
689 .expect("Private key should be present in context before rotation");
690 let public_key_after_rotation = ctx_2
691 .get_public_key(PrivateKeyId::UserPrivateKey)
692 .expect("Private key should be present in context after rotation");
693 assert_eq!(
694 public_key_before_rotation.to_der().unwrap(),
695 public_key_after_rotation.to_der().unwrap(),
696 "Private key should be preserved during rotation from V2 to V2"
697 );
698 }
699
700 #[test]
701 fn test_rotate_v2() {
702 let store: KeyStore<KeyIds> = KeyStore::default();
704 let mut ctx = store.context_mut();
705
706 let user_id = UserId::new_v4();
708 let (old_user_key_id, wrapped_state) =
709 WrappedAccountCryptographicState::make(&mut ctx, user_id).unwrap();
710 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
711 #[allow(deprecated)]
712 let new_user_key_owned = ctx
713 .dangerous_get_symmetric_key(new_user_key_id)
714 .unwrap()
715 .to_owned();
716 wrapped_state
717 .set_to_context(&RwLock::new(None), old_user_key_id, &store, ctx)
718 .unwrap();
719
720 let mut ctx = store.context_mut();
723 let new_user_key_id = ctx.add_local_symmetric_key(new_user_key_owned.clone());
724
725 let rotated_state = wrapped_state
727 .rotate(&SymmetricKeyId::User, &new_user_key_id, user_id, &mut ctx)
728 .unwrap();
729
730 match rotated_state {
736 WrappedAccountCryptographicState::V2 { .. } => {}
737 _ => panic!("Expected V2 after rotation from V2"),
738 }
739 let store_2 = KeyStore::<KeyIds>::default();
740 let mut ctx_2 = store_2.context_mut();
741 let user_key_id = ctx_2.add_local_symmetric_key(new_user_key_owned.clone());
742 rotated_state
743 .set_to_context(&RwLock::new(None), user_key_id, &store_2, ctx_2)
744 .unwrap();
745 let ctx_2 = store_2.context();
747
748 let verifying_key_before_rotation = ctx
750 .get_verifying_key(SigningKeyId::UserSigningKey)
751 .expect("Signing key should be present in context before rotation");
752 let verifying_key_after_rotation = ctx_2
753 .get_verifying_key(SigningKeyId::UserSigningKey)
754 .expect("Signing key should be present in context after rotation");
755 assert_eq!(
756 verifying_key_before_rotation.to_cose(),
757 verifying_key_after_rotation.to_cose(),
758 "Signing key should be preserved during rotation from V2 to V2"
759 );
760
761 let public_key_before_rotation = ctx
762 .get_public_key(PrivateKeyId::UserPrivateKey)
763 .expect("Private key should be present in context before rotation");
764 let public_key_after_rotation = ctx_2
765 .get_public_key(PrivateKeyId::UserPrivateKey)
766 .expect("Private key should be present in context after rotation");
767 assert_eq!(
768 public_key_before_rotation.to_der().unwrap(),
769 public_key_after_rotation.to_der().unwrap(),
770 "Private key should be preserved during rotation from V2 to V2"
771 );
772 }
773}