Enterprise 版本
ASiC 信任綁定
ASiC 容器將已簽署的檔案,與保護這些檔案的簽章打包在一起。真正棘手的問題不是「簽章能不能算得出來?」,而是「誰為這位簽署者背書?」。NextPDF\Enterprise\Security\Asic\AsicTrustBinder 回答的正是這個問題。你把容器簽章中的簽署憑證、一份信任清單,以及一個驗證時間交給它。它會以一份 AsicTrustBindingResult 回覆你:一個信任/不信任的裁決、它據以判定的錨集合版本,以及機器可讀的原因。每一次拒絕都會指名其成因,因此稽核佐證會自動成形。
有一條界線是刻意設計的,值得先講清楚。這個 API 不會解析 ASiC 容器。由你的工具開啟容器並擷取簽署憑證;NextPDF 負責信任決策。
供應與授權
標題為「供應與授權」的區段這項能力隨 NextPDF Enterprise(nextpdf/enterprise)出貨,並透過 Enterprise 級授權封套啟用。缺少該授權的部署不會載入這項能力的類別。比較版本並取得授權。
composer require nextpdf/enterprise啟用需要你的 Enterprise 授權封套。參見 安裝與認證。本頁的類別位於 NextPDF\Enterprise\Security\Asic 與 NextPDF\Enterprise\Security\Tsl 之下。
概念總覽
標題為「概念總覽」的區段ASiC(Associated Signature Containers,ETSI EN 319 162-1)將資料檔案與簽章封裝在單一封存檔中。基準 ASiC 容器僅內嵌 CAdES 或 XAdES 基準簽章。CAdES 基準簽章會將其簽署憑證攜帶在 SignedData.certificates 之中,因此當簽章格式正確且容器工具支援時,驗證者應從容器的簽章中擷取它。擷取出的那份憑證即為這個 API 的輸入。
信任來源是一份 ETSI TS 119 612 信任清單(TSL):一份列舉信任服務提供者及其服務憑證的已簽署 XML 文件。NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider 會把解析後的 TslDocument 轉換成錨集合。只有同時處於 granted 狀態且屬於 CA/QC 服務類型的服務,才會成為錨集合的種子。這個集合帶有一個由 TSL 序號與領域衍生而來的版本字串,外加一個 SHA-256 完整性摘要。
在任何錨比對之前,會執行兩道 fail-closed 閘門:
- TSL 新鮮度。 若信任清單的
NextUpdate時刻已經過去,就必須以逾期為由捨棄。AsicTrustBinder::verify()會在衍生任何一個錨之前,於所提供的驗證時間點主張其新鮮度。逾期的清單,或不帶明確 UTC 指示符的NextUpdate值,都會拋出TslParseException。 - 簽署者有效期。 RFC 5280 路徑驗證要求憑證的有效期必須涵蓋驗證時間。若簽章在密碼學上完整無缺,但其憑證在該時間點已逾期或尚未生效,則會以精確的原因碼予以拒絕。
唯有到此,綁定器才會以每個錨測試簽署憑證。相符會產出 trusted: true,原因為 anchor_signature_match。不相符則產出 trusted: false,原因為 no_anchor_chain。
為何如此設計
標題為「為何如此設計」的區段承載全局的設計決策,是在容器機制與信任決策之間劃下嚴格分界,並強制信任決策必須對時間有明確交代。容器格式各不相同(ASiC-S、ASiC-E、CAdES 或 XAdES 酬載),但信任問題只有一個不變的核心:這份憑證是否在指定時刻,鏈結到某份新鮮信任清單所提供的錨?讓這個核心不沾染 ZIP 與 XML 解析,就能讓它小到足以窮盡測試,並在每一道閘門上都能 fail closed。同樣的推理禁止採用隱含的 now 預設值:驗證時間會改變裁決,因此必須由呼叫端擁有它。新鮮度是在錨衍生路徑本身之中主張的,而非在某個選用的協作者中,因此沒有任何產生路徑能夠略過它。
設計背景:數位簽章如何證明簽署者身分。
API 介面
標題為「API 介面」的區段AsicTrustBinder
標題為「AsicTrustBinder」的區段建構時接受一個錨提供者,負責把信任清單轉換成錨集合。
public function __construct( private readonly TslTrustAnchorProvider $anchorProvider,) {}主要進入點會以信任清單驗證簽署者憑證:
public function verify( string $signerCertPem, TslDocument $tsl, DateTimeInterface $validationTime,): AsicTrustBindingResult$signerCertPem— 非空 PEM 字串:來自 ASiC 簽章的簽署憑證。$tsl— 已解析且經過認證的信任清單。$validationTime— 簽署者憑證有效期必須涵蓋的時刻。沒有預設值。
拋出或失敗於: 當 TSL 逾期(NextUpdate 已過)、NextUpdate 不是標準 UTC 值,或清單不含任何有效的 CA/QC 服務時,會拋出 NextPDF\Enterprise\Security\Tsl\TslParseException。不受信任的簽署者不會拋出;它們會回傳一份帶有 trusted: false 與原因碼的結果。
對於批次工作負載,請以預先建立的集合進行驗證:
public function verifyAgainstBundle( string $signerCertPem, EnterpriseCaTrustAnchorBundle $bundle, DateTimeInterface $validationTime,): AsicTrustBindingResult拋出或失敗於: 它本身不拋出任何例外;每個結果都是一份 AsicTrustBindingResult。請從 TslTrustAnchorProvider::buildBundle() 取得集合 — 不要手動建構它。
TslTrustAnchorProvider
標題為「TslTrustAnchorProvider」的區段public function buildBundle(TslDocument $tsl, DateTimeImmutable $now): EnterpriseCaTrustAnchorBundle拋出或失敗於: 若 TSL 逾期、其 NextUpdate 不是標準 UTC 值,或不含有效的 CA/QC 服務,則拋出 TslParseException。
AsicTrustBindingResult
標題為「AsicTrustBindingResult」的區段public function __construct( public bool $trusted, public string $anchorBundleVersion, public array $reasons,) {}$reasons 是一個機器可讀原因碼的 list<non-empty-string>。$anchorBundleVersion 記錄所使用的錨集合,格式為 tsl-<territory>-seq<N>(例如 tsl-eu-seq42)。
| 原因碼 | 意義 |
|---|---|
anchor_signature_match | 簽署者憑證通過某個源自 TSL 的錨驗證。受信任。 |
no_anchor_chain | 集合中沒有任何錨能驗證該簽署者憑證。不受信任。 |
signer_cert_expired | 驗證時間落在憑證 notAfter 之後。不受信任。 |
signer_cert_not_yet_valid | 驗證時間落在憑證 notBefore 之前。不受信任。 |
cannot_parse_signer_cert | 所提供的 PEM 無法解析為 X.509 憑證。不受信任。 |
程式碼範例 — 快速開始
標題為「程式碼範例 — 快速開始」的區段你的容器工具已經擷取出簽署憑證。將它綁定到一份你已擷取並認證的會員國信任清單(參見 信任清單)。
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Security\Asic\AsicTrustBinder;use NextPDF\Enterprise\Security\Tsl\TslParseException;use NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider;use NextPDF\Enterprise\Security\Tsl\TslXmlParser;
// Extracted by YOUR tooling from META-INF/signature.p7s or signatures.xml.$signerCertPem = (string) file_get_contents(__DIR__ . '/asic-signer.pem');
// A trusted list you have already fetched and authenticated.$tslXml = (string) file_get_contents(__DIR__ . '/member-state-tsl.xml');
$binder = new AsicTrustBinder(new TslTrustAnchorProvider());
try { $tsl = (new TslXmlParser())->parse($tslXml);
$result = $binder->verify( signerCertPem: $signerCertPem, tsl: $tsl, validationTime: new DateTimeImmutable('2026-07-03T12:00:00Z'), );} catch (TslParseException $e) { // Fail closed: stale TSL, malformed NextUpdate, or no active CA/QC services. fwrite(STDERR, 'Trusted list rejected: ' . $e->getMessage() . PHP_EOL); exit(1);}
echo $result->trusted ? "TRUSTED\n" : "NOT TRUSTED\n";echo 'Anchors: ' . $result->anchorBundleVersion . "\n";echo 'Reasons: ' . implode(', ', $result->reasons) . "\n";由列於清單中的 CA/QC 服務所簽發之簽署者,其預期輸出:
TRUSTEDAnchors: tsl-eu-seq42Reasons: anchor_signature_match程式碼範例 — 正式環境
標題為「程式碼範例 — 正式環境」的區段每份信任清單只衍生一次錨集合,然後以它驗證眾多容器簽署者。一份逾期或不可用的 TSL 會讓整個批次 fail closed;個別簽署者的問題則會逐一在各容器上浮現。
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Security\Asic\AsicTrustBinder;use NextPDF\Enterprise\Security\Asic\AsicTrustBindingResult;use NextPDF\Enterprise\Security\Tsl\TslDocument;use NextPDF\Enterprise\Security\Tsl\TslParseException;use NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider;use NextPDF\Enterprise\Security\Tsl\TslXmlParser;
/** * @param array<string, non-empty-string> $signerPemsByContainer PEM per container path. * @return array<string, AsicTrustBindingResult> * @throws TslParseException When no anchor set can be derived from the TSL. */function bindBatch( TslDocument $tsl, array $signerPemsByContainer, DateTimeImmutable $validationTime,): array { $provider = new TslTrustAnchorProvider();
// Derive the anchor set ONCE; a throw here means the trusted list itself // is unusable at this validation time. $bundle = $provider->buildBundle($tsl, $validationTime);
$binder = new AsicTrustBinder($provider);
$results = []; foreach ($signerPemsByContainer as $container => $signerPem) { $results[$container] = $binder->verifyAgainstBundle( signerCertPem: $signerPem, bundle: $bundle, validationTime: $validationTime, ); }
return $results;}
$tsl = (new TslXmlParser())->parse( (string) file_get_contents(__DIR__ . '/member-state-tsl.xml'),);
$signerPems = [ 'invoice-2026-06.asice' => (string) file_get_contents(__DIR__ . '/signer-a.pem'), 'tender-2019.asice' => (string) file_get_contents(__DIR__ . '/signer-b.pem'),];
try { $results = bindBatch( tsl: $tsl, signerPemsByContainer: $signerPems, validationTime: new DateTimeImmutable('now', new DateTimeZone('UTC')), );} catch (TslParseException $e) { // Fail closed for the WHOLE batch: no trustworthy anchor set exists. fwrite(STDERR, 'Anchor derivation failed: ' . $e->getMessage() . PHP_EOL); exit(1);}
foreach ($results as $container => $result) { printf( "%s => %s (%s; anchors %s)\n", $container, $result->trusted ? 'trusted' : 'rejected', implode(',', $result->reasons), $result->anchorBundleVersion, );}當其中一份簽署者憑證已逾期時的預期輸出:
invoice-2026-06.asice => trusted (anchor_signature_match; anchors tsl-eu-seq42)tender-2019.asice => rejected (signer_cert_expired; anchors tsl-eu-seq42)邊界情形與陷阱
標題為「邊界情形與陷阱」的區段- 驗證時間是必要且具決定性的。 沒有隱含的
now預設值。一份在 2019 年通過驗證的簽章,當你在超過notAfter的 2026 年時刻進行驗證時,會回報signer_cert_expired。對於歷史性材料,請傳入你的佐證所支持的時間(例如某個存在性證明時間),而非牆上時鐘的時間。 - 逾期的 TSL 會拋出;它不是「不受信任」的裁決。 來自
verify()或buildBundle()的TslParseException,代表信任來源不可用。請把它當作一次維運失敗來處理:更新清單,不要把它記錄為簽署者被拒。 - 錨是以直接簽發者的身分受測。 每個錨都會被當作簽署了簽署者憑證的那份憑證來嘗試。歐盟會員國的 TSL 會列出簽發用的 CA/QC 服務憑證,因此末端實體的合格憑證通常會直接相符。若簽署者是由某個本身並未列於清單中之有效 CA/QC 服務的中繼 CA 所簽發,則會產出
no_anchor_chain。 - 錨衍生會嚴格過濾。 已撤回的服務,或任何非 CA/QC 類型的服務,都絕不會成為錨。若清單的有效 CA/QC 集合為空,則會拋出,而不會產出一個空集合。
NextUpdate必須是標準 UTC。 不帶明確Z或數值偏移指示符的值會被 fail-closed 拒絕,絕不會依伺服器的本地時區重新解讀。- 格式不正確的輸入會精確地降級。 無法解析的 PEM 會回傳
cannot_parse_signer_cert;尚未生效的憑證會與已逾期的憑證有所區別。 - 記錄
anchorBundleVersion。 它指名每個裁決背後所依據的確切錨集合(tsl-<territory>-seq<N>),這正是稽核人員會要求的資訊。
安全性注意事項
標題為「安全性注意事項」的區段- 從設計上即 fail-closed。 新鮮度會在衍生任何錨之前就先行主張。簽署者有效性閘門會在任何錨比對之前執行。不可用的信任材料會拋出;有疑慮的簽署者會連同原因一併被拒。沒有任何路徑會降級為隱默的放行。
- 信任綁定只是一層,並非整套驗證。 這個 API 不會驗證涵蓋容器內容的 CAdES 簽章值,不會檢查撤銷(不做 CRL 或 OCSP 查詢),也不會認證 TSL 文件本身。請先透過信任清單管線認證該清單(參見 信任清單),以你的簽章工具在密碼學上驗證簽章,並依你的政策加上撤銷檢查。
- 審慎地選擇驗證時間。 裁決是你所傳入時間的函數。請從可信的佐證(一個合格時戳、一筆封存紀錄)衍生它,而非從可被攻擊者影響的時鐘取得。
- 佐證輸出具決定性。
trusted、anchorBundleVersion與reasons都是穩定、機器可讀的值,適合用於已簽署的稽核日誌。
符合性
標題為「符合性」的區段AsicTrustBinder 支援與 ETSI EN 319 162-1(ASiC 基準容器)、ETSI EN 319 122-1(CAdES 基準簽章)及 ETSI TS 119 612(信任清單)相符的工作流程,並在所提供的驗證時間點套用 RFC 5280 有效期閘門。
支援不等於符合,符合不等於認證。NextPDF 實作本頁所描述的檢查;它並未經任何機構針對這些標準進行認證,而且單憑使用這個 API,並不會讓你的輸出在 eIDAS 或任何其他制度下成為「合格」或具法律效力。NextPDF 未持有任何認證,也不授予任何認證。一套完整的驗證流程是否滿足特定法律或採購要求,是由你的評估者判定的事項。
FIPS 模式行為
標題為「FIPS 模式行為」的區段信任綁定會在行程內執行 X.509 憑證簽章檢查;它不會經由 Enterprise FIPS 模式的執行期防護,而啟用 FIPS 模式也不會改變其行為。它不是一項通過 FIPS 驗證的密碼服務,也未主張任何 FIPS 140 認證。負有 FIPS 義務的部署應據此界定這個 API 的範圍,並參見 FIPS 140-2/3 密碼政策。
行為契約
標題為「行為契約」的區段verify()只會從在所提供驗證時間點仍屬新鮮的 TSL 衍生錨;逾期或格式不正確的清單,會在任何錨存在之前就拋出TslParseException。- 錨只會從處於 granted 狀態且屬 CA/QC 服務類型的 TSL 服務衍生;有效集合為空時會拋出。
- 簽署者憑證的有效期必須涵蓋驗證時間;違反者會回傳
signer_cert_expired或signer_cert_not_yet_valid。 - 每個結果都是一份帶有
trusted、anchorBundleVersion與至少一個原因碼的AsicTrustBindingResult;不存在無原因的裁決。 - 不受信任的簽署者一律回傳,絕不拋出;不可用的信任材料一律拋出,絕不作為裁決回傳。
- 容器解析絕不會在這個 API 內部發生;輸入是擷取出的 PEM、信任清單,以及驗證時間。
Core 備援
標題為「Core 備援」的區段NextPDF Core 會以你透過其 CaTrustAnchorBundle 契約明確釘選的信任錨,驗證 PDF(CMS/PAdES)簽章 — 參見 Core 安全性。Core 沒有信任清單(TSL)匯入,也沒有 ASiC 專屬的信任綁定。僅憑 Core,你可以維護自己的錨集合用於 PDF 簽章驗證;而要從 ETSI TS 119 612 信任清單衍生錨,並將 ASiC 容器簽署者綁定到它們,則需要 NextPDF Enterprise。
發布範圍界線
標題為「發布範圍界線」的區段本頁僅記載外部可觀察的行為,以及受支援的公開 API 介面。內部命名空間路徑、輔助類別、機制表格、維運手冊檔名,以及工單前綴皆不在範圍內。
另請參閱
標題為「另請參閱」的區段- 信任清單 — 擷取、認證並解析供給錨提供者的 TSL。
- 簽章驗證 — 針對 PDF 簽章的 Enterprise 驗證介面。
- FIPS 140-2/3 密碼政策 — Enterprise FIPS 模式的態勢。
- 數位簽章如何證明簽署者身分 — 第一原理背景。
- 長期驗證 — 為何驗證時間與保存的佐證至關重要。