跳到內容
getnextpdf.com

Enterprise 版本

eIDAS 保證等級

NextPDF Enterprise 會把 EU 信任清單證據轉換成明確的 eIDAS 保證等級(LoA)。NextPDF\Enterprise\Security\Eidas\LoaMapping 服務會把單一信任服務項目分類為 LowSubstantialHigh。它會回傳一個 LoaAssertion,攜帶該等級以及機器可讀的原因碼。你的工作流程可以依保證等級設關卡——「要求 High」——並把原因歸檔為稽核證據。搭配的守衛 CertPiiGuard 會在稽核紀錄離開行程之前,隱去簽署者身分欄位。

有兩條界線讓這項能力誠實地成立。第一,資格認定(qualification)永遠屬於受會員國監管的信任服務提供者(TSP)。NextPDF 是針對已公布的證據做出分類斷言;它從不授予、賦予或認證資格。第二,本頁只涵蓋 LoA 斷言與對映。結構性 PAdES 政策 eidasQualified()(包含其通過/失敗準則)記載於驗證

這項能力隨 NextPDF Enterprisenextpdf/enterprise)出貨,並以 Enterprise 層級的授權封套啟用。缺少該授權的部署不會載入這項能力的類別。比較版本並取得授權

Terminal window
composer require nextpdf/enterprise

nextpdf/premium 整合套件同樣會解析出 Enterprise 套件。啟用時使用你的 Enterprise 授權封套;請見授權與啟用。eIDAS 類別除了引擎基準之外,不需要任何 PHP 擴充。它們在 NextPDF\Enterprise\Security\EidasNextPDF\Enterprise\Signature\Eidas 之下自動載入。

Regulation (EU) No 910/2014 (eIDAS) 定義了三個保證等級:lowsubstantialhigh(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——計算出等級。這個對映是確定性的:

信任清單證據等級原因碼
服務狀態不是 grantedLowservice_not_granted
服務類型不是 CA/QCLowservice_not_qualified_ca
已授予的 CA/QC,帶 QCWithQSCD 且不帶 QCNoQSCDHighca_qc_with_qscd 加上 esig_or_esealqc_default
其他情況的已授予 CA/QCSubstantialca_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-PASSEDTOTAL-FAILEDINDETERMINATE——而那個裁決屬於驗證層。LoA 對映則是針對信任清單證據、一個獨立且可重播的分類,以原因碼取代裸標籤。這使 NextPDF 永遠不會把保證主張呈現為驗證結果,也不會把驗證結果呈現為資格授予。它也讓對映在設計上就保守:缺失或含糊的證據會降低等級,而永遠不會提高它。

設計背景:合格簽章解說

以下所有符號都是 nextpdf/enterprise 3.1.0 的公開 API。

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 時會丟出 \ValueErrorLoaLevel::tryFrom() 則改為回傳 null

final class LoaMapping
{
public function loaForService(TspService $service): LoaAssertion
}

丟出或失敗於:無。此方法是全域函數——每個 TspService 輸入都會產出一個 LoaAssertion

輸入 DTO NextPDF\Enterprise\Security\Tsl\TspServiceNextPDF\Enterprise\Security\Tsl\TspServiceQualifier 是穩定的公開 DTO(@api)。對映會查閱 TspService::STATUS_GRANTEDTspService::TYPE_CA_QC,以及限定詞常數 TspServiceQualifier::QSCD_STATEMENTQCWithQSCD)、TspServiceQualifier::NO_QSCDQCNoQSCD)、TspServiceQualifier::FOR_ESIGTspServiceQualifier::FOR_ESEAL

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,
) {}
}

丟出或失敗於:無。不可變的值物件。

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,並把它與所要求的下限比較。

loa-quick-start.php
<?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: yes
Meets high: no

分類一個信任清單項目,依所要求的等級設關卡,並產出一筆隱去 PII 的稽核紀錄。

loa-audit-gate.php
<?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()
  • 相互衝突的裝置證據會維持保守。同時攜帶 QCWithQSCDQCNoQSCD 的服務會對映到 Substantial,而非 High
  • 已授予、無任何限定詞的 CA/QC 服務會對映到 Substantial,原因為 ca_qc_no_qscd_or_unspecified——預設合格,但裝置未經證明。
  • 追蹤集合之外的限定詞 URI 不影響分類。未知或未來的限定詞永遠不會提高等級。
  • 對映只讀取當前的服務狀態。它不評估 statusStartingTime 的歷程;時間點窗口屬於驗證層。
  • 持久化列舉的後端 URI,而非 rank() 整數。rank 只為了比較而存在。
  • CertPiiGuard 會把不含 = 的裸名稱整個塌縮為 [REDACTED];空字串會原封不動通過所有守衛。
  • 舊式 OpenSSL 斜線分隔的 DN 會被偵測並在結構上遮罩。RFC 4514 值內部的 / 會被視為內容,而非分隔符。
  • 非 PII 的 DN 屬性(OOUCSTL)會被保留,因此管轄推理在隱去後仍存活。
  • 預設隱私。 守衛建構子預設為 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 為導向的政策,是另一個問題,由驗證模組回答;請見驗證

eIDAS LoA 類別不執行任何密碼運算——不做雜湊、不做簽章驗證、不做隨機性。Enterprise FIPS 模式政策管制密碼選擇,因此在本模組中沒有可管制的對象。啟用 FIPS 模式不會改變 LoA 對映或 PII 守衛行為。簽章與信任清單的密碼驗證由驗證與安全模組管理,FIPS 模式政策在那裡適用。

  • LoaMapping::loaForService() 是全域函數且是確定性的。每個 TspService 都會產出一個 LoaAssertion;此方法從不丟出,也不查閱任何時鐘、網路或全域狀態。
  • 分類是保守的。缺失、未知或衝突的證據會降低等級;除了明確的已授予-CA/QC-帶-QSCD 證據之外,沒有任何東西會提高它。
  • 原因碼是機器可讀且穩定的:service_not_grantedservice_not_qualified_caca_qc_with_qscdesig_or_esealqc_defaultca_qc_no_qscd_or_unspecified
  • 等級順序固定:Low < Substantial < High,透過 rank()meetsOrExceeds() 揭露。
  • CertPiiGuard 預設為隱去,且在標記化存疑時失敗封閉。當 disclosePii: true 時,每個守衛都會原封不動回傳其輸入。
  • 對相同輸入而言,守衛輸出在位元組層級穩定。

NextPDF Core 會以密碼方式驗證 PDF 簽章,並在證據破損時失敗封閉。Core 沒有 EU 信任清單模型、沒有 LoaLevel 詞彙、沒有 LoA 對映,也沒有為稽核序列化而設的 eIDAS 層 PII 守衛。在只有 Core 的情況下,你必須自行從你維護的信任資料推導保證分類,並在稽核紀錄離開行程之前套用你自己的隱去。

本頁僅記載外部可觀察的行為與所支援的公開 API 介面。內部命名空間路徑、輔助類別、機制表格、runbook 檔名與工單前綴皆不在範圍內。