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::KeySlotIds,
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,
24 flags::Flags,
25 login_method::UserLoginMethod,
26 persisted_state::{FLAGS, USER_ID},
27 },
28 error::NotAuthenticatedError,
29 key_management::{
30 MasterPasswordUnlockData, SecurityState, V2UpgradeToken,
31 account_cryptographic_state::WrappedAccountCryptographicState,
32 },
33};
34
35#[allow(missing_docs)]
36pub struct ApiConfigurations {
37 pub identity_client: bitwarden_api_identity::apis::ApiClient,
38 pub api_client: bitwarden_api_api::apis::ApiClient,
39 pub identity_config: bitwarden_api_identity::Configuration,
40 pub api_config: bitwarden_api_api::Configuration,
41 pub device_type: DeviceType,
42}
43
44impl std::fmt::Debug for ApiConfigurations {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct("ApiConfigurations")
47 .field("device_type", &self.device_type)
48 .finish_non_exhaustive()
49 }
50}
51
52impl ApiConfigurations {
53 pub(crate) fn new(
54 identity_config: bitwarden_api_identity::Configuration,
55 api_config: bitwarden_api_api::Configuration,
56 device_type: DeviceType,
57 ) -> Arc<Self> {
58 let identity = Arc::new(identity_config.clone());
59 let api = Arc::new(api_config.clone());
60 let identity_client = bitwarden_api_identity::apis::ApiClient::new(&identity);
61 let api_client = bitwarden_api_api::apis::ApiClient::new(&api);
62 Arc::new(Self {
63 identity_client,
64 api_client,
65 identity_config,
66 api_config,
67 device_type,
68 })
69 }
70
71 #[cfg(feature = "test-fixtures")]
74 pub fn from_api_client(api_client: bitwarden_api_api::apis::ApiClient) -> Self {
75 let dummy_config = bitwarden_api_base::Configuration {
76 base_path: String::new(),
77 client: reqwest_middleware::ClientBuilder::new(reqwest::Client::new()).build(),
78 };
79 Self {
80 api_client,
81 identity_client: bitwarden_api_identity::apis::ApiClient::new(&std::sync::Arc::new(
82 dummy_config.clone(),
83 )),
84 api_config: dummy_config.clone(),
85 identity_config: dummy_config,
86 device_type: DeviceType::SDK,
87 }
88 }
89
90 pub(crate) fn get_key_connector_client(
91 self: &Arc<Self>,
92 key_connector_url: String,
93 ) -> bitwarden_api_key_connector::apis::ApiClient {
94 let api = self.api_config.clone();
95
96 let key_connector = bitwarden_api_base::Configuration {
97 base_path: key_connector_url,
98 client: api.client,
99 };
100
101 bitwarden_api_key_connector::apis::ApiClient::new(&Arc::new(key_connector))
102 }
103}
104
105#[allow(missing_docs)]
106pub struct InternalClient {
107 pub(crate) user_id: OnceLock<UserId>,
108 #[allow(
109 unused,
110 reason = "This is not used directly by SM, but it's used via the middleware"
111 )]
112 pub(crate) token_handler: Arc<dyn TokenHandler>,
113 #[allow(
114 unused,
115 reason = "This is not used directly by SM, but it's used via the middleware"
116 )]
117 pub(crate) login_method: Arc<RwLock<Option<Arc<LoginMethod>>>>,
118
119 pub(super) api_configurations: Arc<ApiConfigurations>,
120
121 #[allow(unused)]
123 pub(crate) external_http_client: reqwest::Client,
124
125 pub(super) key_store: KeyStore<KeySlotIds>,
126 #[cfg(feature = "internal")]
127 pub(crate) security_state: RwLock<Option<SecurityState>>,
128
129 #[allow(dead_code)]
132 pub(crate) state_registry: StateRegistry,
133}
134
135impl InternalClient {
136 #[cfg(feature = "internal")]
139 pub async fn load_flags(&self, flags: std::collections::HashMap<String, bool>) {
140 let flags = Flags::load_from_map(flags);
141 match self.state_registry.setting(FLAGS) {
142 Ok(setting) => {
143 if let Err(e) = setting.update(flags).await {
144 tracing::warn!("Failed to persist flags: {e}");
145 }
146 }
147 Err(e) => tracing::warn!("Flags setting unavailable: {e}"),
148 }
149 }
150
151 #[cfg(feature = "internal")]
153 pub async fn get_flags(&self) -> Flags {
154 let setting = match self.state_registry.setting(FLAGS) {
155 Ok(setting) => setting,
156 Err(e) => {
157 tracing::warn!("Flags setting unavailable, using defaults: {e}");
158 return Flags::default();
159 }
160 };
161 match setting.get().await {
162 Ok(Some(flags)) => flags,
163 Ok(None) => Flags::default(),
164 Err(e) => {
165 tracing::warn!("Failed to read flags, using defaults: {e}");
166 Flags::default()
167 }
168 }
169 }
170
171 #[cfg(feature = "internal")]
172 #[expect(clippy::unused_async)]
173 pub(crate) async fn get_login_method(&self) -> Option<UserLoginMethod> {
174 let lm = self.login_method.read().expect("RwLock is not poisoned");
175 match lm.as_deref()? {
176 LoginMethod::User(ulm) => Some(ulm.clone()),
177 #[allow(unreachable_patterns)]
178 _ => None,
179 }
180 }
181
182 #[cfg(any(feature = "internal", feature = "secrets"))]
183 #[expect(clippy::unused_async)]
184 pub(crate) async fn set_login_method(&self, login_method: LoginMethod) {
185 use tracing::debug;
186
187 debug!(?login_method, "setting login method.");
188 *self.login_method.write().expect("RwLock is not poisoned") = Some(Arc::new(login_method));
189 }
190
191 #[cfg(any(feature = "internal", feature = "secrets"))]
192 pub(crate) async fn set_tokens(
193 &self,
194 token: String,
195 refresh_token: Option<String>,
196 expires_in: u64,
197 ) {
198 self.token_handler
199 .set_tokens(token, refresh_token, expires_in)
200 .await;
201 }
202
203 #[allow(missing_docs)]
204 #[cfg(feature = "internal")]
205 #[expect(clippy::unused_async)]
206 pub async fn get_kdf(&self) -> Result<Kdf, NotAuthenticatedError> {
207 match self
208 .login_method
209 .read()
210 .expect("RwLock is not poisoned")
211 .as_deref()
212 {
213 Some(LoginMethod::User(
214 UserLoginMethod::Username { kdf, .. } | UserLoginMethod::ApiKey { kdf, .. },
215 )) => Ok(kdf.clone()),
216 _ => Err(NotAuthenticatedError),
217 }
218 }
219
220 pub fn get_key_connector_client(
221 &self,
222 key_connector_url: String,
223 ) -> bitwarden_api_key_connector::apis::ApiClient {
224 self.api_configurations
225 .get_key_connector_client(key_connector_url)
226 }
227
228 pub fn get_api_configurations(&self) -> Arc<ApiConfigurations> {
231 self.api_configurations.clone()
232 }
233
234 #[allow(missing_docs)]
235 #[cfg(feature = "internal")]
236 pub fn get_http_client(&self) -> &reqwest::Client {
237 &self.external_http_client
238 }
239
240 #[allow(missing_docs)]
241 pub fn get_key_store(&self) -> &KeyStore<KeySlotIds> {
242 &self.key_store
243 }
244
245 #[cfg(feature = "internal")]
249 pub fn get_security_version(&self) -> u64 {
250 self.security_state
251 .read()
252 .expect("RwLock is not poisoned")
253 .as_ref()
254 .map_or(1, |state| state.version())
255 }
256
257 #[allow(missing_docs)]
258 pub async fn init_user_id(&self, user_id: UserId) -> Result<(), UserIdAlreadySetError> {
259 let set_uuid = self.user_id.get_or_init(|| user_id);
260
261 if *set_uuid != user_id {
265 return Err(UserIdAlreadySetError);
266 }
267
268 #[cfg(feature = "internal")]
269 if let Ok(setting) = self.state_registry.setting(USER_ID)
270 && let Err(e) = setting.update(user_id).await
271 {
272 tracing::warn!("Failed to persist user_id: {e}");
273 }
274
275 Ok(())
276 }
277
278 #[allow(missing_docs)]
279 pub fn get_user_id(&self) -> Option<UserId> {
280 self.user_id.get().copied()
281 }
282
283 #[cfg(feature = "internal")]
284 #[instrument(err, skip_all)]
285 pub(crate) fn initialize_user_crypto_key_connector_key(
286 &self,
287 master_key: MasterKey,
288 user_key: EncString,
289 account_crypto_state: WrappedAccountCryptographicState,
290 upgrade_token: &Option<V2UpgradeToken>,
291 ) -> Result<(), EncryptionSettingsError> {
292 let user_key = master_key.decrypt_user_key(user_key)?;
293 self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state, upgrade_token)
294 }
295
296 #[cfg(feature = "internal")]
297 #[instrument(err, skip_all, fields(user_id = ?self.get_user_id()))]
298 pub(crate) fn initialize_user_crypto_decrypted_key(
299 &self,
300 user_key: SymmetricCryptoKey,
301 account_crypto_state: WrappedAccountCryptographicState,
302 upgrade_token: &Option<V2UpgradeToken>,
303 ) -> Result<(), EncryptionSettingsError> {
304 let mut ctx = self.key_store.context_mut();
305
306 let user_key_id = ctx.add_local_symmetric_key(user_key.clone());
308
309 let user_key_id = match (&user_key, upgrade_token) {
311 (SymmetricCryptoKey::Aes256CbcHmacKey(_), Some(token)) => {
312 info!("V1 user key detected with upgrade token, extracting V2 key");
313 token
314 .unwrap_v2(user_key_id, &mut ctx)
315 .map_err(|_| EncryptionSettingsError::InvalidUpgradeToken)?
316 }
317 (SymmetricCryptoKey::XChaCha20Poly1305Key(_), Some(_)) => {
318 debug!("V2 user key already present, ignoring upgrade token");
319 user_key_id
320 }
321 _ => user_key_id,
322 };
323
324 info!("Setting user key with ID {:?}", user_key_id);
327 account_crypto_state
331 .set_to_context(&self.security_state, user_key_id, &self.key_store, ctx)
332 .map_err(|_| EncryptionSettingsError::CryptoInitialization)
333 }
334
335 #[cfg(feature = "internal")]
336 #[instrument(err, skip_all)]
337 pub(crate) fn initialize_user_crypto_pin(
338 &self,
339 pin_key: PinKey,
340 pin_protected_user_key: EncString,
341 account_crypto_state: WrappedAccountCryptographicState,
342 upgrade_token: &Option<V2UpgradeToken>,
343 ) -> Result<(), EncryptionSettingsError> {
344 let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?;
345 self.initialize_user_crypto_decrypted_key(
346 decrypted_user_key,
347 account_crypto_state,
348 upgrade_token,
349 )
350 }
351
352 #[cfg(feature = "internal")]
353 #[instrument(err, skip_all)]
354 pub(crate) fn initialize_user_crypto_pin_envelope(
355 &self,
356 pin: String,
357 pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
358 account_crypto_state: WrappedAccountCryptographicState,
359 upgrade_token: &Option<V2UpgradeToken>,
360 ) -> Result<(), EncryptionSettingsError> {
361 let decrypted_user_key = {
364 use bitwarden_crypto::safe::PasswordProtectedKeyEnvelopeNamespace;
365 let ctx = &mut self.key_store.context_mut();
366 let decrypted_user_key_id = pin_protected_user_key_envelope
367 .unseal(&pin, PasswordProtectedKeyEnvelopeNamespace::PinUnlock, ctx)
368 .map_err(|_| EncryptionSettingsError::WrongPin)?;
369
370 #[allow(deprecated)]
373 ctx.dangerous_get_symmetric_key(decrypted_user_key_id)?
374 .clone()
375 };
376 self.initialize_user_crypto_decrypted_key(
377 decrypted_user_key,
378 account_crypto_state,
379 upgrade_token,
380 )
381 }
382
383 #[cfg(feature = "secrets")]
384 pub(crate) fn initialize_crypto_single_org_key(
385 &self,
386 organization_id: OrganizationId,
387 key: SymmetricCryptoKey,
388 ) {
389 EncryptionSettings::new_single_org_key(organization_id, key, &self.key_store);
390 }
391
392 #[allow(missing_docs)]
393 #[cfg(feature = "internal")]
394 pub fn initialize_org_crypto(
395 &self,
396 org_keys: Vec<(OrganizationId, UnsignedSharedKey)>,
397 ) -> Result<(), EncryptionSettingsError> {
398 EncryptionSettings::set_org_keys(org_keys, &self.key_store)
399 }
400
401 #[cfg(feature = "internal")]
402 #[instrument(err, skip_all)]
403 pub(crate) fn initialize_user_crypto_master_password_unlock(
404 &self,
405 password: String,
406 master_password_unlock: MasterPasswordUnlockData,
407 account_crypto_state: WrappedAccountCryptographicState,
408 upgrade_token: &Option<V2UpgradeToken>,
409 ) -> Result<(), EncryptionSettingsError> {
410 let master_key = MasterKey::derive(
411 &password,
412 &master_password_unlock.salt,
413 &master_password_unlock.kdf,
414 )?;
415 let user_key =
416 master_key.decrypt_user_key(master_password_unlock.master_key_wrapped_user_key)?;
417 self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state, upgrade_token)
418 }
419
420 #[cfg(feature = "internal")]
423 pub async fn set_user_master_password_unlock(
424 &self,
425 master_password_unlock: MasterPasswordUnlockData,
426 ) -> Result<(), NotAuthenticatedError> {
427 let new_kdf = master_password_unlock.kdf;
428
429 let login_method = self.get_login_method().await.ok_or(NotAuthenticatedError)?;
430
431 let kdf = self.get_kdf().await?;
432
433 if kdf != new_kdf {
434 match login_method {
435 UserLoginMethod::Username {
436 client_id, email, ..
437 } => {
438 self.set_login_method(LoginMethod::User(UserLoginMethod::Username {
439 client_id,
440 email,
441 kdf: new_kdf,
442 }))
443 .await
444 }
445 UserLoginMethod::ApiKey {
446 client_id,
447 client_secret,
448 email,
449 ..
450 } => {
451 self.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey {
452 client_id,
453 client_secret,
454 email,
455 kdf: new_kdf,
456 }))
457 .await
458 }
459 };
460 }
461
462 Ok(())
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use std::num::NonZeroU32;
469
470 use bitwarden_crypto::{EncString, Kdf, MasterKey};
471
472 use crate::{
473 Client,
474 client::{UserLoginMethod, test_accounts::test_bitwarden_com_account},
475 key_management::MasterPasswordUnlockData,
476 };
477
478 const TEST_ACCOUNT_EMAIL: &str = "[email protected]";
479 const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
480
481 #[tokio::test]
482 async fn initializing_user_multiple_times() {
483 use super::*;
484 use crate::client::persisted_state::USER_ID;
485
486 let client = Client::new(None);
487 let user_id = UserId::new_v4();
488
489 assert!(client.internal.init_user_id(user_id).await.is_ok());
491 assert_eq!(client.internal.get_user_id(), Some(user_id));
492
493 let persisted = client
495 .internal
496 .state_registry
497 .setting(USER_ID)
498 .unwrap()
499 .get()
500 .await
501 .unwrap();
502 assert_eq!(persisted, Some(user_id));
503
504 assert!(client.internal.init_user_id(user_id).await.is_ok());
506
507 let different_user_id = UserId::new_v4();
509 assert!(
510 client
511 .internal
512 .init_user_id(different_user_id)
513 .await
514 .is_err()
515 );
516 }
517
518 #[tokio::test]
519 async fn load_flags_round_trips_through_setting() {
520 use std::collections::HashMap;
521
522 use super::*;
523
524 let client = Client::new(None);
525
526 let initial = client.internal.get_flags().await;
528 assert!(!initial.enable_cipher_key_encryption);
529 assert!(!initial.strict_cipher_decryption);
530
531 let mut map = HashMap::new();
533 map.insert("enableCipherKeyEncryption".to_string(), true);
534 map.insert("pm-34500-strict-cipher-decryption".to_string(), true);
535 client.internal.load_flags(map).await;
536
537 let loaded = client.internal.get_flags().await;
539 assert!(loaded.enable_cipher_key_encryption);
540 assert!(loaded.strict_cipher_decryption);
541
542 let persisted = client
544 .internal
545 .state_registry
546 .setting(FLAGS)
547 .unwrap()
548 .get()
549 .await
550 .unwrap()
551 .expect("flags should be persisted after load_flags");
552 assert!(persisted.enable_cipher_key_encryption);
553 assert!(persisted.strict_cipher_decryption);
554 }
555
556 #[tokio::test]
557 async fn test_set_user_master_password_unlock_kdf_updated() {
558 let new_kdf = Kdf::Argon2id {
559 iterations: NonZeroU32::new(4).unwrap(),
560 memory: NonZeroU32::new(65).unwrap(),
561 parallelism: NonZeroU32::new(5).unwrap(),
562 };
563
564 let user_key: EncString = TEST_ACCOUNT_USER_KEY.parse().expect("Invalid user key");
565 let email = TEST_ACCOUNT_EMAIL.to_owned();
566
567 let client = Client::init_test_account(test_bitwarden_com_account()).await;
568
569 client
570 .internal
571 .set_user_master_password_unlock(MasterPasswordUnlockData {
572 kdf: new_kdf.clone(),
573 master_key_wrapped_user_key: user_key,
574 salt: email,
575 })
576 .await
577 .unwrap();
578
579 let kdf = client.internal.get_kdf().await.unwrap();
580 assert_eq!(kdf, new_kdf);
581 }
582
583 #[tokio::test]
584 async fn test_set_user_master_password_unlock_email_and_keys_not_updated() {
585 let password = "asdfasdfasdf".to_string();
586 let new_email = format!("{}@example.com", uuid::Uuid::new_v4());
587 let kdf = Kdf::default_pbkdf2();
588 let expected_email = TEST_ACCOUNT_EMAIL.to_owned();
589
590 let (new_user_key, new_encrypted_user_key) = {
591 let master_key = MasterKey::derive(&password, &new_email, &kdf).unwrap();
592 master_key.make_user_key().unwrap()
593 };
594
595 let client = Client::init_test_account(test_bitwarden_com_account()).await;
596
597 client
598 .internal
599 .set_user_master_password_unlock(MasterPasswordUnlockData {
600 kdf,
601 master_key_wrapped_user_key: new_encrypted_user_key,
602 salt: new_email,
603 })
604 .await
605 .unwrap();
606
607 let login_method = client.internal.get_login_method().await.unwrap();
608 match login_method {
609 UserLoginMethod::Username { email, .. } => {
610 assert_eq!(*email, expected_email);
611 }
612 _ => panic!("Expected username login method"),
613 }
614
615 let user_key = client.crypto().get_user_encryption_key().await.unwrap();
616
617 assert_ne!(user_key, new_user_key.0.to_base64());
618 }
619}