使用在第三方儲存庫上發布的外部相依性和外掛會讓您的建置面臨風險。特別是,您需要知道哪些二進位檔會以遞移方式引入,以及它們是否合法。為了降低安全風險並避免在您的專案中整合受損的相依性,Gradle 支援相依性驗證

相依性驗證本質上是一個不方便使用的功能。這表示每當您要更新相依性時,建置很可能會失敗。這表示合併分支會更困難,因為每個分支可能具有不同的相依性。這表示您會想要將其關閉。

那麼,您為什麼要費心呢?

相依性驗證是關於您取得和運送的內容的信任

沒有相依性驗證,攻擊者很容易透過新增惡意相依性來危害您的供應鏈。有許多真實世界的範例,工具會因新增惡意相依性而受到危害。相依性驗證的目的是保護您免於這些攻擊,方法是強制您確保建置中包含的人工製品就是您預期的。然而,這並非要防止您包含容易受攻擊的相依性。

在安全性與便利性之間找到適當的平衡很難,但 Gradle 會嘗試讓您選擇對您來說「正確的層級」。

相依性驗證包含兩個不同且互補的操作

  • 檢查碼驗證,允許確認相依性的完整性

  • 簽章驗證,允許確認相依性的來源

Gradle 預設支援雜湊值和簽章驗證,但預設不會執行相依性驗證。本節將引導您針對需求正確設定相依性驗證。

此功能可用於

  • 偵測受損的相依性

  • 偵測受損的外掛程式

  • 偵測本機相依性快取中被竄改的相依性

啟用相依性驗證

驗證元資料檔

目前相依性驗證元資料的唯一來源是此 XML 設定檔。未來版本的 Gradle 可能會包含其他來源(例如透過外部服務)。

一旦發現相依性驗證的設定檔,就會自動啟用相依性驗證。此設定檔位於 $PROJECT_ROOT/gradle/verification-metadata.xml。此檔案至少包含下列內容

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <verify-metadata>true</verify-metadata>
      <verify-signatures>false</verify-signatures>
    </configuration>
</verification-metadata>

這樣一來,Gradle 會使用 雜湊值 驗證所有成品,但不會驗證 簽章。Gradle 會驗證使用其相依性管理引擎下載的任何成品,其中包括但不限於

  • 建置期間使用的成品檔(例如 jar 檔、zip 檔…​)

  • 元資料成品(POM 檔、Ivy 描述檔、Gradle 模組元資料)

  • 外掛程式(專案和設定外掛程式)

  • 使用進階相依性解析 API 解析的成品

Gradle 不會 驗證變更的相依性(特別是 SNAPSHOT 相依性)或本機產生的成品(通常是在建置過程中產生的 jar 檔),因為它們的雜湊值和簽章本質上會一直變更。

使用如此最少化的設定檔,使用任何外部相依性或外掛程式的專案會立即開始失敗,因為它不包含任何要驗證的雜湊值。

相依性驗證範圍

相依性驗證設定是全域性的:使用單一檔案來設定整個建置的驗證。特別是,(子)專案和 buildSrc 都使用相同的檔案。

如果使用包含的建置

  • 會使用目前建置的設定檔進行驗證

  • 因此,如果包含的建置本身使用驗證,則會忽略其設定而採用目前的設定

  • 這表示包含組建類似於升級相依性:它可能需要您更新目前的驗證中繼資料

因此,開始的簡單方法是為現有組建產生最小組態

組態主控台輸出

預設情況下,如果相依性驗證失敗,Gradle 會產生一個關於驗證失敗的小摘要,以及一個包含失敗完整資訊的 HTML 報告。如果您的環境無法讀取此 HTML 報告檔案(例如,如果您在 CI 上執行組建,且無法輕易擷取遠端人工製品),Gradle 提供一種方式來選擇詳細的主控台報告。為此,您需要將此 Gradle 屬性新增到您的 gradle.properties 檔案

org.gradle.dependency.verification.console=verbose

引導相依性驗證

值得一提的是,儘管 Gradle 可以為您產生相依性驗證檔案,但您應該隨時檢查 Gradle 為您產生的內容,因為您的組建可能已經包含您不知道的受損相依性。請參閱適當的 檢查碼驗證簽章驗證 部分,以取得更多資訊。

如果您計畫使用 簽章驗證,請也閱讀文件中的 對應部分

引導可用於從頭建立檔案,或用新資訊更新現有檔案。因此,建議您在開始引導後,始終使用相同的參數。

相依性驗證檔案可以用下列 CLI 指令產生

gradle --write-verification-metadata sha256 help

write-verification-metadata 旗標需要您要產生的 檢查碼 清單,或 簽章pgp

執行此命令列會導致 Gradle

  • 解析所有 可解析組態,其中包括

    • 根專案的組態

    • 所有子專案的組態

    • buildSrc 的組態

    • 包含的組建組態

    • 外掛使用的組態

  • 下載解析期間發現的所有人工製品

  • 計算要求的檢查碼,並可能驗證簽章,具體取決於您的要求

  • 在組建結束時,產生組態檔案,其中將包含推斷的驗證中繼資料

因此,verification-metadata.xml 檔案將用於後續組建來驗證相依性。

有些相依性是 Gradle 無法用這種方式發現的。特別是,您會注意到上述 CLI 使用了 help 工作。如果您未指定任何工作,Gradle 會自動執行預設工作,並在組建結束時產生組態檔案。

差異在於 Gradle 可能會根據您執行的任務發現更多相依項和成品。事實上,Gradle 無法自動發現分離組態,這些組態基本上是作為任務執行內部實作細節解析的相依項圖表:它們特別不會宣告為任務的輸入,因為它們實際上會在執行時間相依於任務的組態。

