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