Skip to main content

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