gpg-agent で ssh-agent

gpg-agent は、GnuPG (GPG) においてパスフレーズによる認証状態を保持するために使用されるデーモンプログラムです。GPG における鍵管理が主な役割とはなっているものの、ssh-agent の機能の代替を行うこともできます。今回は gpg-agent を ssh-agent として使用することで、ssh-agent を使うよりもちょっと便利でちょっとセキュアに使う方法について紹介します。

ssh-agent の基本

さて、ssh-agent ですが、かつて学生時代にアルバイトをしていた当時「おもむろに eval ssh-agent と叩いて、ssh-add すればいい」と教わったものです。そうするとだいたいのシステムでは /tmp 以下に ssh-agent デーモンと通信するために使用される socket ファイルが作成され、この socket ファイル経由で秘密鍵を ssh-agent にキャッシュさせることになります。ここでもしも秘密鍵にパスフレーズが設定されていれば、秘密鍵の復号のためにパスフレーズの入力を求められます。

以後、ssh コマンド実行時等にはこの socket ファイルにアクセスすることでキャッシュされた秘密鍵にアクセスし、パスフレーズなしでのログインが可能になります。

ssh-agent の不満

ssh-agent を使う上での不満な点は主に以下の点です。

  1. 操作の途中で ssh-agent が起動していない、もしくは ssh-add していないことに気づくことがある(svn up の途中など)
  2. socket ファイルにアクセスができると、秘密鍵に設定されているパスフレーズを知らなくても当該の秘密鍵でアクセス可能な場所にはアクセスできてしまう

2 つめについては「パスフレーズを入力しないとはそういうもんだ」と言われればそうなのですが、それでもこの状態を積極的に「良しとする」のは気が引けるところでもあります。

というわけで、以下のような状態になればそれなりにバランスよく状態が改善されるだろう、となるのは自然な流れかと思います。

  1. 認証が必要になった時点でパスフレーズを聞いてくれて、その後認証状態(秘密鍵保持状態)をキープしてくれる
  2. 認証状態は適度な時間で expire する

つまり、必要なときに、少しの時間だけ認証状態を有効にしたい、というのが主旨です。あともう一点、個人的には極めて重要な要求があります。

  1. コンソール上で完結できる形で使いたい

gpg-agent を使用すると、これらすべてのニーズを満たすことができます。

a については ssh-agent においても SSH_ASKPASS を設定してあれば必要になった時点でパスフレーズを聞いてもらえます。しかしここで設定できるプログラムはウインドウシステム(実際的には UNIX 系システム上での X を想定)が起動していないと使えないものばかりで、c を満たすことができません。つまり、ssh でログインした先では使えないのです。

b についても ssh-add 時に -t オプションで timeout を設定すればできるのですが、a ができないのに b だけできてもただ不便です。

基本的な設定方法

前置きがこれ以上長くなるのもくどいので本題に入りましょう。必要なものは以下の 2 つです。

  • gpg-agnet (gnupg2)
  • pinentry-curses

GnuPG の設定ファイルは .gnupg 以下に置かれます。もしもこれまでに GnuPG を使用したことがなく、.gnupg ディレクトリが存在しない場合には以下のコマンドを実行し一度ディレクトリを作成してください。

$ gpg-agent

gpg-agent の設定は .gnupg/gpg-agent.conf に書きます。ssh-agent をさせるだけなら以下の記述があれば十分です。

pinentry-program        /usr/pkg/bin/pinentry-curses

enable-ssh-support
default-cache-ttl-ssh   600
max-cache-ttl-ssh       1800

pinentry-program に指定する pinentry-curses コマンドのパスは、実行環境に合わせて適宜修正してください。上記設定では認証状態は ssh コマンド使用後 10 分で expire します (default-cache-ttl-ssh)。ただし前回 ssh 使用後に再度 ssh を使用することがあった場合には、expire するまでの期間を最大で 30 分まで延ばします (max-cache-ttl-ssh)。

gpg-agent は以下のような状態になっていることが最善です。

  • 常に起動している
  • 1 プロセスだけ起動している

.profile 等に以下のようなものを書いておきましょう。

start_gpgagent()
{
        local _info _pid _comm

        which gpg-agent 2>&1 >/dev/null || return

        _info="${HOME}/.gpg-agent-info"

        if [ -f "${_info}" ]; then
                . "${_info}"
                export GPG_AGENT_INFO
                export SSH_AUTH_SOCK
                export SSH_AGENT_PID

                _pid=$(echo ${GPG_AGENT_INFO} |
                       sed -e 's/.*gpg-agent:\([^:]*\):.*$/\1/')
                _comm=$(echo $(ps -p ${_pid} -o comm=) | sed -e 's/ *$//')
                if [ "x${_comm}" = "xgpg-agent" ]; then
                        return
                fi
        fi

        eval $(gpg-agent --daemon --write-env-file "${_info}")
}

start_gpgagent

一度ログインし直すか、以下のようにして gpg-agent を起動します。

$ . ~/.profile

初回使用時のみは gpg-agent が ssh の鍵情報を知らないので、一度 ssh-add して gpg-agent に鍵情報を教えておく必要があります。

$ ssh-add

コマンド実行直後に一度パスフレーズを聞かれ、その後 pinentry-curses が起動して再びパスフレーズを聞かれます。以後は必要に応じて gpg-agent がパスフレーズ入力用のエリアを表示してくれるようになります。

最後に、コンソール上でうまく使用するための調整をしておく必要があります。普通に gpg-agent を使用すると、パスフレーズは ssh を実行した端末ではなく、gpg-agent を起動した端末で聞かれてしまいます。gpg-agent はどの端末にパスフレーズ入力用のエリアを表示すべきかがわからないので、事前にこれを教えてあげる必要があるわけです。

これについては筆者は ssh のラッパスクリプトを作成することで対処しています(以下のスクリプトを ~/bin/ssh に置いています)。他には zsh の preexec を使用するような方法でもいいでしょう。

#!/bin/sh

if which gpg-connect-agent >/dev/null 2>&1; then
        gpg-connect-agent updatestartuptty /bye >/dev/null 2>&1
fi

PATH=$(echo $PATH | sed "s,$(cd $(dirname $0); pwd):,,")
exec $(basename $0) "$@"

gpg-connect-agent により gpg-agent を現在使用中の端末に「呼び」ます。その後自分自身を含むディレクトリを PATH から抜いて再度 ssh を呼びます。このスクリプトは scp という名前でシンボリックリンクを張ってもちゃんと動くようになっています。

認証状態の管理

認証状態を強制的に expire したい場合には、gpg-agent に SIGHUP を送ります。

$ pkill -HUP gpg-agent

また、筆者は使用していませんが、.gnupg/sshcontrol ファイルを使用すると ssh の鍵ごとに認証有効期間を個別に設定したり、必ずパスフレーズの入力を求めるようにもできるようです。必要に応じてカスタマイズしましょう。

まとめ

万事うまくいけば ssh の実行の際に以下のような入力エリアが出ているはずです。ヨカッタネ。

../../_images/gpg-agent.png

gpg-agent は ssh-agent に比べて便利ですが、今回紹介した方法ではセキュリティ面としてはちょっとした事故の防止程度の役にしか立ちません。よりきちんとしたセキュリティを考えるのであれば、複数の秘密鍵を用意してキャッシュする鍵としない鍵を使い分けるようなことも必要になりますので、注意しましょう。

記事執筆者: inajima
記事公開日:2013年04月05日