RFC-0157:Fxfs 加密和多卷支持

RFC-0157:Fxfs 加密和多卷支持
状态已接受
领域
  • 存储
说明

介绍 Fxfs 中的加密和多卷支持。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-04-07
审核日期(年-月-日)2022-04-28

摘要

此 RFC 介绍了 Fxfs 中的加密和多卷支持。

设计初衷

Fuchsia 需要能够支持使用 绑定到不同用户的密码。

利益相关方

审核者:abarth@google.com、enoharemaien@google.com、palmer@google.com、 jfsulliv@google.com、jsankey@google.com、zarvox@google.com、

已咨询:Fuchsia 的安全、存储和隐私团队。

社交化:此设计已由 Storage 团队传达和审核 和上述审核人员,然后再开始此 RFC 流程。

设计

RFC-0136 中介绍了 Fxfs。

要求

  1. 应该可以创建、枚举和删除卷。

  2. 每个卷应使用不同的密钥进行加密。

  3. 文件名、大小和时间戳等对象元数据 加密。

  4. 必须能够在没有密钥时删除卷。

  5. 无需大量资源就能支持按键滚动 迁移工作

  6. 卷的大小应该是有限制的(尽管此卷的设计 留待将来使用)。

  7. 您应该可以查询卷的大小,而无需访问 键。

  8. 应基于屏蔽提供针对指纹攻击的保护措施 计数。这是一种攻击方式,要求知道加密大小(向上舍入为 最近的文件块)来判断 这组文件存在于文件系统中

不在范围内

此设计未涵盖以下方面:

  1. 在设备(包括主机和目标设备)之间传输加密映像 设备),而此操作有助于进行调试 这种做法非常有用,但这在生产设备上 而且也没有先例。

  2. 加密服务的实现。我们将在其他地方介绍此设计。

  3. 设计应支持按键滚动,但支持使用的精确 API 它们不在讨论范围内。

概览

Fxfs 加密

Fxfs 内置加密功能支持按文件使用单独的密钥。谷歌文件极客 通常只有一个加密密钥,但为了支持密钥滚动和文件存储, 该格式支持每个文件使用多个密钥。密钥将被封装 并通过 Fxfs 将与之通信的加密服务解封。

根父存储区和根存储区中的以下对象将具有某种形式 加密:

  1. 该日志(存在于根父级存储区中)将发生变更 针对子存储区中的元数据使用流加密。通过 此加密的密钥将按商店进行。

  2. 子存储区(位于根存储区中)的图层文件将是 加密。对这些文件进行加密的机制与所有 其他文件。该密钥将使用按商店划分的密钥进行封装,并使用 其他未加密商店信息

根存储区和根存储区中的其他对象不会加密,即 超级块、与分配器相关的所有对象、后备的层文件 根对象存储区(用于存储根存储区对象的元数据)和 包含有关子商店的基本信息的对象。以上均不是 构成用户数据。

最终结果是:

  • 用户数据会被加密
  • 所有元数据,包括文件名、文件大小、范围信息和 目录信息将被加密

部分信息不会加密:

  • 卷中的文件数。
  • 分配给卷的范围集。

对象 ID