開始的良好方法是只使用最簡單的任務 help,它將發現盡可能多的內容,如果後續的建置因驗證錯誤而失敗,您可以重新執行產生,並使用適當的任務來「發現」更多相依項。

Gradle 也不會驗證使用自己 HTTP 客戶端的外掛程式之檢查碼或簽章。只有使用 Gradle 提供的基礎架構來執行要求的外掛程式會看到它們的要求已驗證。

使用產生進行增量更新

Gradle 產生的驗證檔案對其所有內容都有嚴格的順序。它也使用現有狀態的資訊將變更限制在嚴格的最小值。

這表示產生實際上是更新驗證檔案的便利工具

  • Gradle 產生的檢查碼條目會有明確的 origin,開頭為「由 Gradle 產生」,這是需要檢閱條目的良好指標,

  • 手動新增的條目會立即列入考量,並在寫入檔案後出現在正確的位置,

  • 檔案的標頭註解會保留,亦即根 XML 節點之前的註解。這讓您可以擁有授權標頭或說明要使用哪些任務和參數來產生該檔案。

有了上述好處,只要再次產生檔案並檢閱變更,就能輕鬆列入新的相依項或相依項版本。

使用乾式執行

預設情況下,開機是增量的,表示如果您執行多次,資訊會新增到檔案,特別是您可以依賴 VCS 來檢查差異。在某些情況下,您可能只想查看產生的驗證元資料檔案會是什麼樣子,而不會實際變更現有的檔案或覆寫它。

為此目的,您只要新增 --dry-run

gradle --write-verification-metadata sha256 help --dry-run

然後,它不會產生 verification-metadata.xml 檔案,而是會產生一個新檔案,稱為 verification-metadata.dryrun.xml

由於 --dry-run 沒有執行任務,因此會快很多,但它會遺漏任務執行時間發生的任何解析。

停用元資料驗證

預設情況下,Gradle 不僅會驗證人工製品(jar、…​),還會驗證與這些人工製品相關的元資料(通常是 POM 檔案)。驗證此項可確保最高層級的安全性:元資料檔案通常會說明將包含哪些傳遞依賴項,因此受損的元資料檔案可能會導致在圖表中引入不需要的依賴項。然而,由於會驗證所有人工製品,因此您通常可以輕易發現此類人工製品,因為它們會導致檢查碼驗證失敗(檢查碼將會從驗證元資料中遺失)。由於元資料驗證會大幅增加組態檔案的大小,因此您可能想要停用元資料驗證。如果您了解這樣做的風險,請在組態檔案中將 <verify-metadata> 旗標設定為 false

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <verify-metadata>false</verify-metadata>
      <verify-signatures>false</verify-signatures>
    </configuration>
    <!-- the rest of this file doesn't need to declare anything about metadata files -->
</verification-metadata>

驗證依賴項檢查碼

檢查碼驗證可讓您確保人工製品的完整性。這是 Gradle 能為您執行的最簡單動作,用於確認您使用的產品未經竄改。

Gradle 支援 MD5、SHA1、SHA-256 和 SHA-512 檢查碼。不過,現今只有 SHA-256 和 SHA-512 檢查碼被視為安全。

新增人工製品的檢查碼

外部元件會以 GAV 座標來識別,然後每個人工製品會以其檔案名稱來識別。若要宣告人工製品的檢查碼,您需要在驗證元資料檔案中新增對應的區段。例如,若要宣告 Apache PDFBox 的檢查碼。GAV 座標為

  • 群組 org.apache.pdfbox

  • 名稱 pdfbox

  • 版本 2.0.17

使用此依賴項會觸發 2 個不同檔案的下載

  • pdfbox-2.0.17.jar,這是主要人工製品

  • pdfbox-2.0.17.pom,這是與此人工製品相關的元資料檔案

