🗝️

Debian APTの鍵を安全に追加する方法

2021/12/15に公開約17,100字2件のコメント

この記事はUMITRON Advent Calendar 2021 15日目の記事です。

まえがき

Debianのパッケージ管理システムであるAPTのセキュリティと正しい鍵の追加方法について調べたことをまとめます。ただ筆者はセキュリティ専門家ではないので正確ではない部分を含む可能性があることをご留意ください。

例えば、Raspberry Pi OSでdebian package repositoryを手動で追加してからapt-get updateすると以下のようなエラーが出ます。

$ echo 'deb http://deb.debian.org/debian bullseye-backports main' | sudo tee /etc/apt/sources.list.d/bullseye-backports.list
deb http://deb.debian.org/debian bullseye-backports main

$ sudo apt-get update
Get:1 http://deb.debian.org/debian bullseye-backports InRelease [43.7 kB]
Hit:2 http://archive.raspberrypi.org/debian bullseye InRelease
Hit:3 http://raspbian.raspberrypi.org/raspbian bullseye InRelease
Err:1 http://deb.debian.org/debian bullseye-backports InRelease
  The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 648ACFD622F3D138 NO_PUBKEY 0E98404D386FA1D9
Reading package lists... Done
W: GPG error: http://deb.debian.org/debian bullseye-backports InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 648ACFD622F3D138 NO_PUBKEY 0E98404D386FA1D9
E: The repository 'http://deb.debian.org/debian bullseye-backports InRelease' is not signed.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.

このエラーは追加したレポジトリの鍵を追加すると解消されます。この記事ではこの鍵の仕組みと安全な鍵の追加方法を解説していきます。

Secure aptの原理

Debian APTにはsecure aptと呼ばれるダウンロードしたファイルが信頼できるファイルかどうかを確認する仕組みが備わっています。この仕組みの原理は誤解を恐れずに言うとTLSにおいて証明書が正しいことを確認する仕組みに似ています。

TLSで証明書を検証する大雑把な原理は

  • 信頼する認証局の公開鍵が事前にコンピュータにインストールされている
  • 認証局が認証局の秘密鍵で申請があったドメインの証明書に署名する
  • 利用者がコンピュータからそのドメインにアクセスした時、ダウンロードした証明書の署名を認証局の公開鍵を用いて検証する。署名が正しいことが確認できればその証明書が本物であることが確信できる。

というものです。

一方Debian APTは

  • Debianパッケージレポジトリのメンテナーの公開鍵がOSイメージにインストールされている
  • メンテナーはDebianパッケージレポジトリのインデックス情報を格納するInReleaseファイル[1]に秘密鍵を用いて署名する
  • 利用者がapt-get updateした時にInReleaseファイルをダウンロードしインストールされている公開鍵を用いて署名を検証する。署名が正しいことが確認できればInReleaseファイルが正しいことが確信できる。

という仕組みです。

安全な鍵の追加方法

ここから、エラーの解消方法としてぐぐるとすぐに出てくる解決方法の問題点を議論し、より安全な鍵の追加方法について解説していきます。

まえがきで紹介した以下のエラーログは、新しく追加したレポジトリのInReleaseファイルの署名を検証できる公開鍵がシステムにインストールされておらず、検証ができないという意味のエラーです。

  The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 648ACFD622F3D138 NO_PUBKEY 0E98404D386FA1D9
Reading package lists... Done
W: GPG error: http://deb.debian.org/debian bullseye-backports InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 648ACFD622F3D138 NO_PUBKEY 0E98404D386FA1D9
E: The repository 'http://deb.debian.org/debian bullseye-backports InRelease' is not signed.

よって対応する公開鍵をインストールすれば解決します。

このあたりをググると以下のコマンドで解決する、という話が出てきますし、実際エラーは消えます。

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 0E98404D386FA1D9

ですが、いくつかの点でこれは良い解決方法とは言えないと思われます。

問題点の議論に移る前に、そもそも上記の apt-key adv コマンドがやっていることを解説します。

apt secureで用いる鍵はOpenPGPと言うプロトコルの鍵のフォーマットに従います。OpenPGPの鍵はIDとしての役割を持つ文字列としてfingerprintとkey IDを持ちます。鍵本体のデータなどを入力にSHA-1ハッシュ値を計算したものがfingerprint、fingerprintの下64 bitsがkey IDになります[2]apt-get update した時のエラーメッセージは、「新しいレポジトリのInReleaseファイルの署名を検証するにはkey ID 0E98404D386FA1D9 あるいは 648ACFD622F3D138 の鍵が必要だけどないよ」と言う内容です。そこで、 apt-key adv コマンドでそのkey IDを指定してキーサーバー(keyserver.ubuntu.com)からダウンロードしてaptの鍵として登録しています。

