Skip to main content

bitwarden_importers/importers/onepassword/access/
keychain.rs

1//! Key store keyed by kid, scheme dispatch, and keyset topological decrypt.
2
3use 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/// A store of AES and RSA keys keyed by their kid.
21#[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    /// Decrypts an envelope by dispatching on its scheme to the AES or RSA key named by its kid.
51    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    /// Whether the keychain currently holds the key needed to decrypt this envelope.
73    ///
74    /// A scheme this module does not implement is an error, not a `false`: the caller cannot tell
75    /// "we lack the key" from "we cannot read this format" otherwise, and would silently drop data.
76    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    /// Decrypts an envelope and parses its JSON plaintext.
92    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    /// Derives the master key from the credentials, then decrypts every keyset into the keychain.
101    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    /// Seeds `root_key` and decrypts everything reachable from it.
113    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    /// Decrypts a keyset's symmetric key then its private key into the keychain.
129    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    /// Decrypts an encrypted AES key and adds it to the keychain.
135    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    /// Decrypts an encrypted RSA key and adds it to the keychain.
147    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
156/// Derives the key of the newest master keyset, the only one carrying KDF parameters.
157fn 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
189/// Orders keysets so each one comes after the key that encrypts it, starting from `root_id`.
190/// Keysets the root cannot reach are left out.
191fn 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    // Visit each keyset once, tracked by position: an entry whose encrypter resolves to its own
201    // uuid would re-enqueue forever, and uuids repeat in real responses.
202    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
219/// The key id that encrypts a keyset: its explicit `encryptedBy`, or the symmetric key's kid when
220/// that is empty.
221fn 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        // This fixture's master keyset carries a `p2s` that no longer matches its encrypted data,
279        // so the walk starts from the master key directly (the derivation is pinned by the kdf
280        // vector).
281        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        // Every keyset comes after the one that encrypts it, and what the root cannot reach is
322        // left out.
323        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        // A keyset naming itself as its encrypter would re-enqueue forever, so a regression here
329        // hangs rather than fails.
330        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}