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::XChaCha20Poly1305);
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 ctx.get_symmetric_key_algorithm(user_key)?
536 != SymmetricKeyAlgorithm::XChaCha20Poly1305
537 {
538 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
539 }
540
541 let private_key_id = ctx
542 .unwrap_private_key(user_key, private_key)
543 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
544 let signing_key_id = ctx
545 .unwrap_signing_key(user_key, signing_key)
546 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
547
548 if let Some(signed_public_key) = signed_public_key {
549 signed_public_key
550 .to_owned()
551 .verify_and_unwrap(&ctx.get_verifying_key(signing_key_id)?)
552 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
553 }
554
555 let verifying_key = ctx.get_verifying_key(signing_key_id)?;
556 let security_state: SecurityState = security_state
557 .to_owned()
558 .verify_and_unwrap(&verifying_key)
559 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
560 info!(
561 security_state_version = security_state.version(),
562 verifying_key = ?verifying_key,
563 "V2 account cryptographic state verified"
564 );
565 ctx.persist_private_key(private_key_id, PrivateKeySlotId::UserPrivateKey)?;
566 ctx.persist_signing_key(signing_key_id, SigningKeySlotId::UserSigningKey)?;
567 ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)?;
568
569 #[cfg(feature = "dangerous-crypto-debug")]
570 #[allow(deprecated)]
571 {
572 let user_key = ctx
573 .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
574 .expect("User key should be set");
575 let private_key = ctx
576 .dangerous_get_private_key(PrivateKeySlotId::UserPrivateKey)
577 .ok();
578 let signing_key = ctx
579 .dangerous_get_signing_key(SigningKeySlotId::UserSigningKey)
580 .ok();
581 let verifying_key =
582 ctx.get_verifying_key(SigningKeySlotId::UserSigningKey).ok();
583 let public_key = ctx.get_public_key(PrivateKeySlotId::UserPrivateKey).ok();
584 info!(
585 ?user_key,
586 ?private_key,
587 ?signing_key,
588 ?verifying_key,
589 ?public_key,
590 ?signed_public_key,
591 ?security_state,
592 "V2 account cryptographic state set to context."
593 );
594 }
595
596 drop(ctx);
599 store.set_security_state_version(security_state.version());
600 *security_state_rwlock.write().expect("RwLock not poisoned") = Some(security_state);
601 }
602 }
603
604 Ok(())
605 }
606
607 fn signed_public_key(
609 &self,
610 ) -> Result<Option<&SignedPublicKey>, AccountCryptographyInitializationError> {
611 match self {
612 WrappedAccountCryptographicState::V1 { .. } => Ok(None),
613 WrappedAccountCryptographicState::V2 {
614 signed_public_key, ..
615 } => Ok(signed_public_key.as_ref()),
616 }
617 }
618}
619
620#[cfg(test)]
621mod tests {
622 use std::{str::FromStr, sync::RwLock};
623
624 use bitwarden_crypto::{KeyStore, PrimitiveEncryptable};
625
626 use super::*;
627 use crate::key_management::{PrivateKeySlotId, SigningKeySlotId, SymmetricKeySlotId};
628
629 #[test]
630 #[ignore = "Manual test to verify debug format"]
631 fn test_debug() {
632 let store: KeyStore<KeySlotIds> = KeyStore::default();
633 let mut ctx = store.context_mut();
634
635 let (_, v1) = WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
636 println!("{:?}", v1);
637
638 let v1 = format!("{v1:?}");
639 assert!(!v1.contains("private_key"));
640
641 let (_, v2) = WrappedAccountCryptographicState::make(&mut ctx).unwrap();
642 println!("{:?}", v2);
643
644 let v2 = format!("{v2:?}");
645 assert!(!v2.contains("private_key"));
646 assert!(!v2.contains("signed_public_key"));
647 assert!(!v2.contains("signing_key"));
648 }
649
650 #[test]
651 fn test_set_to_context_v1() {
652 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
654 let mut temp_ctx = temp_store.context_mut();
655
656 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
658
659 let private_key_id = temp_ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
661 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
662
663 let wrapped = WrappedAccountCryptographicState::V1 {
665 private_key: wrapped_private,
666 };
667 #[allow(deprecated)]
668 let user_key = temp_ctx
669 .dangerous_get_symmetric_key(user_key)
670 .unwrap()
671 .to_owned();
672 drop(temp_ctx);
673 drop(temp_store);
674
675 let store: KeyStore<KeySlotIds> = KeyStore::default();
677 let mut ctx = store.context_mut();
678 let user_key = ctx.add_local_symmetric_key(user_key);
679 let security_state = RwLock::new(None);
680
681 wrapped
683 .set_to_context(&security_state, user_key, &store, ctx)
684 .unwrap();
685 let ctx = store.context();
686
687 assert!(ctx.has_private_key(PrivateKeySlotId::UserPrivateKey));
689 assert!(ctx.has_symmetric_key(SymmetricKeySlotId::User));
690 }
691
692 #[test]
693 fn test_set_to_context_v2() {
694 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
696 let mut temp_ctx = temp_store.context_mut();
697
698 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
700
701 let private_key_id = temp_ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
703 let signing_key_id = temp_ctx.make_signing_key(SignatureAlgorithm::Ed25519);
704 let signed_public_key = temp_ctx
705 .make_signed_public_key(private_key_id, signing_key_id)
706 .unwrap();
707
708 let security_state = SecurityState::new();
710 let signed_security_state = security_state.sign(signing_key_id, &mut temp_ctx).unwrap();
711
712 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
714 let wrapped_signing = temp_ctx.wrap_signing_key(user_key, signing_key_id).unwrap();
715
716 let wrapped = WrappedAccountCryptographicState::V2 {
717 private_key: wrapped_private,
718 signed_public_key: Some(signed_public_key),
719 signing_key: wrapped_signing,
720 security_state: signed_security_state,
721 };
722 #[allow(deprecated)]
723 let user_key = temp_ctx
724 .dangerous_get_symmetric_key(user_key)
725 .unwrap()
726 .to_owned();
727 drop(temp_ctx);
728 drop(temp_store);
729
730 let store: KeyStore<KeySlotIds> = KeyStore::default();
732 let mut ctx = store.context_mut();
733 let user_key = ctx.add_local_symmetric_key(user_key);
734 let security_state = RwLock::new(None);
735
736 wrapped
737 .set_to_context(&security_state, user_key, &store, ctx)
738 .unwrap();
739
740 assert!(store.context().has_symmetric_key(SymmetricKeySlotId::User));
741 assert!(
743 store
744 .context()
745 .has_private_key(PrivateKeySlotId::UserPrivateKey)
746 );
747 assert!(
748 store
749 .context()
750 .has_signing_key(SigningKeySlotId::UserSigningKey)
751 );
752 assert!(security_state.read().unwrap().is_some());
754 }
755
756 #[test]
757 fn test_to_private_keys_request_model_v2() {
758 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
759 let mut temp_ctx = temp_store.context_mut();
760 let (user_key, wrapped_account_cryptography_state) =
761 WrappedAccountCryptographicState::make(&mut temp_ctx).unwrap();
762
763 wrapped_account_cryptography_state
764 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
765 .unwrap();
766
767 let mut ctx = temp_store.context_mut();
768 let model = wrapped_account_cryptography_state
769 .to_request_model(&SymmetricKeySlotId::User, &mut ctx)
770 .expect("to_private_keys_request_model should succeed");
771 drop(ctx);
772
773 let ctx = temp_store.context();
774
775 let sig_pair = model
776 .signature_key_pair
777 .expect("signature_key_pair present");
778 assert_eq!(
779 sig_pair.verifying_key.unwrap(),
780 B64::from(
781 ctx.get_verifying_key(SigningKeySlotId::UserSigningKey)
782 .unwrap()
783 .to_cose()
784 )
785 .to_string()
786 );
787
788 let pk_pair = model.public_key_encryption_key_pair.unwrap();
789 assert_eq!(
790 pk_pair.public_key.unwrap(),
791 B64::from(
792 ctx.get_public_key(PrivateKeySlotId::UserPrivateKey)
793 .unwrap()
794 .to_der()
795 .unwrap()
796 )
797 .to_string()
798 );
799
800 let signed_security_state = model
801 .security_state
802 .clone()
803 .expect("security_state present");
804 let security_state =
805 SignedSecurityState::from_str(signed_security_state.security_state.unwrap().as_str())
806 .unwrap()
807 .verify_and_unwrap(
808 &ctx.get_verifying_key(SigningKeySlotId::UserSigningKey)
809 .unwrap(),
810 )
811 .expect("security state should verify");
812 assert_eq!(
813 security_state.version(),
814 model.security_state.unwrap().security_version as u64
815 );
816 }
817
818 #[test]
819 fn test_set_to_context_v1_corrupt_private_key() {
820 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
823 let mut temp_ctx = temp_store.context_mut();
824
825 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
826 let corrupt_private_key = "not a private key"
827 .encrypt(&mut temp_ctx, user_key)
828 .unwrap();
829
830 let wrapped = WrappedAccountCryptographicState::V1 {
832 private_key: corrupt_private_key,
833 };
834
835 #[expect(deprecated)]
836 let user_key_material = temp_ctx
837 .dangerous_get_symmetric_key(user_key)
838 .unwrap()
839 .to_owned();
840 drop(temp_ctx);
841 drop(temp_store);
842
843 let store: KeyStore<KeySlotIds> = KeyStore::default();
845 let mut ctx = store.context_mut();
846 let user_key = ctx.add_local_symmetric_key(user_key_material);
847 let security_state = RwLock::new(None);
848
849 wrapped
850 .set_to_context(&security_state, user_key, &store, ctx)
851 .unwrap();
852
853 let ctx = store.context();
854
855 assert!(ctx.has_symmetric_key(SymmetricKeySlotId::User));
857 assert!(!ctx.has_private_key(PrivateKeySlotId::UserPrivateKey));
859 }
860
861 #[test]
862 fn test_try_from_response_v2_roundtrip() {
863 use bitwarden_api_api::models::{
864 PublicKeyEncryptionKeyPairResponseModel, SecurityStateModel,
865 SignatureKeyPairResponseModel,
866 };
867
868 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
869 let mut temp_ctx = temp_store.context_mut();
870 let (user_key, wrapped_state) =
871 WrappedAccountCryptographicState::make(&mut temp_ctx).unwrap();
872
873 wrapped_state
874 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
875 .unwrap();
876
877 let mut ctx = temp_store.context_mut();
878 let request_model = wrapped_state
879 .to_request_model(&SymmetricKeySlotId::User, &mut ctx)
880 .unwrap();
881 drop(ctx);
882
883 let pk_pair = request_model.public_key_encryption_key_pair.unwrap();
884 let sig_pair = request_model.signature_key_pair.unwrap();
885 let sec_state = request_model.security_state.unwrap();
886
887 let response = PrivateKeysResponseModel {
888 object: None,
889 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
890 object: None,
891 wrapped_private_key: pk_pair.wrapped_private_key,
892 public_key: pk_pair.public_key,
893 signed_public_key: pk_pair.signed_public_key,
894 }),
895 signature_key_pair: Some(Box::new(SignatureKeyPairResponseModel {
896 object: None,
897 wrapped_signing_key: sig_pair.wrapped_signing_key,
898 verifying_key: sig_pair.verifying_key,
899 })),
900 security_state: Some(Box::new(SecurityStateModel {
901 security_state: sec_state.security_state,
902 security_version: sec_state.security_version,
903 })),
904 };
905
906 let parsed = WrappedAccountCryptographicState::try_from(&response)
907 .expect("V2 response should parse successfully");
908
909 assert_eq!(parsed, wrapped_state);
910 }
911
912 #[test]
913 fn test_try_from_response_v1() {
914 use bitwarden_api_api::models::PublicKeyEncryptionKeyPairResponseModel;
915
916 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
917 let mut temp_ctx = temp_store.context_mut();
918 let (_user_key, wrapped_state) =
919 WrappedAccountCryptographicState::make_v1(&mut temp_ctx).unwrap();
920
921 let wrapped_private_key = match &wrapped_state {
922 WrappedAccountCryptographicState::V1 { private_key } => private_key.to_string(),
923 _ => panic!("Expected V1"),
924 };
925 drop(temp_ctx);
926
927 let response = PrivateKeysResponseModel {
928 object: None,
929 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
930 object: None,
931 wrapped_private_key: Some(wrapped_private_key),
932 public_key: None,
933 signed_public_key: None,
934 }),
935 signature_key_pair: None,
936 security_state: None,
937 };
938
939 let parsed = WrappedAccountCryptographicState::try_from(&response)
940 .expect("V1 response should parse successfully");
941
942 assert_eq!(parsed, wrapped_state);
943 }
944
945 #[test]
946 fn test_try_from_response_missing_private_key() {
947 use bitwarden_api_api::models::PublicKeyEncryptionKeyPairResponseModel;
948
949 let response = PrivateKeysResponseModel {
950 object: None,
951 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
952 object: None,
953 wrapped_private_key: None,
954 public_key: None,
955 signed_public_key: None,
956 }),
957 signature_key_pair: None,
958 security_state: None,
959 };
960
961 let result = WrappedAccountCryptographicState::try_from(&response);
962 assert!(result.is_err());
963 assert!(
964 matches!(
965 result.unwrap_err(),
966 AccountKeysResponseParseError::MissingField(_)
967 ),
968 "Should return MissingField error"
969 );
970 }
971
972 #[test]
973 fn test_try_from_response_v2_encryption_missing_signature_key_pair() {
974 use bitwarden_api_api::models::PublicKeyEncryptionKeyPairResponseModel;
975
976 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
978 let mut temp_ctx = temp_store.context_mut();
979 let (user_key, wrapped_state) =
980 WrappedAccountCryptographicState::make(&mut temp_ctx).unwrap();
981
982 wrapped_state
983 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
984 .unwrap();
985
986 let mut ctx = temp_store.context_mut();
987 let request_model = wrapped_state
988 .to_request_model(&SymmetricKeySlotId::User, &mut ctx)
989 .unwrap();
990 drop(ctx);
991
992 let pk_pair = request_model.public_key_encryption_key_pair.unwrap();
993
994 let response = PrivateKeysResponseModel {
996 object: None,
997 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
998 object: None,
999 wrapped_private_key: pk_pair.wrapped_private_key,
1000 public_key: pk_pair.public_key,
1001 signed_public_key: None,
1002 }),
1003 signature_key_pair: None,
1004 security_state: None,
1005 };
1006
1007 let result = WrappedAccountCryptographicState::try_from(&response);
1008 assert!(matches!(
1009 result.unwrap_err(),
1010 AccountKeysResponseParseError::InconsistentState
1011 ));
1012 }
1013
1014 #[test]
1015 fn test_try_from_response_v1_encryption_with_unexpected_v2_fields() {
1016 use bitwarden_api_api::models::{
1017 PublicKeyEncryptionKeyPairResponseModel, SignatureKeyPairResponseModel,
1018 };
1019
1020 let temp_store: KeyStore<KeySlotIds> = KeyStore::default();
1022 let mut temp_ctx = temp_store.context_mut();
1023 let (_user_key, wrapped_state) =
1024 WrappedAccountCryptographicState::make_v1(&mut temp_ctx).unwrap();
1025
1026 let wrapped_private_key = match &wrapped_state {
1027 WrappedAccountCryptographicState::V1 { private_key } => private_key.to_string(),
1028 _ => panic!("Expected V1"),
1029 };
1030 drop(temp_ctx);
1031
1032 let response = PrivateKeysResponseModel {
1034 object: None,
1035 public_key_encryption_key_pair: Box::new(PublicKeyEncryptionKeyPairResponseModel {
1036 object: None,
1037 wrapped_private_key: Some(wrapped_private_key),
1038 public_key: None,
1039 signed_public_key: None,
1040 }),
1041 signature_key_pair: Some(Box::new(SignatureKeyPairResponseModel {
1042 object: None,
1043 wrapped_signing_key: Some("bogus".to_string()),
1044 verifying_key: None,
1045 })),
1046 security_state: None,
1047 };
1048
1049 let result = WrappedAccountCryptographicState::try_from(&response);
1050 assert!(matches!(
1051 result.unwrap_err(),
1052 AccountKeysResponseParseError::InconsistentState
1053 ));
1054 }
1055
1056 #[test]
1057 fn test_rotate_v1_to_v2() {
1058 let store: KeyStore<KeySlotIds> = KeyStore::default();
1060 let mut ctx = store.context_mut();
1061
1062 let (old_user_key_id, wrapped_state) =
1064 WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
1065 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1066 #[allow(deprecated)]
1067 let new_user_key_owned = ctx
1068 .dangerous_get_symmetric_key(new_user_key_id)
1069 .unwrap()
1070 .to_owned();
1071 wrapped_state
1072 .set_to_context(&RwLock::new(None), old_user_key_id, &store, ctx)
1073 .unwrap();
1074
1075 let mut ctx = store.context_mut();
1078 let new_user_key_id = ctx.add_local_symmetric_key(new_user_key_owned.clone());
1079
1080 let rotated_state = wrapped_state
1082 .rotate(&SymmetricKeySlotId::User, &new_user_key_id, &mut ctx)
1083 .unwrap();
1084
1085 match rotated_state {
1091 WrappedAccountCryptographicState::V2 { .. } => {}
1092 _ => panic!("Expected V2 after rotation from V1"),
1093 }
1094 let store_2 = KeyStore::<KeySlotIds>::default();
1095 let mut ctx_2 = store_2.context_mut();
1096 let user_key_id = ctx_2.add_local_symmetric_key(new_user_key_owned.clone());
1097 rotated_state
1098 .set_to_context(&RwLock::new(None), user_key_id, &store_2, ctx_2)
1099 .unwrap();
1100 let ctx_2 = store_2.context();
1102
1103 let public_key_before_rotation = ctx
1105 .get_public_key(PrivateKeySlotId::UserPrivateKey)
1106 .expect("Private key should be present in context before rotation");
1107 let public_key_after_rotation = ctx_2
1108 .get_public_key(PrivateKeySlotId::UserPrivateKey)
1109 .expect("Private key should be present in context after rotation");
1110 assert_eq!(
1111 public_key_before_rotation.to_der().unwrap(),
1112 public_key_after_rotation.to_der().unwrap(),
1113 "Private key should be preserved during rotation from V2 to V2"
1114 );
1115 }
1116
1117 #[test]
1118 fn test_rotate_v2() {
1119 let store: KeyStore<KeySlotIds> = KeyStore::default();
1121 let mut ctx = store.context_mut();
1122
1123 let (old_user_key_id, wrapped_state) =
1125 WrappedAccountCryptographicState::make(&mut ctx).unwrap();
1126 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1127 #[allow(deprecated)]
1128 let new_user_key_owned = ctx
1129 .dangerous_get_symmetric_key(new_user_key_id)
1130 .unwrap()
1131 .to_owned();
1132 wrapped_state
1133 .set_to_context(&RwLock::new(None), old_user_key_id, &store, ctx)
1134 .unwrap();
1135
1136 let mut ctx = store.context_mut();
1139 let new_user_key_id = ctx.add_local_symmetric_key(new_user_key_owned.clone());
1140
1141 let rotated_state = wrapped_state
1143 .rotate(&SymmetricKeySlotId::User, &new_user_key_id, &mut ctx)
1144 .unwrap();
1145
1146 match rotated_state {
1152 WrappedAccountCryptographicState::V2 { .. } => {}
1153 _ => panic!("Expected V2 after rotation from V2"),
1154 }
1155 let store_2 = KeyStore::<KeySlotIds>::default();
1156 let mut ctx_2 = store_2.context_mut();
1157 let user_key_id = ctx_2.add_local_symmetric_key(new_user_key_owned.clone());
1158 rotated_state
1159 .set_to_context(&RwLock::new(None), user_key_id, &store_2, ctx_2)
1160 .unwrap();
1161 let ctx_2 = store_2.context();
1163
1164 let verifying_key_before_rotation = ctx
1166 .get_verifying_key(SigningKeySlotId::UserSigningKey)
1167 .expect("Signing key should be present in context before rotation");
1168 let verifying_key_after_rotation = ctx_2
1169 .get_verifying_key(SigningKeySlotId::UserSigningKey)
1170 .expect("Signing key should be present in context after rotation");
1171 assert_eq!(
1172 verifying_key_before_rotation.to_cose(),
1173 verifying_key_after_rotation.to_cose(),
1174 "Signing key should be preserved during rotation from V2 to V2"
1175 );
1176
1177 let public_key_before_rotation = ctx
1178 .get_public_key(PrivateKeySlotId::UserPrivateKey)
1179 .expect("Private key should be present in context before rotation");
1180 let public_key_after_rotation = ctx_2
1181 .get_public_key(PrivateKeySlotId::UserPrivateKey)
1182 .expect("Private key should be present in context after rotation");
1183 assert_eq!(
1184 public_key_before_rotation.to_der().unwrap(),
1185 public_key_after_rotation.to_der().unwrap(),
1186 "Private key should be preserved during rotation from V2 to V2"
1187 );
1188 }
1189
1190 #[test]
1191 fn test_to_wrapped_request_model_v1_returns_wrong_user_key_type() {
1192 let store: KeyStore<KeySlotIds> = KeyStore::default();
1193 let mut ctx = store.context_mut();
1194 let (user_key_id, wrapped) = WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
1195 let result = wrapped.to_wrapped_request_model(&user_key_id, &mut ctx);
1196 assert!(matches!(
1197 result.unwrap_err(),
1198 AccountCryptographyInitializationError::WrongUserKeyType
1199 ));
1200 }
1201
1202 #[test]
1203 fn test_to_wrapped_request_model_v2() {
1204 let store: KeyStore<KeySlotIds> = KeyStore::default();
1205 let mut ctx = store.context_mut();
1206 let (user_key_id, wrapped) = WrappedAccountCryptographicState::make(&mut ctx).unwrap();
1207 let result = wrapped
1208 .to_wrapped_request_model(&user_key_id, &mut ctx)
1209 .unwrap();
1210
1211 let wrapped_signing_key_str = result
1212 .signature_key_pair
1213 .wrapped_signing_key
1214 .as_ref()
1215 .unwrap();
1216 assert!(!wrapped_signing_key_str.is_empty());
1217
1218 let enc_signing_key: EncString = wrapped_signing_key_str.parse().unwrap();
1219 let signing_key_tmp = ctx
1220 .unwrap_signing_key(user_key_id, &enc_signing_key)
1221 .unwrap();
1222 let verifying_key = ctx.get_verifying_key(signing_key_tmp).unwrap();
1223 let expected = B64::from(verifying_key.to_cose()).to_string();
1224 assert!(
1225 result
1226 .signature_key_pair
1227 .verifying_key
1228 .as_ref()
1229 .is_some_and(|s| s == &expected),
1230 "verifying_key should match expected value"
1231 );
1232
1233 assert_eq!(
1234 result.signature_key_pair.signature_algorithm.as_deref(),
1235 Some("mldsa44")
1236 );
1237
1238 assert!(
1239 result
1240 .public_key_encryption_key_pair
1241 .wrapped_private_key
1242 .as_ref()
1243 .is_some_and(|s| !s.is_empty()),
1244 "wrapped_private_key should be non-empty"
1245 );
1246 let wrapped_private_key_str = result
1247 .public_key_encryption_key_pair
1248 .wrapped_private_key
1249 .as_ref()
1250 .unwrap();
1251 let enc_private_key: EncString = wrapped_private_key_str.parse().unwrap();
1252 let private_key_tmp = ctx
1253 .unwrap_private_key(user_key_id, &enc_private_key)
1254 .unwrap();
1255 let public_key = ctx.get_public_key(private_key_tmp).unwrap();
1256
1257 let expected = B64::from(public_key.to_der().unwrap()).to_string();
1258 assert!(
1259 result
1260 .public_key_encryption_key_pair
1261 .public_key
1262 .as_ref()
1263 .is_some_and(|s| s == &expected),
1264 "public_key should match expected value"
1265 );
1266 assert!(
1267 result
1268 .public_key_encryption_key_pair
1269 .signed_public_key
1270 .is_some(),
1271 "signed_public_key should be present"
1272 );
1273 assert!(
1274 result
1275 .security_state
1276 .security_state
1277 .as_ref()
1278 .is_some_and(|s| !s.is_empty()),
1279 "security_state string should be non-empty"
1280 );
1281 assert!(result.security_state.security_version == 2);
1282 }
1283
1284 #[test]
1285 fn test_to_wrapped_request_model_wrong_user_key_returns_error() {
1286 let store: KeyStore<KeySlotIds> = KeyStore::default();
1287 let mut ctx = store.context_mut();
1288 let (_user_key_id, wrapped) = WrappedAccountCryptographicState::make(&mut ctx).unwrap();
1289
1290 let wrong_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1292
1293 let result = wrapped.to_wrapped_request_model(&wrong_user_key_id, &mut ctx);
1294 assert!(result.is_err());
1295 assert!(!matches!(
1297 result.unwrap_err(),
1298 AccountCryptographyInitializationError::WrongUserKeyType
1299 ));
1300 }
1301
1302 #[test]
1303 fn test_get_from_key_store_v1() {
1304 let store: KeyStore<KeySlotIds> = KeyStore::default();
1305 let mut ctx = store.context_mut();
1306 let (user_key, state) = WrappedAccountCryptographicState::make_v1(&mut ctx).unwrap();
1307 state
1308 .set_to_context(&RwLock::new(None), user_key, &store, ctx)
1309 .unwrap();
1310
1311 let ctx = store.context();
1312 let result = WrappedAccountCryptographicState::get_from_key_store(&ctx);
1313 assert!(result.is_ok());
1314 assert!(matches!(
1315 result.unwrap(),
1316 WrappedAccountCryptographicState::V1 { .. }
1317 ));
1318 }
1319
1320 #[test]
1321 fn test_get_from_key_store_v2_returns_error() {
1322 let store: KeyStore<KeySlotIds> = KeyStore::default();
1323 let mut ctx = store.context_mut();
1324 let (user_key, state) = WrappedAccountCryptographicState::make(&mut ctx).unwrap();
1325 state
1326 .set_to_context(&RwLock::new(None), user_key, &store, ctx)
1327 .unwrap();
1328
1329 let ctx = store.context();
1330 let result = WrappedAccountCryptographicState::get_from_key_store(&ctx);
1331 assert!(matches!(
1332 result,
1333 Err(RotateCryptographyStateError::InvalidData)
1334 ));
1335 }
1336
1337 #[test]
1338 fn test_get_from_key_store_no_user_key() {
1339 let store: KeyStore<KeySlotIds> = KeyStore::default();
1340 let ctx = store.context();
1341 let result = WrappedAccountCryptographicState::get_from_key_store(&ctx);
1342 assert!(matches!(
1343 result,
1344 Err(RotateCryptographyStateError::KeyMissing)
1345 ));
1346 }
1347}