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 會成功為前提呼叫 Mount;請勿將其用於測試憑證。

匯出根目錄現在會與檔案系統公開的根目錄類似。系統會公開 fs.Admin 服務,並可使用 Shutdown 方法鎖定/關閉磁碟分割區。如果所有與磁碟區的連線都已關閉,磁碟區也會上鎖。當裝置鎖定時,系統會小心處理,確保所有展開的金鑰都會遭到捨棄。

系統會透過 volumes 目錄中的 fuchsia.io 目錄通訊協定進行列舉和移除作業。移除作業可能會非同步,也可能會同步,但除非系統確定最終會移除音量,否則不會傳回成功訊息。名稱可以立即重複使用,但空間可能需要一些時間才能開始使用。

新的磁碟區無法使用目錄通訊協定的開啟方法,因為我們需要提供加密服務和其他選項。而是新增新的通訊協定:

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。

主要隱私權考量是,下列資料「不會」加密:

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

所有其他資料和中繼資料都應加密。這應該足以因應指紋攻擊等相關規定。

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

測試

這項測試會使用單元、整合和端對端測試的常見組合進行。在 CQ 下執行的許多測試都會使用 Fxfs 做為資料分割區,因此會因此曝光。

說明文件

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

本 RFC 中推出的 API 一開始 (並且很可能無限期) 會是樹狀結構內,目前會以 FIDL 的一部分形式記錄。

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

本 RFC 中所述的加密設計比 Zxcrypt 使用的分割區加密更為複雜。

系統設計人員仍可使用以分區為基礎的加密技術,但這會導致磁碟區之間的空間共用方式受到限制:以分區為基礎的方案需要使用磁碟區管理員,才能靈活運用空間 (因此會因額外的間接方式而影響效能),且可能會發生碎片化問題 (在分區之間移動空間可能需要進行重組)。在以分割區為基礎的配置中,要支援安全擦除和金鑰輪替功能也相當困難。

既有技術與參考資料

Zxcrypt 使用 AES-256 XTS 加密區塊,方法與此 RFC 相似,但使用的調整方式有所不同。

這項設計大多與 Android 12 的加密需求相容。其中一個重大差異是,透過 Fxfs 日誌的中繼資料必須使用串流密碼加密,但並未明確提及可接受的密碼。