PGP副鍵をYubikeyに入れた

PGP副鍵をYubikeyに入れてみた。いろいろな人が方法を解説しているが例に漏れず俺も公式ドキュメントの劣化翻訳の再配布をしようと思う。やるときはその時の最新のドキュメントを全部読んでからにしよう。

今まで完全にソフトウェアでPGP鍵を管理していたのだが、秘密鍵をOSに露出せずに署名などの操作ができるデバイスはやはり魅力的だ。ちょうどCloudflareのセールで2つ買ったYubikeyがあったので欲求に負けて副鍵を入れることにした。Yubikeyはファームウェアがアップデートできずそのソースは開示されておらず乱数生成などに脆弱性があれば署名を偽造できる可能性があるがまあYubicoがリコールを恐れて脆弱性を隠すことなんてしないと信じることにする。

Yubikeyには主鍵を入れろと言う話もあるが、この2024年にWeb of Trustに基づいた信頼の輪はあまり機能していない。誰でも鍵とそれに対する署名をアップロードできる従来型のkeyserverではGDPR対応や大量の署名を付すスパムなどが問題になりsks-keyserverpgp.nic.ad.jpなどもサービスを終了した。keys.openpgp.orgやkeys.mailvelope.comのように鍵の同期やユーザーによる署名の付加をせずにある程度本人確認をした鍵のみをアップロードするサーバーが現れてシェアを獲得している。GnuPGThunderbirdはデフォルトでWeb key Directoryを用いて鍵を探す。何が言いたいかというと、主鍵を使うことは副鍵の更新と失効以外にほぼないということだ。どうせ使わないなら使えない場所で眠っていてもらったほうがいい。

準備

まずOpenPGP鍵を用意する。YubikeyはRSA鍵と、楕円曲線暗号の曲線としてNIST P曲線、brainpool曲線、cv25519をサポートしている。どの鍵もcv25519を使って作ることを推奨する。楕円曲線暗号はRSAよりも効率性が高い。その中でもツイストエドワード曲線を用いて最適化されたed25519、x25519の処理が最も効率が高い。さらにed25519は鍵の作成時にしか乱数を使用しないため後からYubikeyの乱数生成器に脆弱性が見つかっても秘密鍵を復元したりすることはできない。NIST P曲線には説明不能なシード値をハッシュすることで得られた係数が含まれている。現実的な攻撃として適用可能なものは報告されていないといえ、過去に暗号規格にバックドアを含んでいたことがある組織の標準化した規格に含まれる説明不能な文字列に対して陰謀論を完全に退ける方法はない。

I no longer trust the constants. I believe the NSA has manipulated them through their relationships with industry.

操作はGnuPGとykmanを用いて行う。GnuPGは低レベルな方法でYubikeyを操作するのに対してykmanはpcscdを叩くので両者が競合する。解決する方法あるが、まあ大概の問題はgpg-connect-agent reloadagent /byesudo systemctl restart pcscd (systemdの場合) をしてyubikeyを抜き差しすれば解決するので問題ではないかもしれない。

gpg --edit-cardで鍵の編集ができる。

$ gpg --edit-card

使えるコマンドを増やす
gpg/card> admin

コマンドのリストを見る
gpg/card> help

ykman openpgp *でopenpgp cardの設定変更ができる。

ヘルプを表示
$ ykman -h

openpgpコマンド以下のヘルプを表示
$ ykman openpgp -h

KDFを有効化する

ファームウェア5.2.3以降のYubikeyにはkdf機能があるのでそれを有効化する。PINをyubikeyに送信する前にハッシュ化するらしい。意味があるとしたらgpg-agentがキャッシュしているPINを取得されても少しは時間稼ぎができることだろうか?まあ現実的に意味があるかは別として、互換性以外に物事が悪化することはなさそうなので気分的に有効にしたほうがいい。鍵を書き込むとOpenPGP Cardをリセットするまで変更できなくなる。

$ gpg --edit-card
gpg/card> admin

kdfを有効化。管理者PINを要求されるので初期の12345678を入力。
gpg/card> kdf-setup

パスワードを変更
gpg/card> passwd

yubikeyのopenpgp cardには3種類の鍵がある。PINは最大127文字でアルファベットも使用できる。User PINは6桁ぐらいのアルファベット小文字のみなどにすると数字だけよりエントロピーを増せてキーボードで打ちやすいのでおすすめ。

PIN (User PIN)
設定に応じて署名、暗号化、認証時に要求される。初期値は123456
Admin PIN
OpenPGP cardの管理やUser PINのリセットに使われる。忘れた場合はOpenPGP Cardのリセットが必要。初期値は12345678
Reset Code
User PINのリセットに使われる。初期値は未設定。

公開鍵の取得用URLを設定

公開鍵のファイルを置いた場所を指定しておけば別の端末を使う際、gpg --edit-cardの後fetchをすると公開鍵を鍵束にインポートできて便利。

gpg/card> url

PINの入力をキャッシュしないようにする

yubikeyはデフォルトでPIN入力をキャッシュするため、一度PINを入力すると抜き差ししたり再起動するまではPIN入力の必要がなくなる。ただこの仕事は基本的にgpg-agentの領分であり、gpg-agentのcache ttlの設定を尊重するためにもYubikey側ではキャッシュせず毎回要求するようにする。

gpg/card> forcesig

PIN入力の最大試行回数を増やす

デフォルトでは3回の試行でロックされるが、少し厳しいので変更する。gpgは一旦quitで抜けてykmanを使用する。数字は左からPIN, Reset Code, Admin PINの順。

