140文字以上

140文字では伝わらないことを書いています。禁無断転載

twitter(@chromarock), github, bluesky, Mastodon(japanet), misskey, ほしいものリスト, 本人証明, その他プロフィール

gpgで署名用の鍵を作ってgithubに署名付きコミットをする方法

はじめに

もう半年も前のことになりますが、githubでリポジトリにマルウェアが勝手にコミットされているとか汚染されたとか騒がれていました。

実はこれ蓋を開けてみるとアカウントが奪取されたとか、なんか勝手にマルウェアなコードが混ざったとかそんな致命的なことが発生したわけでなく、大凡このような感じでした。

1.誰かが有名所のOSS(githubでは管理されていない、Sourceforgeで管理されているとかなど)のcloneリポジトリを作る
2.悪意のある誰かがクローンしたリポジトリの最後に怪しいURLやコードを(もっともらしいユーザ名とメールアドレスで)コミットする
3.それを何食わぬ顔でGithubにプッシュして元のOSSに「これ適用して」とプルリクを投げる(多分これが悪意ある人の本来の目的らしい)。
だがそのOSSのメンバーがよほど手抜きで無い限りコードレビューされるので怪しかったら拒否されるはず。。
4.暫くして、何も知らない人が大本のOSSリポジトリのクローンではなく、悪意ある人のリポジトリをクローン、もしくはサブモジュールに加えてしまう事例が発生する…

しばらくして、GithubSecurityチームは次のような見解を出しました

GitHub は、2022 年 8 月 3 日水曜日に公開されたツイートを調査しています。
* リポジトリは侵害されませんでした
* リポジトリ自体ではなく、複製されたリポジトリに悪意のあるコードが投稿された
* クローンは隔離され、GitHub またはメンテナー アカウントの明らかな侵害はありませんでした

参考:github securityの当時のツイート

これはgitを使っているとそのうち気がつくことですが、コミットユーザはgit configでのユーザ名とメールアドレスのみでコミットした人が真にその人だよという証明はしているわけではないんです。
つまり、簡単になりすましが出来てしまうんですよね。
しかしこれはgitの仕様といえば仕様です。

広く不特定多数の開発者を抱えているOSSリポジトリだと、上記仕様から勝手にコミッタになりすまして勝手にコミット…は出来なくはないという話で、今回それを本当にやろうとしてちょっと捻って悪用した人が居る…ということなんですね。

ということでなりすまし防止といえばおなじみの「公開鍵暗号方式での電子署名」ということでgitにもgithubにもそれが備わっており、コミット時に鍵で署名をすることでとりあえずなりすまし防止ができるという話です。
これでそのコミットが本人かどうかは署名を見ればわかるみたいな話になります。

これをやっておくとGithubのコミットログに「Verified」マークが付いて、真にこの人がコミットしたことがわかります。

前にgithubはセキュリティ関連でベーシック認証はナシになってsshを強制することになった経緯があったので、これもそのうち強制になるんじゃないかな…なんて思います。

とはいえ、これをやっておけば諸問題が解決するかと言うとそうではなく鍵が奪われて気が付かなかった場合はどうしょうもないですし、コミットする人が悪に目覚めたらこれも仕方ないですし、そもそも署名が本当にその人の署名なのかどうか(つまりオレオレではないのか?)ということも複数のソースからたどって(あるいは本人から公開鍵を聞いたり、keybase.ioに登録してたらそれを見るとか…)確認する必要がありますし、これはもうOSS管理者(?)の気づきの能力に掛かってしまうところだとは思います。。

あとはこの辺りのコミット時の署名を有名所のコミッタの人にやってもらうこと、よくCloneされそうなリポジトリは署名コミット以外は受け付けなくしてもらうとか、クローンしたりサブモジュールとして追加するときは「その人の責任で」署名を検証するとかやってもらえれば、なりすましに関わる問題は最低限防げるのかなと言った感じではありますね。。。

gpgで署名用の鍵ペアを作る

この辺りの流れは実はGithubに細かく書かれています。
新しい GPG キーを生成する

