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