ここから問題点を見ていきます。

まず1つは apt-key コマンドはapt v2.1.8 (2020/08リリース)からdeprecatedで、2022年には削除される予定です。

apt-key は内部で gpg コマンドを用いて、キーサーバから指定の鍵をダウンロードしてaptの鍵ファイルである /etc/apt/trusted.gpg に追加しています。また apt-key add のmanを読むと /etc/apt/trusted.gpg.d/ に適切な名前をつけて保存するほうがより良い、との説明があります。

これらを apt-key を使わず実現する場合、以下のようになります。

$ gpg --keyserver keyserver.ubuntu.com --recv-key 0E98404D386FA1D9
gpg: directory '/home/pi/.gnupg' created
gpg: keybox '/home/pi/.gnupg/pubring.kbx' created
gpg: /home/pi/.gnupg/trustdb.gpg: trustdb created
gpg: key 73A4F27B8DD47936: public key "Debian Archive Automatic Signing Key (11/bullseye) <ftpmaster@debian.org>" imported
gpg: Total number processed: 1
gpg:               imported: 1

$ gpg --export 0E98404D386FA1D9 | sudo tee /etc/apt/trusted.gpg.d/bullseye-backports.gpg >/dev/null

$ gpg --delete-keys 0E98404D386FA1D9
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pub  rsa4096/73A4F27B8DD47936 2021-01-17 Debian Archive Automatic Signing Key (11/bullseye) <ftpmaster@debian.org>

Delete this key from the keyring? (y/N) y

ですが、これでもまだ良くない点が残っています。ここで —-recv-key の引数で指定している 0E98404D386FA1D9 はダウンロードした InReleaseファイルに記述されているkey IDです。

$ curl -s http://ftp.debian.org/debian/dists/bullseye-backports/InRelease | tail -28 | gpg --list-packets
gpg: directory '/home/pi/.gnupg' created
gpg: keybox '/home/pi/.gnupg/pubring.kbx' created
# off=0 ctb=89 tag=2 hlen=3 plen=563
:signature packet: algo 1, keyid 648ACFD622F3D138
	version 4, created 1639296838, md5len 0, sigclass 0x01
	digest algo 8, begin of digest 46 08
	hashed subpkt 33 len 21 (issuer fpr v4 0146DC6D4A0B2914BDED34DB648ACFD622F3D138)
	hashed subpkt 2 len 4 (sig created 2021-12-12)
	subpkt 16 len 8 (issuer key ID 648ACFD622F3D138)
	data: [4094 bits]
# off=566 ctb=89 tag=2 hlen=3 plen=563
:signature packet: algo 1, keyid 0E98404D386FA1D9
	version 4, created 1639296876, md5len 0, sigclass 0x01
	digest algo 8, begin of digest 4e 07
	hashed subpkt 33 len 21 (issuer fpr v4 A7236886F3CCCAAD148A27F80E98404D386FA1D9)
	hashed subpkt 2 len 4 (sig created 2021-12-12)
	subpkt 16 len 8 (issuer key ID 0E98404D386FA1D9)
	data: [4095 bits]

つまり、信頼できないファイルが「僕の鍵は0E98404D386FA1D9だよ」と言ってるのを盲目的に信頼してインストールしている状態になってしまっています。よって、この鍵が信頼できるものかどうかを確認する必要があります。しかし現在この鍵を確認する標準化された方法はありません[3]

今回の場合はDebian公式のbackportsですので、以下のページで鍵の確認が出来ます。このページはhttpsでドメインがdebian.orgなので信頼できます。

ここの announcement for the Debian 11/bullseye keys のリンク先を見ると鍵の詳細が見れます。

pub   rsa4096 2021-01-17 [SC] [expires: 2029-01-15]
      1F89 983E 0081 FDE0 18F3  CC96 73A4 F27B 8DD4 7936
uid           Debian Archive Automatic Signing Key (11/bullseye) <ftpmaster@debian.org>
sub   rsa4096 2021-01-17 [S] [expires: 2029-01-15]
      A723 6886 F3CC CAAD 148A  27F8 0E98 404D 386F A1D9

これの2つ目のキー(subkey)の後ろ64 bitsが0E98404D386FA1D9と一致するので、この鍵が正しいと確信できます。ここまで確認することが安全に鍵を追加する上で必要です。