しかし、今回は楕円曲線でEd25519(Curve 25519)な鍵ペアを作りたいのでそのようにします。
アルゴリズムはRSAやNIST P-XXXでもいいのですが、もうEd25519が通らないようなOSなりアーキテクチャはあまり無いと思いますので…

gpgは鍵自身の「証明」用鍵をまず作ってから(=主鍵)、用途ごとに副鍵を作るのが良いとされています。
今回はそのようにします。

ということでまず主鍵の生成。ここでは「証明」以外の機能は持たせないので、「Certify」のみとするのがポイント
あと、Ed25519は楕円曲線(ECC)系なので–expertをつけないと出てこない場合があります。
主鍵を作る途中で主鍵に設定するパスワードを聞かれます

$ gpg --full-gen-key --expert
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

ご希望の鍵の種類を選択してください:
   (1) RSA と RSA (デフォルト)
   (2) DSA と Elgamal
   (3) DSA (署名のみ)
   (4) RSA (署名のみ)
   (7) DSA (機能をあなた自身で設定)
   (8) RSA (機能をあなた自身で設定)
   (9) ECC と ECC
  (10) ECC (署名のみ)
  (11) ECC (機能をあなた自身で設定)
  (13) 既存の鍵
  (14) カードに存在する鍵
あなたの選択は? 11

鍵ECDSA/EdDSAに認められた操作: Sign Certify Authenticate 
現在の認められた操作: Sign Certify 

   (S) 署名機能を反転する
   (A) 認証機能を反転する
   (Q) 完了

あなたの選択は? s

鍵ECDSA/EdDSAに認められた操作: Sign Certify Authenticate 
現在の認められた操作: Certify 

   (S) 署名機能を反転する
   (A) 認証機能を反転する
   (Q) 完了

あなたの選択は? q
ご希望の楕円曲線を選択してください:
   (1) Curve 25519
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
あなたの選択は? 1
鍵の有効期限を指定してください。
         0 = 鍵は無期限
      <n>  = 鍵は n 日間で期限切れ
      <n>w = 鍵は n 週間で期限切れ
      <n>m = 鍵は n か月間で期限切れ
      <n>y = 鍵は n 年間で期限切れ
鍵の有効期間は? (0) 0
鍵は無期限です
これで正しいですか? (y/N) y

GnuPGはあなたの鍵を識別するためにユーザIDを構成する必要があります。

本名: <名前>
電子メール・アドレス: <メールアドレス>
コメント: 
次のユーザIDを選択しました:
    "ユーザ名 <メールアドレス>"

名前(N)、コメント(C)、電子メール(E)の変更、またはOK(O)か終了(Q)? o
たくさんのランダム・バイトの生成が必要です。キーボードを打つ、マウスを動か
す、ディスクにアクセスするなどの他の操作を素数生成の間に行うことで、乱数生
成器に十分なエントロピーを供給する機会を与えることができます。
gpg: 鍵XXXXXXXXXXXを究極的に信用するよう記録しました
gpg: 失効証明書を 'xxxx' に保管しました。
公開鍵と秘密鍵を作成し、署名しました。

pub   ed25519 20xx-xx-xx [C]
      xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
uid                      ユーザ名 <メールアドレス>

ユーザ名やパスワードは、Githubの登録したものと同じにしておくと面倒がなくて良いですが、鍵を作った後でユーザ名やメールアドレスを追加することも出来ます。
(但し、名前を足すような感じになるので他の名前が知られたくない場合は、これはGithub専用の鍵としておいたほうが良いかもしれません)

次に署名のみの副鍵を生成します。(これをgitやgithubのコミット署名用として使います)

$ gpg --expert --edit-key メールアドレス 
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

秘密鍵が利用できます。

sec  ed25519/xxxxxxxxxxxxxxxxx
     作成: 20xx-xx-xx  有効期限: 無期限       利用法: C   
     信用: 究極        有効性: 究極
[  究極  ] (1). ユーザ名 <メールアドレス>

