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    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 = Self(ChaCha20::new(
238            &chacha20::Key::try_from(&key[..]).expect("Invalid StreamCipher key length"),
239            /* nonce: */ &[0; 12].into(),
240        ));
241        cipher.0.seek(offset);
242        cipher
243    }
244
245    pub fn encrypt(&mut self, buffer: &mut [u8]) {
246        fxfs_trace::duration!("StreamCipher::encrypt", "len" => buffer.len());
247        self.0.apply_keystream(buffer);
248    }
249
250    pub fn decrypt(&mut self, buffer: &mut [u8]) {
251        fxfs_trace::duration!("StreamCipher::decrypt", "len" => buffer.len());
252        self.0.apply_keystream(buffer);
253    }
254
255    pub fn offset(&self) -> u64 {
256        self.0.current_pos()
257    }
258}
259
260/// Different keys are used for metadata and data in order to make certain operations requiring a
261/// metadata key rotation (e.g. secure erase) more efficient.
262pub enum KeyPurpose {
263    /// The key will be used to wrap user data.
264    Data,
265    /// The key will be used to wrap internal metadata.
266    Metadata,
267}
268
269impl TryFrom<fidl_fuchsia_fxfs::KeyPurpose> for KeyPurpose {
270    type Error = zx::Status;
271
272    fn try_from(purpose: fidl_fuchsia_fxfs::KeyPurpose) -> Result<Self, Self::Error> {
273        match purpose {
274            fidl_fuchsia_fxfs::KeyPurpose::Data => Ok(KeyPurpose::Data),
275            fidl_fuchsia_fxfs::KeyPurpose::Metadata => Ok(KeyPurpose::Metadata),
276            _ => Err(zx::Status::INVALID_ARGS),
277        }
278    }
279}
280
281/// The `Crypt` trait below provides a mechanism to unwrap a key or set of keys.
282/// The wrapping keys can be one of these types.
283pub enum WrappingKey {
284    /// This is used for keys of the type WrappedKey::Fxfs.
285    Aes256GcmSiv([u8; 32]),
286    /// This is used for legacy fscrypt keys that use a 64-byte main key.
287    Fscrypt([u8; 64]),
288}
289impl From<[u8; 32]> for WrappingKey {
290    fn from(value: [u8; 32]) -> Self {
291        WrappingKey::Aes256GcmSiv(value)
292    }
293}
294impl From<[u8; 64]> for WrappingKey {
295    fn from(value: [u8; 64]) -> Self {
296        WrappingKey::Fscrypt(value)
297    }
298}
299
300/// The keys it unwraps can be wrapped with either Aes256GcmSiv (ideally) or using via
301/// legacy fscrypt master key + HKDF.
302
303/// An interface trait with the ability to wrap and unwrap encryption keys.
304///
305/// Note that existence of this trait does not imply that an object will **securely**
306/// wrap and unwrap keys; rather just that it presents an interface for wrapping operations.
307#[async_trait]
308pub trait Crypt: Send + Sync {
309    /// `owner` is intended to be used such that when the key is wrapped, it appears to be different
310    /// to that of the same key wrapped by a different owner.  In this way, keys can be shared
311    /// amongst different filesystem objects (e.g. for clones), but it is not possible to tell just
312    /// by looking at the wrapped keys.
313    async fn create_key(
314        &self,
315        owner: u64,
316        purpose: KeyPurpose,
317    ) -> Result<(FxfsKey, UnwrappedKey), zx::Status>;
318
319    /// `owner` is intended to be used such that when the key is wrapped, it appears to be different
320    /// to that of the same key wrapped by a different owner.  In this way, keys can be shared
321    /// amongst different filesystem objects (e.g. for clones), but it is not possible to tell just
322    /// by looking at the wrapped keys.
323    async fn create_key_with_id(
324        &self,
325        owner: u64,
326        wrapping_key_id: WrappingKeyId,
327        object_type: ObjectType,
328    ) -> Result<(EncryptionKey, UnwrappedKey), zx::Status>;
329
330    /// Unwraps a single key, returning a raw unwrapped key.
331    /// This method is generally only used with StreamCipher and FF1.
332    /// Returns `zx::Status::UNAVAILABLE` if the key is known but cannot be unwrapped (e.g. it is
333    /// locked).
334    /// Returns `zx::Status::NOT_FOUND` if the wrapping key is not known.
335    async fn unwrap_key(
336        &self,
337        wrapped_key: &WrappedKey,
338        owner: u64,
339    ) -> Result<UnwrappedKey, zx::Status>;
340
341    /// Unwraps object keys and stores the result as a CipherSet mapping key_id to:
342    ///   - Some(cipher) if unwrapping key was found or
343    ///   - None if unwrapping key was missing.
344    /// The cipher can be used directly to encrypt/decrypt data.
345    async fn unwrap_keys(
346        &self,
347        keys: &BTreeMap<u64, WrappedKey>,
348        owner: u64,
349    ) -> Result<CipherSet, zx::Status> {
350        let futures: FuturesUnordered<_> = keys
351            .iter()
352            .map(|(key_id, key)| {
353                let key_id = *key_id;
354                let owner = owner;
355                async move {
356                    match self.unwrap_key(&key, owner).await {
357                        Ok(unwrapped_key) => cipher::key_to_cipher(key, &unwrapped_key)
358                            .map(|c| (key_id, cipher::CipherHolder::Cipher(c))),
359                        Err(zx::Status::UNAVAILABLE) => {
360                            Ok((key_id, cipher::CipherHolder::Unavailable))
361                        }
362                        Err(e) => Err(e),
363                    }
364                }
365            })
366            .collect();
367        let result = futures.try_collect::<BTreeMap<u64, _>>().await?;
368        Ok(result.into())
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::{StreamCipher, UnwrappedKey};
375
376    #[test]
377    fn test_stream_cipher_offset() {
378        let key = UnwrappedKey::new(vec![
379            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
380            25, 26, 27, 28, 29, 30, 31, 32,
381        ]);
382        let mut cipher1 = StreamCipher::new(&key, 0);
383        let mut p1 = [1, 2, 3, 4];
384        let mut c1 = p1.clone();
385        cipher1.encrypt(&mut c1);
386
387        let mut cipher2 = StreamCipher::new(&key, 1);
388        let p2 = [5, 6, 7, 8];
389        let mut c2 = p2.clone();
390        cipher2.encrypt(&mut c2);
391
392        let xor_fn = |buf1: &mut [u8], buf2| {
393            for (b1, b2) in buf1.iter_mut().zip(buf2) {
394                *b1 ^= b2;
395            }
396        };
397
398        // Check that c1 ^ c2 != p1 ^ p2 (which would be the case if the same offset was used for
399        // both ciphers).
400        xor_fn(&mut c1, &c2);
401        xor_fn(&mut p1, &p2);
402        assert_ne!(c1, p1);
403    }
404}