fxfs_crypto/
lib.rs

1// Copyright 2023 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::cipher::{KeyIvInit, StreamCipher as _, StreamCipherSeek};
6use anyhow::anyhow;
7use async_trait::async_trait;
8use chacha20::{self, ChaCha20};
9use fprint::TypeFingerprint;
10use futures::TryStreamExt as _;
11use futures::stream::FuturesUnordered;
12use serde::de::{Error as SerdeError, Visitor};
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use std::collections::BTreeMap;
15use zx_status as zx;
16
17mod cipher;
18pub mod ff1;
19
20pub use cipher::fxfs::FxfsCipher;
21pub use cipher::{Cipher, CipherSet, FindKeyResult};
22pub use fidl_fuchsia_fxfs::{
23    EmptyStruct, FscryptKeyIdentifier, FscryptKeyIdentifierAndNonce, ObjectType, WrappedKey,
24};
25
26pub use cipher::FSCRYPT_PADDING;
27pub const FXFS_KEY_SIZE: usize = 256 / 8;
28pub const FXFS_WRAPPED_KEY_SIZE: usize = FXFS_KEY_SIZE + 16;
29
30/// Essentially just a vector by another name to indicate that it holds unwrapped key material.
31/// The length of an unwrapped key depends on the type of key that is wrapped.
32#[derive(Debug)]
33pub struct UnwrappedKey(Vec<u8>);
34impl UnwrappedKey {
35    pub fn new(key: Vec<u8>) -> Self {
36        UnwrappedKey(key)
37    }
38}
39impl std::ops::Deref for UnwrappedKey {
40    type Target = Vec<u8>;
41    fn deref(&self) -> &Self::Target {
42        &self.0
43    }
44}
45
46/// A fixed length array of 48 bytes that holds an AES-256-GCM-SIV wrapped key.
47#[repr(transparent)]
48#[derive(Clone, Debug, PartialEq)]
49pub struct WrappedKeyBytes(pub [u8; FXFS_WRAPPED_KEY_SIZE]);
50impl Default for WrappedKeyBytes {
51    fn default() -> Self {
52        Self([0u8; FXFS_WRAPPED_KEY_SIZE])
53    }
54}
55impl TryFrom<Vec<u8>> for WrappedKeyBytes {
56    type Error = anyhow::Error;
57
58    fn try_from(buf: Vec<u8>) -> Result<Self, Self::Error> {
59        Ok(Self(buf.try_into().map_err(|_| anyhow!("wrapped key wrong length"))?))
60    }
61}
62impl From<[u8; FXFS_WRAPPED_KEY_SIZE]> for WrappedKeyBytes {
63    fn from(buf: [u8; FXFS_WRAPPED_KEY_SIZE]) -> Self {
64        Self(buf)
65    }
66}
67impl TypeFingerprint for WrappedKeyBytes {
68    fn fingerprint() -> String {
69        "WrappedKeyBytes".to_owned()
70    }
71}
72
73impl std::ops::Deref for WrappedKeyBytes {
74    type Target = [u8; FXFS_WRAPPED_KEY_SIZE];
75    fn deref(&self) -> &Self::Target {
76        &self.0
77    }
78}
79
80impl std::ops::DerefMut for WrappedKeyBytes {
81    fn deref_mut(&mut self) -> &mut Self::Target {
82        &mut self.0
83    }
84}
85
86// Because default impls of Serialize/Deserialize for [T; N] are only defined for N in 0..=32, we
87// have to define them ourselves.
88impl Serialize for WrappedKeyBytes {
89    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
90    where
91        S: Serializer,
92    {
93        serializer.serialize_bytes(&self[..])
94    }
95}
96
97impl<'de> Deserialize<'de> for WrappedKeyBytes {
98    fn deserialize<D>(deserializer: D) -> Result<WrappedKeyBytes, D::Error>
99    where
100        D: Deserializer<'de>,
101    {
102        struct WrappedKeyVisitor;
103
104        impl<'d> Visitor<'d> for WrappedKeyVisitor {
105            type Value = WrappedKeyBytes;
106
107            fn expecting(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
108                formatter.write_str("Expected wrapped keys to be 48 bytes")
109            }
110
111            fn visit_bytes<E>(self, bytes: &[u8]) -> Result<WrappedKeyBytes, E>
112            where
113                E: SerdeError,
114            {
115                self.visit_byte_buf(bytes.to_vec())
116            }
117
118            fn visit_byte_buf<E>(self, bytes: Vec<u8>) -> Result<WrappedKeyBytes, E>
119            where
120                E: SerdeError,
121            {
122                let orig_len = bytes.len();
123                let bytes: [u8; FXFS_WRAPPED_KEY_SIZE] =
124                    bytes.try_into().map_err(|_| SerdeError::invalid_length(orig_len, &self))?;
125                Ok(WrappedKeyBytes::from(bytes))
126            }
127        }
128        deserializer.deserialize_byte_buf(WrappedKeyVisitor)
129    }
130}
131
132/// This specifies a single key to be used to encrypt/decrypt.
133#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TypeFingerprint)]
134pub enum EncryptionKey {
135    Fxfs(FxfsKey),
136    // NOTE: `key_identifier` can be thought of as the "name" of the key to use; it is not a
137    // per-file or per-directory key. It is similar to Fxfs's wrapping key ID, although it
138    // doesn't wrap anything. Files using the same `key_identifier` are encrypted using the
139    // same underlying key, with just differences in the tweak used. Directories also use the
140    // same underlying key, but some structures are further salted using the provided nonce.
141    FscryptInoLblk32File { key_identifier: [u8; 16] },
142    FscryptInoLblk32Dir { key_identifier: [u8; 16], nonce: [u8; 16] },
143}
144
145impl<'a> arbitrary::Arbitrary<'a> for EncryptionKey {
146    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
147        Ok(match u.int_in_range(0..=2)? {
148            0 => EncryptionKey::Fxfs(u.arbitrary()?),
149            1 => EncryptionKey::FscryptInoLblk32File { key_identifier: u.arbitrary()? },
150            2 => EncryptionKey::FscryptInoLblk32Dir {
151                key_identifier: u.arbitrary()?,
152                nonce: u.arbitrary()?,
153            },
154            _ => unreachable!(),
155        })
156    }
157}
158
159impl From<EncryptionKey> for WrappedKey {
160    fn from(value: EncryptionKey) -> Self {
161        match value {
162            EncryptionKey::Fxfs(key) => WrappedKey::Fxfs(key.into()),
163            EncryptionKey::FscryptInoLblk32File { key_identifier } => {
164                WrappedKey::FscryptInoLblk32File(FscryptKeyIdentifier { key_identifier })
165            }
166            EncryptionKey::FscryptInoLblk32Dir { key_identifier, nonce } => {
167                WrappedKey::FscryptInoLblk32Dir(FscryptKeyIdentifierAndNonce {
168                    key_identifier,
169                    nonce,
170                })
171            }
172        }
173    }
174}
175
176/// An Fxfs encryption key wrapped in AES-256-GCM-SIV and the associated wrapping key ID.
177/// This can be provided to Crypt::unwrap_key to obtain the unwrapped key.
178#[derive(Clone, Default, Debug, Serialize, Deserialize, TypeFingerprint, PartialEq)]
179pub struct FxfsKey {
180    /// The identifier of the wrapping key.  The identifier has meaning to whatever is doing the
181    /// unwrapping.
182    pub wrapping_key_id: WrappingKeyId,
183    /// AES 256 requires a 512 bit key, which is made of two 256 bit keys, one for the data and one
184    /// for the tweak.  It is safe to use the same 256 bit key for both (see
185    /// https://csrc.nist.gov/CSRC/media/Projects/Block-Cipher-Techniques/documents/BCM/Comments/XTS/follow-up_XTS_comments-Ball.pdf)
186    /// which is what we do here.  Since the key is wrapped with AES-GCM-SIV, there are an
187    /// additional 16 bytes paid per key (so the actual key material is 32 bytes once unwrapped).
188    pub key: WrappedKeyBytes,
189}
190
191pub type WrappingKeyId = [u8; 16];
192
193impl From<FxfsKey> for fidl_fuchsia_fxfs::FxfsKey {
194    fn from(value: FxfsKey) -> Self {
195        fidl_fuchsia_fxfs::FxfsKey {
196            wrapping_key_id: value.wrapping_key_id,
197            wrapped_key: value.key.0,
198        }
199    }
200}
201
202impl<'a> arbitrary::Arbitrary<'a> for FxfsKey {
203    fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
204        // There doesn't seem to be much point to randomly generate crypto keys.
205        return Ok(FxfsKey::default());
206    }
207}
208
209/// A thin wrapper around a ChaCha20 stream cipher.  This will use a zero nonce. **NOTE**: Great
210/// care must be taken not to encrypt different plaintext with the same key and offset (even across
211/// multiple boots), so consider if this suits your purpose before using it.
212pub struct StreamCipher(ChaCha20);
213
214impl StreamCipher {
215    pub fn new(key: &UnwrappedKey, offset: u64) -> Self {
216        let mut cipher =
217            Self(ChaCha20::new(chacha20::Key::from_slice(key), /* nonce: */ &[0; 12].into()));
218        cipher.0.seek(offset);
219        cipher
220    }
221
222    pub fn encrypt(&mut self, buffer: &mut [u8]) {
223        fxfs_trace::duration!(c"StreamCipher::encrypt", "len" => buffer.len());
224        self.0.apply_keystream(buffer);
225    }
226
227    pub fn decrypt(&mut self, buffer: &mut [u8]) {
228        fxfs_trace::duration!(c"StreamCipher::decrypt", "len" => buffer.len());
229        self.0.apply_keystream(buffer);
230    }
231
232    pub fn offset(&self) -> u64 {
233        self.0.current_pos()
234    }
235}
236
237/// Different keys are used for metadata and data in order to make certain operations requiring a
238/// metadata key rotation (e.g. secure erase) more efficient.
239pub enum KeyPurpose {
240    /// The key will be used to wrap user data.
241    Data,
242    /// The key will be used to wrap internal metadata.
243    Metadata,
244}
245
246/// The `Crypt` trait below provides a mechanism to unwrap a key or set of keys.
247/// The wrapping keys can be one of these types.
248pub enum WrappingKey {
249    /// This is used for keys of the type WrappedKey::Fxfs.
250    Aes256GcmSiv([u8; 32]),
251    /// This is used for legacy fscrypt keys that use a 64-byte main key.
252    Fscrypt([u8; 64]),
253}
254impl From<[u8; 32]> for WrappingKey {
255    fn from(value: [u8; 32]) -> Self {
256        WrappingKey::Aes256GcmSiv(value)
257    }
258}
259impl From<[u8; 64]> for WrappingKey {
260    fn from(value: [u8; 64]) -> Self {
261        WrappingKey::Fscrypt(value)
262    }
263}
264
265/// The keys it unwraps can be wrapped with either Aes256GcmSiv (ideally) or using via
266/// legacy fscrypt master key + HKDF.
267
268/// An interface trait with the ability to wrap and unwrap encryption keys.
269///
270/// Note that existence of this trait does not imply that an object will **securely**
271/// wrap and unwrap keys; rather just that it presents an interface for wrapping operations.
272#[async_trait]
273pub trait Crypt: Send + Sync {
274    /// `owner` is intended to be used such that when the key is wrapped, it appears to be different
275    /// to that of the same key wrapped by a different owner.  In this way, keys can be shared
276    /// amongst different filesystem objects (e.g. for clones), but it is not possible to tell just
277    /// by looking at the wrapped keys.
278    async fn create_key(
279        &self,
280        owner: u64,
281        purpose: KeyPurpose,
282    ) -> Result<(FxfsKey, UnwrappedKey), zx::Status>;
283
284    /// `owner` is intended to be used such that when the key is wrapped, it appears to be different
285    /// to that of the same key wrapped by a different owner.  In this way, keys can be shared
286    /// amongst different filesystem objects (e.g. for clones), but it is not possible to tell just
287    /// by looking at the wrapped keys.
288    async fn create_key_with_id(
289        &self,
290        owner: u64,
291        wrapping_key_id: WrappingKeyId,
292        object_type: ObjectType,
293    ) -> Result<(EncryptionKey, UnwrappedKey), zx::Status>;
294
295    /// Unwraps a single key, returning a raw unwrapped key.
296    /// This method is generally only used with StreamCipher and FF1.
297    async fn unwrap_key(
298        &self,
299        wrapped_key: &WrappedKey,
300        owner: u64,
301    ) -> Result<UnwrappedKey, zx::Status>;
302
303    /// Unwraps object keys and stores the result as a CipherSet mapping key_id to:
304    ///   - Some(cipher) if unwrapping key was found or
305    ///   - None if unwrapping key was missing.
306    /// The cipher can be used directly to encrypt/decrypt data.
307    async fn unwrap_keys(
308        &self,
309        keys: &BTreeMap<u64, WrappedKey>,
310        owner: u64,
311    ) -> Result<CipherSet, zx::Status> {
312        let futures: FuturesUnordered<_> = keys
313            .iter()
314            .map(|(key_id, key)| {
315                let key_id = *key_id;
316                let owner = owner;
317                async move {
318                    let unwrapped_key = match self.unwrap_key(&key, owner).await {
319                        Ok(unwrapped_key) => unwrapped_key,
320                        Err(zx::Status::NOT_FOUND) => return Ok((key_id, None)),
321                        Err(e) => return Err(e),
322                    };
323
324                    cipher::key_to_cipher(&key, &unwrapped_key).map(|c| (key_id, c))
325                }
326            })
327            .collect();
328        let result = futures.try_collect::<BTreeMap<u64, _>>().await?;
329        Ok(result.into())
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::{StreamCipher, UnwrappedKey};
336
337    #[test]
338    fn test_stream_cipher_offset() {
339        let key = UnwrappedKey::new(vec![
340            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
341            25, 26, 27, 28, 29, 30, 31, 32,
342        ]);
343        let mut cipher1 = StreamCipher::new(&key, 0);
344        let mut p1 = [1, 2, 3, 4];
345        let mut c1 = p1.clone();
346        cipher1.encrypt(&mut c1);
347
348        let mut cipher2 = StreamCipher::new(&key, 1);
349        let p2 = [5, 6, 7, 8];
350        let mut c2 = p2.clone();
351        cipher2.encrypt(&mut c2);
352
353        let xor_fn = |buf1: &mut [u8], buf2| {
354            for (b1, b2) in buf1.iter_mut().zip(buf2) {
355                *b1 ^= b2;
356            }
357        };
358
359        // Check that c1 ^ c2 != p1 ^ p2 (which would be the case if the same offset was used for
360        // both ciphers).
361        xor_fn(&mut c1, &c2);
362        xor_fn(&mut p1, &p2);
363        assert_ne!(c1, p1);
364    }
365}