Skip to main content

bitwarden_core/client/
internal.rs

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    /// Create an `ApiConfigurations` from a mocked API client, filling in dummy
72    /// values for the remaining fields. Only available for testing.
73    #[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    /// Reqwest client useable for external integrations like email forwarders, HIBP.
122    #[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    // TODO: Flags have been migrated to Setting but this will have to stay temporarily until the
130    // feature flags are removed.
131    #[allow(dead_code)]
132    pub(crate) state_registry: StateRegistry,
133}
134
135impl InternalClient {
136    /// Load feature flags. This is intentionally a collection and not the internal `Flag` enum as
137    /// we want to avoid changes in feature flags from being a breaking change.
138    #[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    /// Retrieve the active feature flags.
152    #[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    /// Get the `ApiConfigurations` containing API clients and configurations for making requests to
229    /// the Bitwarden services.
230    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    /// Returns the security version of the user.
246    /// `1` is returned for V1 users that do not have a signed security state.
247    /// `2` or greater is returned for V2 users that have a signed security state.
248    #[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        // Only return an error if the user_id is already set to a different value,
262        // as we want an SDK client to be tied to a single user_id.
263        // If it's the same value, we can just do nothing.
264        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        // Add the decrypted key to KeyStore first
307        let user_key_id = ctx.add_local_symmetric_key(user_key.clone());
308
309        // Upgrade V1 key to V2 if token is present
310        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        // Note: The actual key does not get logged unless the crypto crate has the
325        // dangerous-crypto-debug feature enabled, so this is safe
326        info!("Setting user key with ID {:?}", user_key_id);
327        // The user key gets set to the local context frame here; It then gets persisted to the
328        // context when the cryptographic state was unwrapped correctly, so that there is no
329        // risk of a partial / incorrect setup.
330        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        // Note: This block ensures the ctx that is created in the block is dropped. Otherwise it
362        // would cause a deadlock when initializing the user crypto
363        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            // Allowing deprecated here, until a refactor to pass the Local key ids to
371            // `initialized_user_crypto_decrypted_key`
372            #[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    /// Sets the local KDF state for the master password unlock login method.
421    /// Salt and user key update is not supported yet.
422    #[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        // Setting the user ID for the first time should work.
490        assert!(client.internal.init_user_id(user_id).await.is_ok());
491        assert_eq!(client.internal.get_user_id(), Some(user_id));
492
493        // The user ID should be persisted to the settings repository.
494        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        // Trying to set the same user_id again should not return an error.
505        assert!(client.internal.init_user_id(user_id).await.is_ok());
506
507        // Trying to set a different user_id should return an error.
508        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        // With no flags loaded yet, get_flags should return defaults.
527        let initial = client.internal.get_flags().await;
528        assert!(!initial.enable_cipher_key_encryption);
529        assert!(!initial.strict_cipher_decryption);
530
531        // Loading flags should persist them via the FLAGS setting.
532        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        // get_flags should now return the loaded values.
538        let loaded = client.internal.get_flags().await;
539        assert!(loaded.enable_cipher_key_encryption);
540        assert!(loaded.strict_cipher_decryption);
541
542        // The values should be readable directly from the setting too.
543        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}