RFC-0157:支援 Fxfs 加密和多磁碟區支援

RFC-0157:Fxfs 加密以及多磁碟區支援
狀態已接受
區域
  • 儲存空間
說明

說明 Fxfs 的加密和多磁碟區支援。

問題
  • 110
變更
作者
審查人員
提交日期 (年/月)2022-04-07
審查日期 (年/月)2022-04-28

摘要

本 RFC 說明 Fxfs 的加密和多磁碟區支援。

提振精神

Fuchsia 必須能夠支援多個磁碟區,這些磁碟區所加密的金鑰會繫結至不同使用者的密碼。

相關人員

審查人員:abarth@google.com, enoharemaien@google.com, Paler@google.com、jfsulliv@google.com、jsankey@google.com、zarvox@google.com、

顧問:Fuchsia 的安全性、儲存空間和隱私權團隊。

社交化:在啟動此 RFC 程序之前,這個設計會先與 Storage 團隊一起流通及審查,並提及先前的審查人員。

設計

Fxfs 的說明請見 RFC-0136

相關規定

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

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

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

  4. 只有在沒有鍵時,才能刪除磁碟區。

  5. 系統應無須耗費心力進行遷移,就能支援鍵捲動功能。

  6. 磁碟區大小應該設有限制 (不過這部分是在未來設計)。

  7. 應該要能在不存取金鑰的情況下,查詢磁碟區大小。

  8. 根據排程次數,應採取保護措施來防範指紋攻擊。在這種攻擊中,只要知道集合中多個檔案的加密大小 (四捨五入至最接近區塊),就能判斷該一組檔案是否存在於檔案系統中。

範圍外

本設計不包含以下領域:

  1. 在任一方向之間 (包括主機和目標裝置) 之間傳輸加密映像檔,雖然有助於偵錯,但這有助於偵錯,但實際上,實際工作環境裝置不太可能有這些機會,也沒有任何前瞻性。

  2. 加密服務的實作。我們也會在其他地方提供這項設計。

  3. 此設計應支援關鍵滾動式,但元件之間要使用的精確 API 不在涵蓋範圍內。

總覽

Fxfs 加密

Fxfs 內建加密功能會支援每個檔案的個別金鑰。檔案通常只會有一個加密金鑰,但為了支援金鑰滾動和複製檔案複製功能,這種格式支援每個檔案使用多個金鑰。金鑰會由 Fxfs 用來通訊的加密服務包裝和解除包裝。

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

  1. 日誌 (位於根父項存放區) 會有異動,這些變動適用於使用串流加密在子項儲存區加密的中繼資料。這個加密的金鑰為個別商店。

  2. 系統會加密子項儲存庫 (位於根儲存庫) 的圖層檔案。加密這些檔案的機制與其他檔案相同。系統會使用個別商店金鑰來包裝金鑰,並與其他未加密的商店資訊儲存該金鑰。

根父項和根儲存庫中的其他物件不會經過加密,亦即超區塊、與配置器相關的所有物件、支援根物件儲存庫的層檔案 (用來保留根層級商店物件的中繼資料),以及含有子項儲存基本資訊的物件。沒有任何影響。

這種做法的淨結果為:

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

部分資訊不會加密:

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

物件 ID

在 Fxfs 中,物件 ID 目前是以單調遞增的方式分配,可做為側邊管道使用。為解決此問題,系統會在配置時間使用 ff1 加密,將最少 32 位元的物件 ID 加密。金鑰將在循環過 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 等區塊加密不適合) 將用於加密這些變異。這個金鑰的金鑰會包裝,並與其他未加密的商店資料一起儲存。

在重播時,金鑰可能無法使用,在這種情況下,這些異動會以加密的形式儲存在記憶體中。如果需要清除記憶體內資料 (以便釋出日誌中的空間),這些加密異動會寫入根存放區中的物件。

當金鑰可供使用時,加密變異 (可能是記憶體中或存在於上述檔案中) 可以解密並套用。如要解密異動,您需要加密串流中的偏移,這會連同商店的未加密資訊儲存。

以下是新增至 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 目前不支援內嵌資料,但如果支援,就會以加密中繼資料的相同金鑰進行加密。寫入新圖層檔案時,這些金鑰會有效率地擲出。

安全清除

以安全的方式清除檔案時,即使金鑰 (硬體中的金鑰除外) 隨後遭駭,檔案仍然無法復原。由於檔案系統通常在 Flash 裝置上執行 (涉及垃圾收集),因此清除所有出現的資料並不容易。唯一的實際解決方案是滾動包裝金鑰 (不應儲存在 Flash 中)。

