Aller au contenu
getnextpdf.com

Enterprise édition

Liaison de confiance ASiC

Un conteneur ASiC regroupe des fichiers signés avec les signatures qui les protègent. La vraie question n’est pas « la signature est-elle correcte ? » mais « qui se porte garant du signataire ? ». NextPDF\Enterprise\Security\Asic\AsicTrustBinder répond précisément à cette question. Tu lui fournis le certificat de signature issu de la signature du conteneur, une liste de confiance et un instant de validation. Il répond par un AsicTrustBindingResult : un verdict de confiance ou de défiance, la version du lot d’ancres qu’il a retenue pour se prononcer et des motifs lisibles par une machine. Chaque rejet nomme sa cause, si bien que la preuve d’audit s’écrit d’elle-même.

Une limite est délibérée et mérite d’être posée d’emblée. Cette API n’analyse pas les conteneurs ASiC. Ton outillage ouvre le conteneur et en extrait le certificat de signature ; NextPDF est responsable de la décision de confiance.

Cette fonctionnalité est fournie dans NextPDF Enterprise (nextpdf/enterprise) et s’active avec une enveloppe de licence de niveau Enterprise. Un déploiement dépourvu de ce droit ne charge pas les classes de la fonctionnalité. Comparer les éditions et obtenir une licence.

Fenêtre de terminal
composer require nextpdf/enterprise

L’activation requiert ton enveloppe de licence Enterprise. Voir Installer et s’authentifier. Les classes présentées sur cette page se trouvent sous NextPDF\Enterprise\Security\Asic et NextPDF\Enterprise\Security\Tsl.

ASiC (Associated Signature Containers, ETSI EN 319 162-1) regroupe des fichiers de données et des signatures dans une même archive. Un conteneur ASiC baseline n’embarque que des signatures CAdES ou XAdES baseline. Une signature CAdES baseline transporte son certificat de signature dans SignedData.certificates, de sorte qu’un vérificateur est censé l’extraire de la signature du conteneur lorsque la signature est bien formée et prise en charge par l’outillage de conteneur. Ce certificat extrait constitue l’entrée de cette API.

La source de confiance est une liste de confiance (TSL) ETSI TS 119 612 : un document XML signé qui énumère les prestataires de services de confiance et leurs certificats de service. NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider convertit un TslDocument analysé en un lot d’ancres. Seuls les services à la fois en statut granted et de type de service CA/QC alimentent l’ensemble d’ancres. Le lot porte une chaîne de version dérivée du numéro de séquence et du territoire de la TSL, ainsi qu’un condensé d’intégrité SHA-256.

Deux contrôles à sûreté intégrée s’exécutent avant toute comparaison d’ancres :

  1. Fraîcheur de la TSL. Une liste de confiance dont l’instant NextUpdate est dépassé doit être écartée comme expirée. AsicTrustBinder::verify() vérifie la fraîcheur à l’instant de validation fourni avant de dériver la moindre ancre. Une liste périmée, ou une valeur NextUpdate sans désignateur UTC explicite, lève TslParseException.
  2. Période de validité du signataire. La validation de chemin RFC 5280 exige que la période de validité du certificat inclue l’instant de validation. Une signature cryptographiquement intacte dont le certificat était expiré, ou pas encore valide, à cet instant est rejetée avec un code de motif précis.

Ce n’est qu’ensuite que le lieur teste le certificat de signature face à chaque ancre. Une correspondance donne trusted: true avec le motif anchor_signature_match. L’absence de correspondance donne trusted: false avec le motif no_anchor_chain.

La décision de conception porteuse est une séparation stricte entre la mécanique du conteneur et la décision de confiance, cette dernière étant contrainte d’être explicite sur le temps. Les formats de conteneur varient (ASiC-S, ASiC-E, charges utiles CAdES ou XAdES), mais la question de confiance forme un noyau invariant : ce certificat se rattache-t-il à une ancre issue d’une liste de confiance fraîche à un instant donné ? Garder ce noyau exempt d’analyse ZIP et XML le maintient assez réduit pour être testé de façon exhaustive et pour échouer en sûreté à chaque contrôle. Le même raisonnement interdit un now implicite par défaut : l’instant de validation change le verdict, l’appelant doit donc en assumer la responsabilité. La fraîcheur est vérifiée au sein même du chemin de dérivation des ancres, et non dans un collaborateur optionnel, de sorte qu’aucun chemin producteur ne peut la contourner.

Contexte de conception : Comment une signature numérique prouve qui a signé.

Le constructeur prend le fournisseur d’ancres qui transforme les listes de confiance en lots d’ancres.

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