因此,您需要為這兩個檔案宣告檢查碼(除非您已 停用元資料驗證

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <verify-metadata>true</verify-metadata>
      <verify-signatures>false</verify-signatures>
   </configuration>
   <components>
      <component group="org.apache.pdfbox" name="pdfbox" version="2.0.17">
         <artifact name="pdfbox-2.0.17.jar">
            <sha512 value="7e11e54a21c395d461e59552e88b0de0ebaf1bf9d9bcacadf17b240d9bbc29bf6beb8e36896c186fe405d287f5d517b02c89381aa0fcc5e0aa5814e44f0ab331" origin="PDFBox Official site (https://pdfbox.apache.org/download.cgi)"/>
         </artifact>
         <artifact name="pdfbox-2.0.17.pom">
            <sha512 value="82de436b38faf6121d8d2e71dda06e79296fc0f7bc7aba0766728c8d306fd1b0684b5379c18808ca724bf91707277eba81eb4fe19518e99e8f2a56459b79742f" origin="Generated by Gradle"/>
         </artifact>
      </component>
   </components>
</verification-metadata>

從何處取得檢查碼?

一般來說,檢查碼會與人工製品一同發布在公開儲存庫中。然而,如果儲存庫中的依賴項遭到入侵,其檢查碼也可能會遭到入侵,因此建議從其他地方取得檢查碼,通常是程式庫本身的網站。

事實上,將人工製品的檢查碼發布在與人工製品本身寄存的伺服器不同的伺服器上是一種良好的安全措施:要同時入侵儲存庫官方網站上的程式庫會更加困難。

在上述範例中,檢查碼發布在 JAR 的網站上,但 POM 檔案沒有。這就是為什麼通常較容易 讓 Gradle 產生檢查碼,並透過仔細檢閱產生的檔案來驗證。

在這個範例中,我們不僅可以檢查雜湊值是否正確,還可以從官方網站找到它,這就是我們將 sha512 元素中 origin 屬性的值從 Generated by Gradle 變更為 PDFBox Official site 的原因。變更 origin 可以讓使用者知道您的建置有多值得信賴。

有趣的是,使用 pdfbox 會需要比這 2 個成品更多的東西,因為它也會帶來傳遞依賴項。如果依賴項驗證檔案只包含您使用的主要成品的雜湊值,建置會失敗,並出現類似這樣的錯誤

Execution failed for task ':compileJava'.
> Dependency verification failed for configuration ':compileClasspath':
    - On artifact commons-logging-1.2.jar (commons-logging:commons-logging:1.2) in repository 'MavenRepo': checksum is missing from verification metadata.
    - On artifact commons-logging-1.2.pom (commons-logging:commons-logging:1.2) in repository 'MavenRepo': checksum is missing from verification metadata.

這表示您的建置在執行 compileJava 時需要 commons-logging,但驗證檔案沒有包含足夠的資訊讓 Gradle 驗證依賴項的完整性,這表示您需要將必要的資訊新增到驗證元資料檔案。

請參閱疑難排解依賴項驗證,深入瞭解在這種情況下該怎麼做。

驗證哪些雜湊值?

如果依賴項驗證元資料檔案為依賴項宣告多個雜湊值,Gradle 會驗證所有雜湊值,如果任何一個失敗,就會失敗。例如,下列設定會檢查 md5sha1 雜湊值

<component group="org.apache.pdfbox" name="pdfbox" version="2.0.17">
   <artifact name="pdfbox-2.0.17.jar">
      <md5 value="c713a8e252d0add65e9282b151adf6b4" origin="official site"/>
      <sha1 value="b5c8dff799bd967c70ccae75e6972327ae640d35" origin="official site" reason="Additional check for this artifact"/>
   </artifact>
</component>

您會想要這樣做的原因有很多

  1. 官方網站沒有發布安全的雜湊值(SHA-256、SHA-512),但發布了多個不安全的雜湊值(MD5、SHA1)。雖然偽造 MD5 雜湊值很容易,偽造 SHA1 雜湊值很難,但可能辦得到,但要為同一個成品偽造兩個雜湊值就更難了。

  2. 您可能想要將產生的雜湊值新增到上述清單

  3. 在使用更安全的雜湊值更新依賴項驗證檔案時,您不希望意外刪除雜湊值

驗證依賴項簽章

除了雜湊值之外,Gradle 也支援驗證簽章。簽章用於評估依賴項的來源(它會告訴您誰簽署了成品,通常會對應到誰製作了成品)。

由於啟用簽章驗證通常表示安全性較高,您可能想要用簽章驗證取代雜湊值驗證。

簽章也可以用來評估依賴項的完整性,類似於校驗和。簽章是人工製品雜湊的簽章,而不是人工製品本身。這表示如果簽章是在不安全的雜湊(甚至 SHA1)上完成的,那麼您並未正確評估檔案的完整性。因此,如果您同時關心這兩者,您需要將簽章校驗和新增到您的驗證中繼資料。

然而

  • Gradle 僅支援驗證在遠端儲存庫上發布為 ASCII 裝甲 PGP 檔案的簽章

  • 並非所有人工製品都以簽章發布

  • 良好的簽章並不表示簽署者是合法的

因此,簽章驗證通常會與校驗和驗證一起使用。

關於過期金鑰

發現以過期金鑰簽署的人工製品非常常見。這對於驗證來說並非問題:金鑰過期主要用於避免使用遭竊金鑰簽署。如果人工製品在過期前已簽署,它仍然有效。

啟用簽章驗證

因為驗證簽章更耗費成本(無論是 I/O 還是 CPU)且更難手動檢查,因此預設並未啟用。

啟用它需要您變更 verification-metadata.xml 檔案中的組態選項

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <verify-signatures>true</verify-signatures>
   </configuration>
</verification-metadata>

了解簽章驗證

一旦啟用簽章驗證,對於每個人工製品,Gradle 將會

  • 嘗試下載對應的 .asc 檔案

  • 如果它存在

    • 自動下載執行簽章驗證所需的金鑰

    • 使用已下載的公開金鑰驗證人工製品

    • 如果簽章驗證通過,執行額外要求的校驗和驗證

  • 如果它不存在,則回退到校驗和驗證

也就是說,如果啟用簽章驗證,Gradle 的驗證機制會比僅使用校驗和驗證強大許多。特別是

  • 如果人工製品以多個金鑰簽署,則所有金鑰都必須通過驗證,否則建置將會失敗

  • 如果人工製品通過驗證,則為人工製品組態的任何額外校驗和也會被檢查

然而,人工製品通過簽章驗證並不表示您可以信任它:您需要信任金鑰

實際上,這表示您需要列出您信任的每個人工製品的金鑰,這是透過新增 pgp 項目而不是 sha1 來完成的

<component group="com.github.javaparser" name="javaparser-core" version="3.6.11">
   <artifact name="javaparser-core-3.6.11.jar">
      <pgp value="8756c4f765c9ac3cb6b85d62379ce192d401ab61"/>
   </artifact>
</component>

對於 pgptrusted-key 元素,Gradle 需要完整的指紋 ID(例如 b801e2f8ef035068ec1139cc29579f18fa8fd93b,而不是長 ID 29579f18fa8fd93b)。這可將 碰撞攻擊 的機率降到最低。

目前,V4 金鑰指紋 的長度為 160 位元(40 個字元)。我們接受較長的鑰匙,以在引入較長的金鑰指紋時,能具備前瞻性。

ignore-key 元素中,可以使用指紋或長(64 位元)ID。較短的 ID 只會導致較大的排除範圍,因此使用起來很安全。

這實際上表示,如果您使用金鑰 8756c4f765c9ac3cb6b85d62379ce192d401ab61 簽署 com.github.javaparser:javaparser-core:3.6.11,您便會信任它。

如果不這樣做,則建置會因這個錯誤而失敗

> Dependency verification failed for configuration ':compileClasspath':
    - On artifact javaparser-core-3.6.11.jar (com.github.javaparser:javaparser-core:3.6.11) in repository 'MavenRepo': Artifact was signed with key '8756c4f765c9ac3cb6b85d62379ce192d401ab61' (Bintray (by JFrog) <****>) and passed verification but the key isn't in your trusted keys list.

Gradle 在錯誤訊息中顯示的金鑰 ID 是它嘗試驗證的簽章檔中找到的金鑰 ID。這並不表示您一定要信任這些金鑰。特別是,如果簽章正確,但卻是由惡意實體所為,Gradle 也不會告訴您。

全球信任金鑰

簽章驗證的優點是,它可以讓相依性驗證的設定更簡單,因為不必像只進行雜湊驗證那樣,明確列出所有成品。事實上,通常可以使用同一個金鑰來簽署多個成品。如果是這樣,您可以將受信任的金鑰從成品層級移到全球設定區塊

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <verify-metadata>true</verify-metadata>
      <verify-signatures>true</verify-signatures>
      <trusted-keys>
         <trusted-key id="8756c4f765c9ac3cb6b85d62379ce192d401ab61" group="com.github.javaparser"/>
      </trusted-keys>
   </configuration>
   <components/>
</verification-metadata>

以上的設定表示,對於屬於群組 com.github.javaparser 的任何成品,如果它使用 8756c4f765c9ac3cb6b85d62379ce192d401ab61 指紋簽署,我們便會信任它。

trusted-key 元素的工作方式與 trusted-artifact 元素類似

  • group,要信任的成品的群組

  • name,要信任的成品的名稱

  • version,要信任的成品的版本

  • file,要信任的成品檔案的名稱

  • regex,一個布林值,表示 groupnameversionfile 屬性是否需要詮釋為正規表示式(預設為 false

全球信任金鑰時,您應該小心謹慎。

試著將其限制在適當的群組或人工製品

  • 有效的金鑰可能已用於簽署您信任的人工製品 A

  • 稍後,金鑰會被竊取並用於簽署人工製品 B

這表示您可以信任金鑰 A 的第一個人工製品,可能僅限於金鑰被竊取前的已發布版本,但不能信任 B

請記住,任何人都可以在產生 PGP 金鑰時輸入任意名稱,因此請勿僅根據金鑰名稱信任金鑰。驗證金鑰是否列於官方網站。例如,Apache 專案通常會提供您可以信任的 KEYS.txt 檔案。

指定金鑰伺服器和忽略金鑰

Gradle 會自動下載驗證簽章所需的公開金鑰。為此,它會使用已知且受信任的金鑰伺服器清單(清單可能因 Gradle 版本而異,請參閱實作以找出預設使用的伺服器)。

您可以透過將金鑰伺服器清單新增至組態中,明確設定要使用的金鑰伺服器清單

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <verify-metadata>true</verify-metadata>
      <verify-signatures>true</verify-signatures>
      <key-servers>
         <key-server uri="hkp://my-key-server.org"/>
         <key-server uri="https://my-other-key-server.org"/>
      </key-servers>
   </configuration>
</verification-metadata>

儘管如此,金鑰仍有可能不可用

  • 因為它未發布至公開金鑰伺服器

  • 因為它遺失了

在這種情況下,您可以在組態區塊中忽略金鑰

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <verify-metadata>true</verify-metadata>
      <verify-signatures>true</verify-signatures>
      <ignored-keys>
         <ignored-key id="abcdef1234567890" reason="Key is not available in any key server"/>
      </ignored-keys>
   </configuration>
</verification-metadata>

一旦忽略金鑰,即使簽章檔案提到它,也不會用於驗證。但是,如果無法使用至少一個其他金鑰驗證簽章,Gradle 會強制您提供檢查碼。

如果 Gradle 在引導期間無法下載金鑰,它會將其標記為已忽略。如果您可以找到金鑰,但 Gradle 找不到,您可以手動將其新增至鑰匙圈檔案。

匯出金鑰以加快驗證

Gradle 會自動下載所需的 key,但此操作可能相當緩慢,且需要每個人都下載 key。為了避免此情況,Gradle 提供使用包含所需公開金鑰的本機鑰匙圈檔案的功能。請注意,僅儲存和使用公開金鑰封包和每個金鑰的單一 userId。所有其他資訊(使用者屬性、簽章等)都會從下載或匯出的金鑰中移除。

Gradle 支援兩種不同的鑰匙圈檔案格式:二進位格式(.gpg 檔案)和純文字格式(.keys),也稱為 ASCII 裝甲格式。

每種格式都有優缺點:二進位格式較為精簡,且可透過 GPG 指令直接更新,但完全不透明(二進位)。相反地,ASCII 裝甲格式可供人閱讀,可輕鬆手動更新,且由於可讀的差異,較容易進行程式碼檢閱。

您可以透過新增 keyring-format 設定選項來設定要使用的檔案類型

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <verify-metadata>true</verify-metadata>
      <verify-signatures>true</verify-signatures>
      <keyring-format>armored</keyring-format>
   </configuration>
</verification-metadata>

鑰匙圈格式的可用選項為 armoredbinary

如果沒有 keyring-format,且存在 gradle/verification-keyring.gpggradle/verification-keyring.keys 檔案,Gradle 會優先在那裡搜尋金鑰。如果已經有 .gpg 檔案(二進位版本優先),則會忽略純文字檔案。

您可以在引導期間要求 Gradle 將用於驗證此組建的所有金鑰匯出到鑰匙圈

./gradlew --write-verification-metadata pgp,sha256 --export-keys

除非指定 keyring-format,否則此指令將產生二進位版本和 ASCII 裝甲檔案。使用此選項選擇偏好的格式。您應該只為您的專案選擇一個。

建議將此檔案提交至 VCS(只要您信任您的 VCS)。如果您使用 git 並使用二進位版本,請務必透過將此內容新增至 .gitattributes 檔案,讓 git 將此檔案視為二進位。

*.gpg           binary

您也可以要求 Gradle 匯出所有受信任的金鑰,而不會更新驗證元資料檔案

./gradlew --export-keys
此指令不會報告驗證錯誤,只會匯出金鑰。

引導和簽章驗證

簽章驗證引導採用樂觀觀點,認為簽章驗證就夠了。因此,如果您也關心完整性,您必須先使用檢查總和驗證進行引導,然後再使用簽章驗證進行引導。

與檢查碼的引導程序類似,Gradle 提供了一個便利的功能,用於引導啟用簽章驗證的組態檔。為此,只要將 pgp 選項新增到要產生的驗證清單即可。不過,由於可能會發生驗證失敗、遺失金鑰或遺失簽章檔案,因此必須提供備用檢查碼驗證演算法

./gradlew --write-verification-metadata pgp,sha256

這表示當發生問題時,Gradle 會驗證簽章並備用 SHA-256 檢查碼。

在引導程序中,Gradle 會執行樂觀驗證,因此假設建置環境是合理的。因此它會

  • 在驗證通過後立即自動新增受信任金鑰

  • 自動為無法從公開金鑰伺服器下載的金鑰新增忽略的金鑰。請參閱這裡,瞭解如何在需要時手動新增金鑰

  • 自動為沒有簽章或忽略的金鑰的成品產生檢查碼

如果在產生過程中驗證因故失敗,Gradle 會自動產生一個忽略的金鑰項目,但會警告您務必檢查發生了什麼事。

這種情況很常見,如此區段所說明:一個典型的案例是相依項的 POM 檔案在不同的儲存庫中有所不同(通常沒有意義)。

此外,Gradle 會嘗試自動將金鑰分組,並產生 trusted-keys 區塊,以盡可能縮小組態檔的大小。

強制只使用本機金鑰環

當需要金鑰來驗證成品時,可以使用本機金鑰環檔案(.gpg.keys)來避免連線到金鑰伺服器。不過,本機金鑰環可能不包含金鑰,在這種情況下,Gradle 會使用金鑰伺服器來擷取遺失的金鑰。如果本機金鑰環檔案沒有定期使用金鑰匯出更新,那麼您的 CI 建置(例如)可能會過於頻繁地連線到金鑰伺服器(特別是如果您使用一次性容器進行建置)。

為避免這種情況,Gradle 提供了完全禁止使用金鑰伺服器:只會使用本機金鑰環檔案,如果這個檔案中遺失了金鑰,建置就會失敗。

若要啟用此模式,您需要在組態檔中停用金鑰伺服器

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <key-servers enabled="false"/>
      ...
   </configuration>
   ...
</verification-metadata>
如果您要求 Gradle產生驗證中繼資料檔,並且現有的驗證中繼資料檔將 enabled 設定為 false,那麼這個旗標會被忽略,以便下載可能遺失的金鑰。

停用驗證或讓驗證變得寬鬆

相依性驗證可能很昂貴,有時驗證會妨礙日常開發(例如,因為頻繁的相依性升級)。

或者,您可能想在 CI 伺服器上啟用驗證,但在本地機器上不啟用。

Gradle 實際上提供了 3 種不同的驗證模式

  • strict,這是預設值。驗證會盡可能早地失敗,以避免在建置期間使用受損的相依性。

  • lenient,即使驗證失敗,也會執行建置。驗證錯誤會在建置期間顯示,而不會導致建置失敗。

  • off,當驗證完全被忽略時。

所有這些模式都可以使用 --dependency-verification 旗標在 CLI 上啟用,例如

./gradlew --dependency-verification lenient build

或者,您可以在 CLI 上設定 org.gradle.dependency.verification 系統屬性

./gradlew -Dorg.gradle.dependency.verification=lenient build

或在 gradle.properties 檔案中

org.gradle.dependency.verification=lenient

僅針對某些組態停用相依性驗證

為了提供最強的安全等級,相依性驗證已在全球啟用。這將確保,例如,您信任您使用的所有外掛程式。但是,外掛程式本身可能需要解析其他相依性,而要求使用者接受這些相依性沒有意義。為此,Gradle 提供了一個 API,允許停用某些特定組態的相依性驗證

如果您關心安全性,停用相依性驗證並不是一個好主意。此 API 主要存在於檢查相依性沒有意義的情況。但是,為了安全起見,每當針對特定組態停用驗證時,Gradle 都會系統性地列印警告。

例如,外掛程式可能需要檢查是否有較新的函式庫版本可用,並列出這些版本。在此背景下,要求使用者輸入較新版本的 POM 檔案的檢查總和沒有意義,因為根據定義,他們不知道這些版本。因此,外掛程式可能需要獨立於相依性驗證組態執行其程式碼。

為執行此操作,您需要呼叫 ResolutionStrategy#disableDependencyVerification 方法

build.gradle.kts
configurations {
    "myPluginClasspath" {
        resolutionStrategy {
            disableDependencyVerification()
        }
    }
}
build.gradle
configurations {
    myPluginClasspath {
        resolutionStrategy {
            disableDependencyVerification()
        }
    }
}

在下列範例中,也可以停用分離組態的驗證

build.gradle.kts
tasks.register("checkDetachedDependencies") {
    val detachedConf: FileCollection = configurations.detachedConfiguration(dependencies.create("org.apache.commons:commons-lang3:3.3.1")).apply {
        resolutionStrategy.disableDependencyVerification()
    }
    doLast {
        println(detachedConf.files)
    }
}
build.gradle
tasks.register("checkDetachedDependencies") {
    def detachedConf = configurations.detachedConfiguration(dependencies.create("org.apache.commons:commons-lang3:3.3.1"))
    detachedConf.resolutionStrategy.disableDependencyVerification()
    doLast {
        println(detachedConf.files)
    }
}

信任特定人工製品

您可能想比其他人工製品更信任某些人工製品。例如,認為公司製作並僅在內部儲存庫中找到的人工製品是安全的,但您想檢查每個外部元件,這是合理的。

這是一個典型的公司政策。實際上,沒有任何事物可以防止您的內部儲存庫受到危害,因此最好也檢查您的內部人工製品!

為了這個目的,Gradle 提供一種自動信任某些人工製品的方法。您可以透過將以下內容新增到組態中,來信任群組中的所有人工製品

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <trusted-artifacts>
         <trust group="com.mycompany" reason="We trust mycompany artifacts"/>
      </trusted-artifacts>
   </configuration>
</verification-metadata>

這表示群組為 com.mycompany 的所有元件都將自動受到信任。信任表示 Gradle 根本不會執行任何驗證。

trust 元素接受這些屬性

  • group,要信任的成品的群組

  • name,要信任的成品的名稱

  • version,要信任的成品的版本

  • file,要信任的成品檔案的名稱

  • regex,一個布林值,表示 groupnameversionfile 屬性是否需要詮釋為正規表示式(預設為 false

  • reason,一個選擇性的原因,說明為什麼匹配的人工製品受到信任

在上述範例中,表示受信任的人工製品將是 com.mycompany 中的人工製品,但不是 com.mycompany.other。若要信任 com.mycompany 中的所有人工製品和所有子群組,您可以使用

<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
   <configuration>
      <trusted-artifacts>
         <trust group="^com[.]mycompany($|([.].*))" regex="true" reason="We trust all mycompany artifacts"/>
      </trusted-artifacts>
   </configuration>
</verification-metadata>

信任人工製品的多個檢查碼

在實際情況中,同一個人工製品有不同的檢查碼是很常見的。這是怎麼可能的呢?儘管有進展,但開發人員通常會發佈,例如,分別發佈到 Maven Central 和另一個儲存庫,並使用不同的建置。一般來說,這不是問題,但有時表示元資料檔案會不同(不同的時間戳記、額外的空白字元,…​)。再加上您的建置可能會使用多個儲存庫或儲存庫鏡像,這使得單一建置很可能「看到」同一個元件的不同元資料檔案!一般來說,這不是惡意的(但您必須驗證人工製品是否實際上正確),因此 Gradle 讓您可以宣告額外的人工製品檢查碼。例如

      <component group="org.apache" name="apache" version="13">
         <artifact name="apache-13.pom">
            <sha256 value="2fafa38abefe1b40283016f506ba9e844bfcf18713497284264166a5dbf4b95e">
               <also-trust value="ff513db0361fd41237bef4784968bc15aae478d4ec0a9496f811072ccaf3841d"/>
            </sha256>
         </artifact>
      </component>

您可以擁有任意數量的 also-trust 項目,但一般來說您不應超過 2 個。

略過 Javadoc 和來源

預設情況下,Gradle 會驗證所有已下載的成品,包括 Javadoc 和原始碼。一般而言,這不會造成問題,但您可能會遇到 IDE 的問題,它們會在匯入期間自動嘗試下載這些成品:如果您沒有為這些成品設定檢查碼,匯入就會失敗。

為避免此問題,您可以設定 Gradle 自動信任所有 javadoc/原始碼

<trusted-artifacts>
   <trust file=".*-javadoc[.]jar" regex="true"/>
   <trust file=".*-sources[.]jar" regex="true"/>
</trusted-artifacts>

手動將金鑰新增到鑰匙圈

將金鑰新增到 ASCII 裝甲鑰匙圈

新增的金鑰必須採用 ASCII 裝甲格式,且可以簡單地新增到檔案的結尾。如果您已下載正確格式的金鑰,您可以直接將其附加到檔案。

或者,您可以透過發出下列指令來修訂現有的 KEYS 檔案

$ gpg --no-default-keyring --keyring /tmp/keyring.gpg --recv-keys 8756c4f765c9ac3cb6b85d62379ce192d401ab61

gpg: keybox '/tmp/keyring.gpg' created
gpg: key 379CE192D401AB61: public key "Bintray (by JFrog) <****>" imported
gpg: Total number processed: 1
gpg:               imported: 1

# Write its ASCII-armored version
$ gpg --keyring /tmp/keyring.gpg --export --armor 8756c4f765c9ac3cb6b85d62379ce192d401ab61 > gradle/verification-keyring.keys

完成後,請務必再次執行產生指令,以便 Gradle 處理金鑰。這將執行下列動作

  • 將標準標頭新增到金鑰

  • 使用 Gradle 自有的格式重新撰寫金鑰,將金鑰修剪至最精簡

  • 將金鑰移至其排序位置,保持檔案可複製

將金鑰新增到二進位鑰匙圈

您可以使用 GPG 將金鑰新增到二進位版本,例如發出下列指令(語法可能取決於您使用的工具)

$ gpg --no-default-keyring --keyring gradle/verification-keyring.gpg --recv-keys 8756c4f765c9ac3cb6b85d62379ce192d401ab61

gpg: keybox 'gradle/verification-keyring.gpg' created
gpg: key 379CE192D401AB61: public key "Bintray (by JFrog) <****>" imported
gpg: Total number processed: 1
gpg:               imported: 1

$ gpg --no-default-keyring --keyring gradle/verification-keyring.gpg --recv-keys 6f538074ccebf35f28af9b066a0975f8b1127b83

gpg: key 0729A0AFF8999A87: public key "Kotlin Release <****>" imported
gpg: Total number processed: 1
gpg:               imported: 1

處理驗證失敗

相依性驗證可能會以不同的方式失敗,本節說明您應該如何處理各種情況。

驗證中繼資料遺失

您可能會遇到的最簡單的失敗是相依性驗證檔案中缺少驗證中繼資料。例如,如果您使用檢查碼驗證,然後您更新相依性,並引入相依性的新版本(以及其潛在的傳遞性相依性)。

Gradle 會告訴您缺少哪些中繼資料

Execution failed for task ':compileJava'.
> Dependency verification failed for configuration ':compileClasspath':
    - On artifact commons-logging-1.2.jar (commons-logging:commons-logging:1.2) in repository 'MavenRepo': checksum is missing from verification metadata.
  • 遺失的模組群組為 commons-logging,其成品名稱為 commons-logging,其版本為 1.2。對應的成品為 commons-logging-1.2.jar,因此您需要將下列項目新增到驗證檔案

<component group="commons-logging" name="commons-logging" version="1.2">
   <artifact name="commons-logging-1.2.jar">
      <sha256 value="daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636" origin="official distribution"/>
   </artifact>
</component>

或者,你可以使用自舉機制要求 Gradle 產生遺失的資訊:現有的資訊會保留在元資料檔案中,Gradle 只會新增遺失的驗證元資料。

不正確的雜湊值

當實際的雜湊值驗證失敗時,會產生一個更具問題性的問題

Execution failed for task ':compileJava'.
> Dependency verification failed for configuration ':compileClasspath':
    - On artifact commons-logging-1.2.jar (commons-logging:commons-logging:1.2) in repository 'MavenRepo': expected a 'sha256' checksum of '91f7a33096ea69bac2cbaf6d01feb934cac002c48d8c8cfa9c240b40f1ec21df' but was 'daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636'

這次,Gradle 會告訴你哪個相依項有問題、預期的雜湊值為何(你在驗證元資料檔案中宣告的雜湊值)以及在驗證期間實際計算出的雜湊值為何。

此類失敗表示相依項可能已遭到破壞。在此階段,你必須執行手動驗證並查看會發生什麼事。可能會發生下列幾件事

  • 相依項在 Gradle 的本機相依項快取中遭到竄改。這通常無害:從快取中刪除檔案,Gradle 會重新下載相依項。

  • 相依項在多個來源中可用,但二進位檔案略有不同(額外的空白字元,…​)

    • 請告知程式庫的維護人員他們有此類問題

    • 你可以使用also-trust來接受額外的雜湊值

  • 相依項遭到破壞

    • 立即告知程式庫的維護人員

    • 通知遭到破壞的程式庫的儲存庫維護人員

請注意,遭到破壞的程式庫的變體通常是名稱搶注,駭客會使用看起來合法的 GAV 座標,但實際上只有一個字元不同,或儲存庫陰影,當具有官方 GAV 座標的相依項在你的建置中首先出現的惡意儲存庫中發布時。

不受信任的簽章

如果你已啟用簽章驗證,Gradle 會執行簽章驗證,但不會自動信任它們

> Dependency verification failed for configuration ':compileClasspath':
    - On artifact javaparser-core-3.6.11.jar (com.github.javaparser:javaparser-core:3.6.11) in repository 'MavenRepo': Artifact was signed with key '379ce192d401ab61' (Bintray (by JFrog) <****>) and passed verification but the key isn't in your trusted keys list.

在這種情況下,表示你需要自行檢查用於驗證的密鑰(因此也包括簽章)是否可信,如果是,請參閱這份文件,找出如何宣告受信任的密鑰。

簽章驗證失敗

如果 Gradle 無法驗證簽章,您需要採取行動並手動驗證人工製品,因為這可能表示相依性遭到入侵

如果發生這種情況,Gradle 將會失敗,並顯示

> Dependency verification failed for configuration ':compileClasspath':
    - On artifact javaparser-core-3.6.11.jar (com.github.javaparser:javaparser-core:3.6.11) in repository 'MavenRepo': Artifact was signed with key '379ce192d401ab61' (Bintray (by JFrog) <****>) but signature didn't match

有幾個選項

  1. 簽章一開始就錯誤,這在發佈在不同儲存庫中的相依性中經常發生。

  2. 簽章正確,但人工製品遭到入侵(在本地相依性快取或遠端)

這裡正確的方法是前往相依性的官方網站,查看他們是否為其人工製品發佈簽章。如果他們有發佈,請驗證 Gradle 下載的簽章與發佈的簽章是否相符。

如果您已檢查相依性沒有遭到入侵,而且「只有」簽章錯誤,您應該宣告人工製品層級金鑰排除

   <components>
       <component group="com.github.javaparser" name="javaparser-core" version="3.6.11">
          <artifact name="javaparser-core-3.6.11.pom">
             <ignored-keys>
                <ignored-key id="379ce192d401ab61" reason="internal repo has corrupted POM"/>
             </ignored-keys>
          </artifact>
       </component>
   </components>

但是,如果您只這樣做,Gradle 仍會失敗,因為這個人工製品的所有金鑰都將被忽略,而且您沒有提供檢查碼

   <components>
       <component group="com.github.javaparser" name="javaparser-core" version="3.6.11">
          <artifact name="javaparser-core-3.6.11.pom">
             <ignored-keys>
                <ignored-key id="379ce192d401ab61" reason="internal repo has corrupted POM"/>
             </ignored-keys>
             <sha256 value="a2023504cfd611332177f96358b6f6db26e43d96e8ef4cff59b0f5a2bee3c1e1"/>
          </artifact>
       </component>
   </components>

手動驗證相依性

您可能會遇到相依性驗證失敗(檢查碼驗證或簽章驗證),而且需要找出相依性是否遭到入侵。

在本節中,我們提供一個範例,說明如何手動檢查相依性是否遭到入侵。

為此,我們將採用這個失敗範例

> Dependency verification failed for configuration ':compileClasspath':
- On artifact j2objc-annotations-1.1.jar (com.google.j2objc:j2objc-annotations:1.1) in repository 'MyCompany Mirror': Artifact was signed with key '29579f18fa8fd93b' but signature didn't match

這個錯誤訊息提供有問題相依性的 GAV 座標,以及相依性從何處擷取的指示。在此,相依性來自MyCompany Mirror,這是我們在建置中宣告的儲存庫。

因此,首先要做的就是從鏡像手動下載人工製品及其簽章

$ curl https://my-company-mirror.com/repo/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar --output j2objc-annotations-1.1.jar
$ curl https://my-company-mirror.com/repo/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar.asc --output j2objc-annotations-1.1.jar.asc

然後,我們可以使用錯誤訊息中提供的金鑰資訊,來匯入本地的金鑰

$ gpg --recv-keys B801E2F8EF035068EC1139CC29579F18FA8FD93B

並執行驗證

$ gpg --verify j2objc-annotations-1.1.jar.asc
gpg: assuming signed data in 'j2objc-annotations-1.1.jar'
gpg: Signature made Thu 19 Jan 2017 12:06:51 AM CET
gpg:                using RSA key 29579F18FA8FD93B
gpg: BAD signature from "Tom Ball <****>" [unknown]

這告訴我們問題不在本地機器上:儲存庫已包含一個錯誤簽章

下一步是透過下載 Maven Central 上的實際內容來執行相同動作

$ curl https://my-company-mirror.com/repo/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar  --output central-j2objc-annotations-1.1.jar
$ curl https://my-company-mirror.com/repo/com/google/j2objc/j2objc-annotations/1/1/j2objc-annotations-1.1.jar.asc  --output central-j2objc-annotations-1.1.jar.asc

現在,我們可以再次檢查簽章

$ gpg --verify central-j2objc-annotations-1.1.jar.asc

gpg: assuming signed data in 'central-j2objc-annotations-1.1.jar'
gpg: Signature made Thu 19 Jan 2017 12:06:51 AM CET
gpg:                using RSA key 29579F18FA8FD93B
gpg: Good signature from "Tom Ball <****>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: B801 E2F8 EF03 5068 EC11  39CC 2957 9F18 FA8F D93B

這表示相依性在 Maven Central 上有效。在此階段,我們已知道問題出在鏡像中,它可能已遭入侵,但我們需要驗證。

一個好主意是比較 2 個成品,你可以使用 diffoscope 等工具來執行此動作。

我們接著發現意圖並非惡意,但某個組建已以較新版本覆寫(Central 中的版本較我們儲存庫中的版本新)。

在這種情況下,你可以決定

  • 略過此成品的簽章,並信任不同的可能檢查碼(舊成品和新版本都適用)

  • 或清除你的鏡像,使其包含與 Maven Central 相同的版本

值得注意的是,如果你選擇從你的儲存庫中刪除版本,你也需要從本機 Gradle 快取中移除它。

由於錯誤訊息會告訴你檔案所在位置,因此這很容易執行

> Dependency verification failed for configuration ':compileClasspath':
    - On artifact j2objc-annotations-1.1.jar (com.google.j2objc:j2objc-annotations:1.1) in repository 'MyCompany Mirror': Artifact was signed with key '29579f18fa8fd93b' but signature didn't match

  This can indicate that a dependency has been compromised. Please carefully verify the signatures and checksums.

  For your information here are the path to the files which failed verification:
    - $<<directory_layout.adoc#dir:gradle_user_home,GRADLE_USER_HOME>>/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/976d8d30bebc251db406f2bdb3eb01962b5685b3/j2objc-annotations-1.1.jar (signature: GRADLE_USER_HOME/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/82e922e14f57d522de465fd144ec26eb7da44501/j2objc-annotations-1.1.jar.asc)

  GRADLE_USER_HOME = /home/jiraya/.gradle

你可以安全地刪除成品檔案,因為 Gradle 會自動重新下載它

rm -rf ~/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1

清除驗證檔案

如果你不執行任何動作,隨著你新增相依性或變更版本,相依性驗證資料會隨著時間而增加:Gradle 不會自動從此檔案中移除未使用的項目。原因是 Gradle 無法預先得知相依性是否會在組建期間實際使用。

因此,新增相依性或變更相依性版本很容易導致檔案中出現更多項目,同時遺留不必要的項目。

清除檔案的一個選項是將現有的 verification-metadata.xml 檔案移至不同位置,並使用 --dry-run 模式 呼叫 Gradle:雖然不完美(它不會注意到僅在組態時間解析的相依性),但它會產生一個新檔案,你可以將其與現有檔案進行比較。

我們需要移動現有檔案,因為引導模式和乾式執行模式都是增量的:它們會從現有的資料驗證檔案(特別是受信任金鑰)複製資訊。

更新遺失的 key

Gradle 會快取遺失的 key 24 小時,表示它會在失敗後 24 小時內不會嘗試重新下載遺失的 key。

如果您想要立即重試,您可以使用 --refresh-keys CLI 旗標執行

./gradlew build --refresh-keys

請參閱 此處,了解如果 Gradle 持續無法下載 key,如何手動新增 key。