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