Le point d’entrée principal vérifie un certificat de signataire face à une liste de confiance :

public function verify(
string $signerCertPem,
TslDocument $tsl,
DateTimeInterface $validationTime,
): AsicTrustBindingResult
  • $signerCertPem — chaîne PEM non vide : le certificat de signature issu de la signature ASiC.
  • $tsl — la liste de confiance analysée et authentifiée.
  • $validationTime — l’instant que la période de validité du certificat de signataire doit inclure. Il n’y a pas de valeur par défaut.

Lève ou échoue avec : NextPDF\Enterprise\Security\Tsl\TslParseException lorsque la TSL est périmée (NextUpdate dépassé), lorsque NextUpdate n’est pas une valeur UTC canonique, ou lorsque la liste ne contient aucun service CA/QC actif. Les signataires non fiables ne lèvent pas d’exception ; ils renvoient un résultat avec trusted: false et un code de motif.

Pour les traitements par lots, vérifie face à un lot pré-construit :

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

Lève ou échoue avec : aucune exception qui lui soit propre ; chaque issue est un AsicTrustBindingResult. Obtiens le lot via TslTrustAnchorProvider::buildBundle() — ne le construis pas à la main.

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

Lève ou échoue avec : TslParseException si la TSL est périmée, si son NextUpdate n’est pas une valeur UTC canonique, ou si elle n’a aucun service CA/QC actif.

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

$reasons est une list<non-empty-string> de codes lisibles par une machine. $anchorBundleVersion consigne l’ensemble d’ancres utilisé, sous la forme tsl-<territory>-seq<N> (par exemple tsl-eu-seq42).

Code de motifSignification
anchor_signature_matchLe certificat de signataire se vérifie face à une ancre dérivée de la TSL. De confiance.
no_anchor_chainAucune ancre du lot ne vérifie le certificat de signataire. Non fiable.
signer_cert_expiredL’instant de validation tombe après le notAfter du certificat. Non fiable.
signer_cert_not_yet_validL’instant de validation tombe avant le notBefore du certificat. Non fiable.
cannot_parse_signer_certLe PEM fourni ne s’analyse pas comme un certificat X.509. Non fiable.

Ton outillage de conteneur a déjà extrait le certificat de signature. Lie-le à une liste de confiance d’un État membre que tu as récupérée et authentifiée (voir Listes de confiance).

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";

Sortie attendue pour un signataire émis par un service CA/QC répertorié :

TRUSTED
Anchors: tsl-eu-seq42
Reasons: anchor_signature_match

Dérive le lot d’ancres une fois par liste de confiance, puis vérifie de nombreux signataires de conteneur face à lui. Une seule TSL périmée ou inutilisable fait échouer tout le lot en sûreté ; les problèmes de signataire individuels remontent par conteneur.

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

Sortie attendue lorsqu’un certificat de signataire a expiré :

invoice-2026-06.asice => trusted (anchor_signature_match; anchors tsl-eu-seq42)
tender-2019.asice => rejected (signer_cert_expired; anchors tsl-eu-seq42)
  • L’instant de validation est obligatoire et décisif. Il n’y a pas de now implicite par défaut. Une signature qui se vérifiait en 2019 signale signer_cert_expired lorsque tu valides à un instant de 2026 postérieur au notAfter. Pour du matériel historique, passe l’instant que tes preuves étayent (par exemple un instant de preuve d’existence), et non l’horloge murale.
  • Une TSL périmée lève une exception ; ce n’est pas un verdict de « défiance ». Une TslParseException issue de verify() ou buildBundle() signifie que la source de confiance est inutilisable. Traite-la comme une défaillance opérationnelle : rafraîchis la liste, ne la consigne pas comme un rejet de signataire.
  • Les ancres sont testées comme émetteurs directs. Chaque ancre est essayée comme le certificat qui a signé le certificat de signataire. Les TSL des États membres de l’UE répertorient les certificats de service CA/QC émetteurs, de sorte que les certificats qualifiés d’entité finale correspondent généralement de façon directe. Un signataire émis par une AC intermédiaire qui n’est pas elle-même un service CA/QC actif répertorié donne no_anchor_chain.
  • La dérivation des ancres filtre sévèrement. Les services retirés, ou de tout type autre que CA/QC, ne deviennent jamais des ancres. Une liste dont l’ensemble CA/QC actif est vide lève une exception plutôt que de produire un lot vide.
  • NextUpdate doit être en UTC canonique. Une valeur sans désignateur Z explicite ou décalage numérique est rejetée en sûreté intégrée, jamais réinterprétée dans le fuseau horaire local du serveur.
  • Une entrée mal formée se dégrade avec précision. Un PEM qui ne s’analyse pas renvoie cannot_parse_signer_cert ; un certificat pas encore valide se distingue d’un certificat expiré.
  • Consigne anchorBundleVersion. Il nomme l’ensemble d’ancres exact (tsl-<territory>-seq<N>) derrière chaque verdict, ce que réclamera un auditeur.
  • Sûreté intégrée par construction. La fraîcheur est vérifiée avant toute dérivation d’ancre. Le contrôle de validité du signataire s’exécute avant toute comparaison d’ancres. Le matériel de confiance inutilisable lève une exception ; les signataires douteux sont rejetés avec des motifs. Aucun chemin ne se dégrade en une réussite silencieuse.
  • La liaison de confiance est une couche, pas l’ensemble de la validation. Cette API ne vérifie pas la valeur de signature CAdES sur le contenu du conteneur, ne contrôle pas la révocation (aucune consultation CRL ou OCSP) et n’authentifie pas le document TSL lui-même. Authentifie d’abord la liste via le pipeline de listes de confiance (voir Listes de confiance), vérifie la signature de façon cryptographique avec ton outillage de signature, et ajoute le contrôle de révocation selon ta politique.
  • Choisis l’instant de validation délibérément. Le verdict est fonction de l’instant que tu passes. Dérive-le d’une preuve digne de confiance (un horodatage qualifié, un enregistrement d’archives), et non d’une horloge influençable par un attaquant.
  • Les sorties de preuve sont déterministes. trusted, anchorBundleVersion et reasons sont des valeurs stables et lisibles par une machine, adaptées à des journaux d’audit signés.

