Enterprise 版本
eIDAS 保證等級
NextPDF Enterprise 會把 EU 信任清單證據轉換成明確的 eIDAS 保證等級(LoA)。NextPDF\Enterprise\Security\Eidas\LoaMapping 服務會把單一信任服務項目分類為 Low、Substantial 或 High。它會回傳一個 LoaAssertion,攜帶該等級以及機器可讀的原因碼。你的工作流程可以依保證等級設關卡——「要求 High」——並把原因歸檔為稽核證據。搭配的守衛 CertPiiGuard 會在稽核紀錄離開行程之前,隱去簽署者身分欄位。
有兩條界線讓這項能力誠實地成立。第一,資格認定(qualification)永遠屬於受會員國監管的信任服務提供者(TSP)。NextPDF 是針對已公布的證據做出分類斷言;它從不授予、賦予或認證資格。第二,本頁只涵蓋 LoA 斷言與對映。結構性 PAdES 政策 eidasQualified()(包含其通過/失敗準則)記載於驗證。
供應與授權
標題為「供應與授權」的區段這項能力隨 NextPDF Enterprise(nextpdf/enterprise)出貨,並以 Enterprise 層級的授權封套啟用。缺少該授權的部署不會載入這項能力的類別。比較版本並取得授權。
composer require nextpdf/enterprisenextpdf/premium 整合套件同樣會解析出 Enterprise 套件。啟用時使用你的 Enterprise 授權封套;請見授權與啟用。eIDAS 類別除了引擎基準之外,不需要任何 PHP 擴充。它們在 NextPDF\Enterprise\Security\Eidas 與 NextPDF\Enterprise\Signature\Eidas 之下自動載入。
概念總覽
標題為「概念總覽」的區段Regulation (EU) No 910/2014 (eIDAS) 定義了三個保證等級:low、substantial 與 high(Article 8(1))。每個等級表達對所主張身分的信心程度。high 等級加入了目的在於防止——而非僅是降低——身分被誤用或竄改的控制(Article 8(2)(c))。Article 8 是為電子識別機制定義這些等級。NextPDF 沿用相同的詞彙,來分類簽章憑證背後的信任服務證據。這種沿用是一種用於政策關卡與稽核的工程慣例,並非法律上的等同。
LoaLevel 列舉建模了這三個等級。它的後端值是 eIDAS LoA URI,而非裸標籤,因此持久化後的斷言會攜帶完整識別碼。rank() 提供全序(Low = 1、Substantial = 2、High = 3),而 meetsOrExceeds() 會與所要求的下限比較。
LoaMapping 從單一信任清單項目——來自 Enterprise 信任清單子系統(NextPDF\Enterprise\Security\Tsl)的一個 TspService——計算出等級。這個對映是確定性的:
| 信任清單證據 | 等級 | 原因碼 |
|---|---|---|
| 服務狀態不是 granted | Low | service_not_granted |
服務類型不是 CA/QC | Low | service_not_qualified_ca |
已授予的 CA/QC,帶 QCWithQSCD 且不帶 QCNoQSCD | High | ca_qc_with_qscd 加上 esig_or_eseal 或 qc_default |
其他情況的已授予 CA/QC | Substantial | ca_qc_no_qscd_or_unspecified |
QSCD(合格簽章建立裝置)限定詞是關鍵樞紐。依 Article 3(12),一個合格電子簽章同時需要合格憑證與合格建立裝置。因此,信任清單中「憑證在 QSCD 上管理」的陳述,就是支撐 High 斷言的證據。缺少該陳述,一個已授予的合格 CA 仍只支撐 Substantial,永遠不會是 High。
結果是一個 LoaAssertion:等級加上一份原因碼清單。這些原因讓稽核使用者日後能從相同證據重新推導出分類。下游政策評估器可以把該斷言與簽章驗證結果一併記錄。
本模組還隨附一個部件:CertPiiGuard。當驗證產物被序列化成 JSON 稽核組合時,簽署者憑證會攜帶個人資料——Subject CN、email 屬性,以及 serialNumber 屬性(對自然人而言可能編碼了國民身分識別碼)。GDPR Article 5(1)(c) 要求處理限於必要範圍。因此守衛預設會隱去那些欄位,把值替換成 [REDACTED],同時保留結構性封套(組織、國家、憑證鏈與狀態欄位)。使用者仍可以驗證某個簽章是否通過,而不必得知是誰簽署。
為何這樣設計
標題為「為何這樣設計」的區段這個承重的決策,是把保證斷言與驗證裁決分開。簽章驗證依 ETSI EN 319 102-1 會以一個狀態指示作結——TOTAL-PASSED、TOTAL-FAILED 或 INDETERMINATE——而那個裁決屬於驗證層。LoA 對映則是針對信任清單證據、一個獨立且可重播的分類,以原因碼取代裸標籤。這使 NextPDF 永遠不會把保證主張呈現為驗證結果,也不會把驗證結果呈現為資格授予。它也讓對映在設計上就保守:缺失或含糊的證據會降低等級,而永遠不會提高它。
設計背景:合格簽章解說。
API 介面
標題為「API 介面」的區段以下所有符號都是 nextpdf/enterprise 3.1.0 的公開 API。
LoaLevel
標題為「LoaLevel」的區段enum LoaLevel: string{ case Low = 'http://eidas.europa.eu/LoA/low'; case Substantial = 'http://eidas.europa.eu/LoA/substantial'; case High = 'http://eidas.europa.eu/LoA/high';
public function rank(): int
public function meetsOrExceeds(self $required): bool}丟出或失敗於:rank() 或 meetsOrExceeds() 不會有任何情況。透過 LoaLevel::from() 的原生列舉建構在遇到無法辨識的 URI 時會丟出 \ValueError;LoaLevel::tryFrom() 則改為回傳 null。
LoaMapping
標題為「LoaMapping」的區段final class LoaMapping{ public function loaForService(TspService $service): LoaAssertion}丟出或失敗於:無。此方法是全域函數——每個 TspService 輸入都會產出一個 LoaAssertion。
輸入 DTO NextPDF\Enterprise\Security\Tsl\TspService 與 NextPDF\Enterprise\Security\Tsl\TspServiceQualifier 是穩定的公開 DTO(@api)。對映會查閱 TspService::STATUS_GRANTED、TspService::TYPE_CA_QC,以及限定詞常數 TspServiceQualifier::QSCD_STATEMENT(QCWithQSCD)、TspServiceQualifier::NO_QSCD(QCNoQSCD)、TspServiceQualifier::FOR_ESIG 與 TspServiceQualifier::FOR_ESEAL。
LoaAssertion
標題為「LoaAssertion」的區段final readonly class LoaAssertion{ /** * @param list<non-empty-string> $reasons Machine-readable reason codes for the assertion. */ public function __construct( public LoaLevel $level, public array $reasons, ) {}}丟出或失敗於:無。不可變的值物件。
CertPiiGuard
標題為「CertPiiGuard」的區段final readonly class CertPiiGuard{ public function __construct( private bool $disclosePii = false, ) {}
public function disclosesPii(): bool
public function guardSignerCommonName(string $signer): string
public function guardDistinguishedName(string $dn): string
public function guardTsaName(string $tsaName): string
public function guardRootIssuer(string $issuer): string
public function guardChainIssue(string $issue): string}丟出或失敗於:無。守衛都是純字串轉換。對於無法有把握地標記化的 DN 元件,守衛會失敗封閉,把該元件整個塌縮為 [REDACTED],而不是輸出部分遮罩的值。
程式碼範例 — 快速上手
標題為「程式碼範例 — 快速上手」的區段解析一個 LoA URI,並把它與所要求的下限比較。
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Security\Eidas\LoaLevel;
// A LoA URI as persisted in an audit record or received from a peer system.$uri = 'http://eidas.europa.eu/LoA/substantial';
try { $level = LoaLevel::from($uri);} catch (\ValueError $e) { // Unknown URI: refuse to classify. Never guess an assurance level. echo "Unrecognized LoA URI: {$uri}\n"; exit(1);}
echo 'Level: ' . $level->name . ' (rank ' . $level->rank() . ")\n";echo 'Meets substantial: ' . ($level->meetsOrExceeds(LoaLevel::Substantial) ? 'yes' : 'no') . "\n";echo 'Meets high: ' . ($level->meetsOrExceeds(LoaLevel::High) ? 'yes' : 'no') . "\n";預期輸出:
Level: Substantial (rank 2)Meets substantial: yesMeets high: no程式碼範例 — 正式環境
標題為「程式碼範例 — 正式環境」的區段分類一個信任清單項目,依所要求的等級設關卡,並產出一筆隱去 PII 的稽核紀錄。
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Security\Eidas\LoaLevel;use NextPDF\Enterprise\Security\Eidas\LoaMapping;use NextPDF\Enterprise\Security\Tsl\TspService;use NextPDF\Enterprise\Security\Tsl\TspServiceQualifier;use NextPDF\Enterprise\Signature\Eidas\CertPiiGuard;
// Normally produced by the Enterprise trusted-list subsystem from a// member-state TSL; constructed inline here for a self-contained example.$caPem = (string) file_get_contents(__DIR__ . '/example-qc-ca.pem');
$service = new TspService( tspName: 'Example Qualified TSP', serviceName: 'Example Qualified CA G2', serviceTypeIdentifier: TspService::TYPE_CA_QC, serviceStatus: TspService::STATUS_GRANTED, statusStartingTime: '2024-01-01T00:00:00Z', serviceCertificatePem: $caPem, qualifiers: [ new TspServiceQualifier(qualifierUri: TspServiceQualifier::QSCD_STATEMENT), new TspServiceQualifier(qualifierUri: TspServiceQualifier::FOR_ESIG), ], additionalServiceInformation: [],);
try { // Required floor from deployment configuration; defaults to High. $required = LoaLevel::from(getenv('LOA_REQUIRED') ?: LoaLevel::High->value);} catch (\ValueError $e) { echo "Invalid LOA_REQUIRED URI; refusing to continue.\n"; exit(1);}
$mapping = new LoaMapping();$assertion = $mapping->loaForService($service);
// Privacy by default: signer identity fields are redacted in audit output.$guard = new CertPiiGuard();
$audit = [ 'loa' => $assertion->level->value, 'reasons' => $assertion->reasons, 'meets_required' => $assertion->level->meetsOrExceeds($required), 'signer' => $guard->guardSignerCommonName('CN=Jane Example, O=Example Corp, C=DE'),];
echo json_encode($audit, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";預期輸出:
{ "loa": "http://eidas.europa.eu/LoA/high", "reasons": [ "ca_qc_with_qscd", "esig_or_eseal" ], "meets_required": true, "signer": "CN=[REDACTED], O=Example Corp, C=DE"}邊界情況與陷阱
標題為「邊界情況與陷阱」的區段LoaLevel::from()在遇到未知 URI 時會丟出\ValueError。當偏好處理null時,改用LoaLevel::tryFrom()。- 相互衝突的裝置證據會維持保守。同時攜帶
QCWithQSCD與QCNoQSCD的服務會對映到Substantial,而非High。 - 已授予、無任何限定詞的
CA/QC服務會對映到Substantial,原因為ca_qc_no_qscd_or_unspecified——預設合格,但裝置未經證明。 - 追蹤集合之外的限定詞 URI 不影響分類。未知或未來的限定詞永遠不會提高等級。
- 對映只讀取當前的服務狀態。它不評估
statusStartingTime的歷程;時間點窗口屬於驗證層。 - 持久化列舉的後端 URI,而非
rank()整數。rank 只為了比較而存在。 CertPiiGuard會把不含=的裸名稱整個塌縮為[REDACTED];空字串會原封不動通過所有守衛。- 舊式 OpenSSL 斜線分隔的 DN 會被偵測並在結構上遮罩。RFC 4514 值內部的
/會被視為內容,而非分隔符。 - 非 PII 的 DN 屬性(
O、OU、C、ST、L)會被保留,因此管轄推理在隱去後仍存活。
安全備註
標題為「安全備註」的區段- 預設隱私。 守衛建構子預設為
disclosePii: false。只在你握有處理簽署者身分之記錄在案的合法依據時,才建構new CertPiiGuard(disclosePii: true)。這在序列化邊界落實了 GDPR Article 5(1)(c) 的資料最小化。 - 失敗封閉的隱去。 當一個 DN 元件無法有把握地標記化時,整個元件會塌縮為
[REDACTED]。隱私控制永遠不會失敗敞開。 - 確定性輸出。 守衛使用純字串處理——沒有時鐘、沒有隨機性——因此對相同輸入而言,遮罩後的輸出在位元組層級穩定。穩定的輸出讓稽核差異保持有意義。
- 隱去不是加密。
[REDACTED]會把值從紀錄中移除。如果你需要身分可還原,請把它另行儲存在其自身的合法依據與存取控制之下。 - 輸入是垃圾,輸出就是垃圾。 一個
LoaAssertion的可信度只等同於它背後的信任清單證據。在把項目餵給對映之前,請透過 Enterprise 信任清單子系統取得並簽章檢核信任清單。
符合性
標題為「符合性」的區段NextPDF Enterprise 實作的行為,是由 Regulation (EU) No 910/2014 Article 8(保證等級)與 Article 3(12)(合格電子簽章的要素),以及 ETSI 信任清單限定詞詞彙所啟發。支援不是符合,符合也不是認證。NextPDF 未持有任何認證,也不授予任何認證。NextPDF 不是合格信任服務提供者、不是符合性評鑑機構,也不是監管機構。一個 LoaAssertion 是對已公布證據的軟體分類。它不是對資格或保證的法律裁定,也無法使一個簽章成為合格簽章。
Regulation (EU) 2024/1183 (eIDAS 2) 持續引用 Article 8 的等級,並要求歐洲數位身分錢包以 high 保證等級提供。本頁把這一點作為法規脈絡引用;NextPDF 不對錢包相關做出任何能力主張。
某個特定簽章是否滿足結構性、以 eIDAS 為導向的政策,是另一個問題,由驗證模組回答;請見驗證。
FIPS 模式行為
標題為「FIPS 模式行為」的區段eIDAS LoA 類別不執行任何密碼運算——不做雜湊、不做簽章驗證、不做隨機性。Enterprise FIPS 模式政策管制密碼選擇,因此在本模組中沒有可管制的對象。啟用 FIPS 模式不會改變 LoA 對映或 PII 守衛行為。簽章與信任清單的密碼驗證由驗證與安全模組管理,FIPS 模式政策在那裡適用。
行為契約
標題為「行為契約」的區段LoaMapping::loaForService()是全域函數且是確定性的。每個TspService都會產出一個LoaAssertion;此方法從不丟出,也不查閱任何時鐘、網路或全域狀態。- 分類是保守的。缺失、未知或衝突的證據會降低等級;除了明確的已授予-
CA/QC-帶-QSCD 證據之外,沒有任何東西會提高它。 - 原因碼是機器可讀且穩定的:
service_not_granted、service_not_qualified_ca、ca_qc_with_qscd、esig_or_eseal、qc_default、ca_qc_no_qscd_or_unspecified。 - 等級順序固定:
Low<Substantial<High,透過rank()與meetsOrExceeds()揭露。 CertPiiGuard預設為隱去,且在標記化存疑時失敗封閉。當disclosePii: true時,每個守衛都會原封不動回傳其輸入。- 對相同輸入而言,守衛輸出在位元組層級穩定。
Core 回退
標題為「Core 回退」的區段NextPDF Core 會以密碼方式驗證 PDF 簽章,並在證據破損時失敗封閉。Core 沒有 EU 信任清單模型、沒有 LoaLevel 詞彙、沒有 LoA 對映,也沒有為稽核序列化而設的 eIDAS 層 PII 守衛。在只有 Core 的情況下,你必須自行從你維護的信任資料推導保證分類,並在稽核紀錄離開行程之前套用你自己的隱去。
發佈邊界
標題為「發佈邊界」的區段本頁僅記載外部可觀察的行為與所支援的公開 API 介面。內部命名空間路徑、輔助類別、機制表格、runbook 檔名與工單前綴皆不在範圍內。
- 驗證 — 結構性符合政策,包含
eidasQualified()語意與通過/失敗準則 - 簽章驗證 — AdES/PAdES 密碼驗證側,其報告受 PII 守衛保護
- 安全 — 深度參考 — 安全模組的深度參考
- 合格簽章解說 — 關於資格與保證的 Insider 專文
- 簽章如何證明是誰簽署 — 關於驗證側信任的 Insider 專文