Featured image of post ぼくの考えた最強の
GPG Key 環境 for Windows 11

ぼくの考えた最強の GPG Key 環境 for Windows 11

Summary

上記で、 Windows 11 環境にひと通り環境構築したわけですが ssh-agent を利用して開発環境用に Remote のサーバーに ForwardAgent で転送して使うと ssh-add -l など挙動をした時にハングアップしてしまい対策に困っていた。まだ完全とはいかないが少なくても以前よりは安定した状態にできたのでこちらにまとめる。

OpenSSH のバージョンアップ

Windows 10 から OpenSSH が付属している。
がこれは、皆さんがよく使っている Linux に付属する OpenSSH と違い Microsoft が Windows 用に実装した OpenSSH である。これの利点は混沌としている、 Windows の SSH 環境で使えるように実装されており NamedPipe を利用して ssh-agent 機能が提供できるようになっている。

ここまではいいのだが、 Windows 11 付属の OpenSSH は version 8.6p1, LibreSSL 3.4.3 となっているため Ubuntu 22.04 などと合わせて使ったり WSL2 上に Ubuntu 22.04 を立てて使う場合最新版までの間に追加された拡張命令が来た瞬間に応答を停止するなど事象がある。

そのため、個人的にはアップデートをおすすめする。
また今回の version は共存できるが、 Shell の Path 順序での判定で気持ち悪いので標準付属の物はアンインストールしてしまうことにした。

OpenSSH 8.6p のアンインストール

1
2
> ssh -V
OpenSSH_for_Windows_8.6p1, LibreSSL 3.4.3
Note

PowerShell 7 だとうまくいかず、下記のエラーを吐きます

1
Get-WindowsCapability: クラスが登録されていません

そのため、明示的に powershell.exe を呼び出し実行とします

1
> powershell.exe
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#
# 管理者権限プロンプトを立ち上げて作業
#

> powershell.exe
> Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'

Name  : OpenSSH.Client~~~~0.0.1.0
State : Installed

Name  : OpenSSH.Server~~~~0.0.1.0
State : NotPresent

> Remove-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0

Path          :
Online        : True
RestartNeeded : True

> Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'

Name  : OpenSSH.Client~~~~0.0.1.0
State : UninstallPending

Name  : OpenSSH.Server~~~~0.0.1.0
State : NotPresent
Topic
ここで一度再起動が必要

OpenSSH 9.5p のインストール

winget でインストールをおすすめする。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
> winget search openssh
名前         ID                     バージョン 一致         ソース
------------------------------------------------------------------
SSHells      dzonder.sshells        0.1.4      Tag: openssh winget
OpenSSH Beta Microsoft.OpenSSH.Beta 9.5.0.0                 winget

> winget install Microsoft.OpenSSH.Beta
見つかりました OpenSSH Beta [Microsoft.OpenSSH.Beta] バージョン 9.5.0.0
このアプリケーションは所有者からライセンス供与されます
Microsoft はサードパーティのパッケージに対して責任を負わずライセンスも付与しません
ダウンロード中 https://github.com/PowerShell/Win32-OpenSSH/releases/download/v9.5.0.0p1-Beta/OpenSSH-Win64-v9.5.0.0.msi
  ██████████████████████████████  5.47 MB / 5.47 MB
インストーラーハッシュが正常に検証されました
パッケージのインストールを開始しています...
インストールが完了しました

これで標準付属のやつはアンインストールされ 9.5p がインストールされた状態になるはずのためアンインストールが出来ているか確認する

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#
# 管理者権限プロンプトを立ち上げて作業
#

> powershell.exe
> Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'

Name  : OpenSSH.Client~~~~0.0.1.0
State : NotPresent

Name  : OpenSSH.Server~~~~0.0.1.0
State : NotPresent
Note
再起動後、 新しくインストールした OpenSSH 9.5.0.0 の ssh-agent と sshd が起動しているので これの自動起動を停止、しサービスを停止する。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#
# 管理者権限プロンプトを立ち上げて作業
#

