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