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 的安全、儲存空間和隱私權團隊。

社交化:在開始這項 RFC 程序之前,這項設計已流通並由儲存團隊和上述審查人員審查。

設計

Fxfs 的說明請參閱 RFC-0136

需求條件

  1. 應該可以建立、列舉及刪除磁碟區。

  2. 每個磁碟區都應使用不同的金鑰加密。

  3. 檔案名稱、大小和時間戳記等物件中繼資料應加密。

  4. 如果沒有金鑰,就必須能夠刪除磁碟區。

  5. 應可支援金鑰輪替,而不需耗費大量心力進行遷移。

  6. 磁碟區大小應設有限制 (但這項設計留待日後處理)。

  7. 即使沒有金鑰存取權,也應該可以查詢磁碟區大小。

  8. 應根據區塊計數防範指紋攻擊。這類攻擊會利用一組檔案中多個檔案的加密大小 (向上取整至最接近的區塊),判斷該組檔案是否存在於檔案系統。

不在涵蓋範圍內的事項

這項設計未涵蓋下列領域:

  1. 在裝置 (包括主機和目標裝置) 之間雙向傳輸加密圖片。雖然這有助於偵錯,但不太可能在生產裝置上實現,而且沒有先例。

  2. 加密服務的實作方式。這項設計已在其他地方說明。

  3. 設計應支援金鑰輪替,但元件之間使用的確切 API 不在範圍內。

總覽

Fxfs 加密

Fxfs 內建加密功能將支援個別檔案金鑰。檔案通常只會有一個加密金鑰,但為了支援金鑰輪替和檔案複製,格式會支援每個檔案有多個金鑰。金鑰會由 Fxfs 通訊的加密服務包裝及解除包裝。

根父項和根存放區中的下列物件會採用某種形式的加密:

  1. (存在於根父項商店中的) 日誌會包含子項商店中繼資料的變動,這些變動會使用串流加密法加密。這個密碼的加密金鑰會依商店而異。

  2. 子商店的圖層檔案 (位於根商店內) 會經過加密。加密這些檔案的機制與所有其他檔案相同。金鑰會使用每家商店的金鑰包裝,並與其他未加密的商店資訊一起儲存。

根父項和根存放區中的其他物件不會加密,包括超級區塊、所有與分配器相關的物件、支援根物件存放區的層檔案 (其中包含根存放區物件的中繼資料),以及包含子存放區基本資訊的物件。這些都不是使用者資料。

因此,最終結果是:

  • 使用者資料會經過加密
  • 所有中繼資料 (包括檔案名稱、檔案大小、範圍資訊和目錄資訊) 都會加密。

部分資訊不會加密:

  • 磁碟區中的檔案數量。
  • 分配給磁碟區的範圍集。

物件 ID

在 Fxfs 中,物件 ID 目前是以單調遞增的方式分配,可用做側管道。為解決這個問題,系統會使用 ff1 加密方式,加密物件 ID 最不重要的 32 位元 (在分配時間)。在循環使用 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) 樹狀結構,以保留中繼資料,這些資料會以與儲存空間內檔案相同的方式加密。系統會建立圖層檔案使用的金鑰,並將用途設為「中繼資料」。

交易提交後,系統會將變異寫入日誌。這些變動會套用至 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 物件 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 功能,這不在本設計的範圍內)。

在 Fxfs 中執行主要壓縮作業,即可相對輕鬆地完成步驟 2。由於這是自然發生的情況,因此這項程序可以定期執行,也可以視需要執行。可以安排 Fxfs 每週保證進行一次主要壓縮,但這取決於可用的鍵。

這項程序需要使用與資料不同的包裝金鑰來包裝中繼資料金鑰 (否則會強制重新編寫所有資料,這是不允許的),因此提供給 create_key 方法的中繼資料引數。

請注意,這項程序需要重新編寫卷冊的所有中繼資料,因此不應經常執行。

金鑰輪替

如要安全清除資料,請按照上述步驟,為中繼資料包裝金鑰進行金鑰輪替。您應可使用下列程序,輪替用於包裝資料金鑰的金鑰:

  1. 使用新的包裝金鑰包裝新金鑰。傳回以這種方式包裝的金鑰的新 wrapping_key_id。

  2. 請 Fxfs 重新包裝所有與指定 wrapping_key_id 相符的金鑰。這項功能的 API 留待後續設計,不應要求變更磁碟格式。

  3. 按照安全清除程序包裝中繼資料金鑰。由於金鑰只會寫入中繼資料檔案,因此在舊的包裝金鑰遭到銷毀後,應可確保資料包裝金鑰順利輪替。

