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