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: 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    #[allow(missing_docs)]
213    #[cfg(feature = "internal")]
214    pub fn get_http_client(&self) -> &reqwest::Client {
215        &self.external_http_client
216    }
217
218    #[allow(missing_docs)]
219    pub fn get_key_store(&self) -> &KeyStore<KeySlotIds> {
220        &self.key_store
221    }
222
223    /// Returns the security version of the user.
224    /// `1` is returned for V1 users that do not have a signed security state.
225    /// `2` or greater is returned for V2 users that have a signed security state.
226    #[cfg(feature = "internal")]
227    pub fn get_security_version(&self) -> u64 {
228        self.security_state
229            .read()
230            .expect("RwLock is not poisoned")
231            .as_ref()
232            .map_or(1, |state| state.version())
233    }
234
235    #[allow(missing_docs)]
236    pub async fn init_user_id(&self, user_id: UserId) -> Result<(), UserIdAlreadySetError> {
237        let set_uuid = self.user_id.get_or_init(|| user_id);
238
239        // Only return an error if the user_id is already set to a different value,
240        // as we want an SDK client to be tied to a single user_id.
241        // If it's the same value, we can just do nothing.
242        if *set_uuid != user_id {
243            return Err(UserIdAlreadySetError);
244        }
245
246        #[cfg(feature = "internal")]
247        if let Ok(setting) = self.state_registry.setting(USER_ID)
248            && let Err(e) = setting.update(user_id).await
249        {
250            tracing::warn!("Failed to persist user_id: {e}");
251        }
252
253        Ok(())
254    }
255
256    #[allow(missing_docs)]
257    pub fn get_user_id(&self) -> Option<UserId> {
258        self.user_id.get().copied()
259    }
260
261    #[cfg(feature = "internal")]
262    #[bitwarden_logging::instrument(err)]
263    pub(crate) fn initialize_user_crypto_key_connector_key(
264        &self,
265        master_key: MasterKey,
266        user_key: EncString,
267        account_crypto_state: WrappedAccountCryptographicState,
268        upgrade_token: &Option<V2UpgradeToken>,
269    ) -> Result<(), EncryptionSettingsError> {
270        let user_key = master_key.decrypt_user_key(user_key)?;
271        self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state, upgrade_token)
272    }
273
274    #[cfg(feature = "internal")]
275    #[bitwarden_logging::instrument(err, fields(user_id = ?self.get_user_id()))]
276    pub fn initialize_user_crypto_decrypted_key(
277        &self,
278        user_key: SymmetricCryptoKey,
279        account_crypto_state: WrappedAccountCryptographicState,
280        upgrade_token: &Option<V2UpgradeToken>,
281    ) -> Result<(), EncryptionSettingsError> {
282        let mut ctx = self.key_store.context_mut();
283
284        // Add the decrypted key to KeyStore first
285        let user_key_id = ctx.add_local_symmetric_key(user_key.clone());
286
287        // Upgrade V1 key to V2 if token is present
288        let user_key_id = match (&user_key, upgrade_token) {
289            (SymmetricCryptoKey::Aes256CbcHmacKey(_), Some(token)) => {
290                info!("V1 user key detected with upgrade token, extracting V2 key");
291                token
292                    .unwrap_v2(user_key_id, &mut ctx)
293                    .map_err(|_| EncryptionSettingsError::InvalidUpgradeToken)?
294            }
295            (SymmetricCryptoKey::XAes256GcmKey(_), Some(_)) => {
296                debug!("V2 user key already present, ignoring upgrade token");
297                user_key_id
298            }
299            _ => user_key_id,
300        };
301
302        // Note: The actual key does not get logged unless the crypto crate has the
303        // dangerous-crypto-debug feature enabled, so this is safe
304        info!("Setting user key with ID {:?}", user_key_id);
305
306        // The key store should not already have any keys initialized
307        if ctx.has_symmetric_key(SymmetricKeySlotId::User)
308            || ctx.has_private_key(PrivateKeySlotId::UserPrivateKey)
309            || ctx.has_signing_key(SigningKeySlotId::UserSigningKey)
310        {
311            return Err(EncryptionSettingsError::CryptoInitialization);
312        }
313
314        // The user key gets set to the local context frame here; It then gets persisted to the
315        // context when the cryptographic state was unwrapped correctly, so that there is no
316        // risk of a partial / incorrect setup.
317        account_crypto_state
318            .set_to_context(&self.security_state, user_key_id, &self.key_store, ctx)
319            .map_err(|_| EncryptionSettingsError::CryptoInitialization)
320    }
321
322    #[cfg(feature = "internal")]
323    #[bitwarden_logging::instrument(err)]
324    pub(crate) fn initialize_user_crypto_pin(
325        &self,
326        pin_key: PinKey,
327        pin_protected_user_key: EncString,
328        account_crypto_state: WrappedAccountCryptographicState,
329        upgrade_token: &Option<V2UpgradeToken>,
330    ) -> Result<(), EncryptionSettingsError> {
331        let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?;
332        self.initialize_user_crypto_decrypted_key(
333            decrypted_user_key,
334            account_crypto_state,
335            upgrade_token,
336        )
337    }
338
339    #[cfg(feature = "internal")]
340    #[bitwarden_logging::instrument(err)]
341    pub(crate) fn initialize_user_crypto_pin_envelope(
342        &self,
343        pin: String,
344        pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
345        account_crypto_state: WrappedAccountCryptographicState,
346        upgrade_token: &Option<V2UpgradeToken>,
347    ) -> Result<(), EncryptionSettingsError> {
348        // Note: This block ensures the ctx that is created in the block is dropped. Otherwise it
349        // would cause a deadlock when initializing the user crypto
350        let decrypted_user_key = {
351            use bitwarden_crypto::safe::PasswordProtectedKeyEnvelopeNamespace;
352            let ctx = &mut self.key_store.context_mut();
353            let decrypted_user_key_id = pin_protected_user_key_envelope
354                .unseal(&pin, PasswordProtectedKeyEnvelopeNamespace::PinUnlock, ctx)
355                .map_err(|_| EncryptionSettingsError::WrongPin)?;
356
357            // Allowing deprecated here, until a refactor to pass the Local key ids to
358            // `initialized_user_crypto_decrypted_key`
359            #[allow(deprecated)]
360            ctx.dangerous_get_symmetric_key(decrypted_user_key_id)?
361                .clone()
362        };
363        self.initialize_user_crypto_decrypted_key(
364            decrypted_user_key,
365            account_crypto_state,
366            upgrade_token,
367        )
368    }
369
370    #[cfg(feature = "secrets")]
371    pub(crate) fn initialize_crypto_single_org_key(
372        &self,
373        organization_id: OrganizationId,
374        key: SymmetricCryptoKey,
375    ) {
376        EncryptionSettings::new_single_org_key(organization_id, key, &self.key_store);
377    }
378
379    #[allow(missing_docs)]
380    #[cfg(feature = "internal")]
381    pub fn initialize_org_crypto(
382        &self,
383        org_keys: Vec<(OrganizationId, UnsignedSharedKey)>,
384    ) -> Result<(), EncryptionSettingsError> {
385        EncryptionSettings::set_org_keys(org_keys, &self.key_store)
386    }
387
388    #[cfg(feature = "internal")]
389    #[bitwarden_logging::instrument(err)]
390    pub(crate) fn initialize_user_crypto_master_password_unlock(
391        &self,
392        password: String,
393        master_password_unlock: MasterPasswordUnlockData,
394        account_crypto_state: WrappedAccountCryptographicState,
395        upgrade_token: &Option<V2UpgradeToken>,
396    ) -> Result<(), EncryptionSettingsError> {
397        let master_key = MasterKey::derive(
398            &password,
399            &master_password_unlock.salt,
400            &master_password_unlock.kdf,
401        )?;
402        let user_key =
403            master_key.decrypt_user_key(master_password_unlock.master_key_wrapped_user_key)?;
404        self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state, upgrade_token)
405    }
406
407    /// Sets the local KDF state for the master password unlock login method.
408    /// Salt and user key update is not supported yet.
409    #[cfg(feature = "internal")]
410    pub async fn set_user_master_password_unlock(
411        &self,
412        master_password_unlock: MasterPasswordUnlockData,
413    ) -> Result<(), NotAuthenticatedError> {
414        let new_kdf = master_password_unlock.kdf;
415
416        let login_method = self.get_login_method().await.ok_or(NotAuthenticatedError)?;
417
418        let kdf = self.get_kdf().await?;
419
420        if kdf != new_kdf {
421            match login_method {
422                UserLoginMethod::Username {
423                    client_id, email, ..
424                } => {
425                    self.set_login_method(LoginMethod::User(UserLoginMethod::Username {
426                        client_id,
427                        email,
428                        kdf: new_kdf,
429                    }))
430                    .await
431                }
432                UserLoginMethod::ApiKey {
433                    client_id,
434                    client_secret,
435                    email,
436                    ..
437                } => {
438                    self.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey {
439                        client_id,
440                        client_secret,
441                        email,
442                        kdf: new_kdf,
443                    }))
444                    .await
445                }
446            };
447        }
448
449        Ok(())
450    }
451}
452
453#[cfg(test)]
454mod tests {
455    use std::num::NonZeroU32;
456
457    use bitwarden_crypto::{EncString, Kdf, MasterKey};
458
459    use crate::{
460        Client,
461        client::{UserLoginMethod, test_accounts::test_bitwarden_com_account},
462        key_management::MasterPasswordUnlockData,
463    };
464
465    const TEST_ACCOUNT_EMAIL: &str = "[email protected]";
466    const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
467
468    #[tokio::test]
469    async fn initializing_user_multiple_times() {
470        use super::*;
471        use crate::client::persisted_state::USER_ID;
472
473        let client = Client::new(None);
474        let user_id = UserId::new_v4();
475
476        // Setting the user ID for the first time should work.
477        assert!(client.internal.init_user_id(user_id).await.is_ok());
478        assert_eq!(client.internal.get_user_id(), Some(user_id));
479
480        // The user ID should be persisted to the settings repository.
481        let persisted = client
482            .internal
483            .state_registry
484            .setting(USER_ID)
485            .unwrap()
486            .get()
487            .await
488            .unwrap();
489        assert_eq!(persisted, Some(user_id));
490
491        // Trying to set the same user_id again should not return an error.
492        assert!(client.internal.init_user_id(user_id).await.is_ok());
493
494        // Trying to set a different user_id should return an error.
495        let different_user_id = UserId::new_v4();
496        assert!(
497            client
498                .internal
499                .init_user_id(different_user_id)
500                .await
501                .is_err()
502        );
503    }
504
505    #[tokio::test]
506    async fn test_set_user_master_password_unlock_kdf_updated() {
507        let new_kdf = Kdf::Argon2id {
508            iterations: NonZeroU32::new(4).unwrap(),
509            memory: NonZeroU32::new(65).unwrap(),
510            parallelism: NonZeroU32::new(5).unwrap(),
511        };
512
513        let user_key: EncString = TEST_ACCOUNT_USER_KEY.parse().expect("Invalid user key");
514        let email = TEST_ACCOUNT_EMAIL.to_owned();
515
516        let client = Client::init_test_account(test_bitwarden_com_account()).await;
517
518        client
519            .internal
520            .set_user_master_password_unlock(MasterPasswordUnlockData {
521                kdf: new_kdf.clone(),
522                master_key_wrapped_user_key: user_key,
523                salt: email,
524                contained_key_id: None,
525            })
526            .await
527            .unwrap();
528
529        let kdf = client.internal.get_kdf().await.unwrap();
530        assert_eq!(kdf, new_kdf);
531    }
532
533    #[tokio::test]
534    async fn test_set_user_master_password_unlock_email_and_keys_not_updated() {
535        let password = "asdfasdfasdf".to_string();
536        let new_email = format!("{}@example.com", uuid::Uuid::new_v4());
537        let kdf = Kdf::default_pbkdf2();
538        let expected_email = TEST_ACCOUNT_EMAIL.to_owned();
539
540        let (new_user_key, new_encrypted_user_key) = {
541            let master_key = MasterKey::derive(&password, &new_email, &kdf).unwrap();
542            master_key.make_user_key().unwrap()
543        };
544
545        let client = Client::init_test_account(test_bitwarden_com_account()).await;
546
547        client
548            .internal
549            .set_user_master_password_unlock(MasterPasswordUnlockData {
550                kdf,
551                master_key_wrapped_user_key: new_encrypted_user_key,
552                salt: new_email,
553                contained_key_id: None,
554            })
555            .await
556            .unwrap();
557
558        let login_method = client.internal.get_login_method().await.unwrap();
559        match login_method {
560            UserLoginMethod::Username { email, .. } => {
561                assert_eq!(*email, expected_email);
562            }
563            _ => panic!("Expected username login method"),
564        }
565
566        let user_key = client.crypto().get_user_encryption_key().await.unwrap();
567
568        assert_ne!(user_key, new_user_key.0.to_base64());
569    }
570}