在 Fxfs 中,对象 ID 目前以单调递增的形式分配 可用作边信道。为解决这一问题, 对象 ID 的最小有效 32 位会被加密(在分配时 (例如,使用 ff1 加密)。密钥将在循环 32 秒后轮替 对象 ID 的位数。该密钥以封装方式存储 未加密商店信息的剩余部分对象 ID 的前 32 位 键每次滚动都会单调递增。

通过指定卷内使用的对象数量和空间, 边信道(基本上是通过 statvfs 获取的所有信息)。寻址 这超出了此设计的范围(对 Fxfs 而言并不新)。

密钥管理

密钥封装和解封分别由单独的“加密”负责,服务 它将提供如下协议:

/// Designates the purpose of a key.
type KeyPurpose = flexible enum {
    /// The key will be used to encrypt metadata.
    METADATA = 1;
    /// The key will be used to encrypt data.
    DATA = 2;
};

protocol Crypt {
    /// Creates a new wrapped key.  `owner`, effectively a nonce, identifies the
    /// owner (an object ID) of the key and must be supplied to `UnwrapKey`.
    /// `metadata` indicates that the key will be used to encrypt metadata which
    /// might influence the choice of wrapping key used by the service.  Returns
    /// the wrapping key ID used to wrap this key along with the wrapped and
    /// unwrapped key.  Errors:
    ///   ZX_ERR_INVALID_ARGS: purpose is not recognised.
    ///   ZX_ERR_BAD_STATE: the crypt service has not been correctly initialized
    ///     with a wrapping key for the specified purpose.
    CreateKey(struct {
        owner uint64;
        purpose KeyPurpose;
    }) -> (struct {
        wrapping_key_id uint64;
        wrapped_key bytes:48;
        unwrapped_key bytes:32;
    }) error zx.status;

    /// Unwraps keys that are wrapped by the key identified by `wrapping_key_id`.
    /// `owner` must be the same as that passed to `CreateKey`.  This can fail due
    /// to permission reasons or if an incorrect key is supplied.
    UnwrapKey(struct {
        wrapping_key_id uint64;
        owner uint64;
        key bytes:48;
    }) -> (struct {
        unwrapped_key bytes:32;
    }) error zx.status;
};
  • wrapping_key_id 的含义由加密算法的实现者决定 服务。Fxfs 不会对它的值应用任何显著性。

  • 每个已加密的 Fxfs 卷,从而将服务器置于不同的进程中,

  • 目前将支持 256 位密钥(与 Zxcrypt 一致)。

  • 加密服务预计将使用 AEAD 来封装密钥 也就是说,封装的密钥可能是 48 个字节。

  • purpose 用于分隔元数据所用的键与数据所用的键, 并且旨在促进按键滚动(见下文)。

加密服务的精确实现以及密钥管理政策 超出了本设计的范围。

磁盘格式

每个文件都包含如下内容:

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum EncryptionKeys {
    None,
    AES256XTS(WrappedKeys),
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct WrappedKeys {
    /// The keys (wrapped).  To support key rolling and clones, there can be more
    /// than one key.  Each of the keys is given an identifier.  The identifier is
    /// unique to the object.  AES256-XTS requires a 512 bit key, which is made
    /// of two 256 bit keys, one for the data and one for the tweak.  Both those
    /// keys are derived from the single 256 bit key we have here.
    pub keys: Vec<(/* wrapping_key_id= */ u64, /* id= */ u64, [u8; 48])>,
}

每个范围都大致如下:

pub enum ExtentValue {
    /// Indicates a deleted extent; that is, the logical range described by the
    /// extent key is considered to be deleted.
    None,
    /// The location of the extent and other related information.  `key_id`
    /// identifies which of the object's keys should be used.  `checksums` hold
    /// post-encryption checksums.
    Some { device_offset: u64, checksums: Checksums, key_id: u64 },
}

数据块将使用 AES-XTS-256 进行加密(与 Zxcrypt)。文件中的逻辑偏移量将用于调整。

元数据

对象存储区会维护日志结构合并 (LSM) 树,以保存其元数据 这些文件的加密方式与存储区中包含的文件相同。通过 创建用于图层文件的密钥,并将用途设置为 “metadata”。

事务被提交时,系统会将变更写入日志。这些 变更会应用于 LSM 树的内存层。一段时间 内存中层将刷新为持久层。任何变更 都需要在将数据写入 日志。为了支持这一点,Fxfs 使用了一项新的变更:

EncryptedObjectStore(Box<[u8]>),

流加密(不适合使用 AES-XTS-256 等分块加密)、Chacha20、 将用于加密这些变更。该密钥会被封装 与其他未加密存储数据一起存储的数据。

在重放时,键可能不可用,在这种情况下,这些变更 将以加密形式保存在内存中如果需要清空内存中 数据(以释放日志的空间),这些加密的更改将被 写入到根存储区中的对象。

当密钥可用时,加密的更改(可能是 或存在于前述文件中,可以解密和应用。接收者 解密变更,则需要加密流中的偏移量;这是 存储的未加密信息。

这些是添加到 StoreInfo 中的新字段(未加密的 商店):

    // The (wrapped) key that encrypted mutations should use.
    mutations_key: Option<Box<[u8]>>,

    // Mutations for the store are encrypted using a stream cipher.  To decrypt the
    // mutations, we need to know the offset in the cipher stream to start it.
    mutations_cipher_offset: u64,

    // If we have to flush the store whilst we do not have the key, we need to
    // write the encrypted mutations to an object. This is the object ID of that
    // file if it exists.
    encrypted_mutations_object_id: u64,

删除卷

为了支持删除卷,分配元数据将包含 拥有元数据的商店 ID:

pub struct AllocatorKey {
    pub device_range: Range<u64>,
}

pub enum AllocationRefCount {
    // Tombstone variant indicating an extent is no longer allocated.
    None,
    // Used when we know there are no possible allocations below us in the stack.
    // (e.g. on the first allocation of an extent.)
    // This variant also tracks the owning ObjectStore for the extent.
    Abs { count: u64, owner_object_id: u64 },
}

pub struct AllocatorValue {
     /// Reference count for the allocated range.
     /// (A zero reference count is treated as a 'tombstone', indicating that older
     /// values in the LSM Tree for this range should be ignored).
     pub refs: AllocationRefCount,
}

删除卷涉及更改分配器,使其体现所有 属于已删除卷的记录。经过重大压缩后, 确保这些记录已不复存在,这样我们就会忘记 卷。分配器会保留已删除卷列表的相关信息 与其元数据的其余部分相关联

内嵌数据

Fxfs 目前不支持内嵌数据,但支持后就会 密钥与元数据的加密密钥相同。这些键 在写入新的图层文件时滚动。

安全擦除

若要安全地清除文件,必须确保文件不可恢复,即使密钥 (硬件中的容器除外)随后会遭到破坏。鉴于此, 文件系统通常在闪存设备上运行 收集),擦除所有出现的数据非常重要。唯一实用的 解决方案是滚动封装密钥(不应存储在闪存中)。

安全擦除并非计划要实现的功能,但应该 按照以下步骤操作:

  1. 开始使用新的封装密钥封装新的元数据密钥。

  2. Fxfs 会使用旧的元数据键重写所有对象。

  3. 旧的元数据封装密钥会被销毁(通常使用 TPM 功能) (直接或间接),这超出了本设计的范围)。

通过执行一个主要步骤,第 2 步可以在 Fxfs 中 压缩。由于这是自然发生的,因此这是一个程序, 可以定期进行,也可按需进行。可以 安排 Fxfs 来保证每次(比如每周)进行一次重大压缩, 具体取决于可用的键。

此过程要求使用不同 将密钥封装到数据(否则,系统会强制重写所有数据, 这会造成限制),因此便是提供给 create_key 的元数据参数 方法。

请注意,此过程需要重写卷的所有元数据,这样做 因此不应频繁执行

按键操作

为安全起见,上文中概述了元数据封装密钥的滚动密钥 擦除内容。应该可以使用 以下程序:

  1. 开始使用新的封装密钥封装新密钥。返回新的 wrapping_key_id 。

  2. 要求 Fxfs 重新封装与给定 wrapping_key_id 匹配的所有密钥。用于实现以下目的的 API: 则留给后续设计使用:应该不需要任何磁盘格式 更改。

  3. 按照安全擦除程序封装元数据密钥。由于键是 只写入到元数据文件,则应确保封装数据 旧封装密钥被粉碎后,成功滚动该密钥。

请注意,您可以合并第 2 步和第 3 步,应该能够 主要压缩和重新封装密钥。

与安全擦除一样,并未计划实现密钥滚动支持 。

Fsck

没有密钥的 Fsck 将执行一组有限的检查。该 显然是不可行的, 范围和一致性。

在密钥可用的情况下,将可以执行完全一致性 或者只是检查单个卷的元数据。

多卷支持

fshost 将导出一个新的目录:volume。目录中的节点 将代表 fshost 导出的卷,并且将支持新的卷协议 (节点上将不支持其他协议,即这些节点不支持 fuchsia.io Node 协议,因此无法克隆)。协议将 例如:

type MountOptions = table {
    // A handle to the crypt service. Unencrypted volumes will ignore this option.
    crypt client_end:Crypt;
}

protocol Volume {
    // Mounts the volume and starts serving the filesystem. An error will be
    // returned if the volume is currently being served from a previous call
    // to Mount.
    Mount(resource struct {
        export_root server_end:Directory;
        options MountOptions;
    }) -> () error zx.status;
}

如果提供了错误的加密服务,装载将失败,但这应该 极好;用户应该在调用 Mount 时 succeed;不应将其用作测试凭据的方法。

导出根目录现在与文件系统公开的根目录类似。通过 系统将公开 fs.Admin 服务,并且可以使用 关停方法。如果所有连接到该卷的设备,该卷也会被锁定。 书卷已关闭。锁定音量时,请务必小心谨慎,确保 所有解封装的密钥都会被舍弃。

枚举和移除操作将通过 fuchsia.io 目录协议在 存储卷目录移除不一定会异步执行,但操作会成功 则 不会将其返回 已移除。该名称可以立即重新使用,但可能需要一些时间 以供使用

新卷无法使用目录协议的 open 方法,因为我们需要 提供加密服务和其他选项而是会添加新的协议:

type CreateVolumeOptions = table {
    // Reserved for future use.
}

protocol Manager {
    // Creates a new data volume.
    CreateVolume(resource struct {
        name string:MAX_FILENAME;
        crypt client_end:Crypt;
        export_root server_end: Directory;
        options CreateVolumeOptions;
    }) -> () error zx.status;;
}

最初,实施将继续进行,假设 Fxfs 将管理所有 但将来应该可以提供 支持由 Zxcrypt 支持的文件系统。

卷上可能存在的卷的名称和确切的卷集将 ,因此不在本 RFC 的讨论范围之内。

实现

对多卷和加密的支持将在典型的 。

目前还没有计划支持密钥滚动、安全清除和 inline-data。

AES-XTS-256、ChaCha20 和 ff1 加密将由第三方 crate 提供。

必须接受安全审核。

性能

加密会影响性能。现有文件系统基准 将用于评估效果。实现 Zxcrypt 后 这应该会抵消由于这种实施造成的任何损失。不限 以及当前性能的回归问题。

向后兼容性

这将是对 Fxfs 的重大更改,并且需要重新格式化。

安全注意事项

此 RFC 已由安全团队审核。

以下要求由安全性驱动:

  1. 每个卷应使用不同的密钥进行加密。

  2. 文件数据和元数据应加密。

  3. 应该很难将对象 ID 用作旁路。

实现将需要接受安全审核。

隐私注意事项

此 RFC 已由隐私权团队审核。

我们在隐私保护方面主要考虑的因素是,以下数据不会 加密:

  • 卷中的文件数。
  • 分配给卷的范围集(包括 分配给卷的空间)。
  • 卷的名称。

所有其他数据和元数据都应加密。这应该足以 以满足包括指纹攻击的要求。

实现的安全审核应验证设计是否符合以下要求 要求。

测试

将使用单元、集成和 和端到端测试Fxfs 将用于许多 在 CQ 下运行,因此可以得到曝光。

文档

在某些阶段,需要提供文档以帮助系统设计人员选择 指定产品的不同存储选项之间具有无限制。不过, 具体配置尚未确定,并且不在 此 RFC 范围。

此 RFC 中引入的 API 最初会(并且可能无限期) 在树内,并且暂时将记录为 FIDL 的一部分。

缺点、替代方案和未知问题

此 RFC 中概述的加密设计要比 基于分区的加密,这与 Zxcrypt 所用的加密机制相同。

系统设计人员仍然可以使用基于分区的方法 但是对于卷之间的空间共享存在限制: 基于分区的方案,该方案非常灵活,有空间,需要 Volume-manager(这会导致性能下降,因为 并且可能遭遇碎片化问题(移动空间 分区可能需要进行碎片整理)。同样,采用自动化技术的 支持对基于分区的方案进行安全清空和密钥滚动。

先验技术和参考资料

Zxcrypt 使用 AES-256 XTS 以与此 RFC 类似的方式对分块进行加密 所用的调整方法有所不同。

此处的设计主要与 Android 12 的加密功能兼容 要求。其中一个显著区别在于,通过 Fxfs 的日志需要通过流加密方式进行加密, 明确提到可以接受。