Ubuntu Weekly Recipe

第848回TangとTPMを用いて⁠Ubuntu上の暗号化ストレージの自動復号をより強固にする

第846回のTPMを用いて、Ubuntu上の暗号化ストレージの復号を自動的に行うでは、暗号化されたUbuntuのストレージを、TPMを用いて自動的に復号する方法を紹介しました。これはストレージだけ盗難されるケースに対しては有効ですが、PCごと盗難される場合には無力とまでは言わないものの、少し弱い状況になります。そこで今回はTangサービスを利用して、特定のネットワークに接続した場合にのみ自動復号をできるようにしましょう。

先にお伝えしておくと、今回の手順は有線接続でのみ利用可能です。ノートPCのようにWi-Fiが必須の場合は、それ相応の設定が必要になる点、注意してください。

Tangとは

最初にTangについて説明しておきましょう。Tangとは任意のデータを特定のネットワークに紐付けるための仕組みです。Clevisと同じ開発者がメンテナンスしています。

Tangサーバーはネットワークに対して特定の公開鍵リストをJWK(JSON Web Key)として提供しています。まず最初にクライアント(今回だと暗号化ストレージを持っているPC)はこの公開鍵と自分で作成した乱数を用いて、暗号化用の鍵を作成します。ただしこの鍵は暗号化後に削除されます。復号時は再びTangサーバーから公開鍵を取得して、復号用の鍵を生成し、復号するのです。

この鍵の作成・交換時には「McCallum-Relyea交換」と呼ばれる手法を使用することで、通信経路上に直接秘密データが流れることはありません。よってHTTP等の暗号化されていない経路を利用可能です。

これによりPCは起動時にTangサーバーへHTTP接続できる場合のみ、自動的に復号できる仕組みを作れます。たとえば家庭内LANで起動した場合は自動復号し、外で起動した場合はパスフレーズの入力を必要とするようにしてみましょう。そうすると、たとえPCごと持ち出されたとしても自動復号はされないことになります。これをNetwork Bound Disk Encryption(NBDE)と呼びます。

前回TPMによる自動復号時に利用したClevisは、Tangにも対応しています。改めてまとめると、Tangで自動復号するためには次の仕組みが必要になります。

  • Tangサービスを提供するサーバーマシン
  • Tangサービスに接続できるクライアントPC

Ubuntuの場合は、前者はtangパッケージとjoseパッケージがあれば実現できます。後者についてはclevisパッケージだけで十分です。ただしノートPCのように、Wi-Fi接続しかない(有線LANポートがない)場合、今回紹介する設定だけでは利用できません。initramfsの中でWi-Fiに自動的に接続する仕組みが必要になります。

Tangサーバーのインストール

最初にTangサービスを提供するサーバーマシンを用意しましょう。Ubuntuであれば次の方法でインストールできます。

$ sudo apt install tang jose

Ubuntuパッケージの場合は次のような挙動になります。

  • systemd用のtangd.socketが設定されポート80で待ち受ける
  • ポート80にアクセスされた場合は、/usr/libexec/tangdが実行されるがデーモンとしては残らない
  • Tangサービス用の鍵は/var/lib/tang/以下に保存される

まず重要なのは、初期設定だと「ポート80」を使うということです。おそらくこれはほとんどのケースで困ることになるでしょう。そこでまずTangサービスのポート番号を変更します。

$ sudo systemctl edit tangd.socket

テキストエディタが起動するので、次のように設定してください。

[Socket]
ListenStream=
ListenStream=8080

ListenStreamは複数指定が可能なので、一旦ListenStream=で既存の設定を削除した上で、8080ポートを指定しています。特定のネットワークにのみ公開したい場合はListenStream=192.0.2.1:8080のように指定します。

設定を反映するために、tangd.socketを再起動しましょう。ポート8080で待ち受けていたらOKです。