> Get-Service | Where-Object {$_.Name -match '(CertPropSvc|SCardSvr|ssh-agent|sshd)'} | Select Status, Name, StartType, DisplayName

 Status Name        StartType DisplayName
 ------ ----        --------- -----------
Stopped CertPropSvc Automatic Certificate Propagation
Running SCardSvr    Automatic Smart Card
Stopped ssh-agent   Automatic OpenSSH Authentication Agent
Stopped sshd        Automatic OpenSSH SSH Server


# 自動起動の無効化
> Set-Service -Name "CertPropSvc" -StartupType Disabled
> Stop-Service -Name "CertPropSvc"

> Set-Service -Name "ssh-agent" -StartupType Disabled
> Stop-Service -Name "ssh-agent"
> Set-Service -Name "sshd" -StartupType Disabled
> Stop-Service -Name "sshd"

SCardSvr のみ自動起動で、他は Disabled になっていれば問題ありません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#
# 管理者権限プロンプトを立ち上げて作業
#

# 起動設定確認
> Get-Service | Where-Object {$_.Name -match '(CertPropSvc|SCardSvr|ssh-agent|sshd)'} | Select Status, Name, StartType, DisplayName
 Status Name        StartType DisplayName
 ------ ----        --------- -----------
Stopped CertPropSvc  Disabled Certificate Propagation
Stopped SCardSvr    Automatic Smart Card
Stopped ssh-agent    Disabled OpenSSH Authentication Agent
Stopped sshd         Disabled OpenSSH SSH Server

gpg-agent 設定

$env:APPDATA\gnupg\gpg-agent.conf に gpg-agent.conf を作成します。
この時 enable-win32-openssh-support を追加することで Windows 搭載の OpenSSH からも利用することができるようになります。
enable-ssh-support は Windows では機能しないため、記載する必要はありません

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$confContent = @"
# Ref: https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html

###+++--- GPGConf ---+++###
enable-putty-support
enable-win32-openssh-support
###+++--- GPGConf ---+++###
# GPGConf edited this configuration file.
# It will disable options before this marked block, but it will
# never change anything below these lines.

"@

New-Item "$env:APPDATA\gnupg" -ItemType Directory
Set-Content -Path $env:APPDATA\gnupg\gpg-agent.conf -Value $confContent

Scdaemon 設定

PC/SC(Personal Computer/Smart Card) を gpg-agent で使う場合 PC/SC を専有する設定になっているようなので、共有するように変更し gpg-agent に内蔵されている CCID ドライバーを無効化し Windows 標準の Smart Card サービスを使うように設定します。

YubiKey のデバイス名

YubiKey の Device Name が Series 4, 5 で名前が違うらしいため、自身の利用する YubiKey を刺して確認する
私の、 YubiKey 5 NFC は Yubico YubiKey でしたのでこちらを後で使います。

