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_managed_settings_types::ManagementProfile;
11use bitwarden_state::registry::StateRegistry;
12#[cfg(feature = "internal")]
13use tracing::{debug, info};
14
15use crate::{
16    DeviceType, UserId, auth::auth_tokens::TokenHandler, error::UserIdAlreadySetError,
17    key_management::KeySlotIds,
18};
19#[cfg(any(feature = "internal", feature = "secrets"))]
20use crate::{
21    OrganizationId, client::encryption_settings::EncryptionSettings,
22    client::login_method::LoginMethod,
23};
24#[cfg(feature = "internal")]
25use crate::{
26    client::{
27        encryption_settings::EncryptionSettingsError,
28        login_method::UserLoginMethod,
29        persisted_state::{USER_ID, USER_LOGIN_METHOD},
30    },
31    error::NotAuthenticatedError,
32    key_management::{
33        MasterPasswordUnlockData, PrivateKeySlotId, SecurityState, SigningKeySlotId,
34        SymmetricKeySlotId, V2UpgradeToken,
35        account_cryptographic_state::WrappedAccountCryptographicState, state_bridge::StateBridge,
36    },
37};
38
39#[allow(missing_docs)]
40pub struct ApiConfigurations {
41    pub identity_client: bitwarden_api_identity::apis::ApiClient,
42    pub api_client: bitwarden_api_api::apis::ApiClient,
43    pub identity_config: bitwarden_api_identity::Configuration,
44    pub api_config: bitwarden_api_api::Configuration,
45    pub device_type: DeviceType,
46}
47
48impl std::fmt::Debug for ApiConfigurations {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        f.debug_struct("ApiConfigurations")
51            .field("device_type", &self.device_type)
52            .finish_non_exhaustive()
53    }
54}
55
56impl ApiConfigurations {
57    pub(crate) fn new(
58        identity_config: bitwarden_api_identity::Configuration,
59        api_config: bitwarden_api_api::Configuration,
60        device_type: DeviceType,
61    ) -> Arc<Self> {
62        let identity = Arc::new(identity_config.clone());
63        let api = Arc::new(api_config.clone());
64        let identity_client = bitwarden_api_identity::apis::ApiClient::new(&identity);
65        let api_client = bitwarden_api_api::apis::ApiClient::new(&api);
66        Arc::new(Self {
67            identity_client,
68            api_client,
69            identity_config,
70            api_config,
71            device_type,
72        })
73    }
74
75    /// Create an `ApiConfigurations` from a mocked API client, filling in dummy
76    /// values for the remaining fields. Only available for testing.
77    #[cfg(feature = "test-fixtures")]
78    pub fn from_api_client(api_client: bitwarden_api_api::apis::ApiClient) -> Self {
79        let dummy_config = bitwarden_api_base::Configuration::new(String::new());
80        Self {
81            api_client,
82            identity_client: bitwarden_api_identity::apis::ApiClient::new(&std::sync::Arc::new(
83                dummy_config.clone(),
84            )),
85            api_config: dummy_config.clone(),
86            identity_config: dummy_config,
87            device_type: DeviceType::SDK,
88        }
89    }
90
91    pub(crate) fn get_key_connector_client(
92        self: &Arc<Self>,
93        key_connector_url: String,
94    ) -> bitwarden_api_key_connector::apis::ApiClient {
95        let api = self.api_config.clone();
96
97        let key_connector = bitwarden_api_base::Configuration {
98            base_path: key_connector_url,
99            client: api.client,
100        };
101
102        bitwarden_api_key_connector::apis::ApiClient::new(&Arc::new(key_connector))
103    }
104}
105
106#[allow(missing_docs)]
107pub struct InternalClient {
108    pub(crate) user_id: OnceLock<UserId>,
109    #[cfg_attr(not(any(feature = "internal", feature = "secrets")), allow(dead_code))]
110    pub(crate) token_handler: Arc<dyn TokenHandler>,
111
112    pub(super) api_configurations: Arc<ApiConfigurations>,
113
114    /// Reqwest client useable for external integrations like email forwarders, HIBP.
115    #[allow(unused)]
116    pub(crate) external_http_client: reqwest::Client,
117
118    pub(super) key_store: KeyStore<KeySlotIds>,
119    #[cfg(feature = "internal")]
120    pub(crate) security_state: RwLock<Option<SecurityState>>,
121
122    // TODO: Flags have been migrated to Setting but this will have to stay temporarily until the
123    // feature flags are removed.
124    #[cfg_attr(not(feature = "internal"), allow(dead_code))]
125    pub(crate) state_registry: Arc<StateRegistry>,
126
127    // A bridge used to map in KM state into the SDK, until a more robust solution is implemented
128    // by platform. This is not a stable API and other teams should not use it. It will be
129    // removed as soon as KM state can be mapped via the platform APIs.
130    #[cfg(feature = "internal")]
131    pub(crate) state_bridge: StateBridge,
132
133    /// Administrator-forced settings acquired from the operating system's device-management
134    /// channel. The host application owns this cell and pushes profiles into it. The SDK only
135    /// reads. Shared with the host, so updates are observed without rebuilding the client.
136    pub(crate) managed_profile: Arc<RwLock<Option<ManagementProfile>>>,
137}
138
139impl InternalClient {
140    #[cfg(feature = "internal")]
141    pub(crate) async fn get_login_method(&self) -> Option<UserLoginMethod> {
142        self.state_registry
143            .setting(USER_LOGIN_METHOD)
144            .ok()?
145            .get()
146            .await
147            .ok()
148            .flatten()
149    }
150
151    #[cfg(any(feature = "internal", feature = "secrets"))]
152    pub(crate) async fn set_login_method(&self, login_method: LoginMethod) {
153        match login_method {
154            #[cfg(feature = "internal")]
155            LoginMethod::User(lm) => {
156                if let Ok(setting) = self.state_registry.setting(USER_LOGIN_METHOD) {
157                    setting.update(lm).await.ok();
158                }
159            }
160            #[cfg(feature = "secrets")]
161            LoginMethod::ServiceAccount(lm) => {
162                self.token_handler.set_sm_login_method(lm).await;
163            }
164        }
165    }
166
167    #[cfg(any(feature = "internal", feature = "secrets"))]
168    pub(crate) async fn set_tokens(
169        &self,
170        token: String,
171        refresh_token: Option<String>,
172        expires_in: u64,
173    ) {
174        self.token_handler
175            .set_tokens(token, refresh_token, expires_in)
176            .await;
177    }
178
179    #[allow(missing_docs)]
180    #[cfg(feature = "internal")]
181    pub async fn get_kdf(&self) -> Result<Kdf, NotAuthenticatedError> {
182        match self.get_login_method().await {
183            Some(UserLoginMethod::Username { kdf, .. } | UserLoginMethod::ApiKey { kdf, .. }) => {
184                Ok(kdf)
185            }
186            None => Err(NotAuthenticatedError),
187        }
188    }
189
190    pub fn get_key_connector_client(
191        &self,
192        key_connector_url: String,
193    ) -> bitwarden_api_key_connector::apis::ApiClient {
194        self.api_configurations
195            .get_key_connector_client(key_connector_url)
196    }
197
198    /// Get the `ApiConfigurations` containing API clients and configurations for making requests to
199    /// the Bitwarden services.
200    pub fn get_api_configurations(&self) -> Arc<ApiConfigurations> {
201        self.api_configurations.clone()
202    }
203
204    /// Get the shared managed-settings profile cell.
205    ///
206    /// Prefer `ManagedSettingsClientExt::managed_settings` from `bitwarden-managed-settings` over
207    /// reading this handle directly.
208    pub fn managed_profile_handle(&self) -> Arc<RwLock<Option<ManagementProfile>>> {
209        self.managed_profile.clone()
210    }
211
212    /// Shared handle to the SDK's state registry, for the dev-only debug browse.
213    ///
214    /// Prefer `bitwarden_state::debug::StateDebug` (reached through the client's
215    /// debug tree) over reading this handle directly.
216    #[cfg(feature = "debug-capabilities")]
217    pub fn state_registry(&self) -> Arc<StateRegistry> {
218        self.state_registry.clone()
219    }
220
221    #[allow(missing_docs)]
222    #[cfg(feature = "internal")]
223    pub fn get_http_client(&self) -> &reqwest::Client {
224        &self.external_http_client
225    }
226
227    #[allow(missing_docs)]
228    pub fn get_key_store(&self) -> &KeyStore<KeySlotIds> {
229        &self.key_store
230    }
231
232    /// Returns the security version of the user.
233    /// `1` is returned for V1 users that do not have a signed security state.
234    /// `2` or greater is returned for V2 users that have a signed security state.
235    #[cfg(feature = "internal")]
236    pub fn get_security_version(&self) -> u64 {
237        self.security_state
238            .read()
239            .expect("RwLock is not poisoned")
240            .as_ref()
241            .map_or(1, |state| state.version())
242    }
243
244    #[allow(missing_docs)]
245    pub async fn init_user_id(&self, user_id: UserId) -> Result<(), UserIdAlreadySetError> {
246        let set_uuid = self.user_id.get_or_init(|| user_id);
247
248        // Only return an error if the user_id is already set to a different value,
249        // as we want an SDK client to be tied to a single user_id.
250        // If it's the same value, we can just do nothing.
251        if *set_uuid != user_id {
252            return Err(UserIdAlreadySetError);
253        }
254
255        #[cfg(feature = "internal")]
256        if let Ok(setting) = self.state_registry.setting(USER_ID)
257            && let Err(e) = setting.update(user_id).await
258        {
259            tracing::warn!("Failed to persist user_id: {e}");
260        }
261
262        Ok(())
263    }
264
265    #[allow(missing_docs)]
266    pub fn get_user_id(&self) -> Option<UserId> {
267        self.user_id.get().copied()
268    }
269
270    #[cfg(feature = "internal")]
271    #[bitwarden_logging::instrument(err)]
272    pub(crate) fn initialize_user_crypto_key_connector_key(
273        &self,
274        master_key: MasterKey,
275        user_key: EncString,
276        account_crypto_state: WrappedAccountCryptographicState,
277        upgrade_token: &Option<V2UpgradeToken>,
278    ) -> Result<(), EncryptionSettingsError> {
279        let user_key = master_key.decrypt_user_key(user_key)?;
280        self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state, upgrade_token)
281    }
282
283    #[cfg(feature = "internal")]
284    #[bitwarden_logging::instrument(err, fields(user_id = ?self.get_user_id()))]
285    pub fn initialize_user_crypto_decrypted_key(
286        &self,
287        user_key: SymmetricCryptoKey,
288        account_crypto_state: WrappedAccountCryptographicState,
289        upgrade_token: &Option<V2UpgradeToken>,
290    ) -> Result<(), EncryptionSettingsError> {
291        let mut ctx = self.key_store.context_mut();
292
293        // Add the decrypted key to KeyStore first
294        let user_key_id = ctx.add_local_symmetric_key(user_key.clone());
295
296        // Upgrade V1 key to V2 if token is present
297        let user_key_id = match (&user_key, upgrade_token) {
298            (SymmetricCryptoKey::Aes256CbcHmacKey(_), Some(token)) => {
299                info!("V1 user key detected with upgrade token, extracting V2 key");
300                token
301                    .unwrap_v2(user_key_id, &mut ctx)
302                    .map_err(|_| EncryptionSettingsError::InvalidUpgradeToken)?
303            }
304            (SymmetricCryptoKey::XAes256GcmKey(_), Some(_)) => {
305                debug!("V2 user key already present, ignoring upgrade token");
306                user_key_id
307            }
308            _ => user_key_id,
309        };
310
311        // Note: The actual key does not get logged unless the crypto crate has the
312        // dangerous-crypto-debug feature enabled, so this is safe
313        info!(
314            "Setting user key with ID {:?}",
315            ctx.get_symmetric_key_id(user_key_id)
316        );
317
318        // The key store should not already have any keys initialized
319        if ctx.has_symmetric_key(SymmetricKeySlotId::User)
320            || ctx.has_private_key(PrivateKeySlotId::UserPrivateKey)
321            || ctx.has_signing_key(SigningKeySlotId::UserSigningKey)
322        {
323            return Err(EncryptionSettingsError::CryptoInitialization);
324        }
325
326        // The user key gets set to the local context frame here; It then gets persisted to the
327        // context when the cryptographic state was unwrapped correctly, so that there is no
328        // risk of a partial / incorrect setup.
329        account_crypto_state
330            .set_to_context(&self.security_state, user_key_id, &self.key_store, ctx)
331            .map_err(|_| EncryptionSettingsError::CryptoInitialization)
332    }
333
334    #[cfg(feature = "internal")]
335    #[bitwarden_logging::instrument(err)]
336    pub(crate) fn initialize_user_crypto_pin(
337        &self,
338        pin_key: PinKey,
339        pin_protected_user_key: EncString,
340        account_crypto_state: WrappedAccountCryptographicState,
341        upgrade_token: &Option<V2UpgradeToken>,
342    ) -> Result<(), EncryptionSettingsError> {
343        let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?;
344        self.initialize_user_crypto_decrypted_key(
345            decrypted_user_key,
346            account_crypto_state,
347            upgrade_token,
348        )
349    }
350
351    #[cfg(feature = "internal")]
352    #[bitwarden_logging::instrument(err)]
353    pub(crate) fn initialize_user_crypto_pin_envelope(
354        &self,
355        pin: String,
356        pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
357        account_crypto_state: WrappedAccountCryptographicState,
358        upgrade_token: &Option<V2UpgradeToken>,
359    ) -> Result<(), EncryptionSettingsError> {
360        // Note: This block ensures the ctx that is created in the block is dropped. Otherwise it
361        // would cause a deadlock when initializing the user crypto
362        let decrypted_user_key = {
363            use bitwarden_crypto::safe::PasswordProtectedKeyEnvelopeNamespace;
364            let ctx = &mut self.key_store.context_mut();
365            let decrypted_user_key_id = pin_protected_user_key_envelope
366                .unseal(&pin, PasswordProtectedKeyEnvelopeNamespace::PinUnlock, ctx)
367                .map_err(|_| EncryptionSettingsError::WrongPin)?;
368
369            // Allowing deprecated here, until a refactor to pass the Local key ids to
370            // `initialized_user_crypto_decrypted_key`
371            #[allow(deprecated)]
372            ctx.dangerous_get_symmetric_key(decrypted_user_key_id)?
373                .clone()
374        };
375        self.initialize_user_crypto_decrypted_key(
376            decrypted_user_key,
377            account_crypto_state,
378            upgrade_token,
379        )
380    }
381
382    #[cfg(feature = "secrets")]
383    pub(crate) fn initialize_crypto_single_org_key(
384        &self,
385        organization_id: OrganizationId,
386        key: SymmetricCryptoKey,
387    ) {
388        EncryptionSettings::new_single_org_key(organization_id, key, &self.key_store);
389    }
390
391    #[allow(missing_docs)]
392    #[cfg(feature = "internal")]
393    pub fn initialize_org_crypto(
394        &self,
395        org_keys: Vec<(OrganizationId, UnsignedSharedKey)>,
396    ) -> Result<(), EncryptionSettingsError> {
397        EncryptionSettings::set_org_keys(org_keys, &self.key_store)
398    }
399
400    #[cfg(feature = "internal")]
401    #[bitwarden_logging::instrument(err)]
402    pub(crate) fn initialize_user_crypto_master_password_unlock(
403        &self,
404        password: String,
405        master_password_unlock: MasterPasswordUnlockData,
406        account_crypto_state: WrappedAccountCryptographicState,
407        upgrade_token: &Option<V2UpgradeToken>,
408    ) -> Result<(), EncryptionSettingsError> {
409        let master_key = MasterKey::derive(
410            &password,
411            &master_password_unlock.salt,
412            &master_password_unlock.kdf,
413        )?;
414        let user_key =
415            master_key.decrypt_user_key(master_password_unlock.master_key_wrapped_user_key)?;
416        self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state, upgrade_token)
417    }
418
419    /// Sets the local KDF state for the master password unlock login method.
420    /// Salt and user key update is not supported yet.
421    #[cfg(feature = "internal")]
422    pub async fn set_user_master_password_unlock(
423        &self,
424        master_password_unlock: MasterPasswordUnlockData,
425    ) -> Result<(), NotAuthenticatedError> {
426        let new_kdf = master_password_unlock.kdf;
427
428        let login_method = self.get_login_method().await.ok_or(NotAuthenticatedError)?;
429
430        let kdf = self.get_kdf().await?;
431
432        if kdf != new_kdf {
433            match login_method {
434                UserLoginMethod::Username {
435                    client_id, email, ..
436                } => {
437                    self.set_login_method(LoginMethod::User(UserLoginMethod::Username {
438                        client_id,
439                        email,
440                        kdf: new_kdf,
441                    }))
442                    .await
443                }
444                UserLoginMethod::ApiKey {
445                    client_id,
446                    client_secret,
447                    email,
448                    ..
449                } => {
450                    self.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey {
451                        client_id,
452                        client_secret,
453                        email,
454                        kdf: new_kdf,
455                    }))
456                    .await
457                }
458            };
459        }
460
461        Ok(())
462    }
463}
464
465#[cfg(test)]
466mod tests {
467    use std::num::NonZeroU32;
468
469    use bitwarden_crypto::{EncString, Kdf, MasterKey};
470
471    use crate::{
472        Client,
473        client::{UserLoginMethod, test_accounts::test_bitwarden_com_account},
474        key_management::MasterPasswordUnlockData,
475    };
476
477    const TEST_ACCOUNT_EMAIL: &str = "[email protected]";
478    const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
479
480    #[tokio::test]
481    async fn initializing_user_multiple_times() {
482        use super::*;
483        use crate::client::persisted_state::USER_ID;
484
485        let client = Client::new(None);
486        let user_id = UserId::new_v4();
487
488        // Setting the user ID for the first time should work.
489        assert!(client.internal.init_user_id(user_id).await.is_ok());
490        assert_eq!(client.internal.get_user_id(), Some(user_id));
491
492        // The user ID should be persisted to the settings repository.
493        let persisted = client
494            .internal
495            .state_registry
496            .setting(USER_ID)
497            .unwrap()
498            .get()
499            .await
500            .unwrap();
501        assert_eq!(persisted, Some(user_id));
502
503        // Trying to set the same user_id again should not return an error.
504        assert!(client.internal.init_user_id(user_id).await.is_ok());
505
506        // Trying to set a different user_id should return an error.
507        let different_user_id = UserId::new_v4();
508        assert!(
509            client
510                .internal
511                .init_user_id(different_user_id)
512                .await
513                .is_err()
514        );
515    }
516
517    #[tokio::test]
518    async fn test_set_user_master_password_unlock_kdf_updated() {
519        let new_kdf = Kdf::Argon2id {
520            iterations: NonZeroU32::new(4).unwrap(),
521            memory: NonZeroU32::new(65).unwrap(),
522            parallelism: NonZeroU32::new(5).unwrap(),
523        };
524
525        let user_key: EncString = TEST_ACCOUNT_USER_KEY.parse().expect("Invalid user key");
526        let email = TEST_ACCOUNT_EMAIL.to_owned();
527
528        let client = Client::init_test_account(test_bitwarden_com_account()).await;
529
530        client
531            .internal
532            .set_user_master_password_unlock(MasterPasswordUnlockData {
533                kdf: new_kdf.clone(),
534                master_key_wrapped_user_key: user_key,
535                salt: email,
536                contained_key_id: None,
537            })
538            .await
539            .unwrap();
540
541        let kdf = client.internal.get_kdf().await.unwrap();
542        assert_eq!(kdf, new_kdf);
543    }
544
545    #[tokio::test]
546    async fn test_set_user_master_password_unlock_email_and_keys_not_updated() {
547        let password = "asdfasdfasdf".to_string();
548        let new_email = format!("{}@example.com", uuid::Uuid::new_v4());
549        let kdf = Kdf::default_pbkdf2();
550        let expected_email = TEST_ACCOUNT_EMAIL.to_owned();
551
552        let (new_user_key, new_encrypted_user_key) = {
553            let master_key = MasterKey::derive(&password, &new_email, &kdf).unwrap();
554            master_key.make_user_key().unwrap()
555        };
556
557        let client = Client::init_test_account(test_bitwarden_com_account()).await;
558
559        client
560            .internal
561            .set_user_master_password_unlock(MasterPasswordUnlockData {
562                kdf,
563                master_key_wrapped_user_key: new_encrypted_user_key,
564                salt: new_email,
565                contained_key_id: None,
566            })
567            .await
568            .unwrap();
569
570        let login_method = client.internal.get_login_method().await.unwrap();
571        match login_method {
572            UserLoginMethod::Username { email, .. } => {
573                assert_eq!(*email, expected_email);
574            }
575            _ => panic!("Expected username login method"),
576        }
577
578        let user_key = client.crypto().get_user_encryption_key().await.unwrap();
579
580        assert_ne!(user_key, new_user_key.0.to_base64());
581    }
582}