Skip to main content

key_bag/
lib.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use aes_gcm_siv::aead::Aead;
6use aes_gcm_siv::{Aes128GcmSiv, Aes256GcmSiv, KeyInit};
7
8use itertools::Itertools;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use std::collections::HashMap;
11use std::collections::hash_map::Entry;
12use std::fmt::Debug;
13use std::ops::{Deref, DerefMut};
14use std::os::fd::{FromRawFd as _, IntoRawFd as _};
15use thiserror::Error;
16
17/// KeyBag is a store for collections of wrapped keys.  This is stored in plaintext,
18/// and each key is only accessible if the appropriate wrapping key is known.
19#[derive(Debug, Deserialize, Serialize)]
20pub struct KeyBag {
21    version: u16,
22    keys: HashMap<KeySlot, WrappedKey>,
23}
24
25#[derive(Error, Debug)]
26pub enum OpenError {
27    #[error("Path to keybag was invalid")]
28    InvalidPath,
29    #[error("Failed to open the keybag: {0:?}")]
30    FailedToOpen(#[from] std::io::Error),
31    #[error("Keybag failed to parse due to {0}")]
32    KeyBagInvalid(String),
33    #[error("Keybag was of wrong version (have {0} want {1})")]
34    KeyBagVersionMismatch(u16, u16),
35    #[error("Failed to persist keybag")]
36    FailedToPersist,
37}
38
39impl From<OpenError> for zx::Status {
40    fn from(error: OpenError) -> zx::Status {
41        match error {
42            OpenError::InvalidPath => zx::Status::INVALID_ARGS,
43            OpenError::FailedToOpen(..) => zx::Status::IO,
44            OpenError::KeyBagInvalid(..) => zx::Status::IO_DATA_INTEGRITY,
45            OpenError::KeyBagVersionMismatch(..) => zx::Status::NOT_SUPPORTED,
46            OpenError::FailedToPersist => zx::Status::IO,
47        }
48    }
49}
50
51#[derive(Error, Debug, PartialEq)]
52pub enum Error {
53    #[error("Failed to persist keybag")]
54    FailedToPersist,
55    #[error("Key at given slot not found")]
56    SlotNotFound,
57    #[error("Key slot is already in use")]
58    SlotAlreadyUsed,
59    #[error("Internal")]
60    Internal,
61}
62
63impl From<Error> for zx::Status {
64    fn from(error: Error) -> zx::Status {
65        match error {
66            Error::FailedToPersist => zx::Status::IO,
67            Error::SlotNotFound => zx::Status::NOT_FOUND,
68            Error::SlotAlreadyUsed => zx::Status::ALREADY_EXISTS,
69            Error::Internal => zx::Status::INTERNAL,
70        }
71    }
72}
73
74#[derive(Error, Debug, PartialEq)]
75pub enum UnwrapError {
76    #[error("Key at given slot not found")]
77    SlotNotFound,
78    #[error("Failed to unwrap the key, most likely due to the wrong wrapping key")]
79    AccessDenied,
80}
81
82impl From<UnwrapError> for zx::Status {
83    fn from(error: UnwrapError) -> zx::Status {
84        match error {
85            UnwrapError::SlotNotFound => zx::Status::NOT_FOUND,
86            UnwrapError::AccessDenied => zx::Status::ACCESS_DENIED,
87        }
88    }
89}
90
91impl Default for KeyBag {
92    fn default() -> Self {
93        Self { version: CURRENT_VERSION, keys: Default::default() }
94    }
95}
96
97/// Manages the persistence of a KeyBag.
98///
99/// All operations on the keybag are atomic.
100pub struct KeyBagManager {
101    key_bag: KeyBag,
102    dir: openat::Dir,
103    path: String,
104}
105
106pub const AES128_KEY_SIZE: usize = 16;
107pub const AES256_KEY_SIZE: usize = 32;
108
109const CURRENT_VERSION: u16 = 1;
110const AES256_GCM_SIV_NONCE_SIZE: usize = 12;
111const WRAPPED_AES256_KEY_SIZE: usize = AES256_KEY_SIZE + 16;
112
113pub type KeySlot = u16;
114
115/// An AES256 key which has been wrapped using an AEAD, e.g. AES256-GCM-SIV.
116/// This can be safely stored in plaintext, and requires the wrapping key to be decoded.
117#[derive(Deserialize, Serialize, Debug)]
118pub enum WrappedKey {
119    Aes128GcmSivWrapped(Nonce, KeyBytes),
120    Aes256GcmSivWrapped(Nonce, KeyBytes),
121}
122
123// Helper for generating serde implementations for structs like 'KeyBytes' and 'Nonce'.
124// Serializes the object as a hexadecimal string, e.g. "af00178db0001200".
125macro_rules! impl_serde {
126    ($T:ty) => {
127        impl Serialize for $T {
128            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
129            where
130                S: Serializer,
131            {
132                serializer.serialize_str(
133                    &self
134                        .0
135                        .iter()
136                        .format_with("", |item, f| f(&format_args!("{:02x}", item)))
137                        .to_string(),
138                )
139            }
140        }
141
142        impl<'de> Deserialize<'de> for $T {
143            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
144            where
145                D: Deserializer<'de>,
146            {
147                <String>::deserialize(deserializer).and_then(|s| {
148                    let mut this = Self::default();
149                    let decoded = hex::decode(s).map_err(|_| {
150                        serde::de::Error::custom("failed to parse byte string".to_owned())
151                    })?;
152                    if decoded.len() != this.0.len() {
153                        return Err(serde::de::Error::custom(format!(
154                            "Invalid length (have {} want {})",
155                            decoded.len(),
156                            this.0.len()
157                        )));
158                    }
159                    this.0.copy_from_slice(&decoded[..]);
160                    Ok(this)
161                })
162            }
163        }
164    };
165}
166
167#[derive(Debug, Default)]
168pub struct Nonce([u8; AES256_GCM_SIV_NONCE_SIZE]);
169
170impl Nonce {
171    fn as_crypto_nonce(&self) -> &aes_gcm_siv::Nonce {
172        (&self.0[..]).try_into().unwrap()
173    }
174}
175
176impl_serde!(Nonce);
177
178/// A raw byte-string containing a wrapped AES256 key.
179#[derive(Debug)]
180pub struct KeyBytes([u8; WRAPPED_AES256_KEY_SIZE]);
181
182impl Deref for KeyBytes {
183    type Target = [u8; WRAPPED_AES256_KEY_SIZE];
184    fn deref(&self) -> &Self::Target {
185        &self.0
186    }
187}
188
189impl DerefMut for KeyBytes {
190    fn deref_mut(&mut self) -> &mut Self::Target {
191        &mut self.0
192    }
193}
194
195impl TryFrom<Vec<u8>> for KeyBytes {
196    type Error = Vec<u8>;
197    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
198        if value.len() == WRAPPED_AES256_KEY_SIZE {
199            let mut key = KeyBytes::default();
200            key.0.copy_from_slice(&value[..]);
201            Ok(key)
202        } else {
203            Err(value)
204        }
205    }
206}
207
208impl Default for KeyBytes {
209    fn default() -> Self {
210        Self([0u8; WRAPPED_AES256_KEY_SIZE])
211    }
212}
213
214impl_serde!(KeyBytes);
215
216#[repr(C)]
217#[derive(Default, PartialEq)]
218pub struct Aes256Key([u8; AES256_KEY_SIZE]);
219
220impl TryFrom<Vec<u8>> for Aes256Key {
221    type Error = ();
222    fn try_from(mut value: Vec<u8>) -> Result<Self, Self::Error> {
223        if value.len() != AES256_KEY_SIZE {
224            return Err(());
225        }
226        let mut this = Self([0u8; AES256_KEY_SIZE]);
227        this.0.copy_from_slice(&value[..]);
228        value.fill(0);
229        Ok(this)
230    }
231}
232
233impl Aes256Key {
234    pub const fn create(data: [u8; AES256_KEY_SIZE]) -> Self {
235        Self(data)
236    }
237}
238
239impl Deref for Aes256Key {
240    type Target = [u8; AES256_KEY_SIZE];
241    fn deref(&self) -> &Self::Target {
242        &self.0
243    }
244}
245
246impl DerefMut for Aes256Key {
247    fn deref_mut(&mut self) -> &mut Self::Target {
248        &mut self.0
249    }
250}
251
252impl Debug for Aes256Key {
253    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254        formatter.write_str("Aes256Key")
255    }
256}
257
258#[repr(C)]
259#[derive(PartialEq)]
260pub enum WrappingKey {
261    Aes128([u8; AES128_KEY_SIZE]),
262    Aes256([u8; AES256_KEY_SIZE]),
263}
264
265fn generate_key() -> Aes256Key {
266    let mut key = Aes256Key::default();
267    zx::cprng_draw(&mut key.0);
268    key
269}
270
271fn generate_nonce() -> Nonce {
272    let mut nonce = Nonce::default();
273    zx::cprng_draw(&mut nonce.0);
274    nonce
275}
276
277impl KeyBagManager {
278    /// Opens a key-bag file. If it doesn't exist, or is an empty file, returns Ok(None).
279    pub fn open(
280        directory: std::os::fd::OwnedFd,
281        path: &std::path::Path,
282    ) -> Result<Option<Self>, OpenError> {
283        // Safety:  We're temporarily making |directory| unowned, but then it becomes owned again by
284        // |dir|.
285        let dir = unsafe { openat::Dir::from_raw_fd(directory.into_raw_fd()) };
286        let path_str = path.to_str().map(str::to_string).ok_or(OpenError::InvalidPath)?;
287        match dir.metadata(path) {
288            Err(e) => {
289                if e.kind() == std::io::ErrorKind::NotFound {
290                    return Ok(None);
291                } else {
292                    return Err(e.into());
293                }
294            }
295            Ok(m) if m.len() == 0 => return Ok(None),
296            _ => (),
297        };
298        let reader = std::io::BufReader::new(dir.open_file(path)?);
299        let key_bag: KeyBag =
300            serde_json::from_reader(reader).map_err(|e| OpenError::KeyBagInvalid(e.to_string()))?;
301        if key_bag.version != CURRENT_VERSION {
302            return Err(OpenError::KeyBagVersionMismatch(key_bag.version, CURRENT_VERSION));
303        }
304        Ok(Some(Self { key_bag, dir, path: path_str }))
305    }
306
307    /// Creates a new, empty key-bag file, writing it to disk.
308    pub fn create(
309        directory: std::os::fd::OwnedFd,
310        path: &std::path::Path,
311    ) -> Result<Self, OpenError> {
312        // Safety:  We're temporarily making |directory| unowned, but then it becomes owned again by
313        // |dir|.
314        let dir = unsafe { openat::Dir::from_raw_fd(directory.into_raw_fd()) };
315        let path_str = path.to_str().map(str::to_string).ok_or(OpenError::InvalidPath)?;
316        let mut this = Self { key_bag: KeyBag::default(), dir, path: path_str };
317        this.commit().map_err(|_| OpenError::FailedToPersist)?;
318        return Ok(this);
319    }
320
321    /// Generates and stores a new key in the key-bag, based on |wrapping_key|.  Returns the
322    /// unwrapped key contents.
323    pub fn new_key(
324        &mut self,
325        slot: KeySlot,
326        wrapping_key: &WrappingKey,
327    ) -> Result<Aes256Key, Error> {
328        let key = match self.key_bag.keys.entry(slot) {
329            Entry::Occupied(_) => return Err(Error::SlotAlreadyUsed),
330            Entry::Vacant(v) => {
331                let key = generate_key();
332                let nonce = generate_nonce();
333
334                let entry = match wrapping_key {
335                    WrappingKey::Aes128(bytes) => {
336                        let cipher = Aes128GcmSiv::new_from_slice(bytes).unwrap();
337                        let wrapped = cipher
338                            .encrypt(nonce.as_crypto_nonce(), &key.0[..])
339                            .map_err(|_| Error::Internal)
340                            .and_then(|k| k.try_into().map_err(|_| Error::Internal))?;
341                        WrappedKey::Aes128GcmSivWrapped(nonce, wrapped)
342                    }
343                    WrappingKey::Aes256(bytes) => {
344                        let cipher = Aes256GcmSiv::new_from_slice(bytes).unwrap();
345                        let wrapped = cipher
346                            .encrypt(nonce.as_crypto_nonce(), &key.0[..])
347                            .map_err(|_| Error::Internal)
348                            .and_then(|k| k.try_into().map_err(|_| Error::Internal))?;
349                        WrappedKey::Aes256GcmSivWrapped(nonce, wrapped)
350                    }
351                };
352                v.insert(entry);
353                key
354            }
355        };
356
357        self.commit().map(|_| key)
358    }
359
360    /// Removes a key from the key bag.
361    pub fn remove_key(&mut self, slot: KeySlot) -> Result<(), Error> {
362        if let None = self.key_bag.keys.remove(&slot) {
363            return Err(Error::SlotNotFound);
364        }
365        self.commit()
366    }
367
368    /// Attempts to unwrap a key at |slot| with |wrapping_key|.
369    pub fn unwrap_key(
370        &self,
371        slot: KeySlot,
372        wrapping_key: &WrappingKey,
373    ) -> Result<Aes256Key, UnwrapError> {
374        let key = self.key_bag.keys.get(&slot).ok_or(UnwrapError::SlotNotFound)?;
375        let (nonce, bytes) = match key {
376            WrappedKey::Aes128GcmSivWrapped(nonce, bytes) => (nonce, bytes),
377            WrappedKey::Aes256GcmSivWrapped(nonce, bytes) => (nonce, bytes),
378        };
379        // Intentionally don't check that the algorithms match; we want AccessDenied to be returned
380        // if the wrong key type is specified, to minimize information leakage.
381        let decrypt_res = match wrapping_key {
382            WrappingKey::Aes128(wrap_bytes) => {
383                let cipher = Aes128GcmSiv::new_from_slice(wrap_bytes).unwrap();
384                cipher.decrypt(nonce.as_crypto_nonce(), &bytes[..])
385            }
386            WrappingKey::Aes256(wrap_bytes) => {
387                let cipher = Aes256GcmSiv::new_from_slice(wrap_bytes).unwrap();
388                cipher.decrypt(nonce.as_crypto_nonce(), &bytes[..])
389            }
390        };
391        match decrypt_res {
392            Ok(unwrapped) => {
393                let mut key = Aes256Key([0u8; 32]);
394                key.0.copy_from_slice(&unwrapped[..]);
395                Ok(key)
396            }
397            Err(_) => Err(UnwrapError::AccessDenied),
398        }
399    }
400
401    fn commit(&mut self) -> Result<(), Error> {
402        let path = std::path::Path::new(&self.path);
403        let tmp_path = path.with_extension("tmp");
404        let _ = self.dir.remove_file(&tmp_path);
405        {
406            let tmpfile = std::io::BufWriter::new(
407                self.dir.write_file(&tmp_path, 0).map_err(|_| Error::FailedToPersist)?,
408            );
409            serde_json::to_writer(tmpfile, &self.key_bag).map_err(|_| Error::FailedToPersist)?;
410        }
411        self.dir.local_rename(&tmp_path, path).map_err(|_| Error::FailedToPersist)?;
412        Ok(())
413    }
414}
415
416#[cfg(test)]
417mod tests {
418    use super::{
419        AES256_KEY_SIZE, Aes256Key, Error, KeyBagManager, KeyBytes, Nonce, OpenError, UnwrapError,
420        WRAPPED_AES256_KEY_SIZE, WrappingKey,
421    };
422    use assert_matches::assert_matches;
423    use std::os::fd::{FromRawFd as _, IntoRawFd as _, OwnedFd};
424    use tempfile::NamedTempFile;
425
426    fn open_dir(path: impl openat::AsPath) -> OwnedFd {
427        let dir = openat::Dir::open(path).unwrap();
428        unsafe { OwnedFd::from_raw_fd(dir.into_raw_fd()) }
429    }
430
431    #[test]
432    fn nonexistent_keybag() {
433        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
434        let path: &std::path::Path = owned_path.as_ref();
435        std::fs::remove_file(path).expect("unlink failed");
436        let dir = open_dir(path.parent().unwrap());
437        let keybag = KeyBagManager::create(dir, path).expect("Open nonexistent keybag failed");
438        assert!(keybag.key_bag.keys.is_empty());
439    }
440
441    #[test]
442    fn empty_keybag() {
443        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
444        let path: &std::path::Path = owned_path.as_ref();
445        let dir = open_dir(path.parent().unwrap());
446        let keybag = KeyBagManager::create(dir, path).expect("Open empty keybag failed");
447        assert!(keybag.key_bag.keys.is_empty());
448    }
449
450    #[test]
451    fn add_remove_key() {
452        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
453        let path: &std::path::Path = owned_path.as_ref();
454        {
455            let dir = open_dir(path.parent().unwrap());
456            let mut keybag = KeyBagManager::create(dir, path).expect("Open empty keybag failed");
457            let key = WrappingKey::Aes256([0u8; 32]);
458            keybag.new_key(0, &key).expect("new key failed");
459            assert_eq!(
460                Error::SlotAlreadyUsed,
461                keybag.new_key(0, &key).expect_err("new key on used slot failed")
462            );
463        }
464        {
465            let dir = open_dir(path.parent().unwrap());
466            let mut keybag = KeyBagManager::open(dir, path)
467                .expect("Open keybag failed")
468                .expect("keybag not found");
469            keybag.remove_key(0).expect("remove_key failed");
470            assert_eq!(
471                Error::SlotNotFound,
472                keybag.remove_key(1).expect_err("remove_key with invalid key specified failed")
473            );
474        }
475        let dir = open_dir(path.parent().unwrap());
476        let keybag =
477            KeyBagManager::open(dir, path).expect("Open keybag failed").expect("keybag not found");
478        assert!(keybag.key_bag.keys.is_empty());
479    }
480
481    #[test]
482    fn unwrap_key() {
483        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
484        let path: &std::path::Path = owned_path.as_ref();
485        let dir = open_dir(path.parent().unwrap());
486        let mut keybag = KeyBagManager::create(dir, path).expect("Open empty keybag failed");
487
488        let key = WrappingKey::Aes256([3u8; 32]);
489        let key2 = WrappingKey::Aes128([0xffu8; 16]);
490
491        keybag.new_key(0, &key).expect("new_key failed");
492        keybag.new_key(1, &key2).expect("new_key failed");
493        keybag.new_key(2, &key).expect("new_key failed");
494
495        assert_matches!(keybag.unwrap_key(0, &key), Ok(_));
496        assert_eq!(keybag.unwrap_key(1, &key), Err(UnwrapError::AccessDenied));
497        assert_matches!(keybag.unwrap_key(2, &key), Ok(_));
498        assert_eq!(keybag.unwrap_key(3, &key), Err(UnwrapError::SlotNotFound));
499
500        assert_eq!(keybag.unwrap_key(0, &key2), Err(UnwrapError::AccessDenied));
501        assert_matches!(keybag.unwrap_key(1, &key2), Ok(_));
502        assert_eq!(keybag.unwrap_key(2, &key2), Err(UnwrapError::AccessDenied));
503        assert_eq!(keybag.unwrap_key(3, &key2), Err(UnwrapError::SlotNotFound));
504    }
505
506    #[test]
507    fn from_testdata() {
508        // The testdata file contains three keys, all of which have the same plaintext value
509        // ("secret\0..\0").
510        // Slots 0,2 are encrypted with a null AES256 key, and 1 is encrypted with something else.
511        let path = std::path::Path::new("/pkg/data/key_bag.json");
512        let dir = open_dir(path.parent().unwrap());
513        let keybag =
514            KeyBagManager::open(dir, path).expect("Open keybag failed").expect("keybag not found");
515
516        let mut expected = Aes256Key::default();
517        expected.0[..6].copy_from_slice(b"secret");
518
519        let key = WrappingKey::Aes256([0u8; 32]);
520        assert_eq!(keybag.unwrap_key(0, &key).as_ref().map(|s| &s.0), Ok(&expected.0));
521        assert_eq!(keybag.unwrap_key(1, &key), Err(UnwrapError::AccessDenied));
522        assert_eq!(keybag.unwrap_key(2, &key), Ok(expected));
523        assert_eq!(keybag.unwrap_key(3, &key), Err(UnwrapError::SlotNotFound));
524    }
525
526    #[test]
527    fn error_to_zx_status_conversions() {
528        assert_eq!(zx::Status::from(OpenError::InvalidPath), zx::Status::INVALID_ARGS);
529        assert_eq!(
530            zx::Status::from(OpenError::FailedToOpen(std::io::Error::other("io"))),
531            zx::Status::IO
532        );
533        assert_eq!(
534            zx::Status::from(OpenError::KeyBagInvalid("bad".to_string())),
535            zx::Status::IO_DATA_INTEGRITY
536        );
537        assert_eq!(
538            zx::Status::from(OpenError::KeyBagVersionMismatch(2, 1)),
539            zx::Status::NOT_SUPPORTED
540        );
541        assert_eq!(zx::Status::from(OpenError::FailedToPersist), zx::Status::IO);
542
543        assert_eq!(zx::Status::from(Error::FailedToPersist), zx::Status::IO);
544        assert_eq!(zx::Status::from(Error::SlotNotFound), zx::Status::NOT_FOUND);
545        assert_eq!(zx::Status::from(Error::SlotAlreadyUsed), zx::Status::ALREADY_EXISTS);
546        assert_eq!(zx::Status::from(Error::Internal), zx::Status::INTERNAL);
547
548        assert_eq!(zx::Status::from(UnwrapError::SlotNotFound), zx::Status::NOT_FOUND);
549        assert_eq!(zx::Status::from(UnwrapError::AccessDenied), zx::Status::ACCESS_DENIED);
550    }
551
552    #[test]
553    fn key_bytes_and_aes256_key_methods() {
554        let mut kb = KeyBytes::try_from(vec![0x11u8; WRAPPED_AES256_KEY_SIZE]).unwrap();
555        assert_eq!(kb[0], 0x11);
556        kb[0] = 0x22;
557        assert_eq!(kb[0], 0x22);
558        assert!(KeyBytes::try_from(vec![0u8; 5]).is_err());
559
560        let mut key = Aes256Key::create([0x33u8; AES256_KEY_SIZE]);
561        assert_eq!(key[0], 0x33);
562        key[0] = 0x44;
563        assert_eq!(key[0], 0x44);
564        assert_eq!(format!("{:?}", key), "Aes256Key");
565
566        let from_vec = Aes256Key::try_from(vec![0x55u8; AES256_KEY_SIZE]).unwrap();
567        assert_eq!(from_vec[0], 0x55);
568        assert_eq!(Aes256Key::try_from(vec![0u8; 4]), Err(()));
569    }
570
571    #[test]
572    fn serde_hex_errors_and_open_edge_cases() {
573        assert!(serde_json::from_str::<Nonce>("\"not_valid_hex!\"").is_err());
574        assert!(serde_json::from_str::<Nonce>("\"0011\"").is_err());
575        assert!(serde_json::from_str::<KeyBytes>("\"not_valid_hex!\"").is_err());
576        assert!(serde_json::from_str::<KeyBytes>("\"0011\"").is_err());
577
578        // Opening a non-existent file returns Ok(None).
579        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
580        let path: &std::path::Path = owned_path.as_ref();
581        std::fs::remove_file(path).unwrap();
582        let dir = open_dir(path.parent().unwrap());
583        assert!(KeyBagManager::open(dir, path).unwrap().is_none());
584
585        // Opening an empty file returns Ok(None).
586        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
587        let path: &std::path::Path = owned_path.as_ref();
588        let dir = open_dir(path.parent().unwrap());
589        assert!(KeyBagManager::open(dir, path).unwrap().is_none());
590
591        // Opening invalid JSON returns KeyBagInvalid.
592        std::fs::write(path, b"{invalid json").unwrap();
593        let dir = open_dir(path.parent().unwrap());
594        assert_matches!(KeyBagManager::open(dir, path).err(), Some(OpenError::KeyBagInvalid(_)));
595
596        // Opening wrong version returns KeyBagVersionMismatch.
597        std::fs::write(path, br#"{"version": 99, "keys": {}}"#).unwrap();
598        let dir = open_dir(path.parent().unwrap());
599        assert_matches!(
600            KeyBagManager::open(dir, path).err(),
601            Some(OpenError::KeyBagVersionMismatch(99, 1))
602        );
603    }
604}