用 PGP 保護代碼完整性(六):在 Git 上使用 PGP
在本系列教程中,我們提供了一個使用 PGP 的實用指南,包括基本概念和工具、生成和保護你的密鑰。如果你錯過了前面的文章,你可以查看下面的鏈接。在這篇文章中,我們談一談在 Git 中如何集成 PGP、使用簽名的標籤,然後介紹簽名提交,最後添加簽名推送的支持。
Git 的核心特性之一就是它的去中心化本質 —— 一旦倉庫克隆到你的本地系統,你就擁有了項目的完整歷史,包括所有的標籤、提交和分支。然而由於存在著成百上千的克隆倉庫,如何才能驗證你下載的倉庫沒有被惡意的第三方做過篡改?你可以從 GitHub 或一些貌似官方的位置來克隆它們,但是如果有些人故意欺騙了你怎麼辦?
或者在你參與的一些項目上發現了後門,而 「Author」 行顯示是你乾的,然而你很確定 不是你乾的,會發生什麼情況?
為解決上述問題,Git 添加了 PGP 集成。簽名的標籤通過確認它的內容與創建這個標籤的開發者的工作站上的內容完全一致來證明倉庫的完整性,而簽名的提交幾乎是不可能在不訪問你的 PGP 密鑰的情況下能夠假冒你。
清單
- 了解簽名的標籤、提交和推送(必要)
- 配置 git 使用你的密鑰(必要)
- 學習標籤如何簽名和驗證(必要)
- 配置 git 總是簽名帶注釋標籤(推薦)
- 學習提交如何簽名和驗證工作(必要)
- 配置 git 總是簽名提交(推薦)
- 配置 gpg-agent 選項(必要)
考慮事項
git 實現了 PGP 的多級集成,首先從簽名標籤開始,接著介紹簽名提交,最後添加簽名推送的支持。
了解 Git 哈希
git 是一個複雜的東西,為了你能夠更好地掌握它如何集成 PGP,你需要了解什麼是」哈希「。我們將它歸納為兩種類型的哈希:樹哈希和提交哈希。
樹哈希
每次你向倉庫提交一個變更,對於倉庫中的每個子目錄,git 都會記錄它裡面所有對象的校驗和哈希 —— 內容(blobs)、目錄(trees)、文件名和許可等等。它只對每次提交中發生變更的樹和內容做此操作,這樣在只變更樹的一小部分時就不必去重新計算整個樹的校驗和。
然後再計算和存儲處於頂級的樹的校驗和,這樣如果倉庫的任何一部分發生變化,校驗和將不可避免地發生變化。
提交哈希
一旦創建了樹哈希,git 將計算提交哈希,它將包含有關倉庫和變更的下列信息:
- 樹哈希的校驗和
- 變更前樹哈希的校驗和(父級)
- 有關作者的信息(名字、email、創作時間)
- 有關提交者的信息(名字、email、提交時間)
- 提交信息
哈希函數
在寫這篇文章時,雖然研究一種更強大的、抗碰撞的演算法的工作正在進行,但 git 仍然使用的是 SHA1 哈希機制去計算校驗和。注意,git 已經包含了碰撞防範程序,因此認為對 git 成功進行碰撞攻擊仍然是不可行的。
帶注釋標籤和標籤簽名
在每個 Git 倉庫中,標籤允許開發者標記特定的提交。標籤可以是 「輕量級的」 —— 幾乎只是一個特定提交上的指針,或者它們可以是 「帶注釋的」,它自己將成為 git 樹中的項目。一個帶注釋標籤對象包含所有下列的信息:
- 成為標籤的提交的哈希的校驗和
- 標籤名字
- 關於打標籤的人的信息(名字、email、打標籤時間)
- 標籤信息
一個 PGP 簽名的標籤是一個帶有將所有這些條目封裝進一個 PGP 簽名的帶注釋標籤。當開發者簽名他們的 git 標籤時,他們實際上是向你保證了如下的信息:
- 他們是誰(以及他們為什麼應該被信任)
- 他們在簽名時的倉庫狀態是什麼樣:
- 標籤包含的提交的哈希
- 提交的哈希包含了頂級樹的哈希
- 頂級樹哈希包含了所有文件、內容和子樹的哈希
- 它也包含有關作者的所有信息
- 包含變更發生時的精確時間
- 標籤包含的提交的哈希
當你克隆一個倉庫並驗證一個簽名的標籤時,就是向你以密碼方式保證:倉庫中的所有內容、包括所有它的歷史,與開發者簽名時在它的計算機上的倉庫完全一致。
簽名的提交
簽名的提交與簽名的標籤非常類似 —— PGP 簽名的是提交對象的內容,而不是標籤對象的內容。一個提交簽名也給你提供了開發者簽名時開發者樹上的全部可驗證信息。標籤簽名和提交的 PGP 簽名提供了有關倉庫和它的完整歷史的完全一致的安全保證。
簽名的推送
為了完整起見,在這裡包含了簽名的推送這一功能,因為在你使用這個功能之前,需要在接收推送的伺服器上先啟用它。正如我們在上面所說過的,PGP 簽名一個 git 對象就是提供了開發者的 git 樹當時的可驗證信息,但不提供開發者對那個樹意圖相關的信息。
比如,你可以在你自己復刻的 git 倉庫的一個實驗分支上嘗試一個很酷的特性,為了評估它,你提交了你的工作,但是有人在你的代碼中發現了一個惡意的 bug。由於你的提交是經過正確簽名的,因此有人可能將包含有惡意 bug 的分支推入到 master 分支中,從而在生產系統中引入一個漏洞。由於提交是經過你的密鑰正確簽名的,所以一切看起來都是合理合法的,而當 bug 被發現時,你的聲譽就會因此而受到影響。
在 git push
時,為了驗證提交的意圖而不僅僅是驗證它的內容,添加了要求 PGP 推送簽名的功能。
配置 git 使用你的 PGP 密鑰
如果在你的鑰匙環上只有一個密鑰,那麼你就不需要再做額外的事了,因為它是你的默認密鑰。
然而,如果你有多個密鑰,那麼你必須要告訴 git 去使用哪一個密鑰。([fpr]
是你的密鑰的指紋):
$ git config --global user.signingKey [fpr]
注意:如果你有一個不同的 gpg2
命令,那麼你應該告訴 git 總是去使用它,而不是傳統的版本 1 的 gpg
:
$ git config --global gpg.program gpg2
如何使用簽名標籤
創建一個簽名的標籤,只要傳遞一個簡單地 -s
開關給 tag
命令即可:
$ git tag -s [tagname]
我們建議始終對 git 標籤簽名,這樣讓其它的開發者確信他們使用的 git 倉庫沒有被惡意地修改過(比如,引入後門):
如何驗證簽名的標籤
驗證一個簽名的標籤,只需要簡單地使用 verify-tag
命令即可:
$ git verify-tag [tagname]
如果你要驗證其他人的 git 標籤,那麼就需要你導入他的 PGP 公鑰。請參考 「可信任的團隊溝通」 一文中關於此主題的指導。
在拉取時驗證
如果你從項目倉庫的其它復刻中拉取一個標籤,git 將自動驗證簽名,並在合併操作時顯示結果:
$ git pull [url] tags/sometag
合併信息將包含類似下面的內容:
Merge tag 'sometag' of [url]
[Tag message]
# gpg: Signature made [...]
# gpg: Good signature from [...]
配置 git 始終簽名帶注釋標籤
很可能的是,你正在創建一個帶注釋標籤,你應該去簽名它。強制 git 始終簽名帶注釋的標籤,你可以設置一個全局配置選項:
$ git config --global tag.forceSignAnnotated true
或者,你始終記得每次都傳遞一個 -s
開關:
$ git tag -asm "Tag message" tagname
如何使用簽名的提交
創建一個簽名的提交很容易,但是將它納入到你的工作流中卻很困難。許多項目使用簽名的提交作為一種 「Committed-by:」 的等價行,它記錄了代碼來源 —— 除了跟蹤項目歷史外,簽名很少有人去驗證。在某種意義上,簽名的提交用於 「篡改證據」,而不是 git 工作流的 「篡改證明」。
為創建一個簽名的提交,你只需要 git commit
命令傳遞一個 -S
標誌即可(由於它與另一個標誌衝突,所以改為大寫的 -S
):
$ git commit -S
我們建議始終使用簽名提交,並要求項目所有成員都這樣做,這樣其它人就可以驗證它們(下面就講到如何驗證)。
如何去驗證簽名的提交
驗證簽名的提交需要使用 verify-commit
命令:
$ git verify-commit [hash]
你也可以查看倉庫日誌,要求所有提交簽名是被驗證和顯示的:
$ git log --pretty=short --show-signature
在 git 合併時驗證提交
如果項目的所有成員都簽名了他們的提交,你可以在合併時強制進行簽名檢查(然後使用 -S
標誌對合併操作本身進行簽名):
$ git merge --verify-signatures -S merged-branch
注意,如果有一個提交沒有簽名或驗證失敗,將導致合併操作失敗。通常情況下,技術是最容易的部分 —— 而人的因素使得項目中很難採用嚴格的提交驗證。
如果你的項目在補丁管理上採用郵件列表
如果你的項目在提交和處理補丁時使用一個郵件列表,那麼一般很少使用簽名提交,因為通過那種方式發送時,簽名信息將會丟失。對提交進行簽名仍然是非常有用的,這樣其他人就能引用你託管在公開 git 樹作為參考,但是上游項目接收你的補丁時,仍然不能直接使用 git 去驗證它們。
儘管,你仍然可以簽名包含補丁的電子郵件。
配置 git 始終簽名提交
你可以告訴 git 總是簽名提交:
git config --global commit.gpgSign true
或者你每次都記得給 git commit
操作傳遞一個 -S
標誌(包括 —amend
)。
配置 gpg-agent 選項
GnuPG agent 是一個守護工具,它能在你使用 gpg 命令時隨時自動啟動,並運行在後台來緩存私鑰的密碼。這種方式讓你只需要解鎖一次密鑰就可以重複地使用它(如果你需要在一個自動腳本中籤署一組 git 操作,而不想重複輸入密鑰,這種方式就很方便)。
為了調整緩存中的密鑰過期時間,你應該知道這兩個選項:
default-cache-ttl
(秒):如果在 TTL 過期之前再次使用同一個密鑰,這個倒計時將重置成另一個倒計時周期。預設值是 600(10 分鐘)。max-cache-ttl
(秒):自首次密鑰輸入以後,不論最近一次使用密鑰是什麼時間,只要最大值的 TTL 倒計時過期,你將被要求再次輸入密碼。它的預設值是 30 分鐘。
如果你認為這些預設值過短(或過長),你可以編輯 ~/.gnupg/gpg-agent.conf
文件去設置你自己的值:
# set to 30 minutes for regular ttl, and 2 hours for max ttl
default-cache-ttl 1800
max-cache-ttl 7200
補充:與 ssh 一起使用 gpg-agent
如果你創建了一個 A(驗證)密鑰,並將它移到了智能卡,你可以將它用到 ssh 上,為你的 ssh 會話添加一個雙因子驗證。為了與 agent 溝通你只需要告訴你的環境去使用正確的套接字文件即可。
首先,添加下列行到你的 ~/.gnupg/gpg-agent.conf
文件中:
enable-ssh-support
接著,添加下列行到你的 .bashrc
文件中:
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
為了讓改變生效,你需要殺掉正在運行的 gpg-agent 進程,並重新啟動一個新的登入會話:
$ killall gpg-agent
$ bash
$ ssh-add -L
最後的命令將列出代表你的 PGP Auth 密鑰的 SSH(注釋應該會在結束的位置顯示: cardno:XXXXXXXX,表示它來自智能卡)。
為了啟用 ssh 的基於密鑰的登入,只需要在你要登入的遠程系統上添加 ssh-add -L
的輸出到 ~/.ssh/authorized_keys
中。祝賀你,這將使你的 SSH 登入憑據更難以竊取。
此外,你可以從公共密鑰伺服器上下載其它人的基於 PGP 的 ssh 公鑰,這樣就可以賦予他登入 ssh 的權利:
$ gpg --export-ssh-key [keyid]
如果你有讓開發人員通過 ssh 來訪問 git 倉庫的需要,這將讓你非常方便。下一篇文章,我們將提供像保護你的密鑰那樣保護電子郵件帳戶的小技巧。
via: https://www.linux.com/blog/learn/pgp/2018/3/protecting-code-integrity-pgp-part-6-using-pgp-git
作者:KONSTANTIN RYABITSEV 譯者:qhwdw 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive