Skip to content

Commit 5a04866

Browse files
ixxqcodex
andcommitted
docs(git): address Secure Enclave review feedback
- align the runbook with the active host-specific signing configuration - make identity renewal and rotation steps device-safe - clarify GitHub and local signature verification semantics Co-authored-by: Codex GPT-5.6 Sol Ultra <noreply@openai.com>
1 parent e5a5648 commit 5a04866

2 files changed

Lines changed: 84 additions & 42 deletions

File tree

docs/secure-enclave-signing.md

Lines changed: 79 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Git のコミット署名鍵を macOS の Secure Enclave で管理する。1Pass
1313
- `ssh-keygen``SSH_SK_PROVIDER` で dylib を教えるだけなので、**常駐エージェントは不要**
1414
(Secretive のような GUI アプリも login item も要らない)。
1515
- `~/.ssh/id_git_sign` は鍵ハンドルであって秘密鍵ではない。秘密鍵は Secure Enclave から出せない。
16+
ただし現在の `-t none` は使用時認証を行わないため、同じユーザー権限で動くプロセスは
17+
ユーザー操作なしで署名を要求できる。Secure Enclave が防ぐのは秘密鍵の持ち出しであり、
18+
ローカルプロセスによる鍵の使用ではない。
1619

1720
### 前提
1821

@@ -26,52 +29,58 @@ Git のコミット署名鍵を macOS の Secure Enclave で管理する。1Pass
2629

2730
| 項目 ||
2831
|---|---|
29-
| ホスト | M4Pro のみ (M4Air は 1Password のまま) |
32+
| M4Pro の signer | Secure Enclave (`hosts/M4Pro/default.nix` で共有設定を上書き) |
33+
| M4Air の signer | 1Password (`home/git.nix` の共有既定値) |
3034
| identity label | `git-sign` |
31-
| public key hash (SHA1) | `D93C59C037113F3418EAAB2E4EA5277EB34DCE1B` |
32-
| SSH fingerprint | `SHA256:AsbsNi30tI7r4kUqNSHew9DI86raUYeHeYP3MLCjpck` |
35+
| private key protection | `none` (署名時の Touch ID なし) |
36+
| public key hash (SHA1) | `D93C59C037113F3418EAAB2E4EA5277EB34DCE1B` (現在の M4Pro の値) |
37+
| SSH fingerprint | `SHA256:AsbsNi30tI7r4kUqNSHew9DI86raUYeHeYP3MLCjpck` (現在の M4Pro の値) |
3338
| 証明書有効期限 | **2036/08/07** (作成時は 2027/08/10 = 1年。下記手順で10年に更新済み) |
34-
| GitHub signing key | id=1103015 `Secure Enclave (M4Pro, signing)` |
39+
| GitHub signing key | M4Pro: id=1103015 `Secure Enclave (M4Pro, signing)` / M4Air: id=805051 `1Password (signing)` |
40+
| allowed signers | `home/git.nix``allowedSignerKeys` から生成 |
3541

36-
### 移行の進捗
42+
`~/.ssh/allowed_signers` は Home Manager が Nix store へのシンボリックリンクとして管理する。
43+
鍵を追加・変更するときはこのファイルを直接編集せず、`home/git.nix` を更新して rebuild する。
3744

38-
| ステップ | 状態 |
39-
|---|---|
40-
| Secure Enclave に鍵作成 | 完了 |
41-
| `~/.ssh/id_git_sign{,.pub}` 配置 | 完了 |
42-
| GitHub に Signing Key 登録・`verified=true` 確認 | 完了 |
43-
| 証明書を10年に更新 (2036/08/07) | 完了 |
44-
| `home/git.nix` の切り替え | **未実施** (まだ `op-ssh-sign` を使っている) |
45-
46-
### git 側の設定 (切り替え時)
45+
### Git 側の設定
4746

