bitwarden_core/client/
encryption_settings.rs

1#[cfg(feature = "internal")]
2use std::sync::RwLock;
3
4#[cfg(feature = "internal")]
5use bitwarden_crypto::{
6    Aes256CbcHmacKey, AsymmetricCryptoKey, CoseKeyBytes, CoseSerializable, EncString,
7    KeyDecryptable, Pkcs8PrivateKeyBytes, SigningKey, UnsignedSharedKey, XChaCha20Poly1305Key,
8};
9#[cfg(any(feature = "internal", feature = "secrets"))]
10use bitwarden_crypto::{KeyStore, SymmetricCryptoKey};
11use bitwarden_error::bitwarden_error;
12#[cfg(feature = "internal")]
13use log::warn;
14use thiserror::Error;
15#[cfg(any(feature = "internal", feature = "secrets"))]
16use uuid::Uuid;
17
18#[cfg(feature = "internal")]
19use crate::key_management::{AsymmetricKeyId, SecurityState, SignedSecurityState, SigningKeyId};
20#[cfg(any(feature = "internal", feature = "secrets"))]
21use crate::key_management::{KeyIds, SymmetricKeyId};
22use crate::{error::UserIdAlreadySetError, MissingPrivateKeyError, VaultLockedError};
23
24#[allow(missing_docs)]
25#[bitwarden_error(flat)]
26#[derive(Debug, Error)]
27pub enum EncryptionSettingsError {
28    #[error("Cryptography error, {0}")]
29    Crypto(#[from] bitwarden_crypto::CryptoError),
30
31    #[error(transparent)]
32    InvalidBase64(#[from] base64::DecodeError),
33
34    #[error(transparent)]
35    VaultLocked(#[from] VaultLockedError),
36
37    #[error("Invalid private key")]
38    InvalidPrivateKey,
39
40    #[error("Invalid signing key")]
41    InvalidSigningKey,
42
43    #[error("Invalid security state")]
44    InvalidSecurityState,
45
46    #[error(transparent)]
47    MissingPrivateKey(#[from] MissingPrivateKeyError),
48
49    #[error(transparent)]
50    UserIdAlreadySetError(#[from] UserIdAlreadySetError),
51}
52
53#[allow(clippy::large_enum_variant)]
54#[cfg(feature = "internal")]
55pub(crate) enum AccountEncryptionKeys {
56    V1 {
57        user_key: Aes256CbcHmacKey,
58        private_key: EncString,
59    },
60    V2 {
61        user_key: XChaCha20Poly1305Key,
62        private_key: EncString,
63        signing_key: EncString,
64        security_state: SignedSecurityState,
65    },
66}
67
68#[allow(missing_docs)]
69pub struct EncryptionSettings {}
70
71impl EncryptionSettings {
72    /// Initialize the encryption settings with the decrypted user key and the encrypted user
73    /// private key This should only be used when unlocking the vault via biometrics or when the
74    /// vault is set to lock: "never" Otherwise handling the decrypted user key is dangerous and
75    /// discouraged
76    #[cfg(feature = "internal")]
77    pub(crate) fn new_decrypted_key(
78        encryption_keys: AccountEncryptionKeys,
79        store: &KeyStore<KeyIds>,
80        security_state_rwlock: &RwLock<Option<SecurityState>>,
81    ) -> Result<(), EncryptionSettingsError> {
82        // This is an all-or-nothing check. The server cannot pretend a signing key or security
83        // state to be missing, because they are *always* present when the user key is an
84        // XChaCha20Poly1305Key. Thus, the server or network cannot lie about the presence of these,
85        // because otherwise the entire user account will fail to decrypt.
86        match encryption_keys {
87            AccountEncryptionKeys::V1 {
88                user_key,
89                private_key,
90            } => {
91                Self::init_v1(user_key, private_key, store)?;
92            }
93            AccountEncryptionKeys::V2 {
94                user_key,
95                private_key,
96                signing_key,
97                security_state,
98            } => {
99                Self::init_v2(
100                    user_key,
101                    private_key,
102                    signing_key,
103                    security_state,
104                    store,
105                    security_state_rwlock,
106                )?;
107            }
108        }
109
110        Ok(())
111    }
112
113    #[cfg(feature = "internal")]
114    fn init_v1(
115        user_key: Aes256CbcHmacKey,
116        private_key: EncString,
117        store: &KeyStore<KeyIds>,
118    ) -> Result<(), EncryptionSettingsError> {
119        let user_key = SymmetricCryptoKey::Aes256CbcHmacKey(user_key);
120
121        let private_key = {
122            let dec: Vec<u8> = private_key.decrypt_with_key(&user_key)?;
123
124            // FIXME: [PM-11690] - Temporarily ignore invalid private keys until we have a
125            // recovery process in place.
126            AsymmetricCryptoKey::from_der(&Pkcs8PrivateKeyBytes::from(dec))
127                .map_err(|_| {
128                    warn!("Invalid private key");
129                })
130                .ok()
131
132            // Some(
133            //     AsymmetricCryptoKey::from_der(&dec)
134            //         .map_err(|_| EncryptionSettingsError::InvalidPrivateKey)?,
135            // )
136        };
137
138        // FIXME: [PM-18098] When this is part of crypto we won't need to use deprecated methods
139        #[allow(deprecated)]
140        {
141            let mut ctx = store.context_mut();
142            ctx.set_symmetric_key(SymmetricKeyId::User, user_key)?;
143            if let Some(private_key) = private_key {
144                ctx.set_asymmetric_key(AsymmetricKeyId::UserPrivateKey, private_key)?;
145            }
146        }
147
148        Ok(())
149    }
150
151    #[cfg(feature = "internal")]
152    fn init_v2(
153        user_key: XChaCha20Poly1305Key,
154        private_key: EncString,
155        signing_key: EncString,
156        security_state: SignedSecurityState,
157        store: &KeyStore<KeyIds>,
158        sdk_security_state: &RwLock<Option<SecurityState>>,
159    ) -> Result<(), EncryptionSettingsError> {
160        use crate::key_management::SecurityState;
161
162        let user_key = SymmetricCryptoKey::XChaCha20Poly1305Key(user_key);
163
164        // For v2 users, we mandate the signing key and security state and the private key to be
165        // present and valid Everything MUST decrypt.
166        let signing_key: Vec<u8> = signing_key.decrypt_with_key(&user_key)?;
167        let signing_key = SigningKey::from_cose(&CoseKeyBytes::from(signing_key))
168            .map_err(|_| EncryptionSettingsError::InvalidSigningKey)?;
169        let private_key: Vec<u8> = private_key.decrypt_with_key(&user_key)?;
170        let private_key = AsymmetricCryptoKey::from_der(&Pkcs8PrivateKeyBytes::from(private_key))
171            .map_err(|_| EncryptionSettingsError::InvalidPrivateKey)?;
172
173        let security_state: SecurityState = security_state
174            .verify_and_unwrap(&signing_key.to_verifying_key())
175            .map_err(|_| EncryptionSettingsError::InvalidSecurityState)?;
176        *sdk_security_state.write().expect("RwLock not poisoned") = Some(security_state);
177
178        #[allow(deprecated)]
179        {
180            let mut ctx = store.context_mut();
181            ctx.set_symmetric_key(SymmetricKeyId::User, user_key)?;
182            ctx.set_asymmetric_key(AsymmetricKeyId::UserPrivateKey, private_key)?;
183            ctx.set_signing_key(SigningKeyId::UserSigningKey, signing_key)?;
184        }
185
186        Ok(())
187    }
188
189    /// Initialize the encryption settings with only a single decrypted organization key.
190    /// This is used only for logging in Secrets Manager with an access token
191    #[cfg(feature = "secrets")]
192    pub(crate) fn new_single_org_key(
193        organization_id: Uuid,
194        key: SymmetricCryptoKey,
195        store: &KeyStore<KeyIds>,
196    ) {
197        // FIXME: [PM-18098] When this is part of crypto we won't need to use deprecated methods
198        #[allow(deprecated)]
199        store
200            .context_mut()
201            .set_symmetric_key(SymmetricKeyId::Organization(organization_id), key)
202            .expect("Mutable context");
203    }
204
205    #[cfg(feature = "internal")]
206    pub(crate) fn set_org_keys(
207        org_enc_keys: Vec<(Uuid, UnsignedSharedKey)>,
208        store: &KeyStore<KeyIds>,
209    ) -> Result<(), EncryptionSettingsError> {
210        use crate::key_management::AsymmetricKeyId;
211
212        let mut ctx = store.context_mut();
213
214        // FIXME: [PM-11690] - Early abort to handle private key being corrupt
215        if org_enc_keys.is_empty() {
216            return Ok(());
217        }
218
219        if !ctx.has_asymmetric_key(AsymmetricKeyId::UserPrivateKey) {
220            return Err(MissingPrivateKeyError.into());
221        }
222
223        // Make sure we only keep the keys given in the arguments and not any of the previous
224        // ones, which might be from organizations that the user is no longer a part of anymore
225        ctx.retain_symmetric_keys(|key_ref| !matches!(key_ref, SymmetricKeyId::Organization(_)));
226
227        // Decrypt the org keys with the private key
228        for (org_id, org_enc_key) in org_enc_keys {
229            ctx.decapsulate_key_unsigned(
230                AsymmetricKeyId::UserPrivateKey,
231                SymmetricKeyId::Organization(org_id),
232                &org_enc_key,
233            )?;
234        }
235
236        Ok(())
237    }
238}