GPGの鍵でSSH接続しようとして大変苦労した話

GPGの鍵でSSH接続しようとして大変苦労した話

Intro

以前常用していた5年前のノートPC (NEC PC)が、バッテリーとSSDの交換を経て快適によみがえった。
ついでにそのPC内のWSL環境も一新しておこうと考えた。
その詳細は別の記事に記すが、その際にGPGの鍵でSSH接続しようとして大変苦労したので、その記録を残す。

Tweet

はじめからryeやgpgを前提にした構成にするだけでなく、
shellもzsh (prezto、powerlevel10k)に変更するなどしている。

TL; DR

  • gpgをインストールし、GPGの主鍵とSub鍵を作成する
  • GPGのSub鍵からSSH接続用の公開鍵を出力する
  • gpg-agentをssh-agentとして利用する
  • ssh-agentに従来の秘密鍵とGPGの認証用鍵を登録する
  • 不具合に適切に対処する

GPGとは

GPG (Gnu Privacy Guard) は、OpenPGPの実装であり、データの暗号化や署名を行うためのツールである。
細かい説明は省くが、公開鍵暗号方式によって、署名(S)・証明(C)・認証(A)・暗号化(E)の3つの機能を提供する。

つまり、安全にデータの暗号化、本人証明、改竄検知、SSH接続などができる。

GPGについては、ここの記事が大変詳しく、かつ網羅的だ。素晴らしい。

GPG で始める暗号・署名ライフ

SSH with GPG

上記の記事にも紹介されているが、GPG鍵をSSH接続に利用することができる。
その際の手順と詰まった点を記す。

GPGのinstall, GPG鍵の作成

1
2
3
4
sudo apt install install gnupg
gpg --full-gen-key --expert
# 以下、interacriveに入力
# 記事「主鍵の生成」節を参照

(上記の記事を参考に) GPGのMaster鍵を作成する。
以前に作成したものがあるならそれを利用する。

GPG Sub鍵の作成

安全のため、GPGのMaster鍵をそのまま用いることは推奨されない。
(上記の記事を参考に) GPGのSub鍵を作成する。
その際、暗号化・署名・認証のそれぞれ3つの機能を持つSub鍵を作成できる。

1
2
gpg --edit-key --expert [email protected]
# 記事「副鍵の生成」節を参照

ここまで来ると、ファイルの暗号化や署名もできるようになる。
また、各サービスとの連携や、サービスへの公開鍵の登録も行おう。
(記事「ファイルの暗号化」「ファイルへの署名」「公開鍵の公開」節)

特に、Gitへの署名は重要だ。
(記事「Gitコミットへの署名」節)

公開鍵の出力

参考記事: OpenSSH の認証鍵を GunPG で作成・管理する

こちらの記事でもSub鍵の生成の説明が行われている。

その続きでは、GPGの鍵から公開鍵を生成する。
(公開鍵はGPGのMaster鍵でもSub鍵でも共通である)

1
gpg --export-ssh-key NAME > ~/.ssh/gpg_ssh_key.pub

NAMEは鍵の識別子であり、以下のいずれかを用いる。

  • 鍵のID
  • 鍵に紐づいたメールアドレス
  • 鍵に紐づいた名前

これはgpg --list-keysで確認できる。

1
2
3
4
5
6
7
8
❯ gpg --list-keys
/home/USERNAME/.gnupg/pubring.kbx
-----------------------------
pub rsa2048/ABCD1234EF567890 2021-01-01 [SC]
ABCD1234EF5678901234567890ABCDEF12345678
uid [ultimate] Your Name <[email protected]>
sub rsa2048/12345678ABCDEF12 2021-01-01 [E]
sub rsa2048/87654321FEDCBA98 2021-01-01 [A]

上記の場合は、ABCD1234EF5678901234567890ABCDEF12345678Your Name[email protected]がNAMEとして使える。

ここで出力した公開鍵は、いつものSSHの公開鍵のように、サーバーに登録する。
すなわちscpなどでサーバーに送り、~/.ssh/authorized_keysに追記する。

1
cat ~/.ssh/gpg_ssh_key.pub >> ~/.ssh/authorized_keys

gpg-agentをssh-agentに利用する

恥ずかしながら、ここに来るまで筆者はssh-agentすら用いていなかった (~/.ssh/configまでの管理で済ませていた)。
なので、ssh-agentとgpg-agentの両方の勉強をすることになった。

まずはgpg-agentがssh-agentとして機能するように設定する。

~/.gnupg/gpg-agent.conf
1
enable-ssh-support
~/.bashrc
1
2
export GPG_TTY=$(tty)
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)

これで、gpg-agentがssh-agentとして機能するようになる。

agentへの秘密鍵の登録

ssh-agent (実態はgpg-agent) に従来の秘密鍵とGPGの認証用鍵を登録する。

1
2
3
4
5
6
7
# ssh-addで従来の秘密鍵を登録
ssh-add ~/.ssh/id_ed25519

# ssh-add -l, ssh-add -Lで登録された鍵やその公開鍵を確認できる。
ssh-add -L

ssh-ed25519 ABCDEDFJFIAJOFPW598da1787818f7e81f7815185 name@PC