48-
`ssh-keygen``SSH_SK_PROVIDER` を渡すラッパーが要る。git は `gpg.ssh.program`
49-
環境変数を渡せないため。
50-
51-
```sh
52-
#!/bin/sh
53-
export SSH_SK_PROVIDER=/usr/lib/ssh-keychain.dylib
54-
exec /usr/bin/ssh-keygen "$@"
55-
```
47+
`ssh-keygen``SSH_SK_PROVIDER` を渡すラッパーが要る。Git は `gpg.ssh.program`
48+
環境変数を渡せないため、`hosts/M4Pro/default.nix``sshSign` でラッパーを生成している。
5649

5750
`user.signingkey` は 1Password 時代のリテラル公開鍵ではなく、**ハンドルファイルのパス**
5851
(`~/.ssh/id_git_sign`) を指定する。sk 鍵はハンドルが無いと署名できない。
5952

6053
この鍵は M4Pro にしか存在しないので、`home/git.nix` (全ホスト共有) に直接書くと
61-
**M4Air が次の rebuild でコミット不能になる**。ホスト別に分けること。
54+
**M4Air が次の rebuild でコミット不能になる**。そのため共有設定は 1Password を
55+
`lib.mkDefault` のまま維持し、M4Pro のホスト設定だけで鍵と signer を上書きしている。
56+
57+
### Signing key のローテーション
58+
59+
GitHub の `id=805051` `1Password (signing)` は M4Air が現在も使う**現役鍵**なので、
60+
M4Air を移行するまでは削除しない。GitHub は一度 Verified にしたコミットの検証記録を
61+
repository network 内で永続化するため、登録鍵を後から削除しても、その記録がある過去コミットは
62+
Verified のままになる。ただし削除後に初めて push するコミットや、別の repository network で
63+
初めて検証するコミットには登録鍵が必要になる。
64+
65+
ローカル検証は GitHub と異なり、毎回 `~/.ssh/allowed_signers` を参照する。鍵を作り直したら
66+
`home/git.nix``allowedSignerKeys` に新しい公開鍵を**追記**し、過去の署名を検証するために
67+
古い鍵も残す。鍵が全ホストで退役した後は、古い鍵の先頭に
68+
`valid-before="YYYYMMDDHHMMSSZ"` を付けると、切り替え前の署名だけを有効にできる。
69+
`allowedSignerKeys` では
70+
`"valid-before=\"YYYYMMDDHHMMSSZ\" ssh-ed25519 AAAA..."` のように引用符をエスケープする。
6271

63-
### GitHub 側の不変条件
72+
参考:
6473

