用 PGP 保護程式碼完整性(六):在 Git 上使用 PGP

2019-01-07 00:09:00

我們繼續我們的 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 ttldefault-cache-ttl 1800max-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 倉庫的需要,這將讓你非常方便。下一篇文章,我們將提供像保護你的金鑰那樣保護電子郵件帳戶的小技巧。