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::{info, 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
319        // Note: The actual key does not get logged unless the crypto crate has the
320        // dangerous-crypto-debug feature enabled, so this is safe
321        info!("Setting user key {:?}", user_key);
322        let user_key = ctx.add_local_symmetric_key(user_key);
323        // The user key gets set to the local context frame here; It then gets persisted to the
324        // context when the cryptographic state was unwrapped correctly, so that there is no
325        // risk of a partial / incorrect setup.
326        account_crypto_state
327            .set_to_context(&self.security_state, user_key, &self.key_store, ctx)
328            .map_err(|_| EncryptionSettingsError::CryptoInitialization)
329    }
330
331    #[cfg(feature = "internal")]
332    #[instrument(err, skip_all)]
333    pub(crate) fn initialize_user_crypto_pin(
334        &self,
335        pin_key: PinKey,
336        pin_protected_user_key: EncString,
337        account_crypto_state: WrappedAccountCryptographicState,
338    ) -> Result<(), EncryptionSettingsError> {
339        let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?;
340        self.initialize_user_crypto_decrypted_key(decrypted_user_key, account_crypto_state)
341    }
342
343    #[cfg(feature = "internal")]
344    #[instrument(err, skip_all)]
345    pub(crate) fn initialize_user_crypto_pin_envelope(
346        &self,
347        pin: String,
348        pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
349        account_crypto_state: WrappedAccountCryptographicState,
350    ) -> Result<(), EncryptionSettingsError> {
351        let decrypted_user_key = {
352            // Note: This block ensures ctx is dropped. Otherwise it would cause a deadlock when
353            // initializing the user crypto
354            let ctx = &mut self.key_store.context_mut();
355            let decrypted_user_key_id = pin_protected_user_key_envelope
356                .unseal(&pin, ctx)
357                .map_err(|_| EncryptionSettingsError::WrongPin)?;
358
359            // Allowing deprecated here, until a refactor to pass the Local key ids to
360            // `initialized_user_crypto_decrypted_key`
361            #[allow(deprecated)]
362            ctx.dangerous_get_symmetric_key(decrypted_user_key_id)?
363                .clone()
364        };
365        self.initialize_user_crypto_decrypted_key(decrypted_user_key, account_crypto_state)
366    }
367
368    #[cfg(feature = "secrets")]
369    pub(crate) fn initialize_crypto_single_org_key(
370        &self,
371        organization_id: OrganizationId,
372        key: SymmetricCryptoKey,
373    ) {
374        EncryptionSettings::new_single_org_key(organization_id, key, &self.key_store);
375    }
376
377    #[allow(missing_docs)]
378    #[cfg(feature = "internal")]
379    pub fn initialize_org_crypto(
380        &self,
381        org_keys: Vec<(OrganizationId, UnsignedSharedKey)>,
382    ) -> Result<(), EncryptionSettingsError> {
383        EncryptionSettings::set_org_keys(org_keys, &self.key_store)
384    }
385
386    #[cfg(feature = "internal")]
387    #[instrument(err, skip_all)]
388    pub(crate) fn initialize_user_crypto_master_password_unlock(
389        &self,
390        password: String,
391        master_password_unlock: MasterPasswordUnlockData,
392        account_crypto_state: WrappedAccountCryptographicState,
393    ) -> Result<(), EncryptionSettingsError> {
394        let master_key = MasterKey::derive(
395            &password,
396            &master_password_unlock.salt,
397            &master_password_unlock.kdf,
398        )?;
399        let user_key =
400            master_key.decrypt_user_key(master_password_unlock.master_key_wrapped_user_key)?;
401        self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state)
402    }
403
404    /// Sets the local KDF state for the master password unlock login method.
405    /// Salt and user key update is not supported yet.
406    #[cfg(feature = "internal")]
407    pub fn set_user_master_password_unlock(
408        &self,
409        master_password_unlock: MasterPasswordUnlockData,
410    ) -> Result<(), NotAuthenticatedError> {
411        let new_kdf = master_password_unlock.kdf;
412
413        let login_method = self.get_login_method().ok_or(NotAuthenticatedError)?;
414
415        let kdf = self.get_kdf()?;
416
417        if kdf != new_kdf {
418            match login_method.as_ref() {
419                LoginMethod::User(UserLoginMethod::Username {
420                    client_id, email, ..
421                }) => self.set_login_method(LoginMethod::User(UserLoginMethod::Username {
422                    client_id: client_id.to_owned(),
423                    email: email.to_owned(),
424                    kdf: new_kdf,
425                })),
426                LoginMethod::User(UserLoginMethod::ApiKey {
427                    client_id,
428                    client_secret,
429                    email,
430                    ..
431                }) => self.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey {
432                    client_id: client_id.to_owned(),
433                    client_secret: client_secret.to_owned(),
434                    email: email.to_owned(),
435                    kdf: new_kdf,
436                })),
437                #[cfg(feature = "secrets")]
438                LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError),
439            };
440        }
441
442        Ok(())
443    }
444}
445
446#[cfg(test)]
447mod tests {
448    use std::num::NonZeroU32;
449
450    use bitwarden_crypto::{EncString, Kdf, MasterKey};
451
452    use crate::{
453        Client,
454        client::{LoginMethod, UserLoginMethod, test_accounts::test_bitwarden_com_account},
455        key_management::MasterPasswordUnlockData,
456    };
457
458    const TEST_ACCOUNT_EMAIL: &str = "[email protected]";
459    const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
460
461    #[test]
462    fn initializing_user_multiple_times() {
463        use super::*;
464
465        let client = Client::new(None);
466        let user_id = UserId::new_v4();
467
468        // Setting the user ID for the first time should work.
469        assert!(client.internal.init_user_id(user_id).is_ok());
470        assert_eq!(client.internal.get_user_id(), Some(user_id));
471
472        // Trying to set the same user_id again should not return an error.
473        assert!(client.internal.init_user_id(user_id).is_ok());
474
475        // Trying to set a different user_id should return an error.
476        let different_user_id = UserId::new_v4();
477        assert!(client.internal.init_user_id(different_user_id).is_err());
478    }
479
480    #[tokio::test]
481    async fn test_set_user_master_password_unlock_kdf_updated() {
482        let new_kdf = Kdf::Argon2id {
483            iterations: NonZeroU32::new(4).unwrap(),
484            memory: NonZeroU32::new(65).unwrap(),
485            parallelism: NonZeroU32::new(5).unwrap(),
486        };
487
488        let user_key: EncString = TEST_ACCOUNT_USER_KEY.parse().expect("Invalid user key");
489        let email = TEST_ACCOUNT_EMAIL.to_owned();
490
491        let client = Client::init_test_account(test_bitwarden_com_account()).await;
492
493        client
494            .internal
495            .set_user_master_password_unlock(MasterPasswordUnlockData {
496                kdf: new_kdf.clone(),
497                master_key_wrapped_user_key: user_key,
498                salt: email,
499            })
500            .unwrap();
501
502        let kdf = client.internal.get_kdf().unwrap();
503        assert_eq!(kdf, new_kdf);
504    }
505
506    #[tokio::test]
507    async fn test_set_user_master_password_unlock_email_and_keys_not_updated() {
508        let password = "asdfasdfasdf".to_string();
509        let new_email = format!("{}@example.com", uuid::Uuid::new_v4());
510        let kdf = Kdf::default_pbkdf2();
511        let expected_email = TEST_ACCOUNT_EMAIL.to_owned();
512
513        let (new_user_key, new_encrypted_user_key) = {
514            let master_key = MasterKey::derive(&password, &new_email, &kdf).unwrap();
515            master_key.make_user_key().unwrap()
516        };
517
518        let client = Client::init_test_account(test_bitwarden_com_account()).await;
519
520        client
521            .internal
522            .set_user_master_password_unlock(MasterPasswordUnlockData {
523                kdf,
524                master_key_wrapped_user_key: new_encrypted_user_key,
525                salt: new_email,
526            })
527            .unwrap();
528
529        let login_method = client.internal.get_login_method().unwrap();
530        match login_method.as_ref() {
531            LoginMethod::User(UserLoginMethod::Username { email, .. }) => {
532                assert_eq!(*email, expected_email);
533            }
534            _ => panic!("Expected username login method"),
535        }
536
537        let user_key = client.crypto().get_user_encryption_key().await.unwrap();
538
539        assert_ne!(user_key, new_user_key.0.to_base64());
540    }
541}