gpg> addkey
ご希望の鍵の種類を選択してください:
   (3) DSA (署名のみ)
   (4) RSA (署名のみ)
   (5) Elgamal (暗号化のみ)
   (6) RSA (暗号化のみ)
   (7) DSA (機能をあなた自身で設定)
   (8) RSA (機能をあなた自身で設定)
  (10) ECC (署名のみ)
  (11) ECC (機能をあなた自身で設定)
  (12) ECC (暗号化のみ)
  (13) 既存の鍵
  (14) カードに存在する鍵
あなたの選択は? 10
ご希望の楕円曲線を選択してください:
   (1) Curve 25519
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
あなたの選択は? 1
鍵の有効期限を指定してください。
         0 = 鍵は無期限
      <n>  = 鍵は n 日間で期限切れ
      <n>w = 鍵は n 週間で期限切れ
      <n>m = 鍵は n か月間で期限切れ
      <n>y = 鍵は n 年間で期限切れ
鍵の有効期間は? (0) 0
鍵は無期限です
これで正しいですか? (y/N) y
本当に作成しますか? (y/N) y
たくさんのランダム・バイトの生成が必要です。キーボードを打つ、マウスを動か
す、ディスクにアクセスするなどの他の操作を素数生成の間に行うことで、乱数生
成器に十分なエントロピーを供給する機会を与えることができます。

sec  ed25519/xxxxxxxxxxxxxxxxxxxxx
     作成: 20xx-xx-xx  有効期限: 無期限       利用法: C   
     信用: 究極        有効性: 究極
ssb  ed25519/yyyyyyyyyyyyyyyyyyyyy
     作成: 20xx-yy-yy  有効期限: 無期限       利用法: S   
[  究極  ] (1). ユーザ名 <メールアドレス>

gpg> save
$

最後にsaveでgpgの対話モードを終わることを忘れないようにしてください。

以下で確認。[S]がついていればそれは署名用の鍵で、[C]は認証用鍵を意味します

$ gpg -k

----------------------------
pub   ed25519 20xx-xx-xx [C]
      xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
uid           [  究極  ] ユーザ名 <メールアドレス>
sub   ed25519 20xx-yy-yy [S]

Githubに登録しているユーザ名やメールアドレスが異なる場合は次のようにして、鍵にユーザ名やメールアドレスを足すとOKです。

$ gpg --edit-key 今のユーザ名
gpg> key
sec  ed25519/xxxxxxxxxxxxxxxxxxxx
     作成: 2022-xx-xx  有効期限: 無期限       利用法: C   
     信用: 究極        有効性: 究極
ssb  ed25519/yyyyyyyyyyyyyyyyyyyy
     作成: 2022-yy-yy  有効期限: 無期限       利用法: S   
[  究極  ] (1).  ユーザ名 <メールアドレス>

gpg> key yyyyyyyyyyyyyyyyyyyy
gpg> adduid
本名: Githubユーザ名
電子メール・アドレス: Githubに登録しているメールアドレス
コメント: 
次のユーザIDを選択しました:
    "Githubユーザ名 Githubに登録しているメールアドレス"

名前(N)、コメント(C)、電子メール(E)の変更、またはOK(O)か終了(Q)? o

sec  ed25519/xxxxxxxxxxxxxxxxxxxxxx
     作成: 20xx-xx-xx  有効期限: 無期限       利用法: C   
     信用: 究極        有効性: 究極
ssb* ed25519/yyyyyyyyyyyyyyyyyyyyyyy
     作成: 20x-YY-YY  有効期限: 無期限       利用法: S   
[  究極  ] (1)  ユーザ名 <メールアドレス>
[  不明  ] (2). Githubユーザ名 <Githubに登録しているメールアドレス>

gpg> save
$

なお、ユーザ名が表示されている(番号)に. (ドット)が付いているのがメインIDとして使用されます。
このコマンドで追加した場合は、前の名前やメールアドレスも公開鍵に含められるのでそれを秘密にしておきたい場合は、Github用に主鍵を作ったほうがいいかもしれません。

作った公開鍵をGithubへ登録

