Enterprise 版本
eIDAS 保证级别
NextPDF Enterprise 将欧盟信任列表证据转化为明确的 eIDAS 保证级别(LoA)。NextPDF\Enterprise\Security\Eidas\LoaMapping 服务将单个信任服务条目分类为 Low、Substantial 或 High。它返回一个 LoaAssertion,其中承载级别以及机器可读的原因代码。你的工作流可以基于保证级别进行门控——“要求 High”——并将这些原因归档为审计证据。配套的守护组件 CertPiiGuard 会在审计记录离开进程之前对签名者身份字段进行脱敏。
有两条边界诚实地界定了这项能力。第一,资格认定始终归属于成员国监管下的信任服务提供者(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 |
已授予(granted)的 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 组件,守护组件会失败关闭(fail closed),将该组件整体折叠为 [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代码示例——生产环境
标题为“代码示例——生产环境”的章节对一个信任列表条目进行分类,基于所要求的级别进行门控,并发出一条脱敏的审计记录。
<?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]。隐私控制绝不会失败开放(fail open)。 - 确定性输出。 守护方法使用纯字符串处理——没有时钟,没有随机性——因此对于相同输入,掩码输出是字节稳定的。稳定的输出使审计差异保持有意义。
- 脱敏不是加密。
[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 的级别,并要求 European Digital Identity Wallets 在 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 没有欧盟信任列表模型,没有 LoaLevel 词汇表,没有 LoA 映射,也没有用于审计序列化的 eIDAS 层 PII 守护。仅使用 Core 时,你必须自行从你所维护的信任数据推导保证级别分类,并在审计记录离开进程之前应用你自己的脱敏。
发布边界
标题为“发布边界”的章节本页仅记录外部可观察的行为和受支持的公开 API 表面。内部命名空间路径、辅助类、机制表、运行手册文件名以及工单前缀均不在范围之内。
另请参阅
标题为“另请参阅”的章节- 验证 —— 结构性合规策略,包括
eidasQualified()语义和通过/失败判定标准 - 签名验证 —— AdES/PAdES 密码学验证端,PII 守护保护的正是其报告
- 安全——深度参考 —— 安全模块的深度参考
- 合格签名解析 —— 关于资格与保证的 Insider 文章
- 签名如何证明签署者身份 —— 关于验证端信任的 Insider 文章