AsicTrustBinder prend en charge des flux alignés sur ETSI EN 319 162-1 (conteneurs ASiC baseline), ETSI EN 319 122-1 (signatures CAdES baseline) et ETSI TS 119 612 (listes de confiance), et applique le contrôle de période de validité RFC 5280 à l’instant de validation fourni.

La prise en charge n’est pas la conformité, et la conformité n’est pas la certification. NextPDF met en œuvre les contrôles décrits sur cette page ; il n’a été certifié conforme à ces normes par aucun organisme, et l’utilisation de cette API ne rend pas à elle seule ta sortie « qualifiée » ou juridiquement effective au titre d’eIDAS ou de tout autre régime. NextPDF ne détient aucune certification et n’en confère aucune. Déterminer si un processus de validation complet satisfait une exigence légale ou d’achat donnée revient à tes évaluateurs.

La liaison de confiance effectue des contrôles de signature de certificat X.509 en cours de processus ; elle n’est pas acheminée par le garde d’exécution en mode FIPS d’Enterprise, et l’activation du mode FIPS ne change pas son comportement. Ce n’est pas un service cryptographique validé FIPS, et aucune certification FIPS 140 n’est revendiquée. Les déploiements soumis à des obligations FIPS devraient cadrer cette API en conséquence et consulter Politique cryptographique FIPS 140-2/3.

  • verify() ne dérive des ancres que d’une TSL fraîche à l’instant de validation fourni ; une liste périmée ou mal formée lève TslParseException avant qu’aucune ancre n’existe.
  • Les ancres dérivent exclusivement des services TSL en statut granted et de type de service CA/QC ; un ensemble actif vide lève une exception.
  • La période de validité du certificat de signataire doit inclure l’instant de validation ; les manquements renvoient signer_cert_expired ou signer_cert_not_yet_valid.
  • Chaque issue est un AsicTrustBindingResult portant trusted, anchorBundleVersion et au moins un code de motif ; il n’existe aucun verdict sans motif.
  • Les signataires non fiables sont renvoyés, jamais levés ; le matériel de confiance inutilisable est levé, jamais renvoyé comme verdict.
  • L’analyse de conteneur n’a jamais lieu au sein de cette API ; les entrées sont le PEM extrait, la liste de confiance et l’instant de validation.

NextPDF Core valide les signatures PDF (CMS/PAdES) face aux ancres de confiance que tu épingles explicitement via son contrat CaTrustAnchorBundle — voir Sécurité de Core. Core n’a aucune ingestion de liste de confiance (TSL) ni de liaison de confiance propre à ASiC. Avec Core seul, tu peux maintenir ton propre ensemble d’ancres pour la validation des signatures PDF ; dériver des ancres d’une liste de confiance ETSI TS 119 612 et y lier les signataires de conteneur ASiC requiert NextPDF Enterprise.

Cette page ne documente que le comportement observable de l’extérieur et la surface d’API publique prise en charge. Les chemins de namespace internes, les classes utilitaires, les tables de mécanismes, les noms de fichiers de runbook et les préfixes de ticket sont hors périmètre.