「賞状の額縁だけ見て、中の証書を確認しない人はいないはずだ」と思っていた。
GitHubの緑色の「Verified」バッジについて、そんな思い込みを崩す報告があった。署名を検証できても、表示されている中身がその署名の対象そのものとは限らない、という話だ。
「Verified」の中身を分解する
GitHubの「Verified」バッジは、コミットにGPGやSSHの署名が正しく検証できたことを示す表示だ。今回報告された手法では、署名を作る鍵を持たない第三者でも、署名の対象になっているペイロード自体はそのままに、コミットオブジェクトの外側の情報を組み替えることで、元とは異なるハッシュ値を持つ別のコミットを作れるとされる。署名の検証自体は通ってしまうため、GitHub上ではどちらのコミットにも同じ緑バッジが表示される。
sequenceDiagram
participant 正規のコミッター
participant Gitオブジェクトストア
participant 第三者
participant GitHubの検証
正規のコミッター->>Gitオブジェクトストア: 元のコミットに署名
Gitオブジェクトストア->>GitHubの検証: 署名を検証
GitHubの検証-->>正規のコミッター: Verifiedバッジ表示
第三者->>Gitオブジェクトストア: 同じ署名を保ったまま別ハッシュのコミットを作成
Gitオブジェクトストア->>GitHubの検証: 別ハッシュでも署名は同じ
GitHubの検証-->>第三者: 同じくVerifiedバッジ表示
自分のコードに照らし合わせると
「Verifiedバッジがついている=コミットの中身は署名者が書いた通り」という前提で運用しているチームは少なくないはずだ。CIのリリースゲートやコードレビューの信頼判断に緑バッジの有無を使っている場合、その前提が崩れる。バッジが保証しているのは「鍵の持ち主が何かに署名した」ことまでで、「今表示されている中身がその署名対象そのものである」ことまでは別問題だという区別が必要になる。
非嫌な切り口
面白いのは、この問題が「秘密鍵の漏洩」というよくある原因ではなく、検証の設計そのもの、つまり「ハッシュは内容の一意な指紋だ」という思い込みに起因している点だ。同じ構造の穴が、パッケージマネージャやファームウェア更新など、他の署名検証つきの仕組みにも潜んでいないか気になる。
一つ問いかけ
自分のリリースパイプラインは、「署名があること」と「表示されている中身が正しいこと」を、別々に確認できているだろうか。