Ga naar inhoud
getnextpdf.com

Enterprise editie

ASiC-vertrouwensbinding

Een ASiC-container bundelt ondertekende bestanden met de handtekeningen die ze beschermen. De moeilijke vraag is niet “klopt de handtekening rekenkundig?” maar “wie staat er achter de ondertekenaar?”. NextPDF\Enterprise\Security\Asic\AsicTrustBinder beantwoordt precies die vraag. Je geeft het het ondertekeningscertificaat uit de containerhandtekening, een trusted list en een validatietijd. Het antwoordt met een AsicTrustBindingResult: een trusted/untrusted verdict, de anchor-bundleversie waartegen het besliste en machineleesbare redenen. Elke afwijzing benoemt zijn oorzaak, zodat het auditbewijs zichzelf schrijft.

Eén grens is bewust gekozen en verdient het om vooraf gesteld te worden. Deze API parseert geen ASiC-containers. Jouw tooling opent de container en extraheert het ondertekeningscertificaat; NextPDF bezit de vertrouwensbeslissing.

Deze mogelijkheid zit in NextPDF Enterprise (nextpdf/enterprise) en activeert met een license envelope op Enterprise-niveau. Een deployment zonder dat recht laadt de klassen van de mogelijkheid niet. Vergelijk edities en verkrijg een licentie.

Terminal window
composer require nextpdf/enterprise

Activatie vereist je Enterprise license envelope. Zie Installeren en authenticeren. De klassen op deze pagina leven onder NextPDF\Enterprise\Security\Asic en NextPDF\Enterprise\Security\Tsl.

ASiC (Associated Signature Containers, ETSI EN 319 162-1) verpakt databestanden en handtekeningen in één archief. Een baseline ASiC-container embedt uitsluitend CAdES- of XAdES-baseline-handtekeningen. Een CAdES-baseline-handtekening draagt zijn ondertekeningscertificaat binnen SignedData.certificates, dus van een verifier wordt verwacht dat die het extraheert uit de containerhandtekening wanneer de handtekening welgevormd is en ondersteund wordt door de container tooling. Dat geëxtraheerde certificaat is de input van deze API.

De vertrouwensbron is een ETSI TS 119 612 trusted list (TSL): een ondertekend XML-document dat trust service providers en hun servicecertificaten opsomt. NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider zet een geparseerd TslDocument om in een anchor bundle. Alleen services die zowel in granted status verkeren als van het CA/QC-servicetype zijn, voeden de anchor set. De bundle draagt een versiestring die is afgeleid van het TSL-sequentienummer en het territorium, plus een SHA-256 integriteitsdigest.

Twee fail-closed gates draaien voordat er enige anchor-vergelijking plaatsvindt:

  1. TSL-versheid. Een trusted list waarvan het NextUpdate-moment is verstreken, moet als verlopen worden weggegooid. AsicTrustBinder::verify() bevestigt versheid op de opgegeven validatietijd voordat het ook maar één anchor afleidt. Een verouderde lijst, of een NextUpdate-waarde zonder expliciete UTC-aanduiding, gooit TslParseException.
  2. Geldigheidsperiode van de ondertekenaar. RFC 5280 path validation vereist dat de geldigheidsperiode van het certificaat de validatietijd omvat. Een cryptografisch intacte handtekening waarvan het certificaat op dat moment verlopen was, of nog niet geldig, wordt afgewezen met een precieze reason code.

Pas dan test de binder het ondertekeningscertificaat tegen elke anchor. Een match levert trusted: true op met reden anchor_signature_match. Geen match levert trusted: false op met reden no_anchor_chain.

De dragende ontwerpbeslissing is een strikte scheiding tussen containermechaniek en de vertrouwensbeslissing, waarbij de vertrouwensbeslissing gedwongen wordt om expliciet te zijn over tijd. Containerformaten variëren (ASiC-S, ASiC-E, CAdES- of XAdES-payloads), maar de vertrouwensvraag is één invariante kern: chaint dit certificaat naar een anchor uit een verse trusted list op een gesteld moment? Die kern vrijhouden van ZIP- en XML-parsing houdt hem klein genoeg om uitputtend te testen en om bij elke gate fail-closed te gaan. Dezelfde redenering verbiedt een stille now-default: validatietijd verandert het verdict, dus de aanroeper moet die bezitten. Versheid wordt bevestigd binnen het anchor-afleidingspad zelf, niet in een optionele collaborator, zodat geen enkel producerpad het kan overslaan.

Ontwerpachtergrond: Hoe een digitale handtekening bewijst wie ondertekende.

De constructie neemt de anchor provider die trusted lists omzet in anchor bundles.

public function __construct(
private readonly TslTrustAnchorProvider $anchorProvider,
) {}

Het primaire toegangspunt verifieert een ondertekenaarscertificaat tegen een trusted list:

public function verify(
string $signerCertPem,
TslDocument $tsl,
DateTimeInterface $validationTime,
): AsicTrustBindingResult
  • $signerCertPem — non-empty PEM-string: het ondertekeningscertificaat uit de ASiC-handtekening.
  • $tsl — de geparseerde, geauthenticeerde trusted list.
  • $validationTime — het moment dat de geldigheidsperiode van het ondertekenaarscertificaat moet omvatten. Er is geen default.

Gooit of faalt met: NextPDF\Enterprise\Security\Tsl\TslParseException wanneer de TSL verouderd is (NextUpdate verstreken), wanneer NextUpdate geen canonieke UTC-waarde is, of wanneer de lijst geen actieve CA/QC-services bevat. Untrusted ondertekenaars gooien niet; ze geven een resultaat terug met trusted: false en een reason code.

Voor batch-workloads verifieer je tegen een vooraf gebouwde bundle:

public function verifyAgainstBundle(
string $signerCertPem,
EnterpriseCaTrustAnchorBundle $bundle,
DateTimeInterface $validationTime,
): AsicTrustBindingResult

Gooit of faalt met: geen eigen excepties; elke uitkomst is een AsicTrustBindingResult. Verkrijg de bundle van TslTrustAnchorProvider::buildBundle() — construeer hem niet met de hand.

public function buildBundle(TslDocument $tsl, DateTimeImmutable $now): EnterpriseCaTrustAnchorBundle

Gooit of faalt met: TslParseException als de TSL verouderd is, zijn NextUpdate geen canonieke UTC-waarde is, of het geen actieve CA/QC-services heeft.

public function __construct(
public bool $trusted,
public string $anchorBundleVersion,
public array $reasons,
) {}

$reasons is een list<non-empty-string> van machineleesbare codes. $anchorBundleVersion registreert de gebruikte anchor set, in de vorm tsl-<territory>-seq<N> (bijvoorbeeld tsl-eu-seq42).

Reason codeBetekenis
anchor_signature_matchHet ondertekenaarscertificaat verifieert tegen een TSL-afgeleide anchor. Trusted.
no_anchor_chainGeen enkele anchor in de bundle verifieert het ondertekenaarscertificaat. Untrusted.
signer_cert_expiredDe validatietijd valt na de notAfter van het certificaat. Untrusted.
signer_cert_not_yet_validDe validatietijd valt vóór de notBefore van het certificaat. Untrusted.
cannot_parse_signer_certDe opgegeven PEM parseert niet als een X.509-certificaat. Untrusted.

Jouw container tooling heeft het ondertekeningscertificaat al geëxtraheerd. Bind het aan een trusted list van een lidstaat die je hebt opgehaald en geauthenticeerd (zie Trusted lists).

asic-trust-binding-quickstart.php
<?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";

Verwachte output voor een ondertekenaar uitgegeven door een vermelde CA/QC-service:

TRUSTED
Anchors: tsl-eu-seq42
Reasons: anchor_signature_match

Leid de anchor bundle één keer per trusted list af, verifieer daarna vele containerondertekenaars ertegen. Eén verouderde of onbruikbare TSL faalt de hele batch fail-closed; individuele ondertekenaarsproblemen komen per container aan de oppervlakte.

asic-trust-binding-batch.php
<?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,
);
}

Verwachte output wanneer één ondertekenaarscertificaat is verlopen:

invoice-2026-06.asice => trusted (anchor_signature_match; anchors tsl-eu-seq42)
tender-2019.asice => rejected (signer_cert_expired; anchors tsl-eu-seq42)
  • Validatietijd is verplicht en beslissend. Er is geen stille now-default. Een handtekening die in 2019 verifieerde, rapporteert signer_cert_expired wanneer je valideert op een moment in 2026 voorbij notAfter. Geef voor historisch materiaal de tijd door die je bewijs ondersteunt (bijvoorbeeld een proof-of-existence-tijd), niet de wandklok.
  • Een verouderde TSL gooit; het is geen “untrusted” verdict. Een TslParseException uit verify() of buildBundle() betekent dat de vertrouwensbron onbruikbaar is. Behandel het als een operationele fout: ververs de lijst, registreer het niet als een ondertekenaarsafwijzing.
  • Anchors worden getest als directe uitgevers. Elke anchor wordt geprobeerd als het certificaat dat het ondertekenaarscertificaat ondertekende. EU-lidstaat-TSL’s vermelden de uitgevende CA/QC-servicecertificaten, dus end-entity gekwalificeerde certificaten matchen doorgaans direct. Een ondertekenaar uitgegeven door een intermediaire CA die zelf geen vermelde actieve CA/QC-service is, levert no_anchor_chain op.
  • Anchor-afleiding filtert hard. Services die zijn ingetrokken, of van enig ander type dan CA/QC, worden nooit anchors. Een lijst waarvan de actieve CA/QC-set leeg is, gooit in plaats van een lege bundle te produceren.
  • NextUpdate moet canonieke UTC zijn. Een waarde zonder expliciete Z of numerieke offset-aanduiding wordt fail-closed afgewezen, nooit geherinterpreteerd in de lokale tijdzone van de server.
  • Malformed input degradeert precies. Een PEM die niet parseert, geeft cannot_parse_signer_cert terug; een nog-niet-geldig certificaat wordt onderscheiden van een verlopen certificaat.
  • Registreer anchorBundleVersion. Het benoemt de exacte anchor set (tsl-<territory>-seq<N>) achter elk verdict, wat een auditor zal vragen.
  • Fail-closed door constructie. Versheid wordt bevestigd voordat er enige anchor wordt afgeleid. De geldigheidsgate van de ondertekenaar draait voor enige anchor-vergelijking. Onbruikbaar vertrouwensmateriaal gooit; twijfelachtige ondertekenaars worden afgewezen met redenen. Geen enkel pad degradeert naar een stille pass.
  • Vertrouwensbinding is één laag, niet de hele validatie. Deze API verifieert niet de CAdES-handtekeningwaarde over de containerinhoud, controleert geen revocation (geen CRL- of OCSP-lookup) en authenticeert het TSL-document zelf niet. Authenticeer de lijst eerst via de trusted-list-pipeline (zie Trusted lists), verifieer de handtekening cryptografisch met jouw signature tooling en voeg revocation checking toe volgens jouw beleid.
  • Kies de validatietijd bewust. Het verdict is een functie van de tijd die je doorgeeft. Leid deze af uit betrouwbaar bewijs (een gekwalificeerd timestamp, een archiefrecord), niet uit een door een aanvaller beïnvloedbare klok.
  • Bewijsoutputs zijn deterministisch. trusted, anchorBundleVersion en reasons zijn stabiele, machineleesbare waarden geschikt voor ondertekende audit logs.

AsicTrustBinder ondersteunt workflows afgestemd op ETSI EN 319 162-1 (ASiC baseline containers), ETSI EN 319 122-1 (CAdES baseline signatures) en ETSI TS 119 612 (trusted lists), en past de RFC 5280 geldigheidsperiode-gate toe op de opgegeven validatietijd.

Ondersteuning is geen conformiteit, en conformiteit is geen certificering. NextPDF implementeert de controles die deze pagina beschrijft; het is door geen enkele instantie tegen deze standaarden gecertificeerd, en het gebruik van deze API maakt jouw output op zichzelf niet “gekwalificeerd” of juridisch geldig onder eIDAS of enig ander regime. NextPDF houdt geen certificering en verleent er geen. Of een compleet validatieproces aan een gegeven juridische of aanbestedingseis voldoet, is een beoordeling voor jouw assessoren.

De vertrouwensbinding voert X.509 certificaat-handtekeningcontroles in-process uit; het wordt niet gerouteerd door de Enterprise FIPS-modus runtime guard, en het inschakelen van FIPS-modus verandert het gedrag niet. Het is geen FIPS-gevalideerde cryptografische service, en er wordt geen FIPS 140-certificering geclaimd. Deployments met FIPS-verplichtingen zouden deze API dienovereenkomstig moeten afbakenen en zie FIPS 140-2/3 cryptografisch beleid.

  • verify() leidt anchors alleen af uit een TSL die vers is op de opgegeven validatietijd; een verouderde of malformed lijst gooit TslParseException voordat er een anchor bestaat.
  • Anchors worden uitsluitend afgeleid uit TSL-services in granted status met het CA/QC-servicetype; een lege actieve set gooit.
  • De geldigheidsperiode van het ondertekenaarscertificaat moet de validatietijd omvatten; overtredingen geven signer_cert_expired of signer_cert_not_yet_valid terug.
  • Elke uitkomst is een AsicTrustBindingResult die trusted, anchorBundleVersion en ten minste één reason code draagt; er is geen verdict zonder reden.
  • Untrusted ondertekenaars worden teruggegeven, nooit gegooid; onbruikbaar vertrouwensmateriaal wordt gegooid, nooit als verdict teruggegeven.
  • Containerparsing gebeurt nooit binnen deze API; de inputs zijn de geëxtraheerde PEM, de trusted list en de validatietijd.

NextPDF Core valideert PDF-handtekeningen (CMS/PAdES) tegen trust anchors die je expliciet pint via zijn CaTrustAnchorBundle-contract — zie Core security. Core heeft geen trusted-list (TSL) ingestie en geen ASiC-specifieke vertrouwensbinding. Met Core alleen kun je je eigen anchor set onderhouden voor PDF-handtekeningvalidatie; anchors afleiden uit een ETSI TS 119 612 trusted list en ASiC-containerondertekenaars eraan binden vereist NextPDF Enterprise.

Deze pagina documenteert alleen extern waarneembaar gedrag en het ondersteunde publieke API-oppervlak. Interne namespace-paden, helper-klassen, mechanismetabellen, runbook-bestandsnamen en ticketprefixen vallen buiten de scope.