先ほど作った副鍵の公開鍵をGithubに登録します。
まず副鍵のIDを知る必要があるので以下入力します。

$ gpg --list-secret-keys --keyid-format LONG
----------------------------
sec   ed25519/主鍵ID 2022-XX-XX [C]
      XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
uid                 [  究極  ] ユーザ名 <メールアドレス>
ssb   ed25519/副鍵ID 2022-YY-YY [S]

副鍵IDが分かったので、以下のようにしてASCIIで出力

gpg --armor --export 副鍵ID
-----BEGIN PGP PUBLIC KEY BLOCK-----
公開鍵の文字列
-----END PGP PUBLIC KEY BLOCK-----

Githubにサインインして、[Setting]-[SSH and GPG keys] [new gpg key]で追加。
これでGithubには登録OK。

当たり前ですが、公開鍵は公開して問題のないものです。
そして秘密鍵は絶対に秘密です。
公開鍵はなくそうが漏らそうが問題ないですが、秘密鍵だけはなくしたり漏らしたら失効しなきゃだめなので色々面倒なことになります。

git にコミットするときに鍵を使って署名するように指定

以下のようにします。

$ git config --local commit.gpgSign true
$ git config --local tag.gpgsign true
$ git config --local user.signingKey 副鍵ID

ボクの場合はリポジトリごとに使用ユーザを替えているのでグローバルにはしていません。
どんなリポジトリにも使いたい場合は –local を –globalにすればOKです。
(windowsとかの場合は、gpgのプログラムもgpg.programで指定しないといけないそうです)

あとは普通にいつものようにCommitします。
(git commit に-sをつけないといけないとかあるそうですが、自分の場合はつけなくても自動的に署名されました)
なお、Commitの際にPGPのパスワードを聞かれます。
ようはこのときにgitがgpgに「秘密鍵を使ってこのコミットを署名してくれ」とお願いして署名をするわけですね。

ローカルで確認する場合は以下のようにするとOK

$ git log --show-signature
commit 86ed1ea3d75b1961a05c2ad4c11fd29aa5c39cc9 (HEAD -> master)
gpg: 2022年08月04日 17時20分19秒 JSTに施された署名
gpg:                EDDSA鍵895298048177DF79E46BFE7989A86B70D99E6120を使用
gpg: "chromabox <chromarockjp@gmail.com>"からの正しい署名 [究極]
gpg:                 別名"chromarock <chromarockjp@gmail.com>" [究極]
Author: chromabox <chromarockjp@gmail.com>
Date:   Thu Aug 4 17:20:19 2022 +0900

    commit test

後はGithubにpushして、このようにコミットログに「Verified」と出てくればOK

登録したのにVerifiedが出てこない場合は、鍵のユーザ名やメールアドレスがGithubに登録したユーザ名とメールアドレスかを確認してみてください。違っているとだめです。

確認できたら、ついでにGithubの各リポジトリを署名したもののみコミット可としておきましょう。
より堅牢となります。

Githubから設定したいリポジトリを開き、[Settings]-[Branches]を選んで、[Branch protection rules]の[Add rules]を押します。
すると、ルール入力画面が出るので保護したいブランチ名(master)を入力して、[Require signed commits]にチェックを入れるとOK。これをするとコミット時に署名が必須となります。

参考:
ブランチ保護ルールを管理する

秘密鍵のバックアップ

他のマシンで同じ鍵を使用して署名など使わなければならない場合、秘密鍵をExportする必要があります。
以下のようにします

$ gpg --export-secret-keys --armor > gpg-private.keys

これを何らかの安全な手段で持っていった後、別PCでインポートするには

$ gpg --import gpg-private.keys

でOK
なお、これは秘密鍵なので絶対に漏らしてはいけません。

鍵類をGUIで確認管理したい

Ubuntuの場合、「ソフトウェアセンター」から「パスワードと鍵」を調べてインストールすればOK(22.04だとデフォルトで入っているかも)
Exportもできます。SSH用の鍵も管理できます。
この画面で鍵を信用するかどうかも設定できるので便利です

共有する 共有用にコピー