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