「安全清除」功能並非預計推出的功能,但可採取以下程序:

  1. 開始使用新的包裝金鑰包裝新的中繼資料金鑰。

  2. Fxfs 會使用舊的中繼資料鍵重新編寫所有物件。

  3. 將舊的中繼資料包裝金鑰清除 (通常使用直接或間接使用 TPM 功能,因此超出這項設計的範圍)。

您可以執行主要壓縮,在 Fxfs 中相對輕鬆完成步驟 2。由於這是自然發生的,這項程序可以定期或隨選進行。雖然取決於可用的金鑰,但您可以安排 Fxfs 每週一次受到大幅壓縮的情況。

這個程序要求中繼資料金鑰使用其他包裝金鑰包裝至資料 (否則會強制重寫所有資料,因而違反規定),因此要將提供給 create_key 方法的中繼資料引數提供給 create_key 方法。

請注意,這個程序需要重新編寫磁碟區的「所有」中繼資料,因此不應頻繁執行某些中繼資料。

金鑰滾動

為確保清除,請參考前述中繼資料包裝金鑰金鑰的滾動方式。如要滾動用來包裝資料金鑰的金鑰,請按照下列程序操作:

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

  2. 要求 Fxfs 重新包裝與指定 wrapping_key_id 相符的所有金鑰。此 API 僅用於後續設計,不需要變更磁碟上格式。

  3. 按照安全清除程序包裝中繼資料金鑰。由於金鑰只會寫入中繼資料檔案,因此應確保在淘汰舊包裝金鑰後,資料包裝金鑰就能成功擲出。

請注意,步驟 2 和 3 可以合併,且應同時執行主要壓縮,並將金鑰重新包裝。

和安全清除功能一樣,我們不打算一開始就實作關鍵滾動式支援。

擊球

內容如果無金鑰,就會執行有限的檢查。我們顯然無法維持已加密存放區的一致性,但可以檢查所有其他未加密儲存庫的既有資料與一致性。

使用可用的金鑰後,就可以執行完整的一致性檢查,或只檢查個別磁碟區的中繼資料。

支援多磁碟區

fshost 會匯出新的目錄:磁碟區。目錄中的節點代表由 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,不應將其用作測試憑證的方式。

匯出根目錄看起來會像現在由檔案系統公開。fs.Admin 服務會公開,而且可使用關閉方法鎖定/關閉磁碟區。如果關閉磁碟區的所有連線,磁碟區也會進入鎖定狀態。鎖定磁碟區時,請務必小心,確保已捨棄所有未包裝的金鑰。

系統會透過磁碟區目錄上的 fuchsia.io 目錄通訊協定來執行列舉和移除作業。移除可能或可能不會以非同步方式執行,但在保證磁碟區最終移除前,不會傳回成功。您可以立即重複使用名稱,但空間可能需要一段時間才能使用。

新磁碟區無法使用 Directory 通訊協定的開放方法,因為我們需要取得加密服務和其他選項。而是要加入新的通訊協定:

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 都會使用 Fxf,因此會以此方式獲得曝光。

說明文件

在某個階段中,您需要說明文件可協助系統設計人員為特定產品選擇不同的儲存空間選項。不過,設定採取的方式尚未定案,因此不在這個 RFC 的範圍內。

在這個 RFC 中導入的 API 最初會在樹狀結構中 (且有可能無限期) 處於樹狀結構中,而目前會記錄為 FIDL 的一部分。

缺點、替代方案和未知

這項 RFC 所述的加密設計遠比 Zxcrypt 使用的分區型加密更複雜。

系統設計人員仍可使用分區加密,但這有點在磁碟區之間共用的空間限制:在磁碟區中彈性使用空間的分區配置需要流量管理工具 (因此會因為額外的間接方式而產生效能優勢),並可能遭受分離的問題 (在分區之間移動空間可能需要重組)。在依據分區配置的情況下,更難支援安全清除和金鑰滾動作業。

先前的圖片和參考資料

Zxcrypt 使用 AES-256 XTS 以與此 RFC 類似的方式加密區塊,儘管使用的調整方式不同。

這裡的設計與 Android 12 的加密規定大部分相容。唯一的差別在於,透過 Fxfs 日誌傳遞的中繼資料需要使用串流加密 (未明確提及為可接受)。