ここまで確認したら安全かと言うと、まだ十分ではありません。問題はkey IDにあります。key IDは前述の通りSHA-1の値の下64 bitsですが、これは衝突する可能性があります。さらに、意図的に同じkey IDの鍵を作り出すことも不可能ではないようです。

RFCにもkey IDがuniqueである想定をするべきではないとの記述があります。

また https://keyserver.ubuntu.com/ などのキーサーバには誰でも鍵の登録が可能ですし、キーサーバの実装まで調べてませんが、おそらく同じkey IDの鍵が複数あればすべてダウンロードする形になりそうです。またソースを読む限り gpg が鍵をダウンロードした時同じkey IDのものが2つあっても2つとも登録するようです(同じfingerprintで、中身が違えば登録に失敗します)。

つまり、key IDを用いてキーサーバからダウンロードすると、悪意のある第三者が正規のレポジトリの鍵のkey IDと同じkey IDを持つ鍵を意図的に作ってキーサーバに登録したケースを防げません。

鍵のfingerprintはまだ狙って特定の鍵と同じkey IDの鍵を作ることは不可能なので、鍵の取得にkey IDではなくfingerprintを使うと解決します。

$  gpg --keyserver keyserver.ubuntu.com --recv-key A7236886F3CCCAAD148A27F80E98404D386FA1D9
gpg: directory '/home/pi/.gnupg' created
gpg: keybox '/home/pi/.gnupg/pubring.kbx' created
gpg: /home/pi/.gnupg/trustdb.gpg: trustdb created
gpg: key 73A4F27B8DD47936: public key "Debian Archive Automatic Signing Key (11/bullseye) <ftpmaster@debian.org>" imported
gpg: Total number processed: 1
gpg:               imported: 1

参考

また、今回の例ではDebian公式のbackportsであり https://ftp-master.debian.org/keys.html には鍵のkey IDやfingerprintだけでなく鍵本体のデータも置いてあるので、こちらから直接ダウンロードする方が安全だと言えます。

$ sudo wget -q --directory-prefix /etc/apt/trusted.gpg.d/ https://ftp-master.debian.org/keys/archive-key-11.asc

公式のレポジトリの鍵追加においては、この方法で十分に安全だと思われます。ですがサードパーティのレポジトリではこれでは不十分です。

そもそも apt-key がdeprecatedになったのは、 apt-key 経由で追加した鍵が登録されたすべてのレポジトリに対して使われることが問題視されたためのようです。現在 apt はどの鍵がどのレポジトリに使われるのかを指定する事ができる設定を提供しているので、これを使うとより安全に鍵が追加できます。次はこの設定していきます。

まず鍵の置き場所を /usr/share/keyrings/ に変えます [4]

$ sudo wget -q --directory-prefix /usr/share/keyrings https://ftp-master.debian.org/keys/archive-key-11.asc

そして、レポジトリの設定に signed-by オプションで先程追加したファイルを設定します。

$ echo 'deb [signed-by=/usr/share/keyrings/archive-key-11.asc] http://deb.debian.org/debian bullseye-backports main' | sudo tee /etc/apt/sources.list.d/bullseye-backports.list

これで使われる鍵とレポジトリが制限されより安全になります。

参考

以上です。

まとめ

まとめると、安全に鍵の追加をするには以下の点を注意する必要があります。

  • apt-key はdeprecatedなので使わない。
  • 鍵は可能であれば https の信頼できるソースからダウンロードする。
  • キーサーバからダウンロードする場合は
    • key IDではなくfingerprintを用いる。
    • そのfingerprintが信頼できる鍵のfingerprintであることを確認する。
  • signed-byオプションで鍵の有効範囲を制限する。

補遺

キーサーバとの通信

gpg --keyserver keyserver.ubuntu.com --recv-key A7236886F3CCCAAD148A27F80E98404D386FA1D9 のようなコマンドで鍵をキーサーバからダウンロードする場合、HKP(HTTP Keyserver Protocol)というプロトコルで通信が行われ、これのベースとなる通信方式はHTTPになります。よって、改ざん等の攻撃が可能となってしまいます。が、本編で解説したとおり fingerprint (ないし key ID)は鍵本体などを入力としたSHA-1ハッシュ値となっているため gpg コマンドの中で指定したfingerprintとダウンロードした鍵から計算したfingerprintが一致するか確認しているため、改ざんされている場合は検知することが出来ます。HKPのIETFのdraftにも鍵のチェックを行うまでダウンロードした鍵が正しいかどうか分からない旨の注意書きがされています。

