Open1

Hyper-V上のUbuntuでvTPMを使い、SSH鍵とGitコミット署名をTPMに保護する

fujiwarafujiwara

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で作る。

  1. Hyper-V Manager で対象VMを停止
  2. 設定 → セキュリティ
  3. Enable Trusted Platform Module にチェック
  4. 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_ptooltpm2-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鍵で行う

~/.gitconfigsigningkey には公開鍵ファイルのパスを指定する。

[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等を渡す