fscrypt/
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.
4pub mod direntry;
5pub mod hkdf;
6pub mod proxy_filename;
7
8use anyhow::{Error, anyhow, ensure};
9use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
10
11pub const POLICY_FLAGS_PAD_16: u8 = 0x02;
12pub const POLICY_FLAGS_INO_LBLK_32: u8 = 0x10;
13const SUPPORTED_POLICY_FLAGS: u8 = POLICY_FLAGS_PAD_16 | POLICY_FLAGS_INO_LBLK_32;
14
15pub const ENCRYPTION_MODE_AES_256_XTS: u8 = 1;
16pub const ENCRYPTION_MODE_AES_256_CTS: u8 = 4;
17
18/// An encryption context is written as an xattr to the directory root of each FBE hierarchy.
19/// For f2fs, this is stored with index 9 and name "c".
20#[repr(C, packed)]
21#[derive(Copy, Clone, Debug, Immutable, KnownLayout, FromBytes, IntoBytes, Unaligned)]
22pub struct Context {
23    pub version: u8,                   // = 2
24    pub contents_encryption_mode: u8,  // = ENCRYPTION_MODE_AES_256_XTS
25    pub filenames_encryption_mode: u8, // = ENCRYPTION_MODE_AES_256_CTS
26    pub flags: u8,                     // = POLICY_FLAGS_*
27    pub log2_data_unit_size: u8,       // = 0
28    _reserved: [u8; 3],
29    pub main_key_identifier: [u8; 16],
30    pub nonce: [u8; 16],
31}
32
33impl Context {
34    pub fn try_from_bytes(raw_context: &[u8]) -> Result<Option<Self>, Error> {
35        let this = Context::read_from_bytes(raw_context)
36            .map_err(|_| anyhow!("Bad sized crypto context"))?;
37        ensure!(this.version == 2, "Bad version number in crypto context");
38        ensure!(
39            this.contents_encryption_mode == ENCRYPTION_MODE_AES_256_XTS,
40            "Unsupported contents_encryption_mode",
41        );
42        ensure!(
43            this.filenames_encryption_mode == ENCRYPTION_MODE_AES_256_CTS,
44            "Unsupported filenames_encryption_mode"
45        );
46        // We assume 16 byte zero padding.
47        // We also only support standard key derivation and INO_LBLK_32, no INO_LBLK_64 or DIRECT.
48        ensure!(this.flags & !SUPPORTED_POLICY_FLAGS == 0, "Unsupported flags in crypto context");
49        // This controls the data unit size used for encryption blocks. We only support the default.
50        ensure!(this.log2_data_unit_size == 0, "Unsupported custom DUN size");
51        Ok(Some(this))
52    }
53}
54
55/// Returns the identifier for a given main key.
56pub fn main_key_to_identifier(main_key: &[u8; 64]) -> [u8; 16] {
57    hkdf::fscrypt_hkdf::<16>(main_key, &[], 1)
58}
59
60pub struct DirectoryKeys {
61    cts_key: [u8; 32],
62    ino_hash_key: [u8; 16],
63    dir_hash_key: [u8; 16],
64}
65
66impl DirectoryKeys {
67    /// Returns the keys in concatenated form (as found in Fxfs's crypt protocol).
68    pub fn to_unwrapped_key(&self) -> Vec<u8> {
69        let mut keys = Vec::with_capacity(64);
70        keys.extend_from_slice(&self.cts_key);
71        keys.extend_from_slice(&self.ino_hash_key);
72        keys.extend_from_slice(&self.dir_hash_key);
73        keys
74    }
75}
76
77/// Returns fscrypt directory keys (for the ino-lblk32 algorithm).  These are all the keys
78/// required to encrypt file names using fscrypt.
79pub fn to_directory_keys(main_key: &[u8], uuid: &[u8], nonce: &[u8]) -> DirectoryKeys {
80    let mut hdkf_info = [0; 17];
81    hdkf_info[0] = ENCRYPTION_MODE_AES_256_CTS;
82    hdkf_info[1..17].copy_from_slice(&uuid);
83    DirectoryKeys {
84        cts_key: hkdf::fscrypt_hkdf(main_key, &hdkf_info, hkdf::HKDF_CONTEXT_IV_INO_LBLK_32_KEY),
85        ino_hash_key: hkdf::fscrypt_hkdf(main_key, &[], hkdf::HKDF_CONTEXT_INODE_HASH_KEY),
86        dir_hash_key: hkdf::fscrypt_hkdf(main_key, &nonce, hkdf::HKDF_CONTEXT_DIRHASH_KEY),
87    }
88}
89
90pub fn to_xts_key(main_key: &[u8], uuid: [u8; 16]) -> [u8; 64] {
91    let mut hdkf_info = [0; 17];
92    hdkf_info[0] = ENCRYPTION_MODE_AES_256_XTS;
93    hdkf_info[1..17].copy_from_slice(&uuid);
94    hkdf::fscrypt_hkdf(&main_key, &hdkf_info, hkdf::HKDF_CONTEXT_IV_INO_LBLK_32_KEY)
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_main_key_to_identifier() {
103        // Nb: Hard coded test vector from an fscrypt instance.
104        let key_digest = "dc34d175ba21b27e2e92829b0dc12666ce8bfbcbae387014c6bb0d8b7678dafa6466bd7565b1a5999cd3f8a39a470528fa6816768e6985f0b10804af7d657810";
105        let key: [u8; 64] = hex::decode(&key_digest).unwrap().try_into().unwrap();
106        assert_eq!(hex::encode(main_key_to_identifier(&key)), "fc7f69a149f89a7529374cf9e96a6d13");
107    }
108}