また、ダウンロードしたデータ自体で検証するため、DNSポイズニング等で接続先が別サーバになっていたとしても問題になりません。ただし、盗聴は防げません。鍵のダウンロードする上でセンシティブな情報は含まれないので問題とはならないと思われますが、ここを気にする場合は HKPS(HKP over HTTPS) を使うことで解決出来ます。以下のように --keyserver の引数にプロトコルを指定するだけで実現できます。

gpg --keyserver hkps://keyserver.ubuntu.com --recv-key A7236886F3CCCAAD148A27F80E98404D386FA1D9

関連

.asc と .gpg

aptの鍵について調べると拡張子が.ascと.gpgの鍵ファイルを見かけます。これは

  • .asc がテキスト形式の鍵ファイル
  • .gpg がバイナリ形式の鍵ファイル

という違いになっています。テキスト形式の鍵ファイルフォーマットをASCII armored formatと言ったりします。gpgのオプションの --armor はoutputの形式をASCII armored formatにするオプションです。

SHA-1は脆弱か

SHA-1はcollision attack(衝突攻撃)が可能であることが2017年にGoogleの研究者らによって示され、話題になりました。

ただ、まだpreimage attack(原像攻撃)は不可能なようで、Debian公式のapt repositoryの鍵を狙って同じfingerprintの偽物の鍵を作成することは現状不可能なようです。

OpenPGPのMessage Format v5

今IETFでOpenPGPのMessage Format v5が提案されており、key IDには256-bit SHA2-256が使われています。

key IDは危険

key IDは危険という話はこれまでも様々なところで指摘されているようです。

鍵の詳細の表示

本編でもしれっと使いましたが、 gpg --list-packets コマンドで鍵の詳細を表示することが出来ます。

$ gpg --list-packets /usr/share/keyrings/archive-key-11.asc
# off=0 ctb=99 tag=6 hlen=3 plen=525
:public key packet:
	version 4, algo 1, created 1610882316, expires 0
	pkey[0]: [4096 bits]
	pkey[1]: [17 bits]
	keyid: 73A4F27B8DD47936
# off=528 ctb=89 tag=2 hlen=3 plen=590
:signature packet: algo 1, keyid 73A4F27B8DD47936
	version 4, created 1610882319, md5len 0, sigclass 0x1f
	digest algo 10, begin of digest 14 ae
	hashed subpkt 33 len 21 (issuer fpr v4 1F89983E0081FDE018F3CC9673A4F27B8DD47936)
	hashed subpkt 2 len 4 (sig created 2021-01-17)
	hashed subpkt 12 len 22 (revocation key: c=80 a=1 f=80E976F14A508A48E9CA3FE9BC372252CA1CF964)
	hashed subpkt 7 len 1 (not revocable)
	subpkt 16 len 8 (issuer key ID 73A4F27B8DD47936)
	data: [4094 bits]
# off=1121 ctb=89 tag=2 hlen=3 plen=590
:signature packet: algo 1, keyid 73A4F27B8DD47936
	version 4, created 1610882319, md5len 0, sigclass 0x1f
	digest algo 10, begin of digest 69 f8
	hashed subpkt 33 len 21 (issuer fpr v4 1F89983E0081FDE018F3CC9673A4F27B8DD47936)
	hashed subpkt 2 len 4 (sig created 2021-01-17)
	hashed subpkt 12 len 22 (revocation key: c=80 a=1 f=FBFABDB541B5DC955BD9BA6EDB16CF5BB12525C4)
	hashed subpkt 7 len 1 (not revocable)
	subpkt 16 len 8 (issuer key ID 73A4F27B8DD47936)
	data: [4096 bits]
(以下略)
脚注
  1. InReleaseファイルの詳細は https://wiki.debian.org/DebianRepository/Format などを参照 ↩︎

  2. version 4の場合 https://datatracker.ietf.org/doc/html/rfc4880.html#section-12.2 ↩︎

  3. https://wiki.debian.org/SecureApt#How_to_find_and_add_a_key ↩︎

  4. 必ずしもこの場所である必要はないですが、ここが一般的なようです ↩︎

Discussion

Keyで信頼するリポジトリの範囲を限定するやり方の紹介ありがとうございました。

GPGはWeb of Trustな仕組みなので「この鍵はどのIDからサインされているのか」を見る、という視点もあったほうが良いです。最終的に自分が信頼する相手も複数信頼しているから妥当であろう、というふうな判断をします。

In order to vote "like" to this artical, I わざわざ create this account.
Great artical, thank you!

ログインするとコメントできます