跳到內容

正確驗證簽章

Spec: RFC 5280, §6 Spec: RFC 6960 Spec: RFC 5652 Evidence: Test-backed

「簽章有效」通常只代表檢查了一件事:數學算式對得上。一次正確的驗證至少要檢查五件彼此獨立的事;其中任何一件出錯,都可能讓那個綠色勾號變得毫無意義。本頁列出完整檢查項目,並說明為什麼不完整的答案很危險。

在這個主題裡,單一布林值是最危險的輸出。它會讓讀者把「有效」當成「值得信任」,然而「有效」可能只代表那些位元組未經竄改——而簽署所用的金鑰,卻來自一張三年前就過期、上個月遭到撤銷、且鏈結不到任何你認可之機構的憑證。上述每一項都是一個獨立的檢查。回報單一布林值的軟體,已經默默替你決定哪些檢查才重要,而且這個決定不是由你做出。在受監管或具契約效力的場合,如果工具只驗證了成本最低的那項屬性,「工具說它有效」並不能作為辯護。

一次完整的驗證會回答五個各自獨立的問題。它們彼此獨立:通過其中一項,並不代表其他項也成立:

  1. 完整性——已簽署的位元組經雜湊後,是否仍與簽章所涵蓋的值相符?(重新計算位元組範圍摘要,並加以比對。)
  2. 真實性——密碼學簽章是否能以簽署憑證中的公開金鑰,針對已簽署屬性完成驗證?
  3. 憑證路徑——該憑證是否鏈結到所選定的信任錨點,且每一個環節都有效?
  4. 時間——在相關時間點,憑證是否仍在有效期間內,而那個時間是否屬於受信任的時間,而不是自行宣稱的時間?
  5. 撤銷——在當時,憑證是否未遭撤銷,且這項證據(OCSP/CRL)是否可由你實際取得,或已內嵌於文件中?

一個未執行全部五項檢查的「有效」,只是看起來像完整答案的不完整答案。

NextPDF 的立場是:每一個問題都各自獨立,且每一個都必須被明確回答。NextPDF 絕不把一個問題壓成單一的樂觀旗標,也絕不因為某項檢查不方便而默默略過。這一點由測試強制約束。這正是本頁被標示為由測試背書(test-backed)、而非由標準背書(standard-backed)的原因:這項行為由測試套件鎖定,而不只是從某條條款論證而來。

完整性與真實性都有端對端測試。測試套件會使用對應於一張已知憑證的金鑰,對一個真實的已簽署屬性結構進行簽署,再以相符的公開金鑰,跨多個時間向量驗證該簽章。因此,任何破壞此正規結構的變更都會讓測試失敗。憑證路徑驗證由一組測試鎖定:它們刻意竄改一個簽章位元組,並斷言結果為有效,且附帶一個結構化的原因——不是被丟棄的例外,而是一筆明確記錄的失敗。時間戳記符記的驗證被拆解成數個離散步驟——解碼、簽署者資訊、已簽署屬性、訊息摘要、憑證綁定、金鑰用途、簽章、產生時間(produced-at)——而每一個步驟都各自接受測試,因此「時間戳記通過驗證」意味著每一個步驟都通過了驗證。撤銷檢查的軟性失敗(無法連線的回應端)在程式碼與測試中,都會與明確的「已撤銷」區分開來。這兩者絕不會被混為同一個答案。

  1. Integrity Recompute the byte-range digest and compare it to the value the signature covers.
  2. Authenticity Verify the cryptographic signature against the certificate’s public key, over the signed attributes — not the raw content.
  3. Certificate path Build and validate the chain to a trust anchor you chose; every link’s signature, validity, and constraints must hold.
  4. Time Confirm the certificate was valid at the relevant instant, and that the instant is trusted time, not the signer’s clock.
  5. Revocation Confirm the certificate was not revoked at that time, using obtainable or embedded OCSP/CRL evidence.
一次正確的 PDF 簽章驗證所依序回答的五個獨立問題。每一項都各自獨立:通過其中一項,並不能說明其他項的任何情況,而略過任何一項都會讓整體結果變得不完整。

Evidence: Test-backed 這項行為以測試為依據,而這些測試實作了各項標準所要求的內容。

完整性對應 Spec: ISO 32000-2, §12.8.1 :摘要會針對位元組範圍重新計算,並與儲存的值比對,任何差異都意味著簽章無效。針對已簽署屬性的真實性,由一項整合測試涵蓋:它會對一組真實的已簽署屬性進行簽署,並以相符的公開金鑰跨多個時間向量加以驗證。憑證路徑這個問題對應 Spec: RFC 5280, §6.1 :有效的路徑始於一個信任錨點,而 Spec: RFC 5280, §6.2 指出,該演算法定義了一條路徑要被視為有效所需的最低條件——一項路徑驗證器單元測試會斷言:一個被竄改的簽章會產生 valid = false,並附帶一個明確的原因,絕不會默默接受。

