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, Key, KeyInit};
7
8use itertools::Itertools;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use std::collections::hash_map::Entry;
11use std::collections::HashMap;
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        aes_gcm_siv::Nonce::from_slice(&self.0)
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 Aes256Key {
221    pub const fn create(data: [u8; AES256_KEY_SIZE]) -> Self {
222        Self(data)
223    }
224}
225
226impl Deref for Aes256Key {
227    type Target = [u8; AES256_KEY_SIZE];
228    fn deref(&self) -> &Self::Target {
229        &self.0
230    }
231}
232
233impl DerefMut for Aes256Key {
234    fn deref_mut(&mut self) -> &mut Self::Target {
235        &mut self.0
236    }
237}
238
239impl Debug for Aes256Key {
240    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        formatter.write_str("Aes256Key")
242    }
243}
244
245#[repr(C)]
246#[derive(PartialEq)]
247pub enum WrappingKey {
248    Aes128([u8; AES128_KEY_SIZE]),
249    Aes256([u8; AES256_KEY_SIZE]),
250}
251
252fn generate_key() -> Aes256Key {
253    let mut key = Aes256Key::default();
254    zx::cprng_draw(&mut key.0);
255    key
256}
257
258fn generate_nonce() -> Nonce {
259    let mut nonce = Nonce::default();
260    zx::cprng_draw(&mut nonce.0);
261    nonce
262}
263
264impl KeyBagManager {
265    /// Opens a key-bag file. If it doesn't exist, or is an empty file, returns Ok(None).
266    pub fn open(
267        directory: std::os::fd::OwnedFd,
268        path: &std::path::Path,
269    ) -> Result<Option<Self>, OpenError> {
270        // Safety:  We're temporarily making |directory| unowned, but then it becomes owned again by
271        // |dir|.
272        let dir = unsafe { openat::Dir::from_raw_fd(directory.into_raw_fd()) };
273        let path_str = path.to_str().map(str::to_string).ok_or(OpenError::InvalidPath)?;
274        match dir.metadata(path) {
275            Err(e) => {
276                if e.kind() == std::io::ErrorKind::NotFound {
277                    return Ok(None);
278                } else {
279                    return Err(e.into());
280                }
281            }
282            Ok(m) if m.len() == 0 => return Ok(None),
283            _ => (),
284        };
285        let reader = std::io::BufReader::new(dir.open_file(path)?);
286        let key_bag: KeyBag =
287            serde_json::from_reader(reader).map_err(|e| OpenError::KeyBagInvalid(e.to_string()))?;
288        if key_bag.version != CURRENT_VERSION {
289            return Err(OpenError::KeyBagVersionMismatch(key_bag.version, CURRENT_VERSION));
290        }
291        Ok(Some(Self { key_bag, dir, path: path_str }))
292    }
293
294    /// Creates a new, empty key-bag file, writing it to disk.
295    pub fn create(
296        directory: std::os::fd::OwnedFd,
297        path: &std::path::Path,
298    ) -> Result<Self, OpenError> {
299        // Safety:  We're temporarily making |directory| unowned, but then it becomes owned again by
300        // |dir|.
301        let dir = unsafe { openat::Dir::from_raw_fd(directory.into_raw_fd()) };
302        let path_str = path.to_str().map(str::to_string).ok_or(OpenError::InvalidPath)?;
303        let mut this = Self { key_bag: KeyBag::default(), dir, path: path_str };
304        this.commit().map_err(|_| OpenError::FailedToPersist)?;
305        return Ok(this);
306    }
307
308    /// Generates and stores a new key in the key-bag, based on |wrapping_key|.  Returns the
309    /// unwrapped key contents.
310    pub fn new_key(
311        &mut self,
312        slot: KeySlot,
313        wrapping_key: &WrappingKey,
314    ) -> Result<Aes256Key, Error> {
315        let key = match self.key_bag.keys.entry(slot) {
316            Entry::Occupied(_) => return Err(Error::SlotAlreadyUsed),
317            Entry::Vacant(v) => {
318                let key = generate_key();
319                let nonce = generate_nonce();
320
321                let entry = match wrapping_key {
322                    WrappingKey::Aes128(bytes) => {
323                        let cipher = Aes128GcmSiv::new(Key::<Aes128GcmSiv>::from_slice(bytes));
324                        let wrapped = cipher
325                            .encrypt(nonce.as_crypto_nonce(), &key.0[..])
326                            .map_err(|_| Error::Internal)
327                            .and_then(|k| k.try_into().map_err(|_| Error::Internal))?;
328                        WrappedKey::Aes128GcmSivWrapped(nonce, wrapped)
329                    }
330                    WrappingKey::Aes256(bytes) => {
331                        let cipher = Aes256GcmSiv::new(Key::<Aes256GcmSiv>::from_slice(bytes));
332                        let wrapped = cipher
333                            .encrypt(nonce.as_crypto_nonce(), &key.0[..])
334                            .map_err(|_| Error::Internal)
335                            .and_then(|k| k.try_into().map_err(|_| Error::Internal))?;
336                        WrappedKey::Aes256GcmSivWrapped(nonce, wrapped)
337                    }
338                };
339                v.insert(entry);
340                key
341            }
342        };
343
344        self.commit().map(|_| key)
345    }
346
347    /// Removes a key from the key bag.
348    pub fn remove_key(&mut self, slot: KeySlot) -> Result<(), Error> {
349        if let None = self.key_bag.keys.remove(&slot) {
350            return Err(Error::SlotNotFound);
351        }
352        self.commit()
353    }
354
355    /// Attempts to unwrap a key at |slot| with |wrapping_key|.
356    pub fn unwrap_key(
357        &self,
358        slot: KeySlot,
359        wrapping_key: &WrappingKey,
360    ) -> Result<Aes256Key, UnwrapError> {
361        let key = self.key_bag.keys.get(&slot).ok_or(UnwrapError::SlotNotFound)?;
362        let (nonce, bytes) = match key {
363            WrappedKey::Aes128GcmSivWrapped(nonce, bytes) => (nonce, bytes),
364            WrappedKey::Aes256GcmSivWrapped(nonce, bytes) => (nonce, bytes),
365        };
366        // Intentionally don't check that the algorithms match; we want AccessDenied to be returned
367        // if the wrong key type is specified, to minimize information leakage.
368        let decrypt_res = match wrapping_key {
369            WrappingKey::Aes128(wrap_bytes) => {
370                let cipher = Aes128GcmSiv::new(Key::<Aes128GcmSiv>::from_slice(wrap_bytes));
371                cipher.decrypt(nonce.as_crypto_nonce(), &bytes[..])
372            }
373            WrappingKey::Aes256(wrap_bytes) => {
374                let cipher = Aes256GcmSiv::new(Key::<Aes256GcmSiv>::from_slice(wrap_bytes));
375                cipher.decrypt(nonce.as_crypto_nonce(), &bytes[..])
376            }
377        };
378        match decrypt_res {
379            Ok(unwrapped) => {
380                let mut key = Aes256Key([0u8; 32]);
381                key.0.copy_from_slice(&unwrapped[..]);
382                Ok(key)
383            }
384            Err(_) => Err(UnwrapError::AccessDenied),
385        }
386    }
387
388    fn commit(&mut self) -> Result<(), Error> {
389        let path = std::path::Path::new(&self.path);
390        let tmp_path = path.with_extension("tmp");
391        let _ = self.dir.remove_file(&tmp_path);
392        {
393            let tmpfile = std::io::BufWriter::new(
394                self.dir.write_file(&tmp_path, 0).map_err(|_| Error::FailedToPersist)?,
395            );
396            serde_json::to_writer(tmpfile, &self.key_bag).map_err(|_| Error::FailedToPersist)?;
397        }
398        self.dir.local_rename(&tmp_path, path).map_err(|_| Error::FailedToPersist)?;
399        Ok(())
400    }
401}
402
403#[cfg(test)]
404mod tests {
405    use super::{Aes256Key, Error, KeyBagManager, UnwrapError, WrappingKey};
406    use assert_matches::assert_matches;
407    use std::os::fd::{FromRawFd as _, IntoRawFd as _, OwnedFd};
408    use tempfile::NamedTempFile;
409
410    fn open_dir(path: impl openat::AsPath) -> OwnedFd {
411        let dir = openat::Dir::open(path).unwrap();
412        unsafe { OwnedFd::from_raw_fd(dir.into_raw_fd()) }
413    }
414
415    #[test]
416    fn nonexistent_keybag() {
417        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
418        let path: &std::path::Path = owned_path.as_ref();
419        std::fs::remove_file(path).expect("unlink failed");
420        let dir = open_dir(path.parent().unwrap());
421        let keybag = KeyBagManager::create(dir, path).expect("Open nonexistent keybag failed");
422        assert!(keybag.key_bag.keys.is_empty());
423    }
424
425    #[test]
426    fn empty_keybag() {
427        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
428        let path: &std::path::Path = owned_path.as_ref();
429        let dir = open_dir(path.parent().unwrap());
430        let keybag = KeyBagManager::create(dir, path).expect("Open empty keybag failed");
431        assert!(keybag.key_bag.keys.is_empty());
432    }
433
434    #[test]
435    fn add_remove_key() {
436        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
437        let path: &std::path::Path = owned_path.as_ref();
438        {
439            let dir = open_dir(path.parent().unwrap());
440            let mut keybag = KeyBagManager::create(dir, path).expect("Open empty keybag failed");
441            let key = WrappingKey::Aes256([0u8; 32]);
442            keybag.new_key(0, &key).expect("new key failed");
443            assert_eq!(
444                Error::SlotAlreadyUsed,
445                keybag.new_key(0, &key).expect_err("new key on used slot failed")
446            );
447        }
448        {
449            let dir = open_dir(path.parent().unwrap());
450            let mut keybag = KeyBagManager::open(dir, path)
451                .expect("Open keybag failed")
452                .expect("keybag not found");
453            keybag.remove_key(0).expect("remove_key failed");
454            assert_eq!(
455                Error::SlotNotFound,
456                keybag.remove_key(1).expect_err("remove_key with invalid key specified failed")
457            );
458        }
459        let dir = open_dir(path.parent().unwrap());
460        let keybag =
461            KeyBagManager::open(dir, path).expect("Open keybag failed").expect("keybag not found");
462        assert!(keybag.key_bag.keys.is_empty());
463    }
464
465    #[test]
466    fn unwrap_key() {
467        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
468        let path: &std::path::Path = owned_path.as_ref();
469        let dir = open_dir(path.parent().unwrap());
470        let mut keybag = KeyBagManager::create(dir, path).expect("Open empty keybag failed");
471
472        let key = WrappingKey::Aes256([3u8; 32]);
473        let key2 = WrappingKey::Aes128([0xffu8; 16]);
474
475        keybag.new_key(0, &key).expect("new_key failed");
476        keybag.new_key(1, &key2).expect("new_key failed");
477        keybag.new_key(2, &key).expect("new_key failed");
478
479        assert_matches!(keybag.unwrap_key(0, &key), Ok(_));
480        assert_eq!(keybag.unwrap_key(1, &key), Err(UnwrapError::AccessDenied));
481        assert_matches!(keybag.unwrap_key(2, &key), Ok(_));
482        assert_eq!(keybag.unwrap_key(3, &key), Err(UnwrapError::SlotNotFound));
483
484        assert_eq!(keybag.unwrap_key(0, &key2), Err(UnwrapError::AccessDenied));
485        assert_matches!(keybag.unwrap_key(1, &key2), Ok(_));
486        assert_eq!(keybag.unwrap_key(2, &key2), Err(UnwrapError::AccessDenied));
487        assert_eq!(keybag.unwrap_key(3, &key2), Err(UnwrapError::SlotNotFound));
488    }
489
490    #[test]
491    fn from_testdata() {
492        // The testdata file contains three keys, all of which have the same plaintext value
493        // ("secret\0..\0").
494        // Slots 0,2 are encrypted with a null AES256 key, and 1 is encrypted with something else.
495        let path = std::path::Path::new("/pkg/data/key_bag.json");
496        let dir = open_dir(path.parent().unwrap());
497        let keybag =
498            KeyBagManager::open(dir, path).expect("Open keybag failed").expect("keybag not found");
499
500        let mut expected = Aes256Key::default();
501        expected.0[..6].copy_from_slice(b"secret");
502
503        let key = WrappingKey::Aes256([0u8; 32]);
504        assert_eq!(keybag.unwrap_key(0, &key).as_ref().map(|s| &s.0), Ok(&expected.0));
505        assert_eq!(keybag.unwrap_key(1, &key), Err(UnwrapError::AccessDenied));
506        assert_eq!(keybag.unwrap_key(2, &key), Ok(expected));
507        assert_eq!(keybag.unwrap_key(3, &key), Err(UnwrapError::SlotNotFound));
508    }
509}