Skip to main content

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