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