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