bitwarden_user_crypto_management/key_rotation/
unlock_method.rs1use bitwarden_api_api::models::{self, UnlockMethodRequestModel};
4use bitwarden_core::key_management::{KeySlotIds, MasterPasswordUnlockData, SymmetricKeySlotId};
5use bitwarden_crypto::{Kdf, KeyStoreContext};
6
7use crate::key_rotation::{
8 RotateUserKeysError, rotate_user_keys::KeyRotationMethod, sync::SyncedAccountData,
9 unlock::ReencryptError,
10};
11
12pub(super) enum PrimaryUnlockMethod {
15 Password {
17 password: String,
18 kdf: Kdf,
19 salt: String,
20 },
21 }
23
24impl PrimaryUnlockMethod {
25 pub(super) fn from_key_rotation_method(
26 method: KeyRotationMethod,
27 synced_account_data: &SyncedAccountData,
28 ) -> Result<Self, RotateUserKeysError> {
29 match method {
30 KeyRotationMethod::Password { password } => {
31 let (kdf, salt) = synced_account_data
32 .kdf_and_salt
33 .clone()
34 .ok_or(RotateUserKeysError::ApiError)?;
35 Ok(PrimaryUnlockMethod::Password {
36 password,
37 kdf,
38 salt,
39 })
40 }
41 KeyRotationMethod::KeyConnector => {
42 Err(RotateUserKeysError::UnimplementedKeyRotationMethod)
43 }
44 KeyRotationMethod::Tde => Err(RotateUserKeysError::UnimplementedKeyRotationMethod),
45 }
46 }
47}
48
49pub(super) fn reencrypt_unlock_method_data(
51 input: PrimaryUnlockMethod,
52 new_user_key_id: SymmetricKeySlotId,
53 ctx: &mut KeyStoreContext<KeySlotIds>,
54) -> Result<UnlockMethodRequestModel, ReencryptError> {
55 match input {
56 PrimaryUnlockMethod::Password {
57 password,
58 kdf,
59 salt,
60 } => {
61 let master_password_unlock_data =
62 MasterPasswordUnlockData::derive(&password, &kdf, &salt, new_user_key_id, ctx)
63 .map_err(|_| ReencryptError::MasterPasswordDerivation)?;
64
65 Ok(UnlockMethodRequestModel {
66 unlock_method: models::UnlockMethod::MasterPassword,
67 master_password_unlock_data: Some(Box::new((&master_password_unlock_data).into())),
68 key_connector_key_wrapped_user_key: None,
69 })
70 }
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use std::num::NonZeroU32;
77
78 use bitwarden_api_api::models::UnlockMethod;
79 use bitwarden_core::key_management::{
80 KeySlotIds, MasterPasswordUnlockData,
81 account_cryptographic_state::WrappedAccountCryptographicState,
82 };
83 use bitwarden_crypto::{Kdf, KeyStore, KeyStoreContext};
84
85 use super::*;
86 use crate::key_rotation::{rotate_user_keys::KeyRotationMethod, sync::SyncedAccountData};
87
88 fn make_synced_account_data(kdf_and_salt: Option<(Kdf, String)>) -> SyncedAccountData {
89 let store: KeyStore<KeySlotIds> = KeyStore::default();
90 let mut ctx = store.context_mut();
91 let (_, wrapped_account_cryptographic_state) =
92 WrappedAccountCryptographicState::make(&mut ctx)
93 .expect("make wrapped account cryptographic state should succeed");
94 SyncedAccountData {
95 wrapped_account_cryptographic_state,
96 folders: vec![],
97 ciphers: vec![],
98 sends: vec![],
99 emergency_access_memberships: vec![],
100 organization_memberships: vec![],
101 trusted_devices: vec![],
102 passkeys: vec![],
103 kdf_and_salt,
104 }
105 }
106
107 fn create_test_kdf_pbkdf2() -> Kdf {
108 Kdf::PBKDF2 {
109 iterations: NonZeroU32::new(600000).expect("valid iterations"),
110 }
111 }
112
113 fn create_test_kdf_argon2id() -> Kdf {
114 Kdf::Argon2id {
115 iterations: NonZeroU32::new(3).expect("valid iterations"),
116 memory: NonZeroU32::new(64).expect("valid memory"),
117 parallelism: NonZeroU32::new(4).expect("valid parallelism"),
118 }
119 }
120
121 fn assert_symmetric_keys_equal(
122 key_id_1: SymmetricKeySlotId,
123 key_id_2: SymmetricKeySlotId,
124 ctx: &mut KeyStoreContext<KeySlotIds>,
125 ) {
126 #[allow(deprecated)]
127 let key_1 = ctx
128 .dangerous_get_symmetric_key(key_id_1)
129 .expect("key 1 should exist");
130 #[allow(deprecated)]
131 let key_2 = ctx
132 .dangerous_get_symmetric_key(key_id_2)
133 .expect("key 2 should exist");
134 assert_eq!(key_1, key_2, "symmetric keys should be equal");
135 }
136
137 #[test]
138 fn test_from_key_rotation_method_password_returns_input() {
139 let kdf = create_test_kdf_pbkdf2();
140 let salt = "[email protected]".to_string();
141 let synced_data = make_synced_account_data(Some((kdf.clone(), salt.clone())));
142
143 let result = PrimaryUnlockMethod::from_key_rotation_method(
144 KeyRotationMethod::Password {
145 password: "pass".to_string(),
146 },
147 &synced_data,
148 );
149
150 let input = result.expect("should succeed");
151 match input {
152 PrimaryUnlockMethod::Password {
153 password,
154 kdf: result_kdf,
155 salt: result_salt,
156 } => {
157 assert_eq!(password, "pass");
158 assert_eq!(result_kdf, kdf);
159 assert_eq!(result_salt, salt);
160 }
161 }
162 }
163
164 #[test]
165 fn test_from_key_rotation_method_password_no_kdf_returns_error() {
166 let synced_data = make_synced_account_data(None);
167
168 let result = PrimaryUnlockMethod::from_key_rotation_method(
169 KeyRotationMethod::Password {
170 password: "pass".to_string(),
171 },
172 &synced_data,
173 );
174
175 assert!(matches!(result, Err(RotateUserKeysError::ApiError)));
176 }
177
178 #[test]
179 fn test_from_key_rotation_method_key_connector_returns_error() {
180 let synced_data = make_synced_account_data(None);
181
182 let result = PrimaryUnlockMethod::from_key_rotation_method(
183 KeyRotationMethod::KeyConnector,
184 &synced_data,
185 );
186
187 assert!(matches!(
188 result,
189 Err(RotateUserKeysError::UnimplementedKeyRotationMethod)
190 ));
191 }
192
193 #[test]
194 fn test_from_key_rotation_method_tde_returns_error() {
195 let synced_data = make_synced_account_data(None);
196
197 let result =
198 PrimaryUnlockMethod::from_key_rotation_method(KeyRotationMethod::Tde, &synced_data);
199
200 assert!(matches!(
201 result,
202 Err(RotateUserKeysError::UnimplementedKeyRotationMethod)
203 ));
204 }
205
206 #[test]
207 fn test_reencrypt_unlock_method_data_password_pbkdf2() {
208 let mock_password = "test_password".to_string();
209 let store: KeyStore<KeySlotIds> = KeyStore::default();
210 let mut ctx = store.context_mut();
211 let user_key_id = ctx.generate_symmetric_key();
212
213 let input = PrimaryUnlockMethod::Password {
214 password: mock_password.clone(),
215 kdf: create_test_kdf_pbkdf2(),
216 salt: "[email protected]".to_string(),
217 };
218
219 let result = reencrypt_unlock_method_data(input, user_key_id, &mut ctx);
220
221 let model = result.expect("should be ok");
222 assert_eq!(model.unlock_method, UnlockMethod::MasterPassword);
223 assert!(model.master_password_unlock_data.is_some());
224 assert!(model.key_connector_key_wrapped_user_key.is_none());
225
226 let master_password_unlock_data_model = model
227 .master_password_unlock_data
228 .expect("should be present");
229 let master_password_unlock_data = MasterPasswordUnlockData {
230 master_key_wrapped_user_key: master_password_unlock_data_model
231 .master_key_wrapped_user_key
232 .parse()
233 .expect("should parse"),
234 kdf: create_test_kdf_pbkdf2(),
235 salt: "[email protected]".to_string(),
236 };
237 let decrypted_user_key = master_password_unlock_data
238 .unwrap_to_context(&mock_password, &mut ctx)
239 .expect("unwrap should succeed");
240 assert_symmetric_keys_equal(user_key_id, decrypted_user_key, &mut ctx);
241 }
242
243 #[test]
244 fn test_reencrypt_unlock_method_data_password_argon2id() {
245 let mock_password = "test_password".to_string();
246 let store: KeyStore<KeySlotIds> = KeyStore::default();
247 let mut ctx = store.context_mut();
248 let user_key_id = ctx.generate_symmetric_key();
249
250 let input = PrimaryUnlockMethod::Password {
251 password: mock_password.clone(),
252 kdf: create_test_kdf_argon2id(),
253 salt: "[email protected]".to_string(),
254 };
255
256 let result = reencrypt_unlock_method_data(input, user_key_id, &mut ctx);
257 assert!(result.is_ok());
258
259 let model = result.expect("should be ok");
260 assert_eq!(model.unlock_method, UnlockMethod::MasterPassword);
261 assert!(model.master_password_unlock_data.is_some());
262 assert!(model.key_connector_key_wrapped_user_key.is_none());
263
264 let master_password_unlock_data_model = model
265 .master_password_unlock_data
266 .expect("should be present");
267 let master_password_unlock_data = MasterPasswordUnlockData {
268 master_key_wrapped_user_key: master_password_unlock_data_model
269 .master_key_wrapped_user_key
270 .parse()
271 .expect("should parse"),
272 kdf: create_test_kdf_argon2id(),
273 salt: "[email protected]".to_string(),
274 };
275 let decrypted_user_key = master_password_unlock_data
276 .unwrap_to_context(&mock_password, &mut ctx)
277 .expect("unwrap should succeed");
278 assert_symmetric_keys_equal(user_key_id, decrypted_user_key, &mut ctx);
279 }
280}