1use std::sync::{Arc, OnceLock, RwLock};
2
3use bitwarden_crypto::KeyStore;
4#[cfg(any(feature = "internal", feature = "secrets"))]
5use bitwarden_crypto::SymmetricCryptoKey;
6#[cfg(feature = "internal")]
7use bitwarden_crypto::{
8 EncString, Kdf, MasterKey, PinKey, UnsignedSharedKey, safe::PasswordProtectedKeyEnvelope,
9};
10use bitwarden_state::registry::StateRegistry;
11#[cfg(feature = "internal")]
12use tracing::{debug, info, instrument};
13
14use crate::{
15 DeviceType, UserId, auth::auth_tokens::TokenHandler, client::login_method::LoginMethod,
16 error::UserIdAlreadySetError, key_management::KeyIds,
17};
18#[cfg(any(feature = "internal", feature = "secrets"))]
19use crate::{OrganizationId, client::encryption_settings::EncryptionSettings};
20#[cfg(feature = "internal")]
21use crate::{
22 client::{
23 encryption_settings::EncryptionSettingsError, flags::Flags, login_method::UserLoginMethod,
24 },
25 error::NotAuthenticatedError,
26 key_management::{
27 MasterPasswordUnlockData, SecurityState, V2UpgradeToken,
28 account_cryptographic_state::WrappedAccountCryptographicState,
29 },
30};
31
32#[allow(missing_docs)]
33pub struct ApiConfigurations {
34 pub identity_client: bitwarden_api_identity::apis::ApiClient,
35 pub api_client: bitwarden_api_api::apis::ApiClient,
36 pub identity_config: bitwarden_api_identity::Configuration,
37 pub api_config: bitwarden_api_api::Configuration,
38 pub device_type: DeviceType,
39}
40
41impl std::fmt::Debug for ApiConfigurations {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.debug_struct("ApiConfigurations")
44 .field("device_type", &self.device_type)
45 .finish_non_exhaustive()
46 }
47}
48
49impl ApiConfigurations {
50 pub(crate) fn new(
51 identity_config: bitwarden_api_identity::Configuration,
52 api_config: bitwarden_api_api::Configuration,
53 device_type: DeviceType,
54 ) -> Arc<Self> {
55 let identity = Arc::new(identity_config.clone());
56 let api = Arc::new(api_config.clone());
57 let identity_client = bitwarden_api_identity::apis::ApiClient::new(&identity);
58 let api_client = bitwarden_api_api::apis::ApiClient::new(&api);
59 Arc::new(Self {
60 identity_client,
61 api_client,
62 identity_config,
63 api_config,
64 device_type,
65 })
66 }
67
68 pub(crate) fn get_key_connector_client(
69 self: &Arc<Self>,
70 key_connector_url: String,
71 ) -> bitwarden_api_key_connector::apis::ApiClient {
72 let api = self.api_config.clone();
73
74 let key_connector = bitwarden_api_base::Configuration {
75 base_path: key_connector_url,
76 client: api.client,
77 };
78
79 bitwarden_api_key_connector::apis::ApiClient::new(&Arc::new(key_connector))
80 }
81}
82
83#[allow(missing_docs)]
84pub struct InternalClient {
85 pub(crate) user_id: OnceLock<UserId>,
86 #[allow(
87 unused,
88 reason = "This is not used directly by SM, but it's used via the middleware"
89 )]
90 pub(crate) token_handler: Arc<dyn TokenHandler>,
91 #[allow(
92 unused,
93 reason = "This is not used directly by SM, but it's used via the middleware"
94 )]
95 pub(crate) login_method: Arc<RwLock<Option<Arc<LoginMethod>>>>,
96
97 #[cfg(feature = "internal")]
98 pub(super) flags: RwLock<Flags>,
99
100 pub(super) api_configurations: Arc<ApiConfigurations>,
101
102 #[allow(unused)]
104 pub(crate) external_http_client: reqwest::Client,
105
106 pub(super) key_store: KeyStore<KeyIds>,
107 #[cfg(feature = "internal")]
108 pub(crate) security_state: RwLock<Option<SecurityState>>,
109
110 #[allow(dead_code)]
113 pub(crate) state_registry: StateRegistry,
114}
115
116impl InternalClient {
117 #[cfg(feature = "internal")]
120 #[expect(clippy::unused_async)]
121 pub async fn load_flags(&self, flags: std::collections::HashMap<String, bool>) {
122 *self.flags.write().expect("RwLock is not poisoned") = Flags::load_from_map(flags);
123 }
124
125 #[cfg(feature = "internal")]
127 #[expect(clippy::unused_async)]
128 pub async fn get_flags(&self) -> Flags {
129 self.flags.read().expect("RwLock is not poisoned").clone()
130 }
131
132 #[cfg(feature = "internal")]
133 #[expect(clippy::unused_async)]
134 pub(crate) async fn get_login_method(&self) -> Option<UserLoginMethod> {
135 let lm = self.login_method.read().expect("RwLock is not poisoned");
136 match lm.as_deref()? {
137 LoginMethod::User(ulm) => Some(ulm.clone()),
138 #[allow(unreachable_patterns)]
139 _ => None,
140 }
141 }
142
143 #[cfg(any(feature = "internal", feature = "secrets"))]
144 #[expect(clippy::unused_async)]
145 pub(crate) async fn set_login_method(&self, login_method: LoginMethod) {
146 use tracing::debug;
147
148 debug!(?login_method, "setting login method.");
149 *self.login_method.write().expect("RwLock is not poisoned") = Some(Arc::new(login_method));
150 }
151
152 #[cfg(any(feature = "internal", feature = "secrets"))]
153 pub(crate) async fn set_tokens(
154 &self,
155 token: String,
156 refresh_token: Option<String>,
157 expires_in: u64,
158 ) {
159 self.token_handler
160 .set_tokens(token, refresh_token, expires_in)
161 .await;
162 }
163
164 #[allow(missing_docs)]
165 #[cfg(feature = "internal")]
166 #[expect(clippy::unused_async)]
167 pub async fn get_kdf(&self) -> Result<Kdf, NotAuthenticatedError> {
168 match self
169 .login_method
170 .read()
171 .expect("RwLock is not poisoned")
172 .as_deref()
173 {
174 Some(LoginMethod::User(
175 UserLoginMethod::Username { kdf, .. } | UserLoginMethod::ApiKey { kdf, .. },
176 )) => Ok(kdf.clone()),
177 _ => Err(NotAuthenticatedError),
178 }
179 }
180
181 pub fn get_key_connector_client(
182 &self,
183 key_connector_url: String,
184 ) -> bitwarden_api_key_connector::apis::ApiClient {
185 self.api_configurations
186 .get_key_connector_client(key_connector_url)
187 }
188
189 pub fn get_api_configurations(&self) -> Arc<ApiConfigurations> {
192 self.api_configurations.clone()
193 }
194
195 #[allow(missing_docs)]
196 #[cfg(feature = "internal")]
197 pub fn get_http_client(&self) -> &reqwest::Client {
198 &self.external_http_client
199 }
200
201 #[allow(missing_docs)]
202 pub fn get_key_store(&self) -> &KeyStore<KeyIds> {
203 &self.key_store
204 }
205
206 #[cfg(feature = "internal")]
210 pub fn get_security_version(&self) -> u64 {
211 self.security_state
212 .read()
213 .expect("RwLock is not poisoned")
214 .as_ref()
215 .map_or(1, |state| state.version())
216 }
217
218 #[allow(missing_docs)]
219 pub fn init_user_id(&self, user_id: UserId) -> Result<(), UserIdAlreadySetError> {
220 let set_uuid = self.user_id.get_or_init(|| user_id);
221
222 if *set_uuid != user_id {
226 Err(UserIdAlreadySetError)
227 } else {
228 Ok(())
229 }
230 }
231
232 #[allow(missing_docs)]
233 pub fn get_user_id(&self) -> Option<UserId> {
234 self.user_id.get().copied()
235 }
236
237 #[cfg(feature = "internal")]
238 #[instrument(err, skip_all)]
239 pub(crate) fn initialize_user_crypto_key_connector_key(
240 &self,
241 master_key: MasterKey,
242 user_key: EncString,
243 account_crypto_state: WrappedAccountCryptographicState,
244 upgrade_token: &Option<V2UpgradeToken>,
245 ) -> Result<(), EncryptionSettingsError> {
246 let user_key = master_key.decrypt_user_key(user_key)?;
247 self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state, upgrade_token)
248 }
249
250 #[cfg(feature = "internal")]
251 #[instrument(err, skip_all, fields(user_id = ?self.get_user_id()))]
252 pub(crate) fn initialize_user_crypto_decrypted_key(
253 &self,
254 user_key: SymmetricCryptoKey,
255 account_crypto_state: WrappedAccountCryptographicState,
256 upgrade_token: &Option<V2UpgradeToken>,
257 ) -> Result<(), EncryptionSettingsError> {
258 let mut ctx = self.key_store.context_mut();
259
260 let user_key_id = ctx.add_local_symmetric_key(user_key.clone());
262
263 let user_key_id = match (&user_key, upgrade_token) {
265 (SymmetricCryptoKey::Aes256CbcHmacKey(_), Some(token)) => {
266 info!("V1 user key detected with upgrade token, extracting V2 key");
267 token
268 .unwrap_v2(user_key_id, &mut ctx)
269 .map_err(|_| EncryptionSettingsError::InvalidUpgradeToken)?
270 }
271 (SymmetricCryptoKey::XChaCha20Poly1305Key(_), Some(_)) => {
272 debug!("V2 user key already present, ignoring upgrade token");
273 user_key_id
274 }
275 _ => user_key_id,
276 };
277
278 info!("Setting user key with ID {:?}", user_key_id);
281 account_crypto_state
285 .set_to_context(&self.security_state, user_key_id, &self.key_store, ctx)
286 .map_err(|_| EncryptionSettingsError::CryptoInitialization)
287 }
288
289 #[cfg(feature = "internal")]
290 #[instrument(err, skip_all)]
291 pub(crate) fn initialize_user_crypto_pin(
292 &self,
293 pin_key: PinKey,
294 pin_protected_user_key: EncString,
295 account_crypto_state: WrappedAccountCryptographicState,
296 upgrade_token: &Option<V2UpgradeToken>,
297 ) -> Result<(), EncryptionSettingsError> {
298 let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?;
299 self.initialize_user_crypto_decrypted_key(
300 decrypted_user_key,
301 account_crypto_state,
302 upgrade_token,
303 )
304 }
305
306 #[cfg(feature = "internal")]
307 #[instrument(err, skip_all)]
308 pub(crate) fn initialize_user_crypto_pin_envelope(
309 &self,
310 pin: String,
311 pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
312 account_crypto_state: WrappedAccountCryptographicState,
313 upgrade_token: &Option<V2UpgradeToken>,
314 ) -> Result<(), EncryptionSettingsError> {
315 let decrypted_user_key = {
318 use bitwarden_crypto::safe::PasswordProtectedKeyEnvelopeNamespace;
319 let ctx = &mut self.key_store.context_mut();
320 let decrypted_user_key_id = pin_protected_user_key_envelope
321 .unseal(&pin, PasswordProtectedKeyEnvelopeNamespace::PinUnlock, ctx)
322 .map_err(|_| EncryptionSettingsError::WrongPin)?;
323
324 #[allow(deprecated)]
327 ctx.dangerous_get_symmetric_key(decrypted_user_key_id)?
328 .clone()
329 };
330 self.initialize_user_crypto_decrypted_key(
331 decrypted_user_key,
332 account_crypto_state,
333 upgrade_token,
334 )
335 }
336
337 #[cfg(feature = "secrets")]
338 pub(crate) fn initialize_crypto_single_org_key(
339 &self,
340 organization_id: OrganizationId,
341 key: SymmetricCryptoKey,
342 ) {
343 EncryptionSettings::new_single_org_key(organization_id, key, &self.key_store);
344 }
345
346 #[allow(missing_docs)]
347 #[cfg(feature = "internal")]
348 pub fn initialize_org_crypto(
349 &self,
350 org_keys: Vec<(OrganizationId, UnsignedSharedKey)>,
351 ) -> Result<(), EncryptionSettingsError> {
352 EncryptionSettings::set_org_keys(org_keys, &self.key_store)
353 }
354
355 #[cfg(feature = "internal")]
356 #[instrument(err, skip_all)]
357 pub(crate) fn initialize_user_crypto_master_password_unlock(
358 &self,
359 password: String,
360 master_password_unlock: MasterPasswordUnlockData,
361 account_crypto_state: WrappedAccountCryptographicState,
362 upgrade_token: &Option<V2UpgradeToken>,
363 ) -> Result<(), EncryptionSettingsError> {
364 let master_key = MasterKey::derive(
365 &password,
366 &master_password_unlock.salt,
367 &master_password_unlock.kdf,
368 )?;
369 let user_key =
370 master_key.decrypt_user_key(master_password_unlock.master_key_wrapped_user_key)?;
371 self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state, upgrade_token)
372 }
373
374 #[cfg(feature = "internal")]
377 pub async fn set_user_master_password_unlock(
378 &self,
379 master_password_unlock: MasterPasswordUnlockData,
380 ) -> Result<(), NotAuthenticatedError> {
381 let new_kdf = master_password_unlock.kdf;
382
383 let login_method = self.get_login_method().await.ok_or(NotAuthenticatedError)?;
384
385 let kdf = self.get_kdf().await?;
386
387 if kdf != new_kdf {
388 match login_method {
389 UserLoginMethod::Username {
390 client_id, email, ..
391 } => {
392 self.set_login_method(LoginMethod::User(UserLoginMethod::Username {
393 client_id,
394 email,
395 kdf: new_kdf,
396 }))
397 .await
398 }
399 UserLoginMethod::ApiKey {
400 client_id,
401 client_secret,
402 email,
403 ..
404 } => {
405 self.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey {
406 client_id,
407 client_secret,
408 email,
409 kdf: new_kdf,
410 }))
411 .await
412 }
413 };
414 }
415
416 Ok(())
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use std::num::NonZeroU32;
423
424 use bitwarden_crypto::{EncString, Kdf, MasterKey};
425
426 use crate::{
427 Client,
428 client::{UserLoginMethod, test_accounts::test_bitwarden_com_account},
429 key_management::MasterPasswordUnlockData,
430 };
431
432 const TEST_ACCOUNT_EMAIL: &str = "[email protected]";
433 const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
434
435 #[test]
436 fn initializing_user_multiple_times() {
437 use super::*;
438
439 let client = Client::new(None);
440 let user_id = UserId::new_v4();
441
442 assert!(client.internal.init_user_id(user_id).is_ok());
444 assert_eq!(client.internal.get_user_id(), Some(user_id));
445
446 assert!(client.internal.init_user_id(user_id).is_ok());
448
449 let different_user_id = UserId::new_v4();
451 assert!(client.internal.init_user_id(different_user_id).is_err());
452 }
453
454 #[tokio::test]
455 async fn test_set_user_master_password_unlock_kdf_updated() {
456 let new_kdf = Kdf::Argon2id {
457 iterations: NonZeroU32::new(4).unwrap(),
458 memory: NonZeroU32::new(65).unwrap(),
459 parallelism: NonZeroU32::new(5).unwrap(),
460 };
461
462 let user_key: EncString = TEST_ACCOUNT_USER_KEY.parse().expect("Invalid user key");
463 let email = TEST_ACCOUNT_EMAIL.to_owned();
464
465 let client = Client::init_test_account(test_bitwarden_com_account()).await;
466
467 client
468 .internal
469 .set_user_master_password_unlock(MasterPasswordUnlockData {
470 kdf: new_kdf.clone(),
471 master_key_wrapped_user_key: user_key,
472 salt: email,
473 })
474 .await
475 .unwrap();
476
477 let kdf = client.internal.get_kdf().await.unwrap();
478 assert_eq!(kdf, new_kdf);
479 }
480
481 #[tokio::test]
482 async fn test_set_user_master_password_unlock_email_and_keys_not_updated() {
483 let password = "asdfasdfasdf".to_string();
484 let new_email = format!("{}@example.com", uuid::Uuid::new_v4());
485 let kdf = Kdf::default_pbkdf2();
486 let expected_email = TEST_ACCOUNT_EMAIL.to_owned();
487
488 let (new_user_key, new_encrypted_user_key) = {
489 let master_key = MasterKey::derive(&password, &new_email, &kdf).unwrap();
490 master_key.make_user_key().unwrap()
491 };
492
493 let client = Client::init_test_account(test_bitwarden_com_account()).await;
494
495 client
496 .internal
497 .set_user_master_password_unlock(MasterPasswordUnlockData {
498 kdf,
499 master_key_wrapped_user_key: new_encrypted_user_key,
500 salt: new_email,
501 })
502 .await
503 .unwrap();
504
505 let login_method = client.internal.get_login_method().await.unwrap();
506 match login_method {
507 UserLoginMethod::Username { email, .. } => {
508 assert_eq!(*email, expected_email);
509 }
510 _ => panic!("Expected username login method"),
511 }
512
513 let user_key = client.crypto().get_user_encryption_key().await.unwrap();
514
515 assert_ne!(user_key, new_user_key.0.to_base64());
516 }
517}