ＧＥＬＩ（４）暗号化ストレージ（ＧＥＯＭ）クラス

# はじめに
ＦｒｅｅＢＳＤにおけるストレージフレームワークである[ＧＥＯＭ](https://man.freebsd.org/geom/4)の、そのフレームワークを構成するクラスの一つ、暗号化クラス[ＧＥＬＩ](https://man.freebsd.org/geli/8)について解説します。
一度設定してしまうとあまり触らないこともあり、久々の操作で戸惑うことが多く、忘れないうちにまとめておこうかと一念発起しました。
最近恐ろしい操作を実施して、驚きの事実に驚愕するとともに、紙一重で回避したこともあり、その性質を理解しておくことが重要だと改めて認識しています。

# ＧＥＬＩとは
暗号ストレージ機能を提供するほぼ唯一のＧＥＯＭクラスになります。同様のクラスに[ＧＢＤＥ](https://man.freebsd.org/gbde/4)や[ＧＳＨＳＥＣ](https://man.freebsd.org/gshsec/8)があります。が、
ＧＢＤＥはＧＥＬＩによって置き換えになりました。ＧＢＤＥで構築された環境のために現在でもサポートされていますが、新規構築での使用は勧められていません。
ＧＳＨＳＥＣは複数のディスクで鍵を共有しつつ全てのディスクが揃わないとアクセス出来ないストレージ（ボリューム）を提供します。全てのディスク（ＵＳＢメモリなど）を集めないとアクセス出来ないストレージと聞いて、懐かしいアニメ[^cobra]を思い出してしまいます。

[^cobra]: [コブラ イレズミの三姉妹](https://www.kadokawa.co.jp/product/321804000825/)。今思うと太っ…

ＧＥＬＩの仕様は１０系（２０１４年リリース）を最後に変化していません。おおよそ１０年前に確立したサブシステムと言えます。以下の特徴を有し、それほど古いものとなっていません（とは言え５年後は淋しい限り）。

- [crypto(9)](https://man.freebsd.org/crypto/9)フレームワークを通して暗号化・復号化ハードウェアを利用します。
- 特に近代的なＣＰＵにはＡＥＳ暗号処理が統合されていることもあり、その恩恵は確実となります。
- 暗号化アルゴリズムにＡＥＳ暗号のＸＴＳやＣＢＣモード、カメリア暗号（ＣＢＣモード）に対応しています。が、事実上ＡＥＳ暗号（ＸＴＳモード）一択となります。
- データ認証（整合性の検証）オプションがあります。ＨＭＡＣとしてＳＨＡ１、ＲＩＰＥＭＤ１６０、ＳＨＡ２５６、ＳＨＡ３８６、ＳＨＡ５１２のアルゴリズムが選択可能です。が、その分使用できる容量が減ります。
- こちらも近代的なＣＰＵであればＳＨＡ－ＮＩ対応によりハードウェアアクセラレーションが期待できます。
- 最大２つまでの区分的なユーザーキーの構成要素を形成します。通常１つのユーザーキーを使用することになります。
- 各ユーザーキーの構成要素毎に複数のファイル、プロンプトによる入力（パスフレーズ等の分散）が可能です。全てのキーが揃って初めてアクセス可能になります。
- ブートパーティションの暗号化が可能です。ブートパーティションをマウントする前にパスフレーズの問い合わせが発生しますが、あまり嬉しい機能ではないかもしれません。
- ルートパーティションの暗号化が可能です。ルートパーティションがマウントする前にパスフレーズの入力無しでマウントさせることが可能です。
- ブートパーティション、ルートパーティション、それ以外のデータパーティションでキーの取り扱いに注意が必要になりますので、後ほど詳細を説明します。
- マスターキーのバックアップとリストアが可能です。
- スワップパーティションのような用途の、使い捨て暗号化ストレージ機能を提供します。

## ユーザーキーの２つの構成要素
ＧＥＬＩを特徴付ける２つの「区分的」なユーザーキーの構成について説明します。

ユーザーキーは「パスフレーズ」と「鍵ファイル」で構成されています。オプション指定によりおパスフレーズ「ファイル」を指定することになりますが、これは「鍵ファイル」を意味しません。ここが「区分的」と言われる所以です。

人の手で入力された「パスフレーズ」と何かデータが入ってる「鍵ファイル」（実体）が大きな違いになります。

[ＧＥＬＩコマンド](https://man.freebsd.org/geli/8)では２つのユーザーキーの指定を `-k`／`-K` と `-j`／`-J` （更に `-p`／`-P`）で区別しています。

| オプション | パスフレーズキー | 鍵ファイル | 備考 |
|:-:|:-:|:-:|:-|
| `-k` | × | ○ | 旧鍵ファイルの指定 |
| `-K` | × | ○ | 新鍵ファイルの指定 |
| `-j` | ○ | × | 旧パスフレーズ（ファイル）の指定|
| `-J` | ○ | × | 新パスフレーズ（ファイル）の指定 |
| `-p` | × | ○ | 旧パスフレーズは存在しない |
| `-P` | × | ○ | 新パスフレーズは存在しない[^ignore-passphrase] |

[^ignore-passphrase]: 無効化…が正しい振る舞い…と思うのだが後述。なお旧パスフレーズを引き継ぐ、が現在の振る舞いである。こんな重要なことを備考に書いていいのか？ダメです。

また `-j`／`-J` オプションの明示的な指定はパスフレーズ入力をファイルに置き換えた物となりますので、これらオプションの指定が無い時は暗黙的に `-j -`／`-J -` が指定されたものとしてパスフレーズの入力が求められます[^default-behavior]。
この時指定したファイルは削除しても構いません。あくまでもパスフレーズ入力をファイルから肩代わりした、という位置付けになります。

[^default-behavior]: オプションが指定されてない場合、確認のためのパスフレーズを聞いてくる点が違うといった振る舞いが変わります。

逆に `-k`／`-K` オプションで指定した鍵ファイルを削除してはいけません。毎回明示的に指定する必要がありますが、このファイルが存在することが要求されます。

以後、暗黙的なパスフレーズ入力を含めて、説明を明示的にするため、`-j`／`-J` オプションが指定された物として理解をお願いします。逆に `-k`／`-K`／`-p`／`-P` オプションが指定されていない場合の振る舞いは `-j -`／`-J -` オプションが指定されていると解釈してください。どちらのキーの話をしているのか意識することが重要です。マニュアルはその前提で書かれているため、わかりにくいのですが、ここではどちらの鍵を指定しているのかをはっきりするためにある程度明示します。

またパスフレーズの入力をオミットしたい場合は積極的に `-p`／`-P` オプションを指定する必要があります。この時 `-j`／`-J` オプションとは排他になりますが、`-j ... -P` や `-p -J ...` といった指定は可能です。

ユーザーキーの指定が無い状況は許されませんので、`-k`／`-K` オプションの指定が無い `-p`／`-P` という組み合わせはあり得ません（後述）。

さらに `-k`／`-K`、`-j`／`-J` は複数指定可能で、指定した分だけ全ての指定が無いとアクセス出来ないことになります。

さらにさらに、`geli setkey` で見られる `-n キー番号`（`0` または `1`）の指定です。`geli init` 時に設定される時のキー番号は０となります。キー番号１にキーを設定することが可能で、そのユースケースはマニュアルによると、セキュリティオフィサーとユーザーの個別にパスフレーズを入力する例があります。

以上のように極めて複雑でフルスペックで指定すると混乱するので、ほどほどのところでパターンを決めて運用するのが吉です。

# 作業工程

## 初期化
下記のコマンドを指定して初期化します。実際の運用に際していくつかのオプションが追加で必要になります。

```shell
geli init -e AES-XTS -l 256 -B none -s 4096 -K 鍵ファイル名 -P ディスク
```

- `-e AES-XTS -l 256`： ２５６ビットＡＥＳ暗号（ＸＴＳモード）を使用します。
- `-B none`： バックアップしません（別途バックアップできます）。
- `-s 4096`： セクターサイズを４０９６バイトにします。
- `-K キーファイル名`： 暗号鍵に使う鍵ファイルを指定します。
- `-P`： 暗号鍵につかうパスフレーズを指定しません。

`-K .. -P` の指定からコンポーネントユーザーキーは指定された「鍵ファイル名」のみになります。`-P` オプションが無い場合、パスフレーズの入力が求められ、`-J` オプションが指定された物（＝`-J -` 相当）として処理されます（繰り返しますが実際にはプロンプトの表示可否の違いがあります）。

指定した「ディスク」を上記指定に基づいて暗号化します。ただこの時点では使用できる状態になく、後述するアタッチすることで「ディスク.eli」としてアクセスすることができます。たとえば `/dev/da0` を指定した場合、アタッチすると `/dev/da0.eli` というデバイスファイルが作成されます。

### 追加オプション
- `-b`： ＯＳ起動時にルートパーティションをマウントする時にアタッチするために必要なフラグをオンにします。これは後から設定が可能です（後述）。またルートパーティションだけの指定となります。それ以外はユーザーランド移行後にマウントされるため、このオプションの設定可否は問われません。
- `-g`： ブートパーティションに指定します。同じく後から設定可能です（後述）。ローダーからブートパーティションにアクセスする時にアタッチするために必要なフラグとなります。ＯＳ起動時にパスフレーズの入力が求められます。

暗号化されたその他領域として使用するのであれば上記オプションの指定は不要ですが、例え指定があっても誤動作になりません。`-g` はブートパーティション（単一パーティション構成であればルートパーティションとイコール）に対しての指定となります。キーファイルの指定ができないため `-K` オプションの指定無し、`-J` オプションの指定が必須です。

まとめると以下の通りとなります。

| オプション | ブートパーティション | ルートパーティション | その他パーティション | 備考 |
|:-:|:-:|:-:|:-:|:-:|
| `-K`（キーファイルの指定） | 使用不可 | ○  | ○  | ブートパーティションに対してキーファイルを指定すると復号化できません |
| `-J`（パスフレーズの設定） | ○ | △ | △ | アタッチする度にパスフレーズの入力が必要なためあまりオススメしません |
| `-g` | ○ | ー | ー | ブートパーティションではこのオプションの指定は必須です |
| `-b` | ー | ○ | ー | ルートパーティションではこのオプションの指定は必須です |

このことから単一パーティション（ブートもルートも同一）では `-J` オプション（相当）のみでしか運用できないことを意味します。ブートパーティションとルートパーティションを分離した場合、ルートパーティションのみを暗号化することで、パスフレーズ入力無しでの暗号化パーティションの利用が可能になります。

また実用的なオプションを追加で指定することもできます。

- `-a HMAC/SHA256`： データ認証（整合性の検証）をＨＭＡＣ／ＳＨＡ２５６アルゴリズムで行います。

このアルゴリズムを指定した場合、セクターサイズが４０９６バイトの場合、元のディスクの８９％まで使用できます（１１％ほどＨＭＡＣの保存に使われます）。またＳＨＡ－ＮＩに対応した環境ではハードウェアアクセラレーションが効きます。
ＳＨＡ２５６以外にも指定は可能ですが、性能と容量、安全性とのバランスを考えると他の指定をお勧めしません。

他にもオプションはありますがあまり使われません。

## 接続（アタッチ）

`geli init` 時に指定したユーザーキーを指定します。`-K`／`-J`／`-P` の組み合わせに応じて順番に `-k`／`-j`／`-p` を指定します。本例では下記のような形で指定します。

```shell
geli attach -k 鍵ファイル名 -p ディスク
```

## キーの変更

`geli init` または直前の `geli setkey` で指定したユーザーキー（`-k`／`-j`／`-p`）を指定した後、新しいユーザーキー（`-K`／`-J`／`-P`）を指定します。

例えば鍵ファイルをパスフレーズに変更する場合は、下記のような指定になります。

```shell
geli setkey -k 旧鍵ファイル ディスク
```

ただしパスフレーズを無効にして鍵ファイルだけにしたい場合は、バッドノウハウが必要です。

```shell
geli setkey -K 新ファイル -P -i $((65536*65536-1)) ディスク
```

`-P` オプションの指定あり／なし関係なく `-i` オプションにマジックナンバーを指定することでパスフレーズを無効にできます。逆に `-P` オプション無しで `-i マジックナンバー` を指定した場合、無駄に新しいパスフレーズを入力させられた上で、パスフレーズが無効になります。

マジックナンバー `65536*65536-1` は３２ビット符号付整数であるところ `-1` （$2^{32}-1$）を意味します。`-i` で指定できる数字が０以上の符号無整数であるため、このような形でごまかす必要があります。

逆に下記のように `-i マジックナンバー` の指定が無い場合、以前設定したパスフレーズが生きたままになるという、驚きの振る舞いをします。

```shell
geli setkey -K 新鍵ファイル -P ディスク
```

この場合、パスフレーズと新鍵ファイルの指定が無いとアタッチできません。ユーザーキーを指定された状態から、色々な遷移でユーザーを指定可能ですが、上記問題のせいでとんでもない設定が可能になっています（後述）。

# 作業事例

## とりあえずやってみた
```console
# truncate -s 1G testfile
# mdconfig -f testfile
md0
# geli init -e AES-XTS -l 256 -B none -s 4096 md0
Enter new passphrase:
Reenter new passphrase:
# geli attach md0
Enter passphrase:
# newfs -U md0.eli
/dev/md0.eli: 1024.0MB (2097144 sectors) block size 32768, fragment size 4096
        using 4 cylinder groups of 256.00MB, 8192 blks, 32768 inodes.
        with soft updates
super-block backups (for fsck_ffs -b #) at:
 192, 524480, 1048768, 1573056
# mount -t ufs /dev/md0.eli /mnt
# geli dump md0
Metadata on md0:
     magic: GEOM::ELI
   version: 7
     flags: 0x200
     ealgo: AES-XTS
    keylen: 256
  provsize: 1073741824
sectorsize: 4096
      keys: 0x01
iterations: 1192614
      Salt: １６進数１２８桁の何か
Master Key: １６進数７６８桁の何か
  MD5 hash: １６進数３２桁の何か
# geli setkey md0
Enter new passphrase:
Reenter new passphrase:
Note, that the master key encrypted with old keys and/or passphrase may still exist in a metadata backup file.
# geli configure -b md0
# geli dump md0
  :
     flags: 0x202
  :
```

## パスフレーズを無効にして鍵ファイルを有効にする手順
```console
# geli init -e AES-XTS -l 256 -B none -s 4096 md0
Enter new passphrase:
Reenter new passphrase:
# geli setkey -K 鍵ファイル名 -P -i $((65536*65536-1)) md0
Enter passphrase:
Note, that the master key encrypted with old keys and/or passphrase may still exist in a metadata backup file.
# geli attach -p -k 鍵ファイル名 md0
# 
```

## 鍵ファイルを無効にしてパスフレーズを有効にする手順
先の手順とは逆に、パスフレーズを有効にして鍵ファイルを無効にするには下記の手順となります。

```console
# geli setkey -p -k 鍵ファイル名 md0
Enter new passphrase:
Reenter new passphrase:
Note, that the master key encrypted with old keys and/or passphrase may still exist in a metadata backup file.
# geli attach md0
Enter passphrase:
```

## ユーザーキーを無くしてみる
:::note alert
デタッチした状態で実行すると二度とアクセスできなくなります。
またデタッチしてしまうと二度とアクセスできくなくなります。
:::

https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=282567

レポート済みですが、現時点（２０２４年１２月時点）ユーザーキーを削除することが可能です。
詳細な手順はレポートを見てもらうとして、この手順を実行すると、二度とアクセスできなくなります[^no-longer-access]。
```
No key components given.
```

[^no-longer-access]: バックアップした後のリストアで復旧するかは未確認。

# よくある質問とその答え

## Ｑ．驚きの事実とは？
Ａ．パスフレーズを無効にしたつもりでいたのに、パスフレーズが有効だった。何を言ってるのか（略）何度再起動してもパスフレーズを要求してきて、頭がおかしくなるかと思った。最小驚きの法則なんてチャチなもんじゃねぇ。

## Ｑ．紙一重とは？
Ａ．ユーザーキーを完全無効化してしまう手順を実環境で実施してしまった。怖くなって検証してみたら…。

## Ｑ．ん？ユーザーキーで暗号化してるんでないの？
Ａ．マスターキーを保護するのにユーザーキーを使用しています。ユーザーキーで暗号化していたら、ユーザーキーを変更する度に、ディスク全体を再暗号しないといけなくなります。

## Ｑ．もっと書く内容があるだろう？
Ａ．時間が無いのでここまで。後日追記するかも…。

## Ｑ．もっと疑問点があるだろう？
Ａ．何にひっかかったのか忘れた。思う存分書いてるので思い出せない…。

## Ｑ．参考文献が足りんな。
Ａ．調査不足です。あまり良いドキュメントがないです。

## Ｑ．ブート時に鍵ファイルを指定する方法は？
Ａ．時間切れです。後日どこかでまとめたいです。

# 参考文献
- [Disk Encryption with geli](https://docs.freebsd.org/en/books/handbook/disks/#disks-encrypting-geli)
- [GEOM: Modular Disk Transformation Framework](https://docs.freebsd.org/en/books/handbook/geom/)
- [FreeBSD GEOM 再々学習](https://www.seichan.org/blog/2023/11/freebsd-geom-再々学習.html)
- [[geli] geli setkey can clear key components, perfectory](https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=282567)
- [geli setkey -P doesn't reset md_iterations](https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=196834)