[英]Why does git sign with GPG keys rather than using SSH keys?
SSH 和 GPG 非對稱密鑰有什么區別?為什么 git 支持使用 GPG 簽名而不是使用 SSH 代理?
2021 年更新:
OpenSSH 8.2+ 可用(例如打包在Git For Windows 2.33.1 中),並且“現在可以使用您的 SSH 密鑰簽署任意數據”( Andrew Ayer ),包括 Git 中的提交。
Andrew 指出git/git
PR 1041 “ssh 簽名:使用 ssh-keygen 通過 SSH 密鑰添加提交和標簽簽名/驗證” , 現在使用 Git 2.34 (2021 年 11 月)
gpg.format
將有一個新值“ ssh
”
將
gpg.format = ssh
和user.signingkey
設置為 ssh 公鑰字符串(例如來自 authorized_keys 文件),並且可以使用 ssh-agent 中的私鑰對提交/標簽進行簽名。
安德魯補充道:
始終警惕將加密密鑰重新用於不同的協議。 如果不小心操作,就會存在跨協議攻擊的風險。
例如,如果 Git 簽名的消息結構與 SSH 協議消息的結構相似,則攻擊者可能能夠通過盜用 SSH 腳本的簽名來偽造 Git 工件。
幸運的是,SSH 協議消息的結構和由 ssh-keygen 簽名的消息的結構非常不同,因此沒有混淆的風險。
這來自:
Git 2.34(2021 年第四季度):使用 ssh 公共加密進行對象和推送證書簽名。
請參閱Fabian Stelzer ( FStelzer
)的提交1bfb57f 、提交f265f2d 、 提交 3326a78 、 提交 facca53 、 提交 4838f62 、 提交 fd9e226 、 提交 29b3157 、 提交 64625c7 、 提交 b5726a5 (2021 年 9 月 10 日)。
(由Junio C Hamano -- gitster
--在提交 18c6653中合並,2021 年 10 月 25 日)
ssh signing
:使用 ssh-keygen 驗證簽名簽字人:Fabian Stelzer
為了驗證 ssh 簽名,我們首先調用
ssh-keygen -Y find-principal
以通過其來自allowedSignersFile
的公鑰查找簽名主體。
如果找到密鑰,那么我們進行驗證。
否則我們只驗證簽名而不能驗證簽名者的身份。驗證使用
gpg.ssh.allowedSignersFile
(參見ssh-keygen(1)
"ALLOWED SIGNERS"),其中包含有效的公鑰和主體(通常是user@domain
)。
根據環境,此文件可以由單個開發人員管理,或者例如由中央存儲庫服務器從具有推送訪問權限的已知 ssh 密鑰生成。
該文件通常存儲在存儲庫之外,但如果存儲庫只允許簽名提交/推送,用戶可能會選擇將其存儲在存儲庫中。要撤銷密鑰,請將不帶主體前綴的公鑰放入
gpg.ssh.revocationKeyring
或生成 KRL(參見ssh-keygen(1)
"KEY REVOCATION LISTS")。
關於信任誰進行驗證的考慮與使用allowedSignersFile
。也可以對這些文件使用 SSH CA 密鑰。
在主體和密鑰之間添加“cert-authority
”作為密鑰選項,以將其標記為 CA,並且所有由它簽名的密鑰都對該 CA 有效。
請參閱ssh-keygen(1)
中的“證書”。
git config
現在包含在其手冊頁中:
gpg.ssh.allowedSignersFile
包含您願意信任的 ssh 公鑰的文件。 該文件由一行或多行主體組成,后跟一個 ssh 公鑰。
例如:user1@example.com,user2@example.com ssh-rsa AAAAX1...
有關詳細信息,請參閱ssh-keygen(1)
“允許的簽名者”。
主體僅用於識別密鑰,在驗證簽名時可用。SSH 沒有像 gpg 那樣的信任級別概念。 為了能夠區分有效簽名和受信任的簽名,簽名驗證的信任級別設置為
fully
,當公鑰存在於allowedSignersFile
中時。
否則信任級別undefined
並且 git verify-commit/tag 將失敗。該文件可以設置在存儲庫之外的位置,並且每個開發人員都維護自己的信任庫。 中央存儲庫服務器可以從具有推送訪問權限的 ssh 密鑰自動生成此文件,以驗證代碼。
在公司環境中,此文件可能是在全球位置從已經處理開發人員 ssh 密鑰的自動化生成的。僅允許簽名提交的存儲庫可以使用相對於工作樹頂層的路徑將文件存儲在存儲庫本身中。 這樣,只有擁有有效密鑰的提交者才能在密鑰環中添加或更改密鑰。
使用帶有 cert-authority 選項的 SSH CA 密鑰(請參閱
ssh-keygen(1)
"CERTIFICATES")也是有效的。
gpg.ssh.revocationFile
SSH KRL 或已撤銷的公鑰列表(不帶主體前綴)。
有關詳細信息,請參閱ssh-keygen(1)
。
如果在此文件中找到公鑰,則它將始終被視為具有“從不”的信任級別,並且簽名將顯示為無效。
使用 Git 2.35 (Q1 2022),擴展使用 SSH 密鑰對對象的簽名,並學會在驗證時注意密鑰有效時間范圍。
請參閱Fabian Stelzer ( FStelzer
)的提交50992f9 、提交122842f 、 提交 dd3aa41 、 提交 4bbf378 、 提交 6393c95 、 提交 30770aa 、 提交 0276943 、 提交 cafd345 、 提交 5a2c1c0 (2021 年 12 月 9 日)。
(由Junio C Hamano -- gitster
--在提交 d2f0b72中合並,2021 年 12 月 21 日)
ssh signing
:使驗證提交考慮密鑰生命周期簽字人:Fabian Stelzer
如果在
allowedSigners
文件中為此簽名密鑰配置了有效的之前/之后日期,則驗證應檢查密鑰在提交時是否有效。
這允許優雅的密鑰翻轉和撤銷密鑰,而不會使所有先前的提交無效。
此功能需要 openssh > 8.8。
較舊的 ssh-keygen 版本將簡單地忽略此標志並使用當前時間。
嚴格來說,此功能在 8.7 中可用,但由於 8.7 有一個錯誤,使其無法在另一個需要的調用中使用,因此我們需要 8.8。時間戳信息存在於大多數
check_signature
調用中。
但是簽名者身份不是。
稍后我們將需要簽名者電子郵件/姓名才能實現“首次使用時信任”功能。
由於有效載荷包含所有必要的信息,我們可以從那里解析它。
調用者只需要通過在signature_check
結構中設置payload_type
來為我們提供一些關於有效載荷的信息。
- 添加
payload_type
字段&枚舉和payload_timestamp
到struct `signature_check- 如果我們知道有效負載類型,則在尚未設置時填充時間戳
- 將用戶時區中的
-Overify-time={payload_timestamp}
傳遞給所有 ssh-keygen 驗證調用- 驗證提交時設置有效負載類型
- 為已過期、尚未生效以及提交日期在密鑰有效性之外和之內的密鑰添加測試
git config
現在包含在其手冊頁中:
從 OpensSSH 8.8 開始,此文件允許使用 valid-after 和 valid-before 選項指定密鑰生存期。
如果簽名密鑰在簽名創建時有效,Git 會將簽名標記為有效。
這允許用戶更改簽名密鑰,而不會使所有先前制作的簽名無效。
而且,仍然使用 Git 2.35(2022 年第一季度),使用 ssh 密鑰的加密簽名可以通過使用“ key::
”前綴機制為名稱不以“ ssh-
”前綴開頭的密鑰類型指定文字密鑰
(例如“ key::ecdsa-sha2-nistp256
”)。
請參閱Fabian Stelzer ( FStelzer
)的提交 3b4b5a7和提交 350a251 (2021 年 11 月 19 日)。
(由Junio C Hamano -- gitster
--在提交 ee1dc49中合並,2021 年 12 月 21 日)
ssh signing
:支持非 ssh-* 密鑰類型簽字人:Fabian Stelzer
用於 ssh 簽名的
user.signingKey
配置支持包含密鑰的文件的路徑,或者為方便起見,支持帶有 ssh 公鑰的文字字符串。為了區分這兩種情況,我們檢查前幾個字符是否包含“
ssh-
”,這不太可能是路徑的開始。
ssh 支持其他不以“ssh-
”為前綴的密鑰類型,當前將被視為文件路徑,因此無法加載。
為了解決這個問題,我們將前綴檢查移到它自己的函數中,並為文字 ssh 鍵引入前綴key::
。
這樣我們就不需要在新的密鑰類型可用時添加它們。
保留現有的ssh-
前綴以與當前用戶配置兼容,但從官方文檔中刪除以阻止其使用。
git config
現在包含在其手冊頁中:
如果
gpg.format
設置為ssh
,則它可以包含您的私有 ssh 密鑰或使用 ssh-agent 時的公共密鑰的路徑。 或者,它可以直接包含前綴為key::
的公鑰(例如:“key::ssh-rsa XXXXXX identifier
”)。私鑰需要通過 ssh-agent 提供。
如果未設置 git 將調用gpg.ssh.defaultKeyCommand
(例如:“ssh-add -L
”)並嘗試使用第一個可用的密鑰。為了向后兼容,以“
ssh-
”開頭的原始密鑰,例如“ssh-rsa XXXXXX identifier
”,被視為“key::ssh-rsa XXXXXX identifier
”,但這種形式已被棄用; 改用key::
形式。
“ git merge $signed_tag
” ( man )開始從它意外使用的默認合並消息中刪除標簽消息,這已在 Git 2.35 (Q1 2022) 中得到糾正。
請參閱Taylor Blau ( ttaylorr
)的提交 c39fc06 (2022 年 1 月 10 日)。
(由Junio C Hamano -- gitster
--在提交 cde28af中合並,2022 年 1 月 12 日)
fmt-merge-msg
: 使用簽名標簽防止 use-after-free報告人:Linus Torvalds
簽字人:Taylor Blau
合並簽名標簽時,
fmt_merge_msg_sigs()
負責使用簽名標簽的名稱、它們的簽名以及這些簽名的有效性填充合並消息的正文。在0276943 (“ssh 簽名:使用 sigc struct 傳遞有效負載”,2021-12-09,Git v2.35.0-rc0 - 批次 #4中列出的合並)中,教導
check_signature()
通過 sigc 傳遞對象有效負載struct 而不是單獨傳遞有效負載緩沖區。實際上, 0276943導致
buf
和sigc.payload
指向內存中的同一區域。
這會導致fmt_tag_signature()
出現問題,它想要從這個位置讀取,因為它是由signature_check_clear()
預先釋放的(它通過 sigc 的payload
成員釋放它)。這使得
fmt_tag_signature()
中的后續使用成為釋放后使用。因此,合並消息不包含任何簽名標簽的正文。
幸運的是,它們也往往不包含垃圾,因為 strstr() 對fmt_tag_signature()
中的對象緩沖區的結果是受保護的:const char *tag_body = strstr(buf, "\n\n"); if (tag_body) { tag_body += 2; strbuf_add(tagbuf, tag_body, buf + len - tag_body); }
通過等待調用
signature_check_clear()
直到可以安全地丟棄其內容來解決此問題。
通過確保我們也可以在 fmt-merge-msg 的輸出中找到簽名的標簽消息,來加強我們在該領域的任何未來回歸。
原始答案(2017):在 Git 中簽署任何東西的第一個概念在ec4465a,Git v0.99,2005 年 4 月(幾乎從一開始)中就被引用了
/**
* A signature file has a very simple fixed format: three lines
* of "object <sha1>" + "type <typename>" + "tag <tagname>",
* followed by some free-form signature that git itself doesn't
* care about, but that can be verified with gpg or similar.
**/
所以你的問題有腿。
第一個簽名提交使用 gpg,但可以使用其他任何東西( 提交 65f0d0e ):
#!/bin/sh
object=${2:-$(cat .git/HEAD)}
type=$(cat-file -t $object) || exit 1
( echo -e "object $object\ntype $type\ntag $1\n"; cat ) > .tmp-tag
rm -f .tmp-tag.asc
gpg -bsa .tmp-tag && cat .tmp-tag.asc >> .tmp-tag
git-mktag < .tmp-tag
#rm .tmp-tag .tmp-tag.sig
從技術上講,您可以使用gpg 代替 ssh 。 不過,我並沒有經常看到相反的情況。
但是您可以將 ssh 密鑰對與 PGP/GPG 一起使用。
這意味着第一個驗證腳本可能仍然有效( commit f336e71 )......除了它需要 PGP 注釋:
#!/bin/sh
GIT_DIR=${GIT_DIR:-.git}
tag=$1
[ -f "$GIT_DIR/refs/tags/$tag" ] && tag=$(cat "$GIT_DIR/refs/tags/$tag")
git-cat-file tag $tag > .tmp-vtag || exit 1
cat .tmp-vtag | sed '/-----BEGIN PGP/Q' | gpg --verify .tmp-vtag -
rm -f .tmp-vtag
所以,“為什么 git 使用 GPG 密鑰而不是使用 SSH 密鑰進行簽名?”:這是 GPG 的本意,與 SSH 不同,SSH不能單獨使用 openssh(它需要 openssl) 。
正如torek所評論的,理論上使用 SSH 是可行的,只是不方便。
此外,PGP 有額外的功能(不是 Git 直接使用它們——Git 本身只是調用一些外部軟件——但在這些情況下,密鑰撤銷之類的東西很有用)。
使用 Git 2.37(2022 年第三季度)解釋ssh.defaultKeyCommand
:
請參閱Fabian Stelzer ( FStelzer
)的提交 ce18a30 (2022 年 6 月 8 日)。
(由Junio C Hamano -- gitster
--在提交 686790f中合並,2022 年 6 月 15 日)
gpg docs
:解釋更好地使用 ssh.defaultKeyCommand簽字人:Fabian Stelzer
對
gpg.ssh.defaultKeyCommand
使用ssh-add -L
不是一個好的建議。
它可能會根據已知鍵的順序切換鍵,並且僅支持ssh-*
並且不支持ecdsa
或其他鍵。澄清我們期望一個以
key::
為前綴的文字鍵,給出有效的示例用例並將user.signingKey
作為首選選項。
git config
現在包含在其手冊頁中:
要求簽名。 成功退出后,其輸出的第一行中會出現一個以
key::
為前綴的有效 ssh 公鑰。 當靜態配置user.signingKey
不切實際時,這允許腳本對正確的公鑰進行動態查找。 例如,當密鑰或 SSH 證書頻繁輪換或正確密鑰的選擇取決於 git 未知的外部因素時。
並且,由Fabian Stelzer ( FStelzer
) 提交 ce18a30 (2022 年 6 月 8 日)。
(由Junio C Hamano -- gitster
--在提交 686790f中合並,2022 年 6 月 15 日)
686790f6c1
:合並分支'fs/ssh-default-key-command-doc'
文檔更新。 * fs/ssh-default-key-command-doc: gpg docs: 解釋更好地使用
ssh.defaultKeyCommand
gpg.ssh.defaultKeyCommand
:此命令將在未設置
user.signingkey
並且請求 ssh 簽名時運行。
成功退出后,其輸出的第一行中會出現一個以key::
為前綴的有效 ssh 公鑰。
當靜態配置user.signingKey
不切實際時,這允許腳本對正確的公鑰進行動態查找。例如,當密鑰或 SSH 證書頻繁輪換或正確密鑰的選擇取決於 git 未知的外部因素時。
您不應該使用ssh
簽署提交的原因是密碼學的常見規則之一:您不應該為不同的應用程序/用例使用相同的密鑰。
在 SSH 中,您使用密鑰進行身份驗證,但這與簽署您的提交不同。 為此,GPG 更適合,因為它已經廣泛用於簽署電子郵件、文件等。
一個可能的原因是不是每個使用 git 的人都在使用 ssh。
你可以創建一個 git repo 並且永遠不要讓它離開你的本地磁盤。 您可以使用 git 協議、http、https 或網絡文件系統……這些都不涉及 ssh,但您仍然可以簽署提交,因為這與任何網絡傳輸或提交的其他推/拉共享無關.
FWIW,允許使用 SSH 密鑰進行簽名(和驗證)的工作正在進行中: https ://lore.kernel.org/git/pull.1041.git.git.1625559593910.gitgitgadget@gmail.com/
這可能在有限的(例如公司)環境中很有價值,其中 git 目前是處理 GPG 的唯一原因,並且只堅持使用 SSH 可以為用戶節省一些密鑰管理和軟件管理開銷......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.