撤銷的檢查順序對應 Spec: RFC 6960, §3.2 :在用戶端接受一個已簽署的撤銷回應為有效之前,它「應當」(SHALL)先確認該回應自身的簽章為有效,且簽署者目前已獲授權——而 Spec: RFC 6960, §4.2.2.2 將該授權定義為由相關 CA 直接簽發的 id-kp-OCSPSigning 委派授權。因此,一個尚未確認由已獲授權且可驗證之簽署者簽出的撤銷答案,是毫無意義的。憑證綁定檢查對應 Spec: RFC 5035, §5.4.2 :如果已簽署之 signing-certificate-v2 屬性中的憑證雜湊,與用來驗證簽章的憑證不相符,那麼該簽章必須被視為無效。這會封住替換漏洞——也就是簽章針對一張由攻擊者選定的憑證通過驗證的情況。時間戳記符記本身,會以 Spec: RFC 5652 的風格,作為一個 CMS 物件逐步驗證,每一個步驟都各自接受測試。

真正有啟發性的,不是某一次 API 呼叫,而是在你依據某個結果採取行動之前,你必須有能力回答的那些問題。請把它當成審查時會用來檢核你的清單。

<?php
declare(strict_types=1);
// A correct validation produces a structured outcome, not one boolean.
// Before you trust a signature, you must be able to answer ALL of these:
//
// integrity : Does the byte-range digest still match? (tamper check)
// authenticity: Does the signature verify over the SIGNED ATTRIBUTES,
// not just the content?
// path : Does the certificate chain to a trust anchor YOU chose,
// with every link valid at the relevant time?
// time : Is the relevant time TRUSTED (a timestamp), or merely the
// signer's self-asserted clock?
// revocation : Was the certificate not revoked at that time, by evidence
// you obtained or that the document embedded?
//
// "valid: true" without an answer to every line above is an incomplete
// result. A path-validation outcome carries a `valid` flag AND a structured
// `reasons` list precisely so a failure says WHY — never a bare false.

如果任何一行的答案是「我不知道」,那麼如實的狀態就不是「有效」。它是「尚未確定」——而把兩者混為一談,正是本頁要防止的錯誤。

陷阱在於把「密碼學上有效」等同於「值得信任」。完整性與真實性合在一起,只能證明這些位元組是由持有這把金鑰的人所簽署。它們對於這把金鑰的憑證是否受信任、是否仍在有效期、或是否未遭撤銷,都隻字未提。一份以自行產生的憑證簽署的文件,可以是「密碼學上有效」卻一文不值。另一個反向陷阱,則是把一個無法確定的撤銷檢查(回應端離線)當成通過——或當成失敗。兩者都不是。它是未知的,而一個正確的驗證器會如實回報它為未知,而不是往任一方向猜測。一個沒有說清楚五項檢查中實際執行了哪些的綠色勾號,並不是驗證結果。它是別人替你做出的決定。

NextPDF 會執行並測試結構性與密碼學檢查。它不會替你選擇信任錨點,也不保證建立在其上的策略。你信任哪些憑證,是引擎無法替你做出的部署決策。一條驗證到某個你本不該信任之錨點的鏈,仍然是你無法仰賴的驗證結果。撤銷證據唯有在可取得或已內嵌時才能被檢查。一個離線的回應端會產生「無法確定」,而把它轉換成裁決結果是一項策略選擇,而非引擎的選擇。本頁描述的是檢查項目,而非法律上的充分性。一個通過驗證的簽章是否具有某種特定的法律效力,取決於憑證、簽署者、司法管轄區,以及相應的義務。內嵌證據如何讓這些檢查在多年後仍能被回答,在 長期驗證 中有所說明;完整性檢查背後的位元組範圍機制,則收錄於 簽章如何置於 PDF 中

驗證介面的方案版本可用性:

Signature validation checks — edition availability
Edition Availability
Core

已簽署屬性的完整性與真實性,加上 RFC 5280 §6 對所提供信任錨點執行的憑證路徑驗證。

Pro

新增 RFC 3161 時間戳記符記驗證,也就是將受信任時間這個問題拆解為可獨立檢查的多個步驟。

Enterprise

新增撤銷評估(OCSP/CRL),以及針對內嵌長期素材的驗證,並區分無法確定的結果與明確結果。

  • 完整性檢查——重新計算位元組範圍摘要,並將其與簽章所涵蓋的值比對。
  • 真實性檢查——以簽署憑證的公開金鑰,針對已簽署屬性驗證該密碼學簽章。
  • 已簽署屬性——簽章實際據以計算的、經過鑑別的 CMS 屬性(content-typemessage-digestsigning-timesigning-certificate-v2)。
  • 憑證路徑驗證——建立並檢查從簽署憑證到所選信任錨點的鏈(RFC 5280 §6)。
  • 信任錨點——一個你決定要信任的憑證授權單位;可接受路徑的根。
  • 撤銷檢查——透過 OCSP 或 CRL,判定一張憑證在相關時間點是否曾遭撤銷。
  • 無法確定——既非「良好」也非「已撤銷」的撤銷結果,因為證據無法取得;既不算通過,也不算失敗。