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