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"];
18const 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 fn decrypt_reachable(
114 &mut self,
115 keysets: &[KeysetInfo],
116 root_key: AesKey,
117 ) -> Result<(), OnePasswordError> {
118 let order = decryption_order(keysets, &root_key.id);
119 self.add_aes(root_key);
120
121 for index in order {
122 self.decrypt_keyset(&keysets[index])?;
123 }
124
125 Ok(())
126 }
127
128 fn decrypt_keyset(&mut self, keyset: &KeysetInfo) -> Result<(), OnePasswordError> {
130 self.decrypt_aes_key(&keyset.enc_sym_key.envelope())?;
131 self.decrypt_rsa_key(&keyset.enc_pri_key)
132 }
133
134 pub(super) fn decrypt_aes_key(
136 &mut self,
137 envelope: &EncryptedEnvelope,
138 ) -> Result<(), OnePasswordError> {
139 let plaintext = self.decrypt(&Encrypted::parse(envelope)?)?;
140 let json: AesKeyJson =
141 serde_json::from_slice(&plaintext).map_err(|_| OnePasswordError::Parse)?;
142 self.add_aes(AesKey::new(json.kid, decode64_loose(&json.k)?));
143 Ok(())
144 }
145
146 fn decrypt_rsa_key(&mut self, envelope: &EncryptedEnvelope) -> Result<(), OnePasswordError> {
148 let plaintext = self.decrypt(&Encrypted::parse(envelope)?)?;
149 let jwk: RsaKeyJwk =
150 serde_json::from_slice(&plaintext).map_err(|_| OnePasswordError::Parse)?;
151 self.add_rsa(RsaKey::parse(&jwk)?);
152 Ok(())
153 }
154}
155
156fn derive_master_key(
158 keysets: &[KeysetInfo],
159 username: &str,
160 password: &str,
161 account_key: &AccountKey,
162) -> Result<AesKey, OnePasswordError> {
163 let master = keysets
164 .iter()
165 .filter(|k| k.encrypted_by == MASTER_KEY_ID)
166 .max_by_key(|k| k.sn)
167 .ok_or_else(|| OnePasswordError::Internal("Master keyset not found".into()))?;
168
169 let info = &master.enc_sym_key;
170 let algorithm = info.alg.as_deref().ok_or_else(|| {
171 OnePasswordError::Internal("master keyset is missing the algorithm".into())
172 })?;
173 let salt =
174 decode64_loose(info.p2s.as_deref().ok_or_else(|| {
175 OnePasswordError::Internal("master keyset is missing the salt".into())
176 })?)?;
177 if info.p2c < kdf::MIN_PBKDF2_ITERATIONS {
178 return Err(OnePasswordError::Unsupported(format!(
179 "master keyset asks for {} iterations, below the minimum of {}",
180 info.p2c,
181 kdf::MIN_PBKDF2_ITERATIONS
182 )));
183 }
184 let key = kdf::derive_master_key(algorithm, info.p2c, &salt, username, password, account_key)?;
185
186 Ok(AesKey::new(MASTER_KEY_ID, key.to_vec()))
187}
188
189fn decryption_order(keysets: &[KeysetInfo], root_id: &str) -> Vec<usize> {
192 let mut encrypts: HashMap<&str, Vec<usize>> = HashMap::new();
193 for (index, keyset) in keysets.iter().enumerate() {
194 encrypts
195 .entry(encrypted_by(keyset))
196 .or_default()
197 .push(index);
198 }
199
200 let mut visited = vec![false; keysets.len()];
203 let mut queue: VecDeque<usize> = encrypts.get(root_id).cloned().unwrap_or_default().into();
204 let mut order = Vec::with_capacity(keysets.len());
205 while let Some(index) = queue.pop_front() {
206 if std::mem::replace(&mut visited[index], true) {
207 continue;
208 }
209
210 order.push(index);
211 if let Some(children) = encrypts.get(keysets[index].uuid.as_str()) {
212 queue.extend(children);
213 }
214 }
215
216 order
217}
218
219fn encrypted_by(keyset: &KeysetInfo) -> &str {
222 if keyset.encrypted_by.is_empty() {
223 &keyset.enc_sym_key.kid
224 } else {
225 &keyset.encrypted_by
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use data_encoding::HEXLOWER;
232
233 use super::{super::wire::KeysetsInfo, *};
234
235 fn hex(s: &str) -> Vec<u8> {
236 HEXLOWER.decode(s.as_bytes()).expect("valid hex")
237 }
238
239 #[test]
240 fn decrypt_aes_key_adds_key_to_keychain() {
241 let mut keychain = Keychain::new();
242 keychain.add_aes(AesKey::new(
243 "mp",
244 hex("44c38e8fedb84a1ab5ba74ed98dde931f6500ae39c1d9c85e20a7268ab2074f0"),
245 ));
246
247 let envelope: EncryptedEnvelope =
248 serde_json::from_str(include_str!("fixtures/encrypted-aes-key.json"))
249 .expect("valid fixture");
250 keychain.decrypt_aes_key(&envelope).expect("decrypts");
251
252 assert!(keychain.get_aes("szerdhg2ww2ahjo4ilz57x7cce").is_some());
253 }
254
255 #[test]
256 fn decrypt_rsa_key_adds_key_to_keychain() {
257 let mut keychain = Keychain::new();
258 keychain.add_aes(AesKey::new(
259 "szerdhg2ww2ahjo4ilz57x7cce",
260 hex("bba932f6032dc4dffaa9b8f03c9fd4b810127b89a49408db7b914a131690c091"),
261 ));
262
263 let envelope: EncryptedEnvelope =
264 serde_json::from_str(include_str!("fixtures/encrypted-rsa-key.json"))
265 .expect("valid fixture");
266 keychain.decrypt_rsa_key(&envelope).expect("decrypts");
267
268 assert!(keychain.get_rsa("szerdhg2ww2ahjo4ilz57x7cce").is_some());
269 }
270
271 #[test]
272 fn decrypt_keysets_decrypts_all_keys() {
273 let keysets: KeysetsInfo =
274 serde_json::from_str(include_str!("fixtures/get-keysets-response.json"))
275 .expect("valid fixture");
276 let mut keychain = Keychain::new();
277
278 let master_key = AesKey::new(
282 MASTER_KEY_ID,
283 hex("44c38e8fedb84a1ab5ba74ed98dde931f6500ae39c1d9c85e20a7268ab2074f0"),
284 );
285 keychain
286 .decrypt_reachable(&keysets.keysets, master_key)
287 .expect("decrypts keysets");
288
289 assert!(keychain.get_aes("mp").is_some());
290 for id in [
291 "szerdhg2ww2ahjo4ilz57x7cce",
292 "yf2ji37vkqdow7pnbo3y37b3lu",
293 "srkx3r5c3qgyzsdswfc4awgh2m",
294 "sm5hkw3mxwdcwcgljf4kyplwea",
295 ] {
296 assert!(keychain.get_aes(id).is_some(), "missing AES key {id}");
297 assert!(keychain.get_rsa(id).is_some(), "missing RSA key {id}");
298 }
299 }
300
301 fn keyset(uuid: &str, encrypted_by: &str) -> KeysetInfo {
302 serde_json::from_value(serde_json::json!({
303 "uuid": uuid,
304 "encryptedBy": encrypted_by,
305 "sn": 1,
306 "encSymKey": {"kid": encrypted_by, "enc": AES_SCHEME, "cty": "b5+jwk+json", "data": ""},
307 "encPriKey": {"kid": encrypted_by, "enc": AES_SCHEME, "cty": "b5+jwk+json", "data": ""},
308 }))
309 .expect("valid keyset")
310 }
311
312 #[test]
313 fn decryption_order_follows_the_chain_from_the_root() {
314 let keysets = [
315 keyset("c", "b"),
316 keyset("a", MASTER_KEY_ID),
317 keyset("b", "a"),
318 keyset("orphan", "nobody"),
319 ];
320
321 assert_eq!(decryption_order(&keysets, MASTER_KEY_ID), vec![1, 2, 0]);
324 }
325
326 #[test]
327 fn decryption_order_visits_a_self_referential_keyset_once() {
328 let keysets = [keyset("a", MASTER_KEY_ID), keyset("a", "a")];
331
332 assert_eq!(decryption_order(&keysets, MASTER_KEY_ID), vec![0, 1]);
333 }
334
335 #[test]
336 fn decrypt_rejects_unknown_scheme() {
337 let keychain = Keychain::new();
338 let encrypted = Encrypted {
339 key_id: "mp".into(),
340 scheme: "A128CBC".into(),
341 iv: Vec::new(),
342 ciphertext: Vec::new(),
343 };
344
345 let err = keychain
346 .can_decrypt(&encrypted)
347 .expect_err("unsupported scheme");
348 assert!(matches!(err, OnePasswordError::Unsupported(_)));
349
350 let err = keychain
351 .decrypt(&encrypted)
352 .expect_err("unsupported scheme");
353 assert!(matches!(err, OnePasswordError::Unsupported(_)));
354 }
355}