$ sudo systemctl restart tangd.socket
$ sudo systemctl status tangd.socket
● tangd.socket - Tang Server socket
     Loaded: loaded (/usr/lib/systemd/system/tangd.socket; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/tangd.socket.d
             └─override.conf
     Active: active (listening) since Sun 2025-01-26 09:41:54 UTC; 1h 35min ago
       Docs: man:tang(8)
     Listen: [::]:8080 (Stream)
(略)

生成されている公開鍵のリストは次のように表示できます。

$ tang-show-keys 8080
CcfRddX0pydLT246ZBKxygoHigjt8JAdJTDOTljCasI

引数にはポート番号を指定してください。この鍵は/var/lib/tang以下に保存されています。

$ sudo ls -l /var/lib/tang/
total 8
-r--r----- 1 _tang _tang 367 Jan 26 06:45 CcfRddX0pydLT246ZBKxygoHigjt8JAdJTDOTljCasI.jwk
-r--r----- 1 _tang _tang 361 Jan 26 06:45 Ve4fziIyZt9K1agrpo0BSUaLP1bTnAz34Fanm1Yz8OA.jwk
$ sudo cat /var/lib/tang/CcfRddX0pydLT246ZBKxygoHigjt8JAdJTDOTljCasI.jwk | jq .
{
  "alg": "ES512",
  "kty": "EC",
  "crv": "P-521",
  "x": "AJ1wPpBLmbcn7R8bnKDUpZOQ14wf2FRTZTs9h3_Cp4z5SI_JNdcFunXrwNNnD4tLH8NR5Niyme_a_yKeLQm7xT8L",
  "y": "AETDo2yCandUXGVBnVxyc7LYTdB8HA3wWsHlYhLCY7SDsFpmK3eXNkMpXTiRgHLKq1_Vt80jld7W9bdfXBizW9_P",
  "d": "APoWbkoqsJBC1T1sXeHbBRAzo2n8PEMoFUGHXQHQU7XopC7FbY3zB192WVrs4Iad6SHGBLoJ9J1-HtEY2sHTlnmC",
  "key_ops": [
    "sign",
    "verify"
  ]
}
$ sudo cat /var/lib/tang/Ve4fziIyZt9K1agrpo0BSUaLP1bTnAz34Fanm1Yz8OA.jwk | jq .
{
  "alg": "ECMR",
  "kty": "EC",
  "crv": "P-521",
  "x": "AKieYPXy3oDHRmptaXFKzHfaInNNVfoU-AykCi6DvS0UEZdhRauT2ATxMz5mSonPAR4SiNdZPxlGoy5GjWOmyySR",
  "y": "AKVbwQrw-YLcwSkxYVcU24NncIpLpXDQTK_PDdasNHeg2pTeg6A7V1i9b4nYd7V5WShPQsbh8jSljWdqwpRPbnbH",
  "d": "AGW5T53B5v8BPentPLX4ktC9Wqx_sY-BJxfZd5WN80ObU6Ef1-m2CH9-BEJY7pq_ytPt8M8sQdRihYt5WKB2PQEu",
  "key_ops": [
    "deriveKey"
  ]
}

このうちkey_opsderiveKeyと書かれている鍵は、サーバーが持つ秘密鍵であり、これは公開されていないことがわかります。

ちなみにtang-show-keysはローカルからアクセスする方法であり、ネットワークの先からテストするには次のようにアクセスします。

$ curl -s http://TangサーバーのIPアドレス:8080/adv | jq .
{
  "payload": "eyJrZXlzIjogW3siYWxnIjogIkVTNTEyIiwgImt0eSI6ICJFQyIsICJjcnYiOiAiUC01MjEiLCAieCI6ICJBSjF3UHBCTG1iY243UjhibktEVXBaT1ExNHdmMkZSVFpUczloM19DcDR6NVNJX0pOZGNGdW5YcndOTm5ENHRMSDhOUjVOaXltZV9hX3lLZUxRbTd4VDhMIiwgInkiOiAiQUVURG8yeUNhbmRVWEdWQm5WeHljN0xZVGRCOEhBM3dXc0hsWWhMQ1k3U0RzRnBtSzNlWE5rTXBYVGlSZ0hMS3ExX1Z0ODBqbGQ3VzliZGZYQml6VzlfUCIsICJrZXlfb3BzIjogWyJ2ZXJpZnkiXX0sIHsiYWxnIjogIkVDTVIiLCAia3R5IjogIkVDIiwgImNydiI6ICJQLTUyMSIsICJ4IjogIkFLaWVZUFh5M29ESFJtcHRhWEZLekhmYUluTk5WZm9VLUF5a0NpNkR2UzBVRVpkaFJhdVQyQVR4TXo1bVNvblBBUjRTaU5kWlB4bEdveTVHaldPbXl5U1IiLCAieSI6ICJBS1Zid1Fydy1ZTGN3U2t4WVZjVTI0Tm5jSXBMcFhEUVRLX1BEZGFzTkhlZzJwVGVnNkE3VjFpOWI0bllkN1Y1V1NoUFFzYmg4alNsaldkcXdwUlBibmJIIiwgImtleV9vcHMiOiBbImRlcml2ZUtleSJdfV19",
  "protected": "eyJhbGciOiJFUzUxMiIsImN0eSI6Imp3ay1zZXQranNvbiJ9",
  "signature": "AAZBNJhOT6yeymIhtmhc1vvKslNWnecsRE93vUWeb1wcpAVrnMjZUtzMD-EWdUImwzViOhmfzyuAldESCqKrHXduAaEphlUsSXg10HIupRaW1Ylq_JkhTWt2-jBmOLj1YqwzJ3duzQmGLu707zn1gN9NVQKd9A47i593pDx3clwNRGB2"
}

実際はこれにjoseコマンドを使って読みやすくしたのが、先ほどのtang-show-keysの出力結果となります。

これでTangサーバーの準備が整いました。

Tangサービスのみを利用して自動復号する

さっそくTangサービスのみを利用して、暗号化ストレージを起動時に自動復号してみましょう。

今回はテストのために既存のTPMによる復号をあらかじめ削除しておきます。

$ sudo clevis luks list -d /dev/nvme0n1p3
1: tpm2 '{"hash":"sha256","key":"ecc","pcr_bank":"sha256","pcr_ids":"0,1,7,9"}'
$ sudo clevis luks unbind -d /dev/nvme0n1p3 -s 1

自動復号の設定は次のように実行するだけです。

$ sudo clevis luks bind -d /dev/nvme0n1p3 tang '{"url": "http://TangサーバーのIPアドレス:8080/"}'
Enter existing LUKS password: 復号時のパスフレーズを入力
The advertisement contains the following signing keys:

CcfRddX0pydLT246ZBKxygoHigjt8JAdJTDOTljCasI

Do you wish to trust these keys? [ynYN] y

$ sudo clevis luks list -d /dev/nvme0n1p3
1: tang '{"url":"http://TangサーバーのIPアドレス:8080/"}'

「TangサーバーのIPアドレス」は名前解決ができるようならホスト名でもかまいません。ただしAvahi/mDNSによる名前は、initramfsの中でavahi関連が動いていないために名前解決ができない点に注意してください。Avahi/mDNSに対応する(foo.localなどを使う)ためには、initramfsの中でそれらを実現するためのツールを取り込む必要があります。

設定としてはこれだけで完了です。あとは再起動して自動復号されることを確認してみましょう[1]

TPMとTangを併用できるSSSピン

上記のようにTangサービスのみを使用する場合、⁠そのネットワークに接続していればいつでも復号できる」状態になります。これは利便性があがるものの、セキュリティの観点からだとTPMよりも悪化しています。なぜならそのネットワークに接続さえできれば、たとえば自動復号した上で、シングルユーザーモードで起動できるためです。

これを回避する一番手っ取り早い方法は、BIOSとGRUBにパスワードを設定することです。GRUBメニューに入れなければ、そうかんたんにシングルユーザーモードにはなれません。もちろんストレージを別PCに入れて起動すれば回避できてしまいます[2]

よってより強固な手段として、TPMとTangを併用することを考えてみましょう。つまり「指定したTPMチップを利用したマシンで、なおかつ指定したネットワークに接続されている」場合にのみストレージを自動復号する設定です。ClevisにはSSS(Shamir Secret Sharing)と呼ばれるピンが用意されており、複数のピンを組み合わせて使うことが可能です。

$ sudo clevis luks bind -d /dev/nvme0n1p3 sss \
  '{"t": 2, "pins": {"tang": {"url":"http://192.168.0.5:8080/"}, "tpm2": {"pcr_bank":"sha256","pcr_ids":"0,1,7,8,9"}}}'
Enter existing LUKS password:
The advertisement contains the following signing keys:

CcfRddX0pydLT246ZBKxygoHigjt8JAdJTDOTljCasI

Do you wish to trust these keys? [ynYN] y

これまで指定したピンを「pins」フィールドに列挙します。上記ではtangピンとtpm2ピンを指定するのです。"t": 2は復号するためのしきい値です。⁠n個」のピンのうち、⁠t個」以上のピンで復号できれば、成功と判定します。今回の例だと2個のピンに対してしきい値も2個なので、tangピンとtpm2ピンの両方が復号できないといけません。これにより特定のネットワーク上の特定のマシンでのみ自動的に復号できるわけです。

tを1にすることで、⁠どちらか一方を復号ができればOK」という設定も可能です。tangピンとtpm2ピンの片方が通ればOKとなります。他にもtangピンの中身を次のように、複数のURLを設定し、どちらか片方にアクセスできればOKなんて使い方もできます。

"tang": [{"url":"http://URL1/"}, {"url:"http://URL2/"}]

鍵の追加やローテーション

何らかの理由で新しい鍵を追加したり、鍵をローテーションしたい場合は、それ用のスクリプトが用意されています。まず鍵を追加するにはtangd-keygenスクリプトを使用します。

$ sudo /usr/libexec/tangd-keygen /var/lib/tang/
$ tang-show-keys 8080
CcfRddX0pydLT246ZBKxygoHigjt8JAdJTDOTljCasI
krt9tPrabRSc9HwOp59DwmNzkqeyWCp8RN5Ifi1Ncl4

既存の鍵をすべて削除し、新しい鍵を生成するにはtangd-rotate-keysコマンドが使えます。

$ sudo /usr/libexec/tangd-rotate-keys -d /var/lib/tang/
$ tang-show-keys 8080
-VjZ9HpkLtA_a3651KGbhqjhwaP0lCYfABFAOE8PLpY

新しい鍵を生成した場合は、その鍵に対してclevis luks bindを実行し直す必要があるので忘れないようにしてください。

暗号化を含むセキュリティに関しては、安全側に倒せば倒すほど手間がかかてしまうのが常です。各利用者やデータに合わせて「良い塩梅」となる手段をそれぞれ見つけるようにしましょう。

おすすめ記事

記事・ニュース一覧