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