$ ykman openpgp access set-retries 10 99 99

鍵を用いた操作にタッチを要求する

Yubikeyにはタッチセンサがついていて、署名や認証などの際にYubikeyへのタッチを要求することができる。OSの制御を奪われていても物理的にYubikeyにタッチして承認した人間がいることの証明になる。鍵の種類はsig, dec, aut, attでそれぞれ署名、復号、認証、鍵への署名 (おそらく鍵の[C]機能) に対応している。オプションはOn, off, Fixed, Cached, Cached-Fixedから選択でき、Fixedのついたオプションを選ぶとOpenPGP Cardをリセットしない限り変更できなくなる。Cachedのついたオプションはタッチのあと15秒間要求されない。dec鍵をOn (都度要求) にするとThunderbirdなどで暗号化されたメールを読むときに1通ずつタッチする必要が生じるのでCachedがおすすめ。

$ ykman openpgp keys set-touch sig On
$ ykman openpgp keys set-touch dec Cached
$ ykman openpgp keys set-touch aut On
$ ykman openpgp keys set-touch att On

鍵を移動する

鍵をYubikeyに移動すると自動的に秘密鍵が削除されるのでバックアップをしておく。副鍵のみを移動するためには、下準備としてgpg --export-secret-keys -a {ID}で主鍵を含む鍵のバックアップをして、gpg --export-secret-subkeys -a {ID}で副鍵のみのバックアップも出力したあと~/.gnupg/private-keys-v1.d~/.gnupg/openpgp-revocs.d以下にある秘密鍵と失効証明書を削除して副鍵のみのバックアップをインポートしておく必要がある。主鍵を暗号化してバックアップする場合はgpgのパスフレーズに頼らず別の暗号化ソフトウェアでmetadataごと暗号化するほうがいい。暗号化したところでパスワードを尋問されれば終わりだが、もっとも紙やUSBメモリにバックアップしたところでそれは同じである。この種の攻撃から身を守るためには情報と自分を24時間武装した人間に警備させる必要があるが、大した秘密を持っているわけではないので俺には暗号化で十分だと思う。

$ gpg --edit-key {ID}
鍵束で一番上に表示されている副鍵を選択
gpg> key1
鍵を移動する。移動中にどのスロットを選択するか聞かれるので署名鍵なら署名用を選ぶ。
gpg> keytocard
key1の選択を解除する
gpg> key1
同様に繰り返す
gpg> key2

この後にsaveをすると自動的に鍵が削除される。quitして保存せずに終了した場合は鍵がPC上に残る。複数のYubikeyに複製する場合は一旦秘密鍵を削除して副鍵のみのバックアップからインポートする必要がある。

sshで使う

yubikeyをsshに使う方法は現在3種類あり、これらには長短がある。公式でもsshに使う方法を比較している

PIVカードの認証用スロットを使う
連邦職員でもない限りメリットはないので無視
OpenPGP Cardを使う
クライアント側でssh-agentにgpg-agentを使うよう設定する必要があるが鍵管理は楽。
OpenSSHのFIDO2サポートを使う
セットアップが簡単だがサーバー側にOpenSSH 8.2以上が必要で、既存の公開鍵認証に他要素認証要素を足すものなので別のPCに展開する際は秘密鍵を置く必要がある。さらに複数のYubikey、環境へ複製することはできない。

少なくともYubikeyの中では全く別の機能として保管されるので、3つとも競合はしないはずだから複数用意してもいい。ここでは2番目の方法を行う。まず、OpenPGP Cardをsshで使えるようにするためにgpg-agentをssh-agentとして使用するための環境変数を設定する。

~/.config/environment.d/gpg_agent.conf

SSH_AGENT_PID=""
SSH_AUTH_SOCK="${XDG_RUNTIME_DIR}/gnupg/S.gpg-agent.ssh"

おそらくこれだけでsshに使えるようになる。シェルを起動したときにおしゃれに変数を設定する方法とかもあったりするがグローバルに設定してしまえばいい。古い解説だとenable-ssh-supportなどの設定が必要だと書かれていたりするが、GnuPG 2.4.5では環境変数以外は特段設定なくとも使用可能だ。

ssh認証に使いたいpgp鍵がkeycard上にない場合はその副鍵のkeygripを$GNUPGHOME/sshcontrolに追加する必要があるが、鍵がcard上にある場合は黙示的にssh鍵として扱われるのでsshcontrolファイルを編集する必要もない。念の為追加しても良い

Thunderbirdで使う

ThunderbirdにOpenPGPサポートが組み込まれたためにEgnimailはサポートされなくなったが、Thunderbirdの使用するRNPライブラリはハードウェアキーをサポートしないため、デフォルトではThunderbirdはYubikey上のPGP鍵を使用できない。秘密鍵はGnuPGの鍵束を参照するように手動で構成する必要がある。

設定/一般の下部にある設定エディタを開いてmail.openpgp.allow_external_gnupgをtrueにする。その後アカウント設定/エンドツーエンド暗号化からキーの追加をクリック、外部のGnuPG鍵を使用するを選択してGPGの鍵IDを入力する。これで秘密鍵を用いた操作はGnuPGの鍵束が参照されるようになる。

外部GnuPG鍵を参照するよう構成しても、Thunderbirdは依然公開鍵についてThunderbird組み込みの鍵束を参照するので、自分の公開鍵をOpenPGP鍵マネージャーから追加しておく必要がある。

参考文献