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::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        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 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(Key::<Aes128GcmSiv>::from_slice(bytes));
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(Key::<Aes256GcmSiv>::from_slice(bytes));
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(Key::<Aes128GcmSiv>::from_slice(wrap_bytes));
384                cipher.decrypt(nonce.as_crypto_nonce(), &bytes[..])
385            }
386            WrappingKey::Aes256(wrap_bytes) => {
387                let cipher = Aes256GcmSiv::new(Key::<Aes256GcmSiv>::from_slice(wrap_bytes));
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::{Aes256Key, Error, KeyBagManager, UnwrapError, WrappingKey};
419    use assert_matches::assert_matches;
420    use std::os::fd::{FromRawFd as _, IntoRawFd as _, OwnedFd};
421    use tempfile::NamedTempFile;
422
423    fn open_dir(path: impl openat::AsPath) -> OwnedFd {
424        let dir = openat::Dir::open(path).unwrap();
425        unsafe { OwnedFd::from_raw_fd(dir.into_raw_fd()) }
426    }
427
428    #[test]
429    fn nonexistent_keybag() {
430        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
431        let path: &std::path::Path = owned_path.as_ref();
432        std::fs::remove_file(path).expect("unlink failed");
433        let dir = open_dir(path.parent().unwrap());
434        let keybag = KeyBagManager::create(dir, path).expect("Open nonexistent keybag failed");
435        assert!(keybag.key_bag.keys.is_empty());
436    }
437
438    #[test]
439    fn empty_keybag() {
440        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
441        let path: &std::path::Path = owned_path.as_ref();
442        let dir = open_dir(path.parent().unwrap());
443        let keybag = KeyBagManager::create(dir, path).expect("Open empty keybag failed");
444        assert!(keybag.key_bag.keys.is_empty());
445    }
446
447    #[test]
448    fn add_remove_key() {
449        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
450        let path: &std::path::Path = owned_path.as_ref();
451        {
452            let dir = open_dir(path.parent().unwrap());
453            let mut keybag = KeyBagManager::create(dir, path).expect("Open empty keybag failed");
454            let key = WrappingKey::Aes256([0u8; 32]);
455            keybag.new_key(0, &key).expect("new key failed");
456            assert_eq!(
457                Error::SlotAlreadyUsed,
458                keybag.new_key(0, &key).expect_err("new key on used slot failed")
459            );
460        }
461        {
462            let dir = open_dir(path.parent().unwrap());
463            let mut keybag = KeyBagManager::open(dir, path)
464                .expect("Open keybag failed")
465                .expect("keybag not found");
466            keybag.remove_key(0).expect("remove_key failed");
467            assert_eq!(
468                Error::SlotNotFound,
469                keybag.remove_key(1).expect_err("remove_key with invalid key specified failed")
470            );
471        }
472        let dir = open_dir(path.parent().unwrap());
473        let keybag =
474            KeyBagManager::open(dir, path).expect("Open keybag failed").expect("keybag not found");
475        assert!(keybag.key_bag.keys.is_empty());
476    }
477
478    #[test]
479    fn unwrap_key() {
480        let owned_path = NamedTempFile::new().unwrap().into_temp_path();
481        let path: &std::path::Path = owned_path.as_ref();
482        let dir = open_dir(path.parent().unwrap());
483        let mut keybag = KeyBagManager::create(dir, path).expect("Open empty keybag failed");
484
485        let key = WrappingKey::Aes256([3u8; 32]);
486        let key2 = WrappingKey::Aes128([0xffu8; 16]);
487
488        keybag.new_key(0, &key).expect("new_key failed");
489        keybag.new_key(1, &key2).expect("new_key failed");
490        keybag.new_key(2, &key).expect("new_key failed");
491
492        assert_matches!(keybag.unwrap_key(0, &key), Ok(_));
493        assert_eq!(keybag.unwrap_key(1, &key), Err(UnwrapError::AccessDenied));
494        assert_matches!(keybag.unwrap_key(2, &key), Ok(_));
495        assert_eq!(keybag.unwrap_key(3, &key), Err(UnwrapError::SlotNotFound));
496
497        assert_eq!(keybag.unwrap_key(0, &key2), Err(UnwrapError::AccessDenied));
498        assert_matches!(keybag.unwrap_key(1, &key2), Ok(_));
499        assert_eq!(keybag.unwrap_key(2, &key2), Err(UnwrapError::AccessDenied));
500        assert_eq!(keybag.unwrap_key(3, &key2), Err(UnwrapError::SlotNotFound));
501    }
502
503    #[test]
504    fn from_testdata() {
505        // The testdata file contains three keys, all of which have the same plaintext value
506        // ("secret\0..\0").
507        // Slots 0,2 are encrypted with a null AES256 key, and 1 is encrypted with something else.
508        let path = std::path::Path::new("/pkg/data/key_bag.json");
509        let dir = open_dir(path.parent().unwrap());
510        let keybag =
511            KeyBagManager::open(dir, path).expect("Open keybag failed").expect("keybag not found");
512
513        let mut expected = Aes256Key::default();
514        expected.0[..6].copy_from_slice(b"secret");
515
516        let key = WrappingKey::Aes256([0u8; 32]);
517        assert_eq!(keybag.unwrap_key(0, &key).as_ref().map(|s| &s.0), Ok(&expected.0));
518        assert_eq!(keybag.unwrap_key(1, &key), Err(UnwrapError::AccessDenied));
519        assert_eq!(keybag.unwrap_key(2, &key), Ok(expected));
520        assert_eq!(keybag.unwrap_key(3, &key), Err(UnwrapError::SlotNotFound));
521    }
522}