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