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