Skip to main content

bitwarden_core/client/
internal.rs

1#[cfg(feature = "internal")]
2use std::sync::RwLock;
3use std::sync::{Arc, OnceLock};
4
5use bitwarden_crypto::KeyStore;
6#[cfg(any(feature = "internal", feature = "secrets"))]
7use bitwarden_crypto::SymmetricCryptoKey;
8#[cfg(feature = "internal")]
9use bitwarden_crypto::{
10    EncString, Kdf, MasterKey, PinKey, UnsignedSharedKey, safe::PasswordProtectedKeyEnvelope,
11};
12use bitwarden_state::registry::StateRegistry;
13#[cfg(feature = "internal")]
14use tracing::{debug, info};
15
16use crate::{
17    DeviceType, UserId, auth::auth_tokens::TokenHandler, error::UserIdAlreadySetError,
18    key_management::KeySlotIds,
19};
20#[cfg(any(feature = "internal", feature = "secrets"))]
21use crate::{
22    OrganizationId, client::encryption_settings::EncryptionSettings,
23    client::login_method::LoginMethod,
24};
25#[cfg(feature = "internal")]
26use crate::{
27    client::{
28        encryption_settings::EncryptionSettingsError,
29        login_method::UserLoginMethod,
30        persisted_state::{USER_ID, USER_LOGIN_METHOD},
31    },
32    error::NotAuthenticatedError,
33    key_management::{
34        MasterPasswordUnlockData, PrivateKeySlotId, SecurityState, SigningKeySlotId,
35        SymmetricKeySlotId, V2UpgradeToken,
36        account_cryptographic_state::WrappedAccountCryptographicState, state_bridge::StateBridge,
37    },
38};
39
40#[allow(missing_docs)]
41pub struct ApiConfigurations {
42    pub identity_client: bitwarden_api_identity::apis::ApiClient,
43    pub api_client: bitwarden_api_api::apis::ApiClient,
44    pub identity_config: bitwarden_api_identity::Configuration,
45    pub api_config: bitwarden_api_api::Configuration,
46    pub device_type: DeviceType,
47}
48
49impl std::fmt::Debug for ApiConfigurations {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        f.debug_struct("ApiConfigurations")
52            .field("device_type", &self.device_type)
53            .finish_non_exhaustive()
54    }
55}
56
57impl ApiConfigurations {
58    pub(crate) fn new(
59        identity_config: bitwarden_api_identity::Configuration,
60        api_config: bitwarden_api_api::Configuration,
61        device_type: DeviceType,
62    ) -> Arc<Self> {
63        let identity = Arc::new(identity_config.clone());
64        let api = Arc::new(api_config.clone());
65        let identity_client = bitwarden_api_identity::apis::ApiClient::new(&identity);
66        let api_client = bitwarden_api_api::apis::ApiClient::new(&api);
67        Arc::new(Self {
68            identity_client,
69            api_client,
70            identity_config,
71            api_config,
72            device_type,
73        })
74    }
75
76    /// Create an `ApiConfigurations` from a mocked API client, filling in dummy
77    /// values for the remaining fields. Only available for testing.
78    #[cfg(feature = "test-fixtures")]
79    pub fn from_api_client(api_client: bitwarden_api_api::apis::ApiClient) -> Self {
80        let dummy_config = bitwarden_api_base::Configuration::new(String::new());
81        Self {
82            api_client,
83            identity_client: bitwarden_api_identity::apis::ApiClient::new(&std::sync::Arc::new(
84                dummy_config.clone(),
85            )),
86            api_config: dummy_config.clone(),
87            identity_config: dummy_config,
88            device_type: DeviceType::SDK,
89        }
90    }
91
92    pub(crate) fn get_key_connector_client(
93        self: &Arc<Self>,
94        key_connector_url: String,
95    ) -> bitwarden_api_key_connector::apis::ApiClient {
96        let api = self.api_config.clone();
97
98        let key_connector = bitwarden_api_base::Configuration {
99            base_path: key_connector_url,
100            client: api.client,
101        };
102
103        bitwarden_api_key_connector::apis::ApiClient::new(&Arc::new(key_connector))
104    }
105}
106
107#[allow(missing_docs)]
108pub struct InternalClient {
109    pub(crate) user_id: OnceLock<UserId>,
110    #[cfg_attr(not(any(feature = "internal", feature = "secrets")), allow(dead_code))]
111    pub(crate) token_handler: Arc<dyn TokenHandler>,
112
113    pub(super) api_configurations: Arc<ApiConfigurations>,
114
115    /// Reqwest client useable for external integrations like email forwarders, HIBP.
116    #[allow(unused)]
117    pub(crate) external_http_client: reqwest::Client,
118
119    pub(super) key_store: KeyStore<KeySlotIds>,
120    #[cfg(feature = "internal")]
121    pub(crate) security_state: RwLock<Option<SecurityState>>,
122
123    // TODO: Flags have been migrated to Setting but this will have to stay temporarily until the
124    // feature flags are removed.
125    #[cfg_attr(not(feature = "internal"), allow(dead_code))]
126    pub(crate) state_registry: StateRegistry,
127
128    // A bridge used to map in KM state into the SDK, until a more robust solution is implemented
129    // by platform. This is not a stable API and other teams should not use it. It will be
130    // removed as soon as KM state can be mapped via the platform APIs.
131    #[cfg(feature = "internal")]
132    pub(crate) state_bridge: StateBridge,
133}
134
135impl InternalClient {
136    #[cfg(feature = "internal")]
137    pub(crate) async fn get_login_method(&self) -> Option<UserLoginMethod> {
138        self.state_registry
139            .setting(USER_LOGIN_METHOD)
140            .ok()?
141            .get()
142            .await
143            .ok()
144            .flatten()
145    }
146
147    #[cfg(any(feature = "internal", feature = "secrets"))]
148    pub(crate) async fn set_login_method(&self, login_method: LoginMethod) {
149        match login_method {
150            #[cfg(feature = "internal")]
151            LoginMethod::User(lm) => {
152                if let Ok(setting) = self.state_registry.setting(USER_LOGIN_METHOD) {
153                    setting.update(lm).await.ok();
154                }
155            }
156            #[cfg(feature = "secrets")]
157            LoginMethod::ServiceAccount(lm) => {
158                self.token_handler.set_sm_login_method(lm).await;
159            }
160        }
161    }
162
163    #[cfg(any(feature = "internal", feature = "secrets"))]
164    pub(crate) async fn set_tokens(
165        &self,
166        token: String,
167        refresh_token: Option<String>,
168        expires_in: u64,
169    ) {
170        self.token_handler
171            .set_tokens(token, refresh_token, expires_in)
172            .await;
173    }
174
175    #[allow(missing_docs)]
176    #[cfg(feature = "internal")]
177    pub async fn get_kdf(&self) -> Result<Kdf, NotAuthenticatedError> {
178        match self.get_login_method().await {
179            Some(UserLoginMethod::Username { kdf, .. } | UserLoginMethod::ApiKey { kdf, .. }) => {
180                Ok(kdf)
181            }
182            None => Err(NotAuthenticatedError),
183        }
184    }
185
186    pub fn get_key_connector_client(
187        &self,
188        key_connector_url: String,
189    ) -> bitwarden_api_key_connector::apis::ApiClient {
190        self.api_configurations
191            .get_key_connector_client(key_connector_url)
192    }
193
194    /// Get the `ApiConfigurations` containing API clients and configurations for making requests to
195    /// the Bitwarden services.
196    pub fn get_api_configurations(&self) -> Arc<ApiConfigurations> {
197        self.api_configurations.clone()
198    }
199
200    #[allow(missing_docs)]
201    #[cfg(feature = "internal")]
202    pub fn get_http_client(&self) -> &reqwest::Client {
203        &self.external_http_client
204    }
205
206    #[allow(missing_docs)]
207    pub fn get_key_store(&self) -> &KeyStore<KeySlotIds> {
208        &self.key_store
209    }
210
211    /// Returns the security version of the user.
212    /// `1` is returned for V1 users that do not have a signed security state.
213    /// `2` or greater is returned for V2 users that have a signed security state.
214    #[cfg(feature = "internal")]
215    pub fn get_security_version(&self) -> u64 {
216        self.security_state
217            .read()
218            .expect("RwLock is not poisoned")
219            .as_ref()
220            .map_or(1, |state| state.version())
221    }
222
223    #[allow(missing_docs)]
224    pub async fn init_user_id(&self, user_id: UserId) -> Result<(), UserIdAlreadySetError> {
225        let set_uuid = self.user_id.get_or_init(|| user_id);
226
227        // Only return an error if the user_id is already set to a different value,
228        // as we want an SDK client to be tied to a single user_id.
229        // If it's the same value, we can just do nothing.
230        if *set_uuid != user_id {
231            return Err(UserIdAlreadySetError);
232        }
233
234        #[cfg(feature = "internal")]
235        if let Ok(setting) = self.state_registry.setting(USER_ID)
236            && let Err(e) = setting.update(user_id).await
237        {
238            tracing::warn!("Failed to persist user_id: {e}");
239        }
240
241        Ok(())
242    }
243
244    #[allow(missing_docs)]
245    pub fn get_user_id(&self) -> Option<UserId> {
246        self.user_id.get().copied()
247    }
248
249    #[cfg(feature = "internal")]
250    #[bitwarden_logging::instrument(err)]
251    pub(crate) fn initialize_user_crypto_key_connector_key(
252        &self,
253        master_key: MasterKey,
254        user_key: EncString,
255        account_crypto_state: WrappedAccountCryptographicState,
256        upgrade_token: &Option<V2UpgradeToken>,
257    ) -> Result<(), EncryptionSettingsError> {
258        let user_key = master_key.decrypt_user_key(user_key)?;
259        self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state, upgrade_token)
260    }
261
262    #[cfg(feature = "internal")]
263    #[bitwarden_logging::instrument(err, fields(user_id = ?self.get_user_id()))]
264    pub fn initialize_user_crypto_decrypted_key(
265        &self,
266        user_key: SymmetricCryptoKey,
267        account_crypto_state: WrappedAccountCryptographicState,
268        upgrade_token: &Option<V2UpgradeToken>,
269    ) -> Result<(), EncryptionSettingsError> {
270        let mut ctx = self.key_store.context_mut();
271
272        // Add the decrypted key to KeyStore first
273        let user_key_id = ctx.add_local_symmetric_key(user_key.clone());
274
275        // Upgrade V1 key to V2 if token is present
276        let user_key_id = match (&user_key, upgrade_token) {
277            (SymmetricCryptoKey::Aes256CbcHmacKey(_), Some(token)) => {
278                info!("V1 user key detected with upgrade token, extracting V2 key");
279                token
280                    .unwrap_v2(user_key_id, &mut ctx)
281                    .map_err(|_| EncryptionSettingsError::InvalidUpgradeToken)?
282            }
283            (SymmetricCryptoKey::XChaCha20Poly1305Key(_), Some(_)) => {
284                debug!("V2 user key already present, ignoring upgrade token");
285                user_key_id
286            }
287            _ => user_key_id,
288        };
289
290        // Note: The actual key does not get logged unless the crypto crate has the
291        // dangerous-crypto-debug feature enabled, so this is safe
292        info!("Setting user key with ID {:?}", user_key_id);
293
294        // The key store should not already have any keys initialized
295        if ctx.has_symmetric_key(SymmetricKeySlotId::User)
296            || ctx.has_private_key(PrivateKeySlotId::UserPrivateKey)
297            || ctx.has_signing_key(SigningKeySlotId::UserSigningKey)
298        {
299            return Err(EncryptionSettingsError::CryptoInitialization);
300        }
301
302        // The user key gets set to the local context frame here; It then gets persisted to the
303        // context when the cryptographic state was unwrapped correctly, so that there is no
304        // risk of a partial / incorrect setup.
305        account_crypto_state
306            .set_to_context(&self.security_state, user_key_id, &self.key_store, ctx)
307            .map_err(|_| EncryptionSettingsError::CryptoInitialization)
308    }
309
310    #[cfg(feature = "internal")]
311    #[bitwarden_logging::instrument(err)]
312    pub(crate) fn initialize_user_crypto_pin(
313        &self,
314        pin_key: PinKey,
315        pin_protected_user_key: EncString,
316        account_crypto_state: WrappedAccountCryptographicState,
317        upgrade_token: &Option<V2UpgradeToken>,
318    ) -> Result<(), EncryptionSettingsError> {
319        let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?;
320        self.initialize_user_crypto_decrypted_key(
321            decrypted_user_key,
322            account_crypto_state,
323            upgrade_token,
324        )
325    }
326
327    #[cfg(feature = "internal")]
328    #[bitwarden_logging::instrument(err)]
329    pub(crate) fn initialize_user_crypto_pin_envelope(
330        &self,
331        pin: String,
332        pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
333        account_crypto_state: WrappedAccountCryptographicState,
334        upgrade_token: &Option<V2UpgradeToken>,
335    ) -> Result<(), EncryptionSettingsError> {
336        // Note: This block ensures the ctx that is created in the block is dropped. Otherwise it
337        // would cause a deadlock when initializing the user crypto
338        let decrypted_user_key = {
339            use bitwarden_crypto::safe::PasswordProtectedKeyEnvelopeNamespace;
340            let ctx = &mut self.key_store.context_mut();
341            let decrypted_user_key_id = pin_protected_user_key_envelope
342                .unseal(&pin, PasswordProtectedKeyEnvelopeNamespace::PinUnlock, ctx)
343                .map_err(|_| EncryptionSettingsError::WrongPin)?;
344
345            // Allowing deprecated here, until a refactor to pass the Local key ids to
346            // `initialized_user_crypto_decrypted_key`
347            #[allow(deprecated)]
348            ctx.dangerous_get_symmetric_key(decrypted_user_key_id)?
349                .clone()
350        };
351        self.initialize_user_crypto_decrypted_key(
352            decrypted_user_key,
353            account_crypto_state,
354            upgrade_token,
355        )
356    }
357
358    #[cfg(feature = "secrets")]
359    pub(crate) fn initialize_crypto_single_org_key(
360        &self,
361        organization_id: OrganizationId,
362        key: SymmetricCryptoKey,
363    ) {
364        EncryptionSettings::new_single_org_key(organization_id, key, &self.key_store);
365    }
366
367    #[allow(missing_docs)]
368    #[cfg(feature = "internal")]
369    pub fn initialize_org_crypto(
370        &self,
371        org_keys: Vec<(OrganizationId, UnsignedSharedKey)>,
372    ) -> Result<(), EncryptionSettingsError> {
373        EncryptionSettings::set_org_keys(org_keys, &self.key_store)
374    }
375
376    #[cfg(feature = "internal")]
377    #[bitwarden_logging::instrument(err)]
378    pub(crate) fn initialize_user_crypto_master_password_unlock(
379        &self,
380        password: String,
381        master_password_unlock: MasterPasswordUnlockData,
382        account_crypto_state: WrappedAccountCryptographicState,
383        upgrade_token: &Option<V2UpgradeToken>,
384    ) -> Result<(), EncryptionSettingsError> {
385        let master_key = MasterKey::derive(
386            &password,
387            &master_password_unlock.salt,
388            &master_password_unlock.kdf,
389        )?;
390        let user_key =
391            master_key.decrypt_user_key(master_password_unlock.master_key_wrapped_user_key)?;
392        self.initialize_user_crypto_decrypted_key(user_key, account_crypto_state, upgrade_token)
393    }
394
395    /// Sets the local KDF state for the master password unlock login method.
396    /// Salt and user key update is not supported yet.
397    #[cfg(feature = "internal")]
398    pub async fn set_user_master_password_unlock(
399        &self,
400        master_password_unlock: MasterPasswordUnlockData,
401    ) -> Result<(), NotAuthenticatedError> {
402        let new_kdf = master_password_unlock.kdf;
403
404        let login_method = self.get_login_method().await.ok_or(NotAuthenticatedError)?;
405
406        let kdf = self.get_kdf().await?;
407
408        if kdf != new_kdf {
409            match login_method {
410                UserLoginMethod::Username {
411                    client_id, email, ..
412                } => {
413                    self.set_login_method(LoginMethod::User(UserLoginMethod::Username {
414                        client_id,
415                        email,
416                        kdf: new_kdf,
417                    }))
418                    .await
419                }
420                UserLoginMethod::ApiKey {
421                    client_id,
422                    client_secret,
423                    email,
424                    ..
425                } => {
426                    self.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey {
427                        client_id,
428                        client_secret,
429                        email,
430                        kdf: new_kdf,
431                    }))
432                    .await
433                }
434            };
435        }
436
437        Ok(())
438    }
439}
440
441#[cfg(test)]
442mod tests {
443    use std::num::NonZeroU32;
444
445    use bitwarden_crypto::{EncString, Kdf, MasterKey};
446
447    use crate::{
448        Client,
449        client::{UserLoginMethod, test_accounts::test_bitwarden_com_account},
450        key_management::MasterPasswordUnlockData,
451    };
452
453    const TEST_ACCOUNT_EMAIL: &str = "[email protected]";
454    const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
455
456    #[tokio::test]
457    async fn initializing_user_multiple_times() {
458        use super::*;
459        use crate::client::persisted_state::USER_ID;
460
461        let client = Client::new(None);
462        let user_id = UserId::new_v4();
463
464        // Setting the user ID for the first time should work.
465        assert!(client.internal.init_user_id(user_id).await.is_ok());
466        assert_eq!(client.internal.get_user_id(), Some(user_id));
467
468        // The user ID should be persisted to the settings repository.
469        let persisted = client
470            .internal
471            .state_registry
472            .setting(USER_ID)
473            .unwrap()
474            .get()
475            .await
476            .unwrap();
477        assert_eq!(persisted, Some(user_id));
478
479        // Trying to set the same user_id again should not return an error.
480        assert!(client.internal.init_user_id(user_id).await.is_ok());
481
482        // Trying to set a different user_id should return an error.
483        let different_user_id = UserId::new_v4();
484        assert!(
485            client
486                .internal
487                .init_user_id(different_user_id)
488                .await
489                .is_err()
490        );
491    }
492
493    #[tokio::test]
494    async fn test_set_user_master_password_unlock_kdf_updated() {
495        let new_kdf = Kdf::Argon2id {
496            iterations: NonZeroU32::new(4).unwrap(),
497            memory: NonZeroU32::new(65).unwrap(),
498            parallelism: NonZeroU32::new(5).unwrap(),
499        };
500
501        let user_key: EncString = TEST_ACCOUNT_USER_KEY.parse().expect("Invalid user key");
502        let email = TEST_ACCOUNT_EMAIL.to_owned();
503
504        let client = Client::init_test_account(test_bitwarden_com_account()).await;
505
506        client
507            .internal
508            .set_user_master_password_unlock(MasterPasswordUnlockData {
509                kdf: new_kdf.clone(),
510                master_key_wrapped_user_key: user_key,
511                salt: email,
512            })
513            .await
514            .unwrap();
515
516        let kdf = client.internal.get_kdf().await.unwrap();
517        assert_eq!(kdf, new_kdf);
518    }
519
520    #[tokio::test]
521    async fn test_set_user_master_password_unlock_email_and_keys_not_updated() {
522        let password = "asdfasdfasdf".to_string();
523        let new_email = format!("{}@example.com", uuid::Uuid::new_v4());
524        let kdf = Kdf::default_pbkdf2();
525        let expected_email = TEST_ACCOUNT_EMAIL.to_owned();
526
527        let (new_user_key, new_encrypted_user_key) = {
528            let master_key = MasterKey::derive(&password, &new_email, &kdf).unwrap();
529            master_key.make_user_key().unwrap()
530        };
531
532        let client = Client::init_test_account(test_bitwarden_com_account()).await;
533
534        client
535            .internal
536            .set_user_master_password_unlock(MasterPasswordUnlockData {
537                kdf,
538                master_key_wrapped_user_key: new_encrypted_user_key,
539                salt: new_email,
540            })
541            .await
542            .unwrap();
543
544        let login_method = client.internal.get_login_method().await.unwrap();
545        match login_method {
546            UserLoginMethod::Username { email, .. } => {
547                assert_eq!(*email, expected_email);
548            }
549            _ => panic!("Expected username login method"),
550        }
551
552        let user_key = client.crypto().get_user_encryption_key().await.unwrap();
553
554        assert_ne!(user_key, new_user_key.0.to_base64());
555    }
556}