さらにGPGの認証用鍵も登録する。keygrip付きでGPG鍵の情報を表示する。

1
2
3
4
5
6
7
8
9
gpg --list-keys --with-keygrip NAME

pub rsa2048/ABCD1234EF567890 2021-01-01 [SC]
ABCD1234EF5678901234567890ABCDEF12345678
uid [ultimate] Your Name <[email protected]>
sub rsa2048/12345678ABCDEF12 2021-01-01 [E]
Keygrip = ABCD1234EF567890123456789A7CB7F9E05C8192
sub rsa2048/87654321FEDCBA98 2021-01-01 [A]
Keygrip = F5C774ABCD1234EF567890123456789FAFAFAC8E

そのうち認証(A)のkeygripをコピーして、~/.gnupg/sshcontrolに追記する。

1
2
3
4
5
# sub   rsa2048/87654321FEDCBA98 2021-01-01 [A]
# Keygrip = F5C774ABCD1234EF567890123456789FAFAFAC8E
# 上記のKeygripをコピーする

echo F5C774ABCD1234EF567890123456789FAFAFAC8E 0 >> ~/.gnupg/sshcontrol

その後、改めてssh-add -Lとすると、GPG鍵も登録されていることが確認できる。

1
2
3
4
ssh-add -L

ssh-ed25519 ABCDEDFJFIAJOFPW598da1787818f7e81f7815185 name@PC
ssh-ed25519 89959ABCDEDFJFIAJOFPW598da1787818f7e81f78 (none)

詰まった点

公開鍵の出力

実は公開鍵の出力にはいくつか方法があり、そのいずれでも構わない。
ただ、不具合が出た際に選択肢が多い分、無駄に公開鍵の出力部分を疑うことになってしまった。

(重要) GPG鍵の認証エラー

1
2
3
4
5
6
7
8
9
10
# SSH接続しようとした際に生じるエラー
# パスワード入力が可能な場合と、不可能な場合がある
ssh HOSTNAME

kex_exchange_identification: read: Connection reset by peer
Connection reset by xxx.xxx.x.xxx port xxxx

# または
sign_and_send_pubkey: signing failed for ED25519 "/home/USERNAME/.ssh/id_ed25519" from agent: agent refused operation
([email protected]) Password:

おそらく、GPG鍵のフレーズ入力に一度裏で失敗し、その状態が残り続けたものと思われる。
gpg-agentをssh-agentとして用いている限り、GPG認証鍵を用いない場合でも、公開鍵認証ができなくなる。
これには大変苦しめられた。

1
2
3
4
5
6
7
8
9
10
# new 解決策
# phrase入力のためのGUIをインストールする
# pinetryにはほかにも種類があるので、適切なものを選択する
sudo apt install pinentry-gtk2

# old 解決策: 解決しない場合があることが判明
echo UPDATESTARTUPTTY | gpg-connect-agent

# gpg-agentを用いないことでも、一時しのぎはできる
eval "$(ssh-agent -s)"

上記を入力するとフレーズを入力する画面になるなどして、解決する。
(引用元: StackExchange)

ちなみに、gpg-agentの再起動では直らなかった。 罪深い……。

1
2
gpgconf --kill gpg-agent
gpgconf --launch gpg-agent

GPG認証鍵の登録

GPG鍵でSSH接続しようという記事は多いのだが、なぜかGPG認証鍵をgpg-agentに登録するくだりが触れられていない記事が多かった (あるいは、僕が見逃してしまいがちだった)。
gpg-agentをssh-agentとして利用している時点で、GPG側の鍵を自動的に登録してくれると思っていたが、そうではなかったようだ。
手順がわかればなんてことはなかったのだが……。

Tweet

ssh接続におけるssh-agentやgpg-agentの挙動を調べるうえでは、Dockerが大変役に立った。
軽くて簡単で再現性のある仮想環境最高すぎる。

Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FROM ubuntu:latest

RUN apt-get update && apt-get install -y openssh-server \
&& mkdir -p /var/run/sshd \
&& echo 'root:YOUR_PASSWORD' | chpasswd \
&& sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/g' /etc/ssh/sshd_config \
&& sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/g' /etc/ssh/sshd_config \
&& sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

COPY ./data /data

RUN mkdir -p /root/.ssh \
&& chmod 700 /root/.ssh \
&& cat /data/*.pub > /root/.ssh/authorized_keys \
&& chmod 644 /root/.ssh/authorized_keys

EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
docker-compose.yml
1
2
3
4
5
services:
app:
build: .
ports:
- 2222:22
1
2
3
4
# 前回のcontainerの削除、build、runを行う
docker compose down --remove-orphans && \
docker compose build app && \
docker compose run --rm --service-ports -d app

コンテナ内外でのport接続を行う場合は、docker-compose.ymlportsの設定に加えて--service-portsオプションが必要。
さらに、終了する場合は--remove-orphansオプションをつける必要がある。

まとめ

GPG鍵を用いたSSH接続は、セキュリティを高めるためにも有用だ。
しかし、その設定にはgpg-agentやssh-agentの設定が必要で、また、その設定には躓きやすい点が多い。
この記事が読者の一助になれば幸いだ。

コメント