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::{anyhow, ensure, Error};
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
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_main_key_to_identifier() {
66        // Nb: Hard coded test vector from an fscrypt instance.
67        let key_digest = "dc34d175ba21b27e2e92829b0dc12666ce8bfbcbae387014c6bb0d8b7678dafa6466bd7565b1a5999cd3f8a39a470528fa6816768e6985f0b10804af7d657810";
68        let key: [u8; 64] = hex::decode(&key_digest).unwrap().try_into().unwrap();
69        assert_eq!(hex::encode(main_key_to_identifier(&key)), "fc7f69a149f89a7529374cf9e96a6d13");
70    }
71}