fxfs_crypt_common/
lib.rs

1// Copyright 2025 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_gcm_siv::aead::Aead;
6use aes_gcm_siv::{Aes256GcmSiv, Key, KeyInit as _, Nonce};
7use async_trait::async_trait;
8use fuchsia_sync::Mutex;
9use fxfs_crypto::{
10    Crypt, EncryptionKey, FscryptKeyIdentifierAndNonce, KeyPurpose, ObjectType, UnwrappedKey,
11    WrappedKey, WrappingKeyId,
12};
13use rand::rngs::StdRng;
14use rand::{RngCore, SeedableRng};
15use std::collections::hash_map::{Entry, HashMap};
16use std::sync::atomic::{AtomicBool, Ordering};
17use zx_status as zx;
18
19fn zero_extended_nonce(val: u64) -> Nonce {
20    let mut nonce = Nonce::default();
21    nonce.as_mut_slice()[..8].copy_from_slice(&val.to_le_bytes());
22    nonce
23}
24
25struct Cipher {
26    // Used to create or unwrap `EncryptionKey::Fxfs`.
27    aes_gcm_siv: Aes256GcmSiv,
28    // Used to create or unwrap `EncryptionKey::FscryptInoLblk32Dir`.
29    wrapping_key: [u8; 32],
30}
31
32impl Cipher {
33    fn new(wrapping_key: [u8; 32]) -> Self {
34        Self {
35            aes_gcm_siv: Aes256GcmSiv::new(Key::<Aes256GcmSiv>::from_slice(&wrapping_key)),
36            wrapping_key,
37        }
38    }
39
40    fn encrypt(&self, nonce: &Nonce, plaintext: &[u8]) -> Result<Vec<u8>, zx::Status> {
41        self.aes_gcm_siv.encrypt(nonce, plaintext).map_err(|_e| zx::Status::INTERNAL)
42    }
43
44    fn decrypt(&self, nonce: &Nonce, ciphertext: &[u8]) -> Result<Vec<u8>, zx::Status> {
45        self.aes_gcm_siv.decrypt(nonce, ciphertext).map_err(|_e| zx::Status::INTERNAL)
46    }
47}
48
49struct CryptBaseInner {
50    ciphers: HashMap<WrappingKeyId, Cipher>,
51    active_data_key: Option<WrappingKeyId>,
52    active_metadata_key: Option<WrappingKeyId>,
53}
54
55/// `CryptBase` is a helper for managing wrapping keys and performing cryptographic operations.
56pub struct CryptBase {
57    inner: Mutex<CryptBaseInner>,
58    shutdown: AtomicBool,
59    // This affects the type of keys we create for fscrypt directories. If using Fxfs keys, create
60    // `EncryptionKey::Fxfs`, else create `EncryptionKeys::FscryptInoLblk32Dir`.
61    use_fxfs_keys_for_fscrypt_dirs: bool,
62    /// Legacy fscrypt uses the filesystem UUID to salt encryption keys in some variants.
63    /// We don't have direct access to the filesystem so we store the UUID here.
64    filesystem_uuid: [u8; 16],
65}
66
67impl CryptBase {
68    pub fn new() -> Self {
69        Self {
70            inner: Mutex::new(CryptBaseInner {
71                ciphers: HashMap::new(),
72                active_data_key: None,
73                active_metadata_key: None,
74            }),
75            shutdown: AtomicBool::new(false),
76            filesystem_uuid: [0; 16],
77            use_fxfs_keys_for_fscrypt_dirs: false,
78        }
79    }
80
81    pub fn add_wrapping_key(&self, id: WrappingKeyId, key: [u8; 32]) -> Result<(), zx::Status> {
82        let mut inner = self.inner.lock();
83        match inner.ciphers.entry(id) {
84            Entry::Occupied(_) => Err(zx::Status::ALREADY_EXISTS),
85            Entry::Vacant(v) => {
86                v.insert(Cipher::new(key));
87                Ok(())
88            }
89        }
90    }
91
92    pub fn set_active_key(&self, purpose: KeyPurpose, id: WrappingKeyId) -> Result<(), zx::Status> {
93        let mut inner = self.inner.lock();
94        if !inner.ciphers.contains_key(&id) {
95            return Err(zx::Status::NOT_FOUND);
96        }
97        match purpose {
98            KeyPurpose::Data => inner.active_data_key = Some(id),
99            KeyPurpose::Metadata => inner.active_metadata_key = Some(id),
100        }
101        Ok(())
102    }
103
104    pub fn forget_wrapping_key(&self, id: &WrappingKeyId) -> Result<(), zx::Status> {
105        let mut inner = self.inner.lock();
106        if let Some(active_id) = inner.active_data_key {
107            if active_id == *id {
108                return Err(zx::Status::INVALID_ARGS);
109            }
110        }
111        if let Some(active_id) = inner.active_metadata_key {
112            if active_id == *id {
113                return Err(zx::Status::INVALID_ARGS);
114            }
115        }
116        inner.ciphers.remove(id);
117        Ok(())
118    }
119
120    pub fn shutdown(&self) {
121        self.shutdown.store(true, Ordering::Relaxed);
122    }
123
124    /// Fscrypt in INO_LBLK32 and INO_LBLK64 modes mix the filesystem_uuid into key derivation
125    /// functions. Crypt should be told the uuid ahead of time to support decryption of migrated
126    /// data. (Note that we make an assumption that there is only one filesystem.)
127    pub fn set_filesystem_uuid(&mut self, uuid: &[u8; 16]) {
128        self.filesystem_uuid = *uuid;
129    }
130
131    pub fn use_fxfs_keys_for_fscrypt_dirs(&mut self) {
132        self.use_fxfs_keys_for_fscrypt_dirs = true;
133    }
134
135    pub fn using_fxfs_keys_for_fscrypt_dirs(&self) -> bool {
136        self.use_fxfs_keys_for_fscrypt_dirs
137    }
138}
139
140#[async_trait]
141impl Crypt for CryptBase {
142    async fn create_key(
143        &self,
144        owner: u64,
145        purpose: KeyPurpose,
146    ) -> Result<(fxfs_crypto::FxfsKey, UnwrappedKey), zx::Status> {
147        if self.shutdown.load(Ordering::Relaxed) {
148            return Err(zx::Status::INTERNAL);
149        }
150        let inner = self.inner.lock();
151        let wrapping_key_id = match purpose {
152            KeyPurpose::Data => inner.active_data_key,
153            KeyPurpose::Metadata => inner.active_metadata_key,
154        }
155        .ok_or(zx::Status::INVALID_ARGS)?;
156
157        let cipher = inner.ciphers.get(&wrapping_key_id).ok_or(zx::Status::UNAVAILABLE)?;
158
159        let nonce = zero_extended_nonce(owner);
160
161        let mut uwnrapped_key = [0u8; 32];
162        StdRng::from_os_rng().fill_bytes(&mut uwnrapped_key);
163
164        let wrapped_key = cipher.encrypt(&nonce, &uwnrapped_key[..])?;
165        Ok((
166            fxfs_crypto::FxfsKey {
167                wrapping_key_id,
168                key: wrapped_key.try_into().map_err(|_| zx::Status::INTERNAL)?,
169            },
170            UnwrappedKey::new(uwnrapped_key.to_vec()),
171        ))
172    }
173
174    async fn create_key_with_id(
175        &self,
176        owner: u64,
177        wrapping_key_id: WrappingKeyId,
178        object_type: ObjectType,
179    ) -> Result<(EncryptionKey, UnwrappedKey), zx::Status> {
180        if self.shutdown.load(Ordering::Relaxed) {
181            return Err(zx::Status::INTERNAL);
182        }
183
184        match object_type {
185            ObjectType::Directory if !self.use_fxfs_keys_for_fscrypt_dirs => {
186                let mut nonce = [0; 16];
187                StdRng::from_os_rng().fill_bytes(&mut nonce);
188                let inner = self.inner.lock();
189                let cipher = inner.ciphers.get(&wrapping_key_id).ok_or(zx::Status::UNAVAILABLE)?;
190                let mut unwrapped_key = [0u8; 96];
191                fscrypt::hkdf::hkdf(&cipher.wrapping_key, &nonce, &mut unwrapped_key);
192                Ok((
193                    EncryptionKey::FscryptInoLblk32Dir {
194                        key_identifier: wrapping_key_id,
195                        nonce: nonce.try_into().map_err(|_| zx::Status::INTERNAL)?,
196                    },
197                    UnwrappedKey::new(unwrapped_key.to_vec()),
198                ))
199            }
200            _ => {
201                let inner = self.inner.lock();
202                let cipher = inner.ciphers.get(&wrapping_key_id).ok_or(zx::Status::UNAVAILABLE)?;
203                let nonce = zero_extended_nonce(owner);
204                let mut unwrapped_key = [0u8; 32];
205                StdRng::from_os_rng().fill_bytes(&mut unwrapped_key);
206                let wrapped = cipher.encrypt(&nonce, &unwrapped_key[..])?;
207                Ok((
208                    EncryptionKey::Fxfs(fxfs_crypto::FxfsKey {
209                        wrapping_key_id,
210                        key: wrapped.try_into().map_err(|_| zx::Status::INTERNAL)?,
211                    }),
212                    UnwrappedKey::new(unwrapped_key.to_vec()),
213                ))
214            }
215        }
216    }
217
218    async fn unwrap_key(
219        &self,
220        wrapped_key: &WrappedKey,
221        owner: u64,
222    ) -> Result<UnwrappedKey, zx::Status> {
223        if self.shutdown.load(Ordering::Relaxed) {
224            return Err(zx::Status::INTERNAL);
225        }
226
227        match wrapped_key {
228            WrappedKey::FscryptInoLblk32Dir(FscryptKeyIdentifierAndNonce {
229                key_identifier,
230                nonce,
231            }) => {
232                let inner = self.inner.lock();
233                let cipher = inner.ciphers.get(key_identifier).ok_or(zx::Status::UNAVAILABLE)?;
234                let mut unwrapped_key = [0u8; 96];
235                fscrypt::hkdf::hkdf(&cipher.wrapping_key, nonce, &mut unwrapped_key);
236                Ok(UnwrappedKey::new(unwrapped_key.to_vec()))
237            }
238            WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey { wrapping_key_id, wrapped_key }) => {
239                let inner = self.inner.lock();
240                let cipher = inner.ciphers.get(wrapping_key_id).ok_or(zx::Status::UNAVAILABLE)?;
241                let mut nonce = Nonce::default();
242                nonce.as_mut_slice()[..8].copy_from_slice(&owner.to_le_bytes());
243                Ok(UnwrappedKey::new(cipher.decrypt(&nonce, wrapped_key)?))
244            }
245            _ => Err(zx::Status::NOT_SUPPORTED),
246        }
247    }
248}
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[fuchsia::test]
254    async fn test_wrap_unwrap() {
255        let crypt = CryptBase::new();
256        let key = [0xABu8; 32];
257        let id = [1u8; 16];
258        crypt.add_wrapping_key(id, key).expect("add_wrapping_key failed");
259        crypt.set_active_key(KeyPurpose::Data, id).expect("set_active_key failed");
260
261        let (fxfs_key, unwrapped_key) =
262            crypt.create_key(0, KeyPurpose::Data).await.expect("create_key failed");
263        assert_eq!(fxfs_key.wrapping_key_id, id);
264        assert_eq!(unwrapped_key.len(), 32);
265
266        let unwrapped_back = crypt
267            .unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 0)
268            .await
269            .expect("unwrap_key failed");
270        assert_eq!(*unwrapped_key, *unwrapped_back);
271    }
272
273    #[fuchsia::test]
274    async fn test_forget_wrapping_key() {
275        let crypt = CryptBase::new();
276        let key = [0xABu8; 32];
277        let id = [1u8; 16];
278        crypt.add_wrapping_key(id, key).expect("add_wrapping_key failed");
279        assert_eq!(crypt.add_wrapping_key(id, key), Err(zx::Status::ALREADY_EXISTS));
280        crypt.forget_wrapping_key(&id).unwrap();
281        assert_eq!(
282            crypt
283                .unwrap_key(
284                    &WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey {
285                        wrapping_key_id: id,
286                        wrapped_key: [0u8; 48]
287                    }),
288                    0
289                )
290                .await
291                .expect_err("unwrap_key should fail when wrapping key is forgotten"),
292            zx::Status::UNAVAILABLE
293        );
294        crypt.add_wrapping_key(id, key).expect("add_wrapping_key failed");
295    }
296
297    #[fuchsia::test]
298    async fn test_active_key_management() {
299        let crypt = CryptBase::new();
300        let key = [0xABu8; 32];
301        let id1 = [0u8; 16];
302        let id2 = [1u8; 16];
303        crypt.add_wrapping_key(id1, key).expect("add_wrapping_key failed");
304        crypt.add_wrapping_key(id2, key).expect("add_wrapping_key failed");
305
306        crypt.set_active_key(KeyPurpose::Data, id1).expect("set_active_key failed");
307        crypt.set_active_key(KeyPurpose::Metadata, id2).expect("set_active_key failed");
308
309        assert_eq!(crypt.forget_wrapping_key(&id1), Err(zx::Status::INVALID_ARGS));
310        assert_eq!(crypt.forget_wrapping_key(&id2), Err(zx::Status::INVALID_ARGS));
311    }
312
313    #[fuchsia::test]
314    async fn test_shutdown() {
315        let crypt = CryptBase::new();
316        let key = [0xABu8; 32];
317        let id = [1u8; 16];
318        crypt.add_wrapping_key(id, key).expect("add_wrapping_key failed");
319        crypt.set_active_key(KeyPurpose::Data, id).expect("set_active_key failed");
320
321        crypt.shutdown();
322
323        assert_eq!(
324            crypt
325                .create_key(0, KeyPurpose::Data)
326                .await
327                .expect_err("create_key should fail when crypt has shut down"),
328            zx::Status::INTERNAL
329        );
330        assert_eq!(
331            crypt
332                .create_key_with_id(0, id, ObjectType::File)
333                .await
334                .expect_err("create_key_with_id should fail when crypt has shut down"),
335            zx::Status::INTERNAL
336        );
337        assert_eq!(
338            crypt
339                .unwrap_key(
340                    &WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey {
341                        wrapping_key_id: id,
342                        wrapped_key: [0u8; 48]
343                    }),
344                    0,
345                )
346                .await
347                .expect_err("unwrap_key should fail when crypt has shut down"),
348            zx::Status::INTERNAL
349        );
350    }
351
352    #[fuchsia::test]
353    async fn test_create_key_no_active_key() {
354        let crypt = CryptBase::new();
355        assert_eq!(
356            crypt
357                .create_key(0, KeyPurpose::Data)
358                .await
359                .expect_err("create_key should fail when no active key is set"),
360            zx::Status::INVALID_ARGS
361        );
362    }
363
364    #[fuchsia::test]
365    async fn test_create_key_with_id_not_found() {
366        let crypt = CryptBase::new();
367        let id = [1u8; 16];
368        assert_eq!(
369            crypt.create_key_with_id(0, id, ObjectType::File).await.expect_err(
370                "create_key_with_id should fail when no active key is set at wrapping key id"
371            ),
372            zx::Status::UNAVAILABLE
373        );
374    }
375
376    #[fuchsia::test]
377    async fn test_unwrap_key_not_found() {
378        let crypt = CryptBase::new();
379        let id = [1u8; 16];
380        assert_eq!(
381            crypt
382                .unwrap_key(
383                    &WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey {
384                        wrapping_key_id: id,
385                        wrapped_key: [0u8; 48]
386                    }),
387                    0,
388                )
389                .await
390                .expect_err("unwrap_key should fail when no active key is set at wrapping key id"),
391            zx::Status::UNAVAILABLE
392        );
393    }
394
395    #[fuchsia::test]
396    async fn test_unwrap_key_wrong_owner() {
397        let crypt = CryptBase::new();
398        let key = [0xABu8; 32];
399        let id = [0u8; 16];
400        crypt.add_wrapping_key(id, key).expect("add_wrapping_key failed");
401        crypt.set_active_key(KeyPurpose::Data, id).expect("set_active_key failed");
402
403        let (fxfs_key, _unwrapped_key) =
404            crypt.create_key(0, KeyPurpose::Data).await.expect("create_key failed");
405        // Try to unwrap with wrong owner (1 instead of 0)
406        assert_eq!(
407            crypt
408                .unwrap_key(&WrappedKey::Fxfs(fxfs_key.into()), 1)
409                .await
410                .expect_err("unwrap_key should fail when owner does not match"),
411            zx::Status::INTERNAL
412        );
413    }
414
415    #[fuchsia::test]
416    async fn test_wrap_unwrap_key_with_arbitrary_wrapping_key_id() {
417        let crypt = CryptBase::new();
418        let key = [0xABu8; 32];
419        let id = [2u8; 16];
420        crypt.add_wrapping_key(id, key).expect("add_key failed");
421
422        let (wrapped_key, unwrapped_key) = crypt
423            .create_key_with_id(0, id, ObjectType::File)
424            .await
425            .expect("create_key_with_id failed");
426        let unwrap_result =
427            crypt.unwrap_key(&WrappedKey::from(wrapped_key), 0).await.expect("unwrap_key failed");
428        assert_eq!(*unwrap_result, *unwrapped_key);
429
430        // Do it twice to make sure the service can use the same key repeatedly.
431        let (wrapped_key, unwrapped_key) = crypt
432            .create_key_with_id(1, id, ObjectType::File)
433            .await
434            .expect("create_key_with_id failed");
435        let unwrap_result =
436            crypt.unwrap_key(&WrappedKey::from(wrapped_key), 1).await.expect("unwrap_key failed");
437        assert_eq!(*unwrap_result, *unwrapped_key);
438    }
439
440    #[fuchsia::test]
441    async fn test_unwrap_key_wrong_key() {
442        let crypt = CryptBase::new();
443        let key = [0xABu8; 32];
444        let id = [0u8; 16];
445        crypt.add_wrapping_key(id, key).expect("add_key failed");
446        crypt.set_active_key(KeyPurpose::Data, id).expect("set_active_key failed");
447
448        let (fxfs_key, _unwrapped_key) =
449            crypt.create_key(0, KeyPurpose::Data).await.expect("create_key failed");
450        let mut modified_wrapped_key = fxfs_key.key.to_vec();
451        for byte in &mut modified_wrapped_key {
452            *byte ^= 0xff;
453        }
454        assert_eq!(
455            crypt
456                .unwrap_key(
457                    &WrappedKey::Fxfs(fidl_fuchsia_fxfs::FxfsKey {
458                        wrapping_key_id: fxfs_key.wrapping_key_id,
459                        wrapped_key: modified_wrapped_key.clone().try_into().unwrap(),
460                    }),
461                    0,
462                )
463                .await
464                .is_err(),
465            true
466        );
467    }
468}