65-
**旧 signing key `id=805051` "1Password (signing)" (ssh-ed25519) は削除しないこと。**
66-
削除すると、その鍵で署名した過去のコミットがすべて Unverified に落ちる。
67-
鍵をローテーションした場合も同様に、古い登録は残したまま新しい鍵を追加する。
74+
- [GitHub: Persistent commit signature verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#persistent-commit-signature-verification)
75+
- [Git: `gpg.ssh.allowedSignersFile`](https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgsshallowedSignersFile)
6876

6977
## 新規セットアップ
7078

7179
```bash
7280
# 1. Secure Enclave に鍵を作る
7381
# -k p-256-ne : ECDSA P-256 / non-exportable (Secure Enclave は P-256 のみ対応)
74-
# -t none : Touch ID を要求しない (-t bio にするとコミットのたびに Touch ID)
82+
# -t none : 使用時認証なし。同じユーザー権限のプロセスは操作なしで署名できる
83+
# 使用時認証が必要なら -t bio にして署名ごとに Touch ID を要求する
7584
sc_auth create-ctk-identity -l git-sign -k p-256-ne -t none -N git-sign
7685

7786
# 2. SSH 鍵ハンドルを書き出す (カレントディレクトリに生成されるので注意)
@@ -85,10 +94,25 @@ mv id_ecdsa_sk_rk.pub ~/.ssh/id_git_sign.pub
8594
gh api -X POST /user/ssh_signing_keys \
8695
-f title="Secure Enclave ($(scutil --get LocalHostName), signing)" \
8796
-f key="$(cat ~/.ssh/id_git_sign.pub)"
97+
98+
# 4. dotfiles に記録する値を表示する
99+
cat ~/.ssh/id_git_sign.pub
100+
/usr/bin/ssh-keygen -lf ~/.ssh/id_git_sign.pub
101+
sc_auth list-ctk-identities -t sha1 -e hex
102+
```
103+
104+
公開鍵の出力を `home/git.nix``allowedSignerKeys` に追記する。過去コミットの検証に必要なので、
105+
既存の鍵は削除しない。併せて fingerprint、public key hash、GitHub key ID を上の「現在の構成」に
106+
反映する。`home/git.nix` とこの文書を保存した後、Home Manager を適用する。
107+
108+
```bash
109+
# 5. dotfiles を反映する
110+
nix run "$HOME/.dotfiles#switch"
88111
```
89112

90-
`-t none` でも署名には FIDO の user-presence フラグが立つため、`allowed_signers`
91-
`no-touch-required` を付ける必要はない (付けると OpenSSH が `unknown key option` で落ちる)。
113+
`-t none` でも署名には FIDO の user-presence フラグが立つが、provider が操作なしで付けるため、
114+
対話的な user presence の証明にはならない。`allowed_signers``no-touch-required` を付ける必要はなく、
115+
付けると OpenSSH が `unknown key option` で落ちる。
92116

93117
作成直後の証明書は**有効期限が1年**しかないので、続けて「証明書の更新」を実行して
94118
10年に伸ばしておくこと。
@@ -119,13 +143,18 @@ SSH はこの証明書を使わない (生の公開鍵しか見ない) ので、
119143

120144
```bash
121145
# 1. 更新前の状態を控える (あとで fingerprint 不変を確認するため)
122-
sc_auth list-ctk-identities
146+
sc_auth list-ctk-identities -t sha1 -e hex
123147
sc_auth list-ctk-identities -t ssh
124148

125149
# 2. CSR を作る
126150
# -h に渡すのは list-ctk-identities のデフォルト表示 = SHA1 (40桁)。
127151
# -t sha256 -e hex の値を渡すと "No identity found for hash" で落ちる。
128-
HASH=D93C59C037113F3418EAAB2E4EA5277EB34DCE1B
152+
# 直前の出力から CN=git-sign の identity に対応する値をコピーする。
153+
HASH='<git-sign identity の SHA1 40桁>'
154+
if [[ ! $HASH =~ ^[0-9A-Fa-f]{40}$ ]]; then
155+
echo "HASH は40桁のSHA1を指定すること" >&2
156+
exit 1
157+
fi
129158
cd "$(mktemp -d)"
130159
sc_auth create-ctk-csr -h "$HASH" -f renew.csr -N git-sign
131160

@@ -172,11 +201,21 @@ GitHub への再登録が必要になる。
172201
# identity が 2 本あると ssh-keygen -K が両方を同じ id_ecdsa_sk_rk に書こうとして
173202
# 上書きプロンプトで詰まる。KEYCHAIN_CERTIFICATES で絞る方法は -w の Secure Key 経路では
174203
# 効かなかった。
175-
sc_auth delete-ctk-identity -h D93C59C037113F3418EAAB2E4EA5277EB34DCE1B
176-
177-
# 2. 「新規セットアップ」を最初からやり直す
178-
179-
# 3. GitHub の古い signing key は削除しないこと (過去コミットが Unverified になる)
204+
cat ~/.ssh/id_git_sign.pub
205+
sc_auth list-ctk-identities -t sha1 -e hex
206+
OLD_HASH='<削除する git-sign identity の SHA1 40桁>'
207+
if [[ ! $OLD_HASH =~ ^[0-9A-Fa-f]{40}$ ]]; then
208+
echo "OLD_HASH は40桁のSHA1を指定すること" >&2
209+
exit 1
210+
fi
211+
printf '削除対象: %s\n' "$OLD_HASH"
212+
sc_auth delete-ctk-identity -h "$OLD_HASH"
213+
214+
# 2. 「新規セットアップ」を最初からやり直す。GitHub への新鍵登録と
215+
# allowedSignerKeys への追記、Home Manager の反映まで実施する
216+
217+
# 3. 新旧の GitHub signing key を確認する
218+
# id=805051 は M4Air が現役利用している間は削除しない
180219
gh api /user/ssh_signing_keys --jq '.[] | "\(.id)\t\(.title)"'
181220
```
182221

@@ -201,17 +240,18 @@ gh auth refresh -h github.com -s admin:ssh_signing_key
201240

202241
| 症状 | 原因 |
203242
|---|---|
204-
| `No identity found for hash` | `-h` に SHA256 を渡している。SHA1 (40桁) を使う |
243+
| `No identity found for hash` | `-h` に SHA256 や別端末の値を渡している。対象 identity の SHA1 (40桁) を使う |
205244
| `ssh-keygen -K` が上書きプロンプトで止まる | CTK identity が複数ある。不要なものを削除する |
206245
| `allowed_signers:1: bad options: unknown key option` | `no-touch-required` を書いている。不要 |
246+
| `No principal matched` | 新しい公開鍵が `home/git.nix``allowedSignerKeys` に無いか、rebuild が未実施 |
207247
| コミットが署名エラーで落ちる | `~/.ssh/id_git_sign` の消失、`ssh-keychain.dylib` のパス変更、identity の消失を先に疑う。`sc_auth list-ctk-identities` で存在と `Valid` を確認 |
208248
| nix で openssh を入れた直後に壊れた | `ssh-keygen` が nix 側に解決されている。`/usr/bin/ssh-keygen` を絶対パスで呼ぶ |
209249

210250
## 検証済み/未検証の区別
211251

212252
このドキュメントのうち実測で確認したもの:
213253

214-
- 新規セットアップ一式 (鍵作成 → 書き出し → 署名 → 検証 → GitHub で `verified=true`)
254+
- 新規セットアップ一式 (鍵作成 → 書き出し → GitHub/`allowed_signers` 登録 → 署名・検証)
215255
- 証明書更新の全工程を本番 identity `git-sign` で実行し、以下を確認 (2026-08-10):
216256
- 有効期限が 2027/08/10 → 2036/08/07 に伸びた
217257
- public key hash / SSH fingerprint / CN が**すべて不変**

home/git.nix

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ let
1111

1212
# ローカルで `git log --show-signature` を通すための信頼済み公開鍵。
1313
# 鍵の実体は不要なので、そのホストに存在しない鍵も載せてよい。
14-
# 過去のコミットを検証できなくなるため、移行しても古い鍵は消さないこと。
14+
# 新しい鍵を作ったらここに追記する。ローカルで過去のコミットを検証するため、
15+
# 退役鍵は削除せず、必要なら `valid-before` で有効期間を制限する。
16+
# ローテーション手順は docs/secure-enclave-signing.md を参照。
1517
allowedSignerKeys = [
16-
# 1Password (M4Air / 移行前の M4Pro)
18+
# 1Password (M4Air / 移行前の M4Pro) — SHA256:G/X9g4PrQznsy2+Qx5MUbI7X1e3ZT1G6KT2CTMP3m3k
1719
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF+zJB91Fifv36IetC+AhWcBE+a9poI/U+A6MlLfABoa"
18-
# Secure Enclave (M4Pro) — docs/secure-enclave-signing.md
20+
# Secure Enclave (M4Pro) — SHA256:AsbsNi30tI7r4kUqNSHew9DI86raUYeHeYP3MLCjpck
1921
"sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBE3fdVRsCoAJtcjjG+is4FILynjzM42+czwaeQfBiuP+8Kr/8YGe/zh/6pYG2v7ARKrywp+XHcv+d7Y1+E7wSOIAAAAEc3NoOg=="
2022
];
2123
allowedSignersFile = "${config.home.homeDirectory}/.ssh/allowed_signers";

0 commit comments

Comments
 (0)