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