1
2
3
> Get-PnpDevice -Class SoftwareDevice | Where-Object {$_.FriendlyName -like "*YubiKey*"} | `
  Select-Object -ExpandProperty FriendlyName
Yubico YubiKey OTP+FIDO+CCID 0

設定ファイルの書き出し

Yubico から設定記事が出ているので、これを参考にする

  • pcsc-shared を設定すると gpg-agent による PIN Cache がされず、毎回入力しなければならないので設定しない。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$confContent = @"
# Ref: https://www.gnupg.org/documentation/manuals/gnupg/Scdaemon-Options.html

###+++--- ScdaemonConf ---+++###
disable-ccid
reader-port Yubico YubiKey
###+++--- ScdaemonConf ---+++###

"@

New-Item "$env:APPDATA\gnupg" -ItemType Directory
Set-Content -Path "$env:APPDATA\gnupg\scdaemon.conf" -Value $confContent

テスト

設定を変更したら下記コマンドで gpg-agent を再起動しましょう

1
2
3
4
5
6
7
8
# 設定の再読み込み
gpgconf --reload

# gpg-agent をすべて強制終了
gpg-connect-agent killagent /bye

# gpg-agent 1次起動 & debug
gpg-agent --daemon --verbose

YubiKey にアクセスできるか?
を確認して、 YubiKey に設定してある公開鍵をダウンロードして配置します。 正しく動けば、公開鍵がインポートされます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
> gpg --card-edit
gpg: keybox'C:\\Users\\naa0yama\\AppData\\Roaming\\gnupg\\pubring.kbx'が作成されました

gpg/card> fetch
gpg: 鍵を'https://keybase.io/naa0yama/pgp_keys.asc'から要求
gpg: C:\\Users\\naa0yama\\AppData\\Roaming\\gnupg\\trustdb.gpg: 信用データベースができました
gpg: 鍵794676DEF45A4D7F: 公開鍵"Naoki Aoyama <[email protected]>"をインポートしました
gpg:           処理数の合計: 1
gpg:             インポート: 1

gpg/card> q

インポートされた鍵を確認してみます。
下記のように鍵が見えれば問題ありません、信頼しておきましょう

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
> gpg -k
C:\Users\naa0yama\AppData\Roaming\gnupg\pubring.kbx
---------------------------------------------------
pub   nistp521 2024-09-13 [C]
      65284A0A9205C52EBC16BBCF794676DEF45A4D7F
uid           [  不明  ] Naoki Aoyama <9667078+naa0yama@users.noreply.github.com>
sub   cv25519 2024-09-13 [E]
sub   ed25519 2024-09-13 [S]
sub   ed25519 2024-09-13 [A]

> gpg --key-edit <KEYID>
gpg> trust

  1 = 知らないまたは何とも言えない
  2 = 信用し ない
  3 = まぁまぁ信用する
  4 = 充分に信用する
  5 = 究極的に信用する
  m = メーンメニューに戻る

あなたの決定は? 5
本当にこの鍵を究極的に信用しますか? (y/N) y

[  不明  ] (1). Naoki Aoyama <9667078+naa0yama@users.noreply.github.com>
プログラムを再起動するまで表示された鍵の有効性は正しくないかもしれない
ということを念頭においてください

gpg> q

> gpg -k
gpg: 信用データベースの検査
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: 深さ: 0  有効性:   1  署名:   0  信用: 0-, 0q, 0n, 0m, 0f, 1u
C:\Users\naa0yama\AppData\Roaming\gnupg\pubring.kbx
---------------------------------------------------
pub   nistp521 2024-09-13 [C]
      65284A0A9205C52EBC16BBCF794676DEF45A4D7F
uid           [  究極  ] Naoki Aoyama <9667078+naa0yama@users.noreply.github.com>
sub   cv25519 2024-09-13 [E]
sub   ed25519 2024-09-13 [S]
sub   ed25519 2024-09-13 [A]
Topic
ここで再起動
1
2
3
4
5
# 起動して各 agent が started になっていることを確認する
> gpg-agent --daemon --verbose
gpg-agent[24196]: gpg-agent (GnuPG) 2.4.5 started
gpg-agent[24196]: putty message loop thread started
gpg-agent[24196]: Win32-OpenSSH thread started

上記を実施したあとに別タブで ssh-add -l を実行すると下記のよう表示される。
表示を確認でしたら一度 gpg-agent を終了する

1
2
3
4
# 下記のように cardno:XX_XXX_XXX の表記がある鍵が2つ見えるはず
> ssh-add -l
256 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX cardno:XX_XXX_XXX (ED25519)
2048 SHA256:/XXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXX cardno:XX_XXX_XXX (RSA)

スタートアップ登録

スタートアップに登録する
gpg-connect-agent.vbs という名前でスタートアップフォルダーに VBS スクリプトを作成して自動起動するようにしておく、クリックする事に一度全て終了させて起動するようにしているのがトラブル回避とこだわりポイント。

1
2
3
4
5
6
7
8
$confContent = @"
Set objWShell = CreateObject("Wscript.Shell")
objWShell.run "gpg-connect-agent killagent /bye", vbHide
WScript.Sleep 1000
objWShell.run "gpg-connect-agent /bye", vbHide
"@

Set-Content -Path "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\gpg-connect-agent.vbs" -Value $confContent

登録したスタートアップを Win + R を実行し shell:startup にある作成した gpg-connect-agent.vbs を起動する。

gpg-agent が稼働していることを確認

1
2
3
4
5
> Get-Process -Name 'gpg-agent'

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
     17     3.86      12.85       0.16    1668   1 gpg-agent

Windows Terminal に戻って ssh-add -l を実行すると先程実行した時と同様に鍵が2つ見える

1
2
3
> ssh-add -l
256 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX cardno:XX_XXX_XXX (ED25519)
2048 SHA256:/XXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXX cardno:XX_XXX_XXX (RSA)

Git 設定

SSH Signing keys を利用するように設定します。

git config --global user.signingkey $confContent は 刺さっているカードの公開鍵を利用するようにしています <SSH 公開鍵の中身>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$confContent = (ssh-add -L | Select-String -Pattern '^(ssh-ed25519\s.*)\s' | % {"$($_.matches.groups[1])"})

git config --global user.email 9667078+naa0yama@users.noreply.github.com
git config --global user.name "Naoki Aoyama"
git config --global user.signingkey $confContent

git config --global commit.gpgsign true
git config --global fetch.prune true
git config --global gpg.format ssh
git config --global merge.ff false
git config --global pull.ff only
git config --global push.autoSetupRemote true

Git for Windows を入れてない場合は下記のように .gitconfig を作成することで代用します

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$confContent = (ssh-add -L | Select-String -Pattern '^(ssh-ed25519\s.*)\s' | % {"$($_.matches.groups[1])"})
$gitconfigContent = @"
[user]
  email = [email protected]
  name = Naoki Aoyama
  signingkey = $confContent

[commit]
  gpgsign = true

[fetch]
    prune = true

[gpg]
    format = ssh

[merge]
    ff = false

[pull]
    ff = only

[push]
    autoSetupRemote = true

"@
$gitconfigContent

OpenSSH

OpenSSH Client

ssh-add -l を実行して鍵が見える場合はなにもしなくても利用できるはずです。
もうファイルで鍵を置く必要はありません。

Server 側

踏み台サーバー上でも ssh-agent を使いたい場合は sshd に設定の追加が必要です

/etc/ssh/sshd_config
1
AllowAgentForwarding yes

追加後 sshd を再起動して設定の反映を確認します

1
2
3
4
5
6
> systemctl restart sshd

> sshd -T | grep allow
allowtcpforwarding yes
allowagentforwarding yes
allowstreamlocalforwarding yes

Tera Term(Pageant)

通常の方法でログイン方式の選択に「Pageantを使う」を選択すればよい

Tera Term の SSH認証

WSL2(OpenSSH -> wsl2-ssh-agent)

事前に wsl2-ssh-agent を導入すれば何もしなくても WSL2 内から鍵を利用できるはずです。
私は WSL2 をヘビーに使うので下記のレポジトリで管理しています。

1
2
3
4
5
6
user@hostname:~$ env | grep SSH_
SSH_AUTH_SOCK=/home/user/.ssh/wsl2-ssh-agent.sock

user@hostname:~$ ssh-add -l
256 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX cardno:XX_XXX_XXX (ED25519)
2048 SHA256:/XXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXX cardno:XX_XXX_XXX (RSA)

VSCode

VSCode で下記の2つを設定しておくと Commit 時に署名を強制できる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "git.enableCommitSigning": true, //                                Enables commit signing with GPG, X.509, or SSH
  "git.alwaysSignOff": true,       //                                Controls the signoff flag for all commits.

  // ssh-agent を WSL2 や devcontainer で使うための回避策
  "remote.SSH.enableAgentForwarding": true,
  "remote.SSH.useLocalServer": false,                             // Window 間で別々の接続にする
  "remote.SSH.useExecServer": false,                              // 従来の接続方式を利用する
  "remote.SSH.remoteServerListenOnSocket": true,                  // Socket Listen を有効化
  "terminal.integrated.persistentSessionReviveProcess": "never",  // ターミナルの復元、再作成を行わない.
  "terminal.integrated.enablePersistentSessions": false           // ターミナルセッション履歴を保持しない
}

remote*, と terminal* は WSL2 上で ssh-agent を使う場合うまく SOCK を引き継げない事象になることがあるので追記している。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。