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::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    Fxfs(FxfsKey),
137    // NOTE: `key_identifier` can be thought of as the "name" of the key to use; it is not a
138    // per-file or per-directory key. It is similar to Fxfs's wrapping key ID, although it
139    // doesn't wrap anything. Files using the same `key_identifier` are encrypted using the
140    // same underlying key, with just differences in the tweak used. Directories also use the
141    // same underlying key, but some structures are further salted using the provided nonce.
142    FscryptInoLblk32File { key_identifier: [u8; 16] },
143    FscryptInoLblk32Dir { key_identifier: [u8; 16], nonce: [u8; 16] },
144}
145
146impl<'a> arbitrary::Arbitrary<'a> for EncryptionKey {
147    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
148        Ok(match u.int_in_range(0..=2)? {
149            0 => EncryptionKey::Fxfs(u.arbitrary()?),
150            1 => EncryptionKey::FscryptInoLblk32File { key_identifier: u.arbitrary()? },
151            2 => EncryptionKey::FscryptInoLblk32Dir {
152                key_identifier: u.arbitrary()?,
153                nonce: u.arbitrary()?,
154            },
155            _ => unreachable!(),
156        })
157    }
158}
159
160impl From<EncryptionKey> for WrappedKey {
161    fn from(value: EncryptionKey) -> Self {
162        match value {
163            EncryptionKey::Fxfs(key) => WrappedKey::Fxfs(key.into()),
164            EncryptionKey::FscryptInoLblk32File { key_identifier } => {
165                WrappedKey::FscryptInoLblk32File(FscryptKeyIdentifier { key_identifier })
166            }
167            EncryptionKey::FscryptInoLblk32Dir { key_identifier, nonce } => {
168                WrappedKey::FscryptInoLblk32Dir(FscryptKeyIdentifierAndNonce {
169                    key_identifier,
170                    nonce,
171                })
172            }
173        }
174    }
175}
176
177impl TryFrom<WrappedKey> for EncryptionKey {
178    type Error = zx::Status;
179
180    fn try_from(value: WrappedKey) -> Result<Self, Self::Error> {
181        Ok(match value {
182            WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey { wrapping_key_id, wrapped_key }) => {
183                EncryptionKey::Fxfs(FxfsKey { wrapping_key_id, key: WrappedKeyBytes(wrapped_key) })
184            }
185            WrappedKey::FscryptInoLblk32File(FscryptKeyIdentifier { key_identifier }) => {
186                EncryptionKey::FscryptInoLblk32File { key_identifier }
187            }
188            WrappedKey::FscryptInoLblk32Dir(FscryptKeyIdentifierAndNonce {
189                key_identifier,
190                nonce,
191            }) => EncryptionKey::FscryptInoLblk32Dir { key_identifier, nonce },
192            _ => return Err(zx::Status::NOT_SUPPORTED),
193        })
194    }
195}
196
197/// An Fxfs encryption key wrapped in AES-256-GCM-SIV and the associated wrapping key ID.
198/// This can be provided to Crypt::unwrap_key to obtain the unwrapped key.
199#[derive(Clone, Default, Debug, Serialize, Deserialize, TypeFingerprint, PartialEq)]
200pub struct FxfsKey {
201    /// The identifier of the wrapping key.  The identifier has meaning to whatever is doing the
202    /// unwrapping.
203    pub wrapping_key_id: WrappingKeyId,
204    /// AES 256 requires a 512 bit key, which is made of two 256 bit keys, one for the data and one
205    /// for the tweak.  It is safe to use the same 256 bit key for both (see
206    /// https://csrc.nist.gov/CSRC/media/Projects/Block-Cipher-Techniques/documents/BCM/Comments/XTS/follow-up_XTS_comments-Ball.pdf)
207    /// which is what we do here.  Since the key is wrapped with AES-GCM-SIV, there are an
208    /// additional 16 bytes paid per key (so the actual key material is 32 bytes once unwrapped).
209    pub key: WrappedKeyBytes,
210}
211
212pub type WrappingKeyId = [u8; 16];
213
214impl From<FxfsKey> for fidl_fuchsia_fxfs::FxfsKey {
215    fn from(value: FxfsKey) -> Self {
216        fidl_fuchsia_fxfs::FxfsKey {
217            wrapping_key_id: value.wrapping_key_id,
218            wrapped_key: value.key.0,
219        }
220    }
221}
222
223impl<'a> arbitrary::Arbitrary<'a> for FxfsKey {
224    fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
225        // There doesn't seem to be much point to randomly generate crypto keys.
226        return Ok(FxfsKey::default());
227    }
228}
229
230/// A thin wrapper around a ChaCha20 stream cipher.  This will use a zero nonce. **NOTE**: Great
231/// care must be taken not to encrypt different plaintext with the same key and offset (even across
232/// multiple boots), so consider if this suits your purpose before using it.
233pub struct StreamCipher(ChaCha20);
234
235impl StreamCipher {
236    pub fn new(key: &UnwrappedKey, offset: u64) -> Self {
237        let mut cipher =
238            Self(ChaCha20::new(chacha20::Key::from_slice(key), /* nonce: */ &[0; 12].into()));
239        cipher.0.seek(offset);
240        cipher
241    }
242
243    pub fn encrypt(&mut self, buffer: &mut [u8]) {
244        fxfs_trace::duration!("StreamCipher::encrypt", "len" => buffer.len());
245        self.0.apply_keystream(buffer);
246    }
247
248    pub fn decrypt(&mut self, buffer: &mut [u8]) {
249        fxfs_trace::duration!("StreamCipher::decrypt", "len" => buffer.len());
250        self.0.apply_keystream(buffer);
251    }
252
253    pub fn offset(&self) -> u64 {
254        self.0.current_pos()
255    }
256}
257
258/// Different keys are used for metadata and data in order to make certain operations requiring a
259/// metadata key rotation (e.g. secure erase) more efficient.
260pub enum KeyPurpose {
261    /// The key will be used to wrap user data.
262    Data,
263    /// The key will be used to wrap internal metadata.
264    Metadata,
265}
266
267impl TryFrom<fidl_fuchsia_fxfs::KeyPurpose> for KeyPurpose {
268    type Error = zx::Status;
269
270    fn try_from(purpose: fidl_fuchsia_fxfs::KeyPurpose) -> Result<Self, Self::Error> {
271        match purpose {
272            fidl_fuchsia_fxfs::KeyPurpose::Data => Ok(KeyPurpose::Data),
273            fidl_fuchsia_fxfs::KeyPurpose::Metadata => Ok(KeyPurpose::Metadata),
274            _ => Err(zx::Status::INVALID_ARGS),
275        }
276    }
277}
278
279/// The `Crypt` trait below provides a mechanism to unwrap a key or set of keys.
280/// The wrapping keys can be one of these types.
281pub enum WrappingKey {
282    /// This is used for keys of the type WrappedKey::Fxfs.
283    Aes256GcmSiv([u8; 32]),
284    /// This is used for legacy fscrypt keys that use a 64-byte main key.
285    Fscrypt([u8; 64]),
286}
287impl From<[u8; 32]> for WrappingKey {
288    fn from(value: [u8; 32]) -> Self {
289        WrappingKey::Aes256GcmSiv(value)
290    }
291}
292impl From<[u8; 64]> for WrappingKey {
293    fn from(value: [u8; 64]) -> Self {
294        WrappingKey::Fscrypt(value)
295    }
296}
297
298/// The keys it unwraps can be wrapped with either Aes256GcmSiv (ideally) or using via
299/// legacy fscrypt master key + HKDF.
300
301/// An interface trait with the ability to wrap and unwrap encryption keys.
302///
303/// Note that existence of this trait does not imply that an object will **securely**
304/// wrap and unwrap keys; rather just that it presents an interface for wrapping operations.
305#[async_trait]
306pub trait Crypt: Send + Sync {
307    /// `owner` is intended to be used such that when the key is wrapped, it appears to be different
308    /// to that of the same key wrapped by a different owner.  In this way, keys can be shared
309    /// amongst different filesystem objects (e.g. for clones), but it is not possible to tell just
310    /// by looking at the wrapped keys.
311    async fn create_key(
312        &self,
313        owner: u64,
314        purpose: KeyPurpose,
315    ) -> Result<(FxfsKey, UnwrappedKey), zx::Status>;
316
317    /// `owner` is intended to be used such that when the key is wrapped, it appears to be different
318    /// to that of the same key wrapped by a different owner.  In this way, keys can be shared
319    /// amongst different filesystem objects (e.g. for clones), but it is not possible to tell just
320    /// by looking at the wrapped keys.
321    async fn create_key_with_id(
322        &self,
323        owner: u64,
324        wrapping_key_id: WrappingKeyId,
325        object_type: ObjectType,
326    ) -> Result<(EncryptionKey, UnwrappedKey), zx::Status>;
327
328    /// Unwraps a single key, returning a raw unwrapped key.
329    /// This method is generally only used with StreamCipher and FF1.
330    /// Returns `zx::Status::UNAVAILABLE` if the key is known but cannot be unwrapped (e.g. it is
331    /// locked).
332    /// Returns `zx::Status::NOT_FOUND` if the wrapping key is not known.
333    async fn unwrap_key(
334        &self,
335        wrapped_key: &WrappedKey,
336        owner: u64,
337    ) -> Result<UnwrappedKey, zx::Status>;
338
339    /// Unwraps object keys and stores the result as a CipherSet mapping key_id to:
340    ///   - Some(cipher) if unwrapping key was found or
341    ///   - None if unwrapping key was missing.
342    /// The cipher can be used directly to encrypt/decrypt data.
343    async fn unwrap_keys(
344        &self,
345        keys: &BTreeMap<u64, WrappedKey>,
346        owner: u64,
347    ) -> Result<CipherSet, zx::Status> {
348        let futures: FuturesUnordered<_> = keys
349            .iter()
350            .map(|(key_id, key)| {
351                let key_id = *key_id;
352                let owner = owner;
353                async move {
354                    match self.unwrap_key(&key, owner).await {
355                        Ok(unwrapped_key) => cipher::key_to_cipher(key, &unwrapped_key)
356                            .map(|c| (key_id, cipher::CipherHolder::Cipher(c))),
357                        Err(zx::Status::UNAVAILABLE) => {
358                            Ok((key_id, cipher::CipherHolder::Unavailable))
359                        }
360                        Err(e) => Err(e),
361                    }
362                }
363            })
364            .collect();
365        let result = futures.try_collect::<BTreeMap<u64, _>>().await?;
366        Ok(result.into())
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::{StreamCipher, UnwrappedKey};
373
374    #[test]
375    fn test_stream_cipher_offset() {
376        let key = UnwrappedKey::new(vec![
377            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
378            25, 26, 27, 28, 29, 30, 31, 32,
379        ]);
380        let mut cipher1 = StreamCipher::new(&key, 0);
381        let mut p1 = [1, 2, 3, 4];
382        let mut c1 = p1.clone();
383        cipher1.encrypt(&mut c1);
384
385        let mut cipher2 = StreamCipher::new(&key, 1);
386        let p2 = [5, 6, 7, 8];
387        let mut c2 = p2.clone();
388        cipher2.encrypt(&mut c2);
389
390        let xor_fn = |buf1: &mut [u8], buf2| {
391            for (b1, b2) in buf1.iter_mut().zip(buf2) {
392                *b1 ^= b2;
393            }
394        };
395
396        // Check that c1 ^ c2 != p1 ^ p2 (which would be the case if the same offset was used for
397        // both ciphers).
398        xor_fn(&mut c1, &c2);
399        xor_fn(&mut p1, &p2);
400        assert_ne!(c1, p1);
401    }
402}