請注意,步驟 2 和 3 可以合併執行,也就是說,您應該可以同時執行主要壓縮作業和重新包裝金鑰。

與安全清除功能一樣,我們最初並未計畫實作金鑰滾動支援功能。

Fsck

如果沒有金鑰,fsck 只能執行有限的檢查。顯然無法確保加密商店的一致性,但可以檢查所有其他未加密商店的範圍和一致性。

取得金鑰後,即可執行完整一致性檢查,或只檢查個別卷冊的中繼資料。

支援多個音量

fshost 會匯出新目錄:volumes。目錄中的節點會代表 fshost 匯出的磁碟區,並支援新的磁碟區通訊協定 (節點不支援其他通訊協定,也就是說,節點不支援 fuchsia.io 節點通訊協定,因此無法複製)。通訊協定會類似如下:

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,並預期 Mount 會成功,不應將其做為測試憑證的方法。

匯出根目錄看起來會與檔案系統目前公開的根目錄完全相同。系統會公開 fs.Admin 服務,並可使用 Shutdown 方法鎖定/關閉磁碟區。如果所有磁碟區連線都已關閉,磁碟區也會遭到鎖定。磁碟區鎖定後,系統會謹慎處理,確保所有未包裝的金鑰都會遭到捨棄。

列舉和移除作業將透過磁碟區目錄上的 fuchsia.io 目錄通訊協定完成。移除作業可能為非同步,但系統會等到確定磁碟區最終會移除,才會傳回成功訊息。名稱可立即重複使用,但空間可能需要一段時間才能使用。

新卷宗無法使用 Directory 通訊協定的 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 的範圍。

實作

支援多個磁碟區和加密功能,將以一般方式實作。

目前沒有支援金鑰輪替、安全清除和內嵌資料的計畫。

AES-XTS-256、ChaCha20 和 ff1 加密功能將由第三方 Crate 提供。

您必須接受安全稽核。

效能

加密會影響效能。系統會使用現有的檔案系統基準來評估效能。實作完成後,即可消除 Zxcrypt,這應可抵銷因實作而造成的任何損失。我們會調查目前效能的任何回歸情形。

回溯相容性

這項變更會破壞 Fxfs,因此需要重新格式化。

安全性考量

資安團隊已審查這項 RFC。

以下規定是基於安全考量而制定:

  1. 每個磁碟區都應以不同的金鑰加密。

  2. 檔案資料和中繼資料應經過加密。

  3. 應難以將物件 ID 做為旁側管道。

實作時需要進行安全審查。

隱私權注意事項

隱私權團隊已審查這項 RFC。

主要隱私權考量是以下資料不會加密:

  • 磁碟區中的檔案數量。
  • 分配給磁碟區的範圍集 (包括分配給磁碟區的空間量)。
  • 磁碟區名稱。

其他所有資料和中繼資料都應加密。這應該足以滿足需求,包括指紋攻擊。

實作作業的安全審查應驗證設計是否符合這些規定。

測試

我們會使用單元、整合和端對端測試的常見組合進行測試。Fxfs 將用於 CQ 下執行的許多測試的資料分割區,因此會以這種方式曝光。

說明文件

在某個階段,系統設計人員需要文件,協助他們為特定產品選擇不同的儲存空間選項。不過,設定方式尚未決定,且不屬於本 RFC 的範圍。

本 RFC 中介紹的 API 最初 (很可能無限期) 會在樹狀結構中,目前會記錄為 FIDL 的一部分。

缺點、替代方案和未知事項

本 RFC 中說明的加密設計,比 Zxcrypt 使用的磁碟分割區加密複雜許多。

系統設計人員仍可使用以磁碟分割為基礎的加密方式,但這會限制磁碟區之間的空間共用:以磁碟分割為基礎的彈性空間配置需要磁碟區管理員 (因此會因額外的間接性而影響效能),且可能會發生片段化問題 (在磁碟分割之間移動空間可能需要重組)。此外,以磁碟分割為基礎的配置也難以支援安全清除和金鑰輪替。

既有技術和參考資料

Zxcrypt 使用 AES-256 XTS 加密區塊,方式與這項 RFC 類似,但使用的微調有所不同。

此處的設計大多符合 Android 12 的加密規定。其中一項重大差異是,通過 Fxfs 日誌的中繼資料必須使用串流加密法加密,但這並未明確提及為可接受的加密法。