Hyper-V上のUbuntuでvTPMを使い、SSH鍵とGitコミット署名をTPMに保護する
SSHやGit署名で使う秘密鍵をファイルとしてディスクに置かず、Hyper-VのvTPM(仮想TPM)内に閉じ込めて使う手順をまとめる。秘密鍵はTPMから一切取り出せない。署名操作だけをTPMに依頼する形。
環境: Windows 11 上のHyper-V、ゲストはUbuntu 24.04。
1. Hyper-V側でvTPMを有効化
vTPMを使えるのはGeneration 2 VMだけ。Gen 1からの変換は不可なので、新規にGen 2で作る。
- Hyper-V Manager で対象VMを停止
- 設定 → セキュリティ
-
Enable Trusted Platform Moduleにチェック - Secure Boot Template を Microsoft UEFI Certificate Authority に変更
2. Ubuntu側でTPMが見えることを確認
ls /dev/tpm*
# /dev/tpm0 /dev/tpmrm0 が見えればOK
sudo apt install tpm2-tools
tpm2_getcap properties-fixed | head
TPM 2.0の情報が出力されれば成功。
3. 必要パッケージのインストール
sudo apt install \
tpm2-tools \
libtpm2-pkcs11-1 \
libtpm2-pkcs11-tools \
opensc
tpm2_ptool は tpm2-tools ではなく libtpm2-pkcs11-tools に入っている点に注意。
4. tssグループに自分を追加
/dev/tpmrm0 へのアクセスにtssグループが必要。
sudo usermod -aG tss $USER
5. TPM内に鍵を生成
PKCS#11トークンを作り、その中にECDSA P-256鍵を生成する。
tpm2_ptool init
tpm2_ptool addtoken --pid=1 --label=ssh --sopin=sopin --userpin=1234
tpm2_ptool addkey --label=ssh --userpin=1234 --algorithm=ecc256 --key-label=ssh-key
PINは適切なものに変える。userpinが普段使う方、sopinはリセット用。
Ed25519は使えない。TPM 2.0仕様にはCurve25519が追加されているが、PC Client Profileに入っていないため、実在するTPM(Hyper-V vTPM含む)はEd25519をサポートしない。ECDSA P-256かRSAを選ぶ。
6. 公開鍵の取り出し
ssh-keygen -D /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.1 > ~/.ssh/id_tpm.pub
cat ~/.ssh/id_tpm.pub
リモートサーバーの authorized_keys やGitHubのSSH/Signing Keyにこれを登録する。
7. SSHログイン
PKCS#11ライブラリを指定して接続。
ssh -I /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.1 user@remote
PINを聞かれるので入力。~/.ssh/config に書いておけば毎回 -I を書かずに済む。
Host remote
HostName remote.example.com
User myuser
PKCS11Provider /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.1
8. ssh-agentに登録(PIN入力を都度回避)
8-1. ssh-agentのPKCS#11ホワイトリスト
OpenSSH 8.9以降のssh-agentは、-P オプションで指定したパターンに合うPKCS#11ライブラリしかロードしない。Ubuntuのデフォルトは限定的なので、TPMライブラリを許可する必要がある。
ssh-agentは -P の比較時にrealpath解決後のパスと突き合わせる。libtpm2_pkcs11.so.1 はバージョン付き実体(例: libtpm2_pkcs11.so.1.9.0)へのシンボリックリンクなので、.so.1 を許可しても弾かれる。
refusing PKCS#11 add of "/usr/lib/.../libtpm2_pkcs11.so.1.9.0": provider not allowed
ワイルドカードで実体までカバーする。
8-2. systemdユーザーユニットを上書き
~/.config/systemd/user/ssh-agent.service.d/override.conf を作成。
[Service]
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK -P /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.*
Environment="TPM2_PKCS11_STORE=%h/.tpm2_pkcs11"
Environment="TPM2_PKCS11_TCTI=device:/dev/tpmrm0"
環境変数も指定しておかないとsystemd配下のssh-agentからTPMが見つからない。
systemctl --user daemon-reload
systemctl --user restart ssh-agent.service
systemd --user のservice再起動だけでは tss group への追加が反映されないことがあるので systemd-run --user --pty --wait id で確認。反映されていなければOS再起動すると確実。
8-3. 鍵を登録
ssh-add -s /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.1
Enter passphrase for PKCS#11: <PIN(userpin)>
Card added: /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.1
「passphrase」と表示されるが入力するのは userpin。
ssh-add -L
# ecdsa-sha2-nistp256 AAAA... <鍵ラベル>
8-4. デバッグ手順
うまく行かないときは、systemd側を止めて手動でデバッグ起動するとログが出る。
systemctl --user stop ssh-agent.service
/usr/bin/ssh-agent -d -a /tmp/agent.sock \
-P '/usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.*'
別ターミナルで。
export SSH_AUTH_SOCK=/tmp/agent.sock
ssh-add -s /usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so.1
provider not allowed ならパターンの問題、Permission denied /dev/tpmrm0 ならtssグループの反映不足。
9. Gitコミット署名をTPM鍵で行う
~/.gitconfig の signingkey には公開鍵ファイルのパスを指定する。
[user]
name = Your Name
email = you@example.com
signingkey = /home/fujiwara/.ssh/id_tpm.pub
[gpg "ssh"]
program = ssh-keygen
[commit]
gpgsign = true
[tag]
gpgsign = true
動作確認。
git commit -S -m "test"
git log --show-signature -1
Good "git" signature が出れば成功。
ローカル検証用 allowed_signers
mkdir -p ~/.config/git
echo "you@example.com $(cat ~/.ssh/id_tpm.pub)" > ~/.config/git/allowed_signers
[gpg "ssh"]
allowedSignersFile = ~/.config/git/allowed_signers
GitHub側
Settings → SSH and GPG keys から Signing Key として id_tpm.pub を登録(Authentication Keyとは別なので注意)
詰まったポイント早見表
| 症状 | 原因 | 対処 |
|---|---|---|
tpm2_ptool: command not found |
パッケージ違い |
libtpm2-pkcs11-tools を入れる |
| Ed25519鍵が作れない | TPMハードウェア未対応 | ECDSA P-256かRSAを使う |
agent refused operation |
-P ホワイトリスト |
libtpm2_pkcs11.so.* で実体までカバー |
Permission denied /dev/tpmrm0 |
tssグループ未反映 | OS再起動 |
| systemd ssh-agentからだけ失敗 | 環境変数なし |
Environment= でTPM2_PKCS11_STORE等を渡す |