bitwarden_importers/importers/onepassword/access/
keychain.rs1use std::collections::{HashMap, VecDeque};
4
5use serde::de::DeserializeOwned;
6
7use super::{
8 account_key::AccountKey,
9 error::OnePasswordError,
10 kdf,
11 opdata::{AesKey, Encrypted, decode64_loose},
12 rsa::RsaKey,
13 wire::{AesKeyJson, EncryptedEnvelope, KeysetInfo, RsaKeyJwk},
14};
15
16const AES_SCHEME: &str = "A256GCM";
17const RSA_SCHEMES: [&str; 2] = ["RSA-OAEP", "RSA-OAEP-256"];
18pub(super) const MASTER_KEY_ID: &str = "mp";
19
20#[derive(Default)]
22pub(super) struct Keychain {
23 aes: HashMap<String, AesKey>,
24 rsa: HashMap<String, RsaKey>,
25}
26
27impl Keychain {
28 pub(super) fn new() -> Keychain {
29 Keychain::default()
30 }
31
32 pub(super) fn add_aes(&mut self, key: AesKey) {
33 self.aes.insert(key.id.clone(), key);
34 }
35
36 pub(super) fn add_rsa(&mut self, key: RsaKey) {
37 self.rsa.insert(key.id.clone(), key);
38 }
39
40 #[cfg(test)]
41 fn get_aes(&self, id: &str) -> Option<&AesKey> {
42 self.aes.get(id)
43 }
44
45 #[cfg(test)]
46 fn get_rsa(&self, id: &str) -> Option<&RsaKey> {
47 self.rsa.get(id)
48 }
49
50 pub(super) fn decrypt(&self, encrypted: &Encrypted) -> Result<Vec<u8>, OnePasswordError> {
52 if encrypted.scheme == AES_SCHEME {
53 let key = self.aes.get(&encrypted.key_id).ok_or_else(|| {
54 OnePasswordError::Internal(format!("AES key '{}' not found", encrypted.key_id))
55 })?;
56 return key.decrypt(encrypted);
57 }
58
59 if RSA_SCHEMES.contains(&encrypted.scheme.as_str()) {
60 let key = self.rsa.get(&encrypted.key_id).ok_or_else(|| {
61 OnePasswordError::Internal(format!("RSA key '{}' not found", encrypted.key_id))
62 })?;
63 return key.decrypt(encrypted);
64 }
65
66 Err(OnePasswordError::Unsupported(format!(
67 "Encryption scheme '{}' is not supported",
68 encrypted.scheme
69 )))
70 }
71
72 pub(super) fn can_decrypt(&self, encrypted: &Encrypted) -> Result<bool, OnePasswordError> {
77 if encrypted.scheme == AES_SCHEME {
78 return Ok(self.aes.contains_key(&encrypted.key_id));
79 }
80
81 if RSA_SCHEMES.contains(&encrypted.scheme.as_str()) {
82 return Ok(self.rsa.contains_key(&encrypted.key_id));
83 }
84
85 Err(OnePasswordError::Unsupported(format!(
86 "Encryption scheme '{}' is not supported",
87 encrypted.scheme
88 )))
89 }
90
91 pub(super) fn decrypt_json<T: DeserializeOwned>(
93 &self,
94 envelope: &EncryptedEnvelope,
95 ) -> Result<T, OnePasswordError> {
96 let plaintext = self.decrypt(&Encrypted::parse(envelope)?)?;
97 serde_json::from_slice(&plaintext).map_err(|_| OnePasswordError::Parse)
98 }
99
100 pub(super) fn decrypt_keysets(
102 &mut self,
103 keysets: &[KeysetInfo],
104 username: &str,
105 password: &str,
106 account_key: &AccountKey,
107 ) -> Result<(), OnePasswordError> {
108 let master_key = derive_master_key(keysets, username, password, account_key)?;
109 self.decrypt_reachable(keysets, master_key)
110 }
111
112 pub(super) fn decrypt_reachable(
115 &mut self,
116 keysets: &[KeysetInfo],
117 root_key: AesKey,
118 ) -> Result<(), OnePasswordError> {
119 let order = decryption_order(keysets, &root_key.id);
120 if order.is_empty() {
121 return Err(master_keyset_not_found());
122 }
123 self.add_aes(root_key);
124
125 for index in order {
126 self.decrypt_keyset(&keysets[index])?;
127 }
128
129 Ok(())
130 }
131
132 fn decrypt_keyset(&mut self, keyset: &KeysetInfo) -> Result<(), OnePasswordError> {
134 self.decrypt_aes_key(&keyset.enc_sym_key.envelope())?;
135 self.decrypt_rsa_key(&keyset.enc_pri_key)
136 }
137
138 pub(super) fn decrypt_aes_key(
140 &mut self,
141 envelope: &EncryptedEnvelope,
142 ) -> Result<(), OnePasswordError> {
143 let plaintext = self.decrypt(&Encrypted::parse(envelope)?)?;
144 let json: AesKeyJson =
145 serde_json::from_slice(&plaintext).map_err(|_| OnePasswordError::Parse)?;
146 self.add_aes(AesKey::new(json.kid, decode64_loose(&json.k)?));
147 Ok(())
148 }
149
150 fn decrypt_rsa_key(&mut self, envelope: &EncryptedEnvelope) -> Result<(), OnePasswordError> {
152 let plaintext = self.decrypt(&Encrypted::parse(envelope)?)?;
153 let jwk: RsaKeyJwk =
154 serde_json::from_slice(&plaintext).map_err(|_| OnePasswordError::Parse)?;
155 self.add_rsa(RsaKey::parse(&jwk)?);
156 Ok(())
157 }
158}
159
160fn derive_master_key(
162 keysets: &[KeysetInfo],
163 username: &str,
164 password: &str,
165 account_key: &AccountKey,
166) -> Result<AesKey, OnePasswordError> {
167 let master = keysets
168 .iter()
169 .filter(|k| k.encrypted_by == MASTER_KEY_ID)
170 .max_by_key(|k| k.sn)
171 .ok_or_else(master_keyset_not_found)?;
172
173 let info = &master.enc_sym_key;
174 let algorithm = info.alg.as_deref().ok_or_else(|| {
175 OnePasswordError::Internal("master keyset is missing the algorithm".into())
176 })?;
177 let salt =
178 decode64_loose(info.p2s.as_deref().ok_or_else(|| {
179 OnePasswordError::Internal("master keyset is missing the salt".into())
180 })?)?;
181 if info.p2c < kdf::MIN_PBKDF2_ITERATIONS {
182 return Err(OnePasswordError::Unsupported(format!(
183 "master keyset asks for {} iterations, below the minimum of {}",
184 info.p2c,
185 kdf::MIN_PBKDF2_ITERATIONS
186 )));
187 }
188 let key = kdf::derive_master_key(algorithm, info.p2c, &salt, username, password, account_key)?;
189
190 Ok(AesKey::new(MASTER_KEY_ID, key.to_vec()))
191}
192
193fn master_keyset_not_found() -> OnePasswordError {
194 OnePasswordError::Internal("Master keyset not found".into())
195}
196
197fn decryption_order(keysets: &[KeysetInfo], root_id: &str) -> Vec<usize> {
200 let mut encrypts: HashMap<&str, Vec<usize>> = HashMap::new();
201 for (index, keyset) in keysets.iter().enumerate() {
202 encrypts
203 .entry(encrypted_by(keyset))
204 .or_default()
205 .push(index);
206 }
207
208 let mut visited = vec![false; keysets.len()];
211 let mut queue: VecDeque<usize> = encrypts.get(root_id).cloned().unwrap_or_default().into();
212 let mut order = Vec::with_capacity(keysets.len());
213 while let Some(index) = queue.pop_front() {
214 if std::mem::replace(&mut visited[index], true) {
215 continue;
216 }
217
218 order.push(index);
219 if let Some(children) = encrypts.get(keysets[index].uuid.as_str()) {
220 queue.extend(children);
221 }
222 }
223
224 order
225}
226
227fn encrypted_by(keyset: &KeysetInfo) -> &str {
230 if keyset.encrypted_by.is_empty() {
231 &keyset.enc_sym_key.kid
232 } else {
233 &keyset.encrypted_by
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use data_encoding::HEXLOWER;
240
241 use super::{super::wire::KeysetsInfo, *};
242
243 fn hex(s: &str) -> Vec<u8> {
244 HEXLOWER.decode(s.as_bytes()).expect("valid hex")
245 }
246
247 #[test]
248 fn decrypt_aes_key_adds_key_to_keychain() {
249 let mut keychain = Keychain::new();
250 keychain.add_aes(AesKey::new(
251 MASTER_KEY_ID,
252 hex("44c38e8fedb84a1ab5ba74ed98dde931f6500ae39c1d9c85e20a7268ab2074f0"),
253 ));
254
255 let envelope: EncryptedEnvelope =
256 serde_json::from_str(include_str!("fixtures/encrypted-aes-key.json"))
257 .expect("valid fixture");
258 keychain.decrypt_aes_key(&envelope).expect("decrypts");
259
260 assert!(keychain.get_aes("szerdhg2ww2ahjo4ilz57x7cce").is_some());
261 }
262
263 #[test]
264 fn decrypt_rsa_key_adds_key_to_keychain() {
265 let mut keychain = Keychain::new();
266 keychain.add_aes(AesKey::new(
267 "szerdhg2ww2ahjo4ilz57x7cce",
268 hex("bba932f6032dc4dffaa9b8f03c9fd4b810127b89a49408db7b914a131690c091"),
269 ));
270
271 let envelope: EncryptedEnvelope =
272 serde_json::from_str(include_str!("fixtures/encrypted-rsa-key.json"))
273 .expect("valid fixture");
274 keychain.decrypt_rsa_key(&envelope).expect("decrypts");
275
276 assert!(keychain.get_rsa("szerdhg2ww2ahjo4ilz57x7cce").is_some());
277 }
278
279 #[test]
280 fn decrypt_keysets_decrypts_all_keys() {
281 let keysets: KeysetsInfo =
282 serde_json::from_str(include_str!("fixtures/get-keysets-response.json"))
283 .expect("valid fixture");
284 let mut keychain = Keychain::new();
285
286 let master_key = AesKey::new(
290 MASTER_KEY_ID,
291 hex("44c38e8fedb84a1ab5ba74ed98dde931f6500ae39c1d9c85e20a7268ab2074f0"),
292 );
293 keychain
294 .decrypt_reachable(&keysets.keysets, master_key)
295 .expect("decrypts keysets");
296
297 assert!(keychain.get_aes(MASTER_KEY_ID).is_some());
298 for id in [
299 "szerdhg2ww2ahjo4ilz57x7cce",
300 "yf2ji37vkqdow7pnbo3y37b3lu",
301 "srkx3r5c3qgyzsdswfc4awgh2m",
302 "sm5hkw3mxwdcwcgljf4kyplwea",
303 ] {
304 assert!(keychain.get_aes(id).is_some(), "missing AES key {id}");
305 assert!(keychain.get_rsa(id).is_some(), "missing RSA key {id}");
306 }
307 }
308
309 fn keyset(uuid: &str, encrypted_by: &str) -> KeysetInfo {
310 serde_json::from_value(serde_json::json!({
311 "uuid": uuid,
312 "encryptedBy": encrypted_by,
313 "sn": 1,
314 "encSymKey": {"kid": encrypted_by, "enc": AES_SCHEME, "cty": "b5+jwk+json", "data": ""},
315 "encPriKey": {"kid": encrypted_by, "enc": AES_SCHEME, "cty": "b5+jwk+json", "data": ""},
316 }))
317 .expect("valid keyset")
318 }
319
320 #[test]
321 fn decryption_order_follows_the_chain_from_the_root() {
322 let keysets = [
323 keyset("c", "b"),
324 keyset("a", MASTER_KEY_ID),
325 keyset("b", "a"),
326 keyset("orphan", "nobody"),
327 ];
328
329 assert_eq!(decryption_order(&keysets, MASTER_KEY_ID), vec![1, 2, 0]);
332 }
333
334 #[test]
335 fn decryption_order_visits_a_self_referential_keyset_once() {
336 let keysets = [keyset("a", MASTER_KEY_ID), keyset("a", "a")];
339
340 assert_eq!(decryption_order(&keysets, MASTER_KEY_ID), vec![0, 1]);
341 }
342
343 #[test]
344 fn decrypt_reachable_fails_when_no_keyset_is_encrypted_by_the_root() {
345 let keysets = [keyset("a", "other-key"), keyset("b", "a")];
346 let mut keychain = Keychain::new();
347
348 let err = keychain
349 .decrypt_reachable(&keysets, AesKey::new(MASTER_KEY_ID, vec![0; 32]))
350 .expect_err("the root opens nothing");
351
352 assert!(
353 matches!(err, OnePasswordError::Internal(message) if message == "Master keyset not found")
354 );
355 }
356
357 #[test]
358 fn decrypt_rejects_unknown_scheme() {
359 let keychain = Keychain::new();
360 let encrypted = Encrypted {
361 key_id: MASTER_KEY_ID.into(),
362 scheme: "A128CBC".into(),
363 iv: Vec::new(),
364 ciphertext: Vec::new(),
365 };
366
367 let err = keychain
368 .can_decrypt(&encrypted)
369 .expect_err("unsupported scheme");
370 assert!(matches!(err, OnePasswordError::Unsupported(_)));
371
372 let err = keychain
373 .decrypt(&encrypted)
374 .expect_err("unsupported scheme");
375 assert!(matches!(err, OnePasswordError::Unsupported(_)));
376 }
377}