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::{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,
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<Arc<LoginMethod>> {
133        self.login_method
134            .read()
135            .expect("RwLock is not poisoned")
136            .clone()
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    #[allow(missing_docs, clippy::unused_async)]
178    pub async fn get_api_configurations(&self) -> Arc<ApiConfigurations> {
179        self.api_configurations.clone()
180    }
181
182    #[allow(missing_docs)]
183    #[cfg(feature = "internal")]
184    pub fn get_http_client(&self) -> &reqwest::Client {
185        &self.external_http_client
186    }
187
188    #[allow(missing_docs)]
189    pub fn get_key_store(&self) -> &KeyStore<KeyIds> {
190        &self.key_store
191    }
192
193    /// Returns the security version of the user.
194    /// `1` is returned for V1 users that do not have a signed security state.
195    /// `2` or greater is returned for V2 users that have a signed security state.
196    #[cfg(feature = "internal")]
197    pub fn get_security_version(&self) -> u64 {
198        self.security_state
199            .read()
200            .expect("RwLock is not poisoned")
201            .as_ref()
202            .map_or(1, |state| state.version())
203    }
204
205    #[allow(missing_docs)]
206    pub fn init_user_id(&self, user_id: UserId) -> Result<(), UserIdAlreadySetError> {
207        let set_uuid = self.user_id.get_or_init(|| user_id);
208
209        // Only return an error if the user_id is already set to a different value,
210        // as we want an SDK client to be tied to a single user_id.
211        // If it's the same value, we can just do nothing.
212        if *set_uuid != user_id {
213            Err(UserIdAlreadySetError)
214        } else {
215            Ok(())
216        }
217    }
218
219    #[allow(missing_docs)]
220    pub fn get_user_id(&self) -> Option<UserId> {
221        self.user_id.get().copied()
222    }
223
224    #[cfg(feature = "internal")]
225    #[instrument(err, skip_all)]
226    pub(crate) fn initialize_user_crypto_key_connector_key(
227        &self,
228        master_key: MasterKey,
229        user_key: EncString,
230        account_crypto_state: WrappedAccountCryptographicState,
231    ) -> Result<(), EncryptionSettingsError> {
232        let user_key = master_key.decrypt_user_key(user_key)?;
233        self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state)
234    }
235
236    #[cfg(feature = "internal")]
237    #[instrument(err, skip_all, fields(user_id = ?self.get_user_id()))]
238    pub(crate) fn initialize_user_crypto_decrypted_key(
239        &self,
240        user_key: SymmetricCryptoKey,
241        account_crypto_state: WrappedAccountCryptographicState,
242    ) -> Result<(), EncryptionSettingsError> {
243        let mut ctx = self.key_store.context_mut();
244
245        // Note: The actual key does not get logged unless the crypto crate has the
246        // dangerous-crypto-debug feature enabled, so this is safe
247        info!("Setting user key {:?}", user_key);
248        let user_key = ctx.add_local_symmetric_key(user_key);
249        // The user key gets set to the local context frame here; It then gets persisted to the
250        // context when the cryptographic state was unwrapped correctly, so that there is no
251        // risk of a partial / incorrect setup.
252        account_crypto_state
253            .set_to_context(&self.security_state, user_key, &self.key_store, ctx)
254            .map_err(|_| EncryptionSettingsError::CryptoInitialization)
255    }
256
257    #[cfg(feature = "internal")]
258    #[instrument(err, skip_all)]
259    pub(crate) fn initialize_user_crypto_pin(
260        &self,
261        pin_key: PinKey,
262        pin_protected_user_key: EncString,
263        account_crypto_state: WrappedAccountCryptographicState,
264    ) -> Result<(), EncryptionSettingsError> {
265        let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?;
266        self.initialize_user_crypto_decrypted_key(decrypted_user_key, account_crypto_state)
267    }
268
269    #[cfg(feature = "internal")]
270    #[instrument(err, skip_all)]
271    pub(crate) fn initialize_user_crypto_pin_envelope(
272        &self,
273        pin: String,
274        pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
275        account_crypto_state: WrappedAccountCryptographicState,
276    ) -> Result<(), EncryptionSettingsError> {
277        let decrypted_user_key = {
278            // Note: This block ensures ctx is dropped. Otherwise it would cause a deadlock when
279            // initializing the user crypto
280            let ctx = &mut self.key_store.context_mut();
281            let decrypted_user_key_id = pin_protected_user_key_envelope
282                .unseal(&pin, ctx)
283                .map_err(|_| EncryptionSettingsError::WrongPin)?;
284
285            // Allowing deprecated here, until a refactor to pass the Local key ids to
286            // `initialized_user_crypto_decrypted_key`
287            #[allow(deprecated)]
288            ctx.dangerous_get_symmetric_key(decrypted_user_key_id)?
289                .clone()
290        };
291        self.initialize_user_crypto_decrypted_key(decrypted_user_key, account_crypto_state)
292    }
293
294    #[cfg(feature = "secrets")]
295    pub(crate) fn initialize_crypto_single_org_key(
296        &self,
297        organization_id: OrganizationId,
298        key: SymmetricCryptoKey,
299    ) {
300        EncryptionSettings::new_single_org_key(organization_id, key, &self.key_store);
301    }
302
303    #[allow(missing_docs)]
304    #[cfg(feature = "internal")]
305    pub fn initialize_org_crypto(
306        &self,
307        org_keys: Vec<(OrganizationId, UnsignedSharedKey)>,
308    ) -> Result<(), EncryptionSettingsError> {
309        EncryptionSettings::set_org_keys(org_keys, &self.key_store)
310    }
311
312    #[cfg(feature = "internal")]
313    #[instrument(err, skip_all)]
314    pub(crate) fn initialize_user_crypto_master_password_unlock(
315        &self,
316        password: String,
317        master_password_unlock: MasterPasswordUnlockData,
318        account_crypto_state: WrappedAccountCryptographicState,
319    ) -> Result<(), EncryptionSettingsError> {
320        let master_key = MasterKey::derive(
321            &password,
322            &master_password_unlock.salt,
323            &master_password_unlock.kdf,
324        )?;
325        let user_key =
326            master_key.decrypt_user_key(master_password_unlock.master_key_wrapped_user_key)?;
327        self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state)
328    }
329
330    /// Sets the local KDF state for the master password unlock login method.
331    /// Salt and user key update is not supported yet.
332    #[cfg(feature = "internal")]
333    pub fn set_user_master_password_unlock(
334        &self,
335        master_password_unlock: MasterPasswordUnlockData,
336    ) -> Result<(), NotAuthenticatedError> {
337        let new_kdf = master_password_unlock.kdf;
338
339        let login_method = self.get_login_method().ok_or(NotAuthenticatedError)?;
340
341        let kdf = self.get_kdf()?;
342
343        if kdf != new_kdf {
344            match login_method.as_ref() {
345                LoginMethod::User(UserLoginMethod::Username {
346                    client_id, email, ..
347                }) => self.set_login_method(LoginMethod::User(UserLoginMethod::Username {
348                    client_id: client_id.to_owned(),
349                    email: email.to_owned(),
350                    kdf: new_kdf,
351                })),
352                LoginMethod::User(UserLoginMethod::ApiKey {
353                    client_id,
354                    client_secret,
355                    email,
356                    ..
357                }) => self.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey {
358                    client_id: client_id.to_owned(),
359                    client_secret: client_secret.to_owned(),
360                    email: email.to_owned(),
361                    kdf: new_kdf,
362                })),
363                #[cfg(feature = "secrets")]
364                LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError),
365            };
366        }
367
368        Ok(())
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use std::num::NonZeroU32;
375
376    use bitwarden_crypto::{EncString, Kdf, MasterKey};
377
378    use crate::{
379        Client,
380        client::{LoginMethod, UserLoginMethod, test_accounts::test_bitwarden_com_account},
381        key_management::MasterPasswordUnlockData,
382    };
383
384    const TEST_ACCOUNT_EMAIL: &str = "[email protected]";
385    const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
386
387    #[test]
388    fn initializing_user_multiple_times() {
389        use super::*;
390
391        let client = Client::new(None);
392        let user_id = UserId::new_v4();
393
394        // Setting the user ID for the first time should work.
395        assert!(client.internal.init_user_id(user_id).is_ok());
396        assert_eq!(client.internal.get_user_id(), Some(user_id));
397
398        // Trying to set the same user_id again should not return an error.
399        assert!(client.internal.init_user_id(user_id).is_ok());
400
401        // Trying to set a different user_id should return an error.
402        let different_user_id = UserId::new_v4();
403        assert!(client.internal.init_user_id(different_user_id).is_err());
404    }
405
406    #[tokio::test]
407    async fn test_set_user_master_password_unlock_kdf_updated() {
408        let new_kdf = Kdf::Argon2id {
409            iterations: NonZeroU32::new(4).unwrap(),
410            memory: NonZeroU32::new(65).unwrap(),
411            parallelism: NonZeroU32::new(5).unwrap(),
412        };
413
414        let user_key: EncString = TEST_ACCOUNT_USER_KEY.parse().expect("Invalid user key");
415        let email = TEST_ACCOUNT_EMAIL.to_owned();
416
417        let client = Client::init_test_account(test_bitwarden_com_account()).await;
418
419        client
420            .internal
421            .set_user_master_password_unlock(MasterPasswordUnlockData {
422                kdf: new_kdf.clone(),
423                master_key_wrapped_user_key: user_key,
424                salt: email,
425            })
426            .unwrap();
427
428        let kdf = client.internal.get_kdf().unwrap();
429        assert_eq!(kdf, new_kdf);
430    }
431
432    #[tokio::test]
433    async fn test_set_user_master_password_unlock_email_and_keys_not_updated() {
434        let password = "asdfasdfasdf".to_string();
435        let new_email = format!("{}@example.com", uuid::Uuid::new_v4());
436        let kdf = Kdf::default_pbkdf2();
437        let expected_email = TEST_ACCOUNT_EMAIL.to_owned();
438
439        let (new_user_key, new_encrypted_user_key) = {
440            let master_key = MasterKey::derive(&password, &new_email, &kdf).unwrap();
441            master_key.make_user_key().unwrap()
442        };
443
444        let client = Client::init_test_account(test_bitwarden_com_account()).await;
445
446        client
447            .internal
448            .set_user_master_password_unlock(MasterPasswordUnlockData {
449                kdf,
450                master_key_wrapped_user_key: new_encrypted_user_key,
451                salt: new_email,
452            })
453            .unwrap();
454
455        let login_method = client.internal.get_login_method().unwrap();
456        match login_method.as_ref() {
457            LoginMethod::User(UserLoginMethod::Username { email, .. }) => {
458                assert_eq!(*email, expected_email);
459            }
460            _ => panic!("Expected username login method"),
461        }
462
463        let user_key = client.crypto().get_user_encryption_key().await.unwrap();
464
465        assert_ne!(user_key, new_user_key.0.to_base64());
466    }
467}