bitwarden_core/key_management/
account_cryptographic_state.rs1use std::sync::RwLock;
12
13use bitwarden_api_api::models::{AccountKeysRequestModel, SecurityStateModel};
14use bitwarden_crypto::{
15 CoseSerializable, CryptoError, EncString, KeyStore, KeyStoreContext,
16 PublicKeyEncryptionAlgorithm, SignatureAlgorithm, SignedPublicKey, SymmetricKeyAlgorithm,
17};
18use bitwarden_encoding::B64;
19use bitwarden_error::bitwarden_error;
20use serde::{Deserialize, Serialize};
21use thiserror::Error;
22use tracing::info;
23#[cfg(feature = "wasm")]
24use tsify::Tsify;
25
26use crate::{
27 UserId,
28 key_management::{
29 AsymmetricKeyId, KeyIds, SecurityState, SignedSecurityState, SigningKeyId, SymmetricKeyId,
30 },
31};
32
33#[derive(Debug, Error)]
35#[bitwarden_error(flat)]
36pub enum AccountCryptographyInitializationError {
37 #[error("The encryption type of the user key does not match the account cryptographic state")]
40 WrongUserKeyType,
41 #[error("Wrong user key")]
44 WrongUserKey,
45 #[error("Decryption succeeded but produced corrupt data")]
47 CorruptData,
48 #[error("Signature or mac verification failed, the data may have been tampered with")]
50 TamperedData,
51 #[error("Key store is already initialized")]
54 KeyStoreAlreadyInitialized,
55 #[error("A generic cryptographic error occurred: {0}")]
57 GenericCrypto(CryptoError),
58}
59
60impl From<CryptoError> for AccountCryptographyInitializationError {
61 fn from(err: CryptoError) -> Self {
62 AccountCryptographyInitializationError::GenericCrypto(err)
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
69#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
70#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
71#[allow(clippy::large_enum_variant)]
72pub enum WrappedAccountCryptographicState {
73 V1 {
75 private_key: EncString,
77 },
78 V2 {
83 private_key: EncString,
85 signed_public_key: Option<SignedPublicKey>,
89 signing_key: EncString,
91 security_state: SignedSecurityState,
93 },
94}
95
96impl WrappedAccountCryptographicState {
97 pub fn to_request_model(
101 &self,
102 user_key: &SymmetricKeyId,
103 ctx: &mut KeyStoreContext<KeyIds>,
104 ) -> Result<AccountKeysRequestModel, AccountCryptographyInitializationError> {
105 let private_key = match self {
106 WrappedAccountCryptographicState::V1 { private_key }
107 | WrappedAccountCryptographicState::V2 { private_key, .. } => private_key.clone(),
108 };
109 let private_key_tmp_id = ctx.unwrap_private_key(*user_key, &private_key)?;
110 let public_key = ctx.get_public_key(private_key_tmp_id)?;
111
112 let signature_keypair = match self {
113 WrappedAccountCryptographicState::V1 { .. } => None,
114 WrappedAccountCryptographicState::V2 { signing_key, .. } => {
115 let signing_key_tmp_id = ctx.unwrap_signing_key(*user_key, signing_key)?;
116 let verifying_key = ctx.get_verifying_key(signing_key_tmp_id)?;
117 Some((signing_key.clone(), verifying_key))
118 }
119 };
120
121 Ok(AccountKeysRequestModel {
122 user_key_encrypted_account_private_key: Some(private_key.to_string()),
124 account_public_key: Some(B64::from(public_key.to_der()?).to_string()),
126 signature_key_pair: signature_keypair
127 .as_ref()
128 .map(|(signing_key, verifying_key)| {
129 Box::new(bitwarden_api_api::models::SignatureKeyPairRequestModel {
130 wrapped_signing_key: Some(signing_key.to_string()),
131 verifying_key: Some(B64::from(verifying_key.to_cose()).to_string()),
132 signature_algorithm: Some(match verifying_key.algorithm() {
133 SignatureAlgorithm::Ed25519 => "ed25519".to_string(),
134 }),
135 })
136 }),
137 public_key_encryption_key_pair: Some(Box::new(
138 bitwarden_api_api::models::PublicKeyEncryptionKeyPairRequestModel {
139 wrapped_private_key: match self {
140 WrappedAccountCryptographicState::V1 { private_key }
141 | WrappedAccountCryptographicState::V2 { private_key, .. } => {
142 Some(private_key.to_string())
143 }
144 },
145 public_key: Some(B64::from(public_key.to_der()?).to_string()),
146 signed_public_key: match self.signed_public_key() {
147 Ok(Some(spk)) => Some(spk.clone().into()),
148 _ => None,
149 },
150 },
151 )),
152 security_state: match (self, signature_keypair.as_ref()) {
153 (_, None) | (WrappedAccountCryptographicState::V1 { .. }, Some(_)) => None,
154 (
155 WrappedAccountCryptographicState::V2 { security_state, .. },
156 Some((_, verifying_key)),
157 ) => {
158 Some(Box::new(SecurityStateModel {
160 security_state: Some(security_state.into()),
161 security_version: security_state
162 .to_owned()
163 .verify_and_unwrap(verifying_key)
164 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?
165 .version() as i32,
166 }))
167 }
168 },
169 })
170 }
171
172 pub fn make(
175 ctx: &mut KeyStoreContext<KeyIds>,
176 user_id: UserId,
177 ) -> Result<(SymmetricKeyId, Self), AccountCryptographyInitializationError> {
178 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
179 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1)?;
180 let signing_key = ctx.make_signing_key(SignatureAlgorithm::Ed25519)?;
181 let signed_public_key = ctx.make_signed_public_key(private_key, signing_key)?;
182
183 let security_state = SecurityState::initialize_for_user(user_id);
184 let signed_security_state = security_state.sign(signing_key, ctx)?;
185
186 Ok((
187 user_key,
188 WrappedAccountCryptographicState::V2 {
189 private_key: ctx.wrap_private_key(user_key, private_key)?,
190 signed_public_key: Some(signed_public_key),
191 signing_key: ctx.wrap_signing_key(user_key, signing_key)?,
192 security_state: signed_security_state,
193 },
194 ))
195 }
196
197 pub(crate) fn set_to_context(
202 &self,
203 security_state_rwlock: &RwLock<Option<SecurityState>>,
204 user_key: SymmetricKeyId,
205 store: &KeyStore<KeyIds>,
206 mut ctx: KeyStoreContext<KeyIds>,
207 ) -> Result<(), AccountCryptographyInitializationError> {
208 if ctx.has_symmetric_key(SymmetricKeyId::User)
209 || ctx.has_asymmetric_key(AsymmetricKeyId::UserPrivateKey)
210 || ctx.has_signing_key(SigningKeyId::UserSigningKey)
211 {
212 return Err(AccountCryptographyInitializationError::KeyStoreAlreadyInitialized);
213 }
214
215 match self {
216 WrappedAccountCryptographicState::V1 { private_key } => {
217 info!("Initializing V1 account cryptographic state");
218 if ctx.get_symmetric_key_algorithm(user_key)?
219 != SymmetricKeyAlgorithm::Aes256CbcHmac
220 {
221 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
222 }
223
224 if let Ok(private_key_id) = ctx.unwrap_private_key(user_key, private_key) {
227 ctx.persist_asymmetric_key(private_key_id, AsymmetricKeyId::UserPrivateKey)?;
228 } else {
229 tracing::warn!(
230 "V1 private key could not be unwrapped, skipping setting private key"
231 );
232 }
233
234 ctx.persist_symmetric_key(user_key, SymmetricKeyId::User)?;
235 }
236 WrappedAccountCryptographicState::V2 {
237 private_key,
238 signed_public_key,
239 signing_key,
240 security_state,
241 } => {
242 info!("Initializing V2 account cryptographic state");
243 if ctx.get_symmetric_key_algorithm(user_key)?
244 != SymmetricKeyAlgorithm::XChaCha20Poly1305
245 {
246 return Err(AccountCryptographyInitializationError::WrongUserKeyType);
247 }
248
249 let private_key_id = ctx
250 .unwrap_private_key(user_key, private_key)
251 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
252 let signing_key_id = ctx
253 .unwrap_signing_key(user_key, signing_key)
254 .map_err(|_| AccountCryptographyInitializationError::WrongUserKey)?;
255
256 if let Some(signed_public_key) = signed_public_key {
257 signed_public_key
258 .to_owned()
259 .verify_and_unwrap(&ctx.get_verifying_key(signing_key_id)?)
260 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
261 }
262
263 let security_state: SecurityState = security_state
264 .to_owned()
265 .verify_and_unwrap(&ctx.get_verifying_key(signing_key_id)?)
266 .map_err(|_| AccountCryptographyInitializationError::TamperedData)?;
267 ctx.persist_asymmetric_key(private_key_id, AsymmetricKeyId::UserPrivateKey)?;
268 ctx.persist_signing_key(signing_key_id, SigningKeyId::UserSigningKey)?;
269 ctx.persist_symmetric_key(user_key, SymmetricKeyId::User)?;
270 drop(ctx);
273 store.set_security_state_version(security_state.version());
274 *security_state_rwlock.write().expect("RwLock not poisoned") = Some(security_state);
275 }
276 }
277
278 Ok(())
279 }
280
281 fn signed_public_key(
283 &self,
284 ) -> Result<Option<&SignedPublicKey>, AccountCryptographyInitializationError> {
285 match self {
286 WrappedAccountCryptographicState::V1 { .. } => Ok(None),
287 WrappedAccountCryptographicState::V2 {
288 signed_public_key, ..
289 } => Ok(signed_public_key.as_ref()),
290 }
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use std::{str::FromStr, sync::RwLock};
297
298 use bitwarden_crypto::{KeyStore, PrimitiveEncryptable};
299
300 use super::*;
301 use crate::key_management::{AsymmetricKeyId, SigningKeyId, SymmetricKeyId};
302
303 #[test]
304 fn test_set_to_context_v1() {
305 let temp_store: KeyStore<KeyIds> = KeyStore::default();
307 let mut temp_ctx = temp_store.context_mut();
308
309 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
311
312 let private_key_id = temp_ctx
314 .make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1)
315 .unwrap();
316 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
317
318 let wrapped = WrappedAccountCryptographicState::V1 {
320 private_key: wrapped_private,
321 };
322 #[allow(deprecated)]
323 let user_key = temp_ctx
324 .dangerous_get_symmetric_key(user_key)
325 .unwrap()
326 .to_owned();
327 drop(temp_ctx);
328 drop(temp_store);
329
330 let store: KeyStore<KeyIds> = KeyStore::default();
332 let mut ctx = store.context_mut();
333 let user_key = ctx.add_local_symmetric_key(user_key);
334 let security_state = RwLock::new(None);
335
336 wrapped
338 .set_to_context(&security_state, user_key, &store, ctx)
339 .unwrap();
340 let ctx = store.context();
341
342 assert!(ctx.has_asymmetric_key(AsymmetricKeyId::UserPrivateKey));
344 assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
345 }
346
347 #[test]
348 fn test_set_to_context_v2() {
349 let temp_store: KeyStore<KeyIds> = KeyStore::default();
351 let mut temp_ctx = temp_store.context_mut();
352
353 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
355
356 let private_key_id = temp_ctx
358 .make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1)
359 .unwrap();
360 let signing_key_id = temp_ctx
361 .make_signing_key(SignatureAlgorithm::Ed25519)
362 .unwrap();
363 let signed_public_key = temp_ctx
364 .make_signed_public_key(private_key_id, signing_key_id)
365 .unwrap();
366
367 let user_id = UserId::new_v4();
369 let security_state = SecurityState::initialize_for_user(user_id);
370 let signed_security_state = security_state.sign(signing_key_id, &mut temp_ctx).unwrap();
371
372 let wrapped_private = temp_ctx.wrap_private_key(user_key, private_key_id).unwrap();
374 let wrapped_signing = temp_ctx.wrap_signing_key(user_key, signing_key_id).unwrap();
375
376 let wrapped = WrappedAccountCryptographicState::V2 {
377 private_key: wrapped_private,
378 signed_public_key: Some(signed_public_key),
379 signing_key: wrapped_signing,
380 security_state: signed_security_state,
381 };
382 #[allow(deprecated)]
383 let user_key = temp_ctx
384 .dangerous_get_symmetric_key(user_key)
385 .unwrap()
386 .to_owned();
387 drop(temp_ctx);
388 drop(temp_store);
389
390 let store: KeyStore<KeyIds> = KeyStore::default();
392 let mut ctx = store.context_mut();
393 let user_key = ctx.add_local_symmetric_key(user_key);
394 let security_state = RwLock::new(None);
395
396 wrapped
397 .set_to_context(&security_state, user_key, &store, ctx)
398 .unwrap();
399
400 assert!(store.context().has_symmetric_key(SymmetricKeyId::User));
401 assert!(
403 store
404 .context()
405 .has_asymmetric_key(AsymmetricKeyId::UserPrivateKey)
406 );
407 assert!(
408 store
409 .context()
410 .has_signing_key(SigningKeyId::UserSigningKey)
411 );
412 assert!(security_state.read().unwrap().is_some());
414 }
415
416 #[test]
417 fn test_to_private_keys_request_model_v2() {
418 let temp_store: KeyStore<KeyIds> = KeyStore::default();
419 let mut temp_ctx = temp_store.context_mut();
420 let user_id = UserId::new_v4();
421 let (user_key, wrapped_account_cryptography_state) =
422 WrappedAccountCryptographicState::make(&mut temp_ctx, user_id).unwrap();
423
424 wrapped_account_cryptography_state
425 .set_to_context(&RwLock::new(None), user_key, &temp_store, temp_ctx)
426 .unwrap();
427
428 let mut ctx = temp_store.context_mut();
429 let model = wrapped_account_cryptography_state
430 .to_request_model(&SymmetricKeyId::User, &mut ctx)
431 .expect("to_private_keys_request_model should succeed");
432 drop(ctx);
433
434 let ctx = temp_store.context();
435
436 let sig_pair = model
437 .signature_key_pair
438 .expect("signature_key_pair present");
439 assert_eq!(
440 sig_pair.verifying_key.unwrap(),
441 B64::from(
442 ctx.get_verifying_key(SigningKeyId::UserSigningKey)
443 .unwrap()
444 .to_cose()
445 )
446 .to_string()
447 );
448
449 let pk_pair = model.public_key_encryption_key_pair.unwrap();
450 assert_eq!(
451 pk_pair.public_key.unwrap(),
452 B64::from(
453 ctx.get_public_key(AsymmetricKeyId::UserPrivateKey)
454 .unwrap()
455 .to_der()
456 .unwrap()
457 )
458 .to_string()
459 );
460
461 let signed_security_state = model
462 .security_state
463 .clone()
464 .expect("security_state present");
465 let security_state =
466 SignedSecurityState::from_str(signed_security_state.security_state.unwrap().as_str())
467 .unwrap()
468 .verify_and_unwrap(&ctx.get_verifying_key(SigningKeyId::UserSigningKey).unwrap())
469 .expect("security state should verify");
470 assert_eq!(
471 security_state.version(),
472 model.security_state.unwrap().security_version as u64
473 );
474 }
475
476 #[test]
477 fn test_set_to_context_v1_corrupt_private_key() {
478 let temp_store: KeyStore<KeyIds> = KeyStore::default();
481 let mut temp_ctx = temp_store.context_mut();
482
483 let user_key = temp_ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
484 let corrupt_private_key = "not a private key"
485 .encrypt(&mut temp_ctx, user_key)
486 .unwrap();
487
488 let wrapped = WrappedAccountCryptographicState::V1 {
490 private_key: corrupt_private_key,
491 };
492
493 #[expect(deprecated)]
494 let user_key_material = temp_ctx
495 .dangerous_get_symmetric_key(user_key)
496 .unwrap()
497 .to_owned();
498 drop(temp_ctx);
499 drop(temp_store);
500
501 let store: KeyStore<KeyIds> = KeyStore::default();
503 let mut ctx = store.context_mut();
504 let user_key = ctx.add_local_symmetric_key(user_key_material);
505 let security_state = RwLock::new(None);
506
507 wrapped
508 .set_to_context(&security_state, user_key, &store, ctx)
509 .unwrap();
510
511 let ctx = store.context();
512
513 assert!(ctx.has_symmetric_key(SymmetricKeyId::User));
515 assert!(!ctx.has_asymmetric_key(AsymmetricKeyId::UserPrivateKey));
517 }
518}