Pular para o conteúdo
getnextpdf.com

Enterprise edição

Vínculo de confiança ASiC

Um contêiner ASiC agrupa arquivos assinados com as assinaturas que os protegem. A pergunta difícil não é “a assinatura confere?”, mas sim “quem responde pelo signatário?”. NextPDF\Enterprise\Security\Asic\AsicTrustBinder responde exatamente a essa pergunta. Você entrega a ele o certificado de assinatura extraído da assinatura do contêiner, uma trusted list e um tempo de validação. Ele responde com um AsicTrustBindingResult: um veredito confiável/não confiável, a versão do pacote de âncoras contra a qual decidiu e motivos legíveis por máquina. Cada rejeição nomeia sua causa, de modo que a evidência de auditoria se escreve sozinha.

Um limite é deliberado e vale a pena declarar de antemão. Esta API não analisa contêineres ASiC. Seu ferramental abre o contêiner e extrai o certificado de assinatura; o NextPDF é o dono da decisão de confiança.

Este recurso é fornecido no NextPDF Enterprise (nextpdf/enterprise) e é ativado com um envelope de licença de nível Enterprise. Uma implantação sem essa habilitação não carrega as classes do recurso. Compare as edições e obtenha uma licença.

Terminal window
composer require nextpdf/enterprise

A ativação exige seu envelope de licença Enterprise. Veja Instalar e autenticar. As classes desta página residem em NextPDF\Enterprise\Security\Asic e NextPDF\Enterprise\Security\Tsl.

O ASiC (Associated Signature Containers, ETSI EN 319 162-1) empacota arquivos de dados e assinaturas em um único arquivo compactado. Um contêiner ASiC baseline incorpora apenas assinaturas CAdES ou XAdES baseline. Uma assinatura CAdES baseline carrega seu certificado de assinatura dentro de SignedData.certificates, de modo que espera-se que um verificador o extraia quando a assinatura estiver bem formada e for suportada pelo ferramental de contêiner a partir da assinatura do contêiner. Esse certificado extraído é a entrada desta API.

A fonte de confiança é uma trusted list (TSL) ETSI TS 119 612: um documento XML assinado que enumera os provedores de serviços de confiança e seus certificados de serviço. NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider converte um TslDocument analisado em um pacote de âncoras. Apenas os serviços que estão simultaneamente em status granted e do tipo de serviço CA/QC alimentam o conjunto de âncoras. O pacote carrega uma string de versão derivada do número de sequência e do território da TSL, além de um resumo de integridade SHA-256.

Dois portões fail-closed são executados antes de qualquer comparação de âncoras:

  1. Frescor da TSL. Uma trusted list cujo instante NextUpdate já passou deve ser descartada como expirada. AsicTrustBinder::verify() afirma o frescor no tempo de validação fornecido antes de derivar uma única âncora. Uma lista desatualizada, ou um valor NextUpdate sem um designador UTC explícito, lança TslParseException.
  2. Período de validade do signatário. A validação de caminho da RFC 5280 exige que o período de validade do certificado inclua o tempo de validação. Uma assinatura criptograficamente intacta cujo certificado estava expirado, ou ainda não válido, naquele momento é rejeitada com um código de motivo preciso.

Somente então o binder testa o certificado de assinatura contra cada âncora. Uma correspondência produz trusted: true com o motivo anchor_signature_match. Nenhuma correspondência produz trusted: false com o motivo no_anchor_chain.

A decisão de projeto que sustenta tudo é uma separação estrita entre a mecânica do contêiner e a decisão de confiança, com a decisão de confiança forçada a ser explícita quanto ao tempo. Os formatos de contêiner variam (ASiC-S, ASiC-E, cargas CAdES ou XAdES), mas a questão de confiança é um único núcleo invariante: este certificado encadeia a uma âncora de uma trusted list fresca em um instante declarado? Manter esse núcleo livre de análise de ZIP e XML o mantém pequeno o suficiente para testar exaustivamente e para falhar fechado em cada portão. O mesmo raciocínio proíbe um padrão now silencioso: o tempo de validação altera o veredito, então quem chama deve ser o dono dele. O frescor é afirmado dentro do próprio caminho de derivação de âncoras, não em um colaborador opcional, de modo que nenhum caminho produtor pode pulá-lo.

Contexto de projeto: Como uma assinatura digital prova quem assinou.

A construção recebe o provedor de âncoras que transforma trusted lists em pacotes de âncoras.

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

O ponto de entrada principal verifica um certificado de signatário contra uma trusted list:

public function verify(
string $signerCertPem,
TslDocument $tsl,
DateTimeInterface $validationTime,
): AsicTrustBindingResult
  • $signerCertPem — string PEM não vazia: o certificado de assinatura extraído da assinatura ASiC.
  • $tsl — a trusted list analisada e autenticada.
  • $validationTime — o instante que o período de validade do certificado do signatário deve incluir. Não há padrão.

Lança ou falha com: NextPDF\Enterprise\Security\Tsl\TslParseException quando a TSL está desatualizada (NextUpdate passou), quando NextUpdate não é um valor UTC canônico ou quando a lista não contém nenhum serviço CA/QC ativo. Signatários não confiáveis não lançam exceção; eles retornam um resultado com trusted: false e um código de motivo.

Para cargas de trabalho em lote, verifique contra um pacote pré-construído:

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

Lança ou falha com: nenhuma exceção própria; todo desfecho é um AsicTrustBindingResult. Obtenha o pacote de TslTrustAnchorProvider::buildBundle() — não o construa manualmente.

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

Lança ou falha com: TslParseException se a TSL estiver desatualizada, se seu NextUpdate não for um valor UTC canônico ou se não tiver nenhum serviço CA/QC ativo.

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

$reasons é uma list<non-empty-string> de códigos legíveis por máquina. $anchorBundleVersion registra o conjunto de âncoras utilizado, na forma tsl-<territory>-seq<N> (por exemplo, tsl-eu-seq42).

Código de motivoSignificado
anchor_signature_matchO certificado do signatário verifica contra uma âncora derivada da TSL. Confiável.
no_anchor_chainNenhuma âncora do pacote verifica o certificado do signatário. Não confiável.
signer_cert_expiredO tempo de validação cai após o notAfter do certificado. Não confiável.
signer_cert_not_yet_validO tempo de validação cai antes do notBefore do certificado. Não confiável.
cannot_parse_signer_certO PEM fornecido não é analisado como um certificado X.509. Não confiável.

Seu ferramental de contêiner já extraiu o certificado de assinatura. Vincule-o a uma trusted list de estado-membro que você buscou e autenticou (veja 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";

Saída esperada para um signatário emitido por um serviço CA/QC listado:

TRUSTED
Anchors: tsl-eu-seq42
Reasons: anchor_signature_match

Derive o pacote de âncoras uma vez por trusted list, depois verifique muitos signatários de contêiner contra ele. Uma única TSL desatualizada ou inutilizável faz o lote inteiro falhar fechado; problemas de signatários individuais aparecem por contêiner.

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

Saída esperada quando um certificado de signatário expirou:

invoice-2026-06.asice => trusted (anchor_signature_match; anchors tsl-eu-seq42)
tender-2019.asice => rejected (signer_cert_expired; anchors tsl-eu-seq42)
  • O tempo de validação é obrigatório e decisivo. Não há padrão now silencioso. Uma assinatura que foi verificada em 2019 relata signer_cert_expired quando você a valida em um instante de 2026 posterior a notAfter. Para material histórico, passe o tempo que sua evidência sustenta (por exemplo, um tempo de prova de existência), não o relógio de parede.
  • Uma TSL desatualizada lança exceção; não é um veredito de “não confiável”. Uma TslParseException de verify() ou buildBundle() significa que a fonte de confiança é inutilizável. Trate isso como uma falha operacional: atualize a lista, não a registre como uma rejeição de signatário.
  • As âncoras são testadas como emissoras diretas. Cada âncora é testada como o certificado que assinou o certificado do signatário. As TSLs de estados-membros da UE listam os certificados de serviço CA/QC emissores, portanto certificados qualificados de entidade final normalmente correspondem diretamente. Um signatário emitido por uma CA intermediária que não é ela mesma um serviço CA/QC ativo listado produz no_anchor_chain.
  • A derivação de âncoras filtra de forma rigorosa. Serviços que foram retirados, ou de qualquer tipo diferente de CA/QC, nunca se tornam âncoras. Uma lista cujo conjunto CA/QC ativo está vazio lança exceção em vez de produzir um pacote vazio.
  • NextUpdate deve ser UTC canônico. Um valor sem um designador Z explícito ou de deslocamento numérico é rejeitado fail-closed, nunca reinterpretado no fuso horário local do servidor.
  • Entradas malformadas degradam com precisão. Um PEM que não é analisado retorna cannot_parse_signer_cert; um certificado ainda não válido é distinguido de um expirado.
  • Registre anchorBundleVersion. Ele nomeia o conjunto exato de âncoras (tsl-<territory>-seq<N>) por trás de cada veredito, que é o que um auditor irá pedir.
  • Fail-closed por construção. O frescor é afirmado antes de qualquer âncora ser derivada. O portão de validade do signatário é executado antes de qualquer comparação de âncoras. Material de confiança inutilizável lança exceção; signatários questionáveis são rejeitados com motivos. Nenhum caminho degrada para uma aprovação silenciosa.
  • O vínculo de confiança é uma camada, não a validação inteira. Esta API não verifica o valor da assinatura CAdES sobre o conteúdo do contêiner, não checa revogação (nenhuma consulta CRL ou OCSP) e não autentica o próprio documento TSL. Autentique a lista primeiro por meio do pipeline de trusted list (veja Trusted lists), verifique a assinatura criptograficamente com seu ferramental de assinatura e adicione a checagem de revogação conforme sua política.
  • Escolha o tempo de validação deliberadamente. O veredito é uma função do tempo que você passa. Derive-o de evidência confiável (um timestamp qualificado, um registro de arquivamento), não de um relógio influenciável por um atacante.
  • As saídas de evidência são determinísticas. trusted, anchorBundleVersion e reasons são valores estáveis e legíveis por máquina, adequados para logs de auditoria assinados.

AsicTrustBinder suporta fluxos de trabalho alinhados com ETSI EN 319 162-1 (contêineres ASiC baseline), ETSI EN 319 122-1 (assinaturas CAdES baseline) e ETSI TS 119 612 (trusted lists), e aplica o portão de período de validade da RFC 5280 no tempo de validação fornecido.

Suporte não é conformidade, e conformidade não é certificação. O NextPDF implementa as verificações que esta página descreve; ele não foi certificado contra esses padrões por nenhum órgão, e usar esta API não torna, por si só, sua saída “qualificada” ou juridicamente eficaz sob o eIDAS ou qualquer outro regime. O NextPDF não detém nenhuma certificação e não concede nenhuma. Se um processo completo de validação atende a um determinado requisito legal ou de aquisição é uma determinação para os seus avaliadores.

O vínculo de confiança executa checagens de assinatura de certificado X.509 em processo; ele não é roteado pelo guarda de runtime do modo FIPS do Enterprise, e habilitar o modo FIPS não altera seu comportamento. Ele não é um serviço criptográfico validado por FIPS, e nenhuma certificação FIPS 140 é reivindicada. Implantações com obrigações FIPS devem delimitar esta API de acordo e consultar a Política criptográfica FIPS 140-2/3.

  • verify() deriva âncoras apenas de uma TSL que esteja fresca no tempo de validação fornecido; uma lista desatualizada ou malformada lança TslParseException antes de qualquer âncora existir.
  • As âncoras derivam exclusivamente de serviços de TSL em status granted com o tipo de serviço CA/QC; um conjunto ativo vazio lança exceção.
  • O período de validade do certificado do signatário deve incluir o tempo de validação; violações retornam signer_cert_expired ou signer_cert_not_yet_valid.
  • Todo desfecho é um AsicTrustBindingResult que carrega trusted, anchorBundleVersion e pelo menos um código de motivo; não existe veredito sem motivo.
  • Signatários não confiáveis são retornados, nunca lançados; material de confiança inutilizável é lançado, nunca retornado como um veredito.
  • A análise do contêiner nunca ocorre dentro desta API; as entradas são o PEM extraído, a trusted list e o tempo de validação.

O NextPDF Core valida assinaturas de PDF (CMS/PAdES) contra âncoras de confiança que você fixa explicitamente por meio do seu contrato CaTrustAnchorBundle — veja Segurança do Core. O Core não tem ingestão de trusted list (TSL) nem vínculo de confiança específico para ASiC. Só com o Core, você pode manter seu próprio conjunto de âncoras para validação de assinaturas de PDF; derivar âncoras de uma trusted list ETSI TS 119 612 e vincular signatários de contêiner ASiC a elas exige o NextPDF Enterprise.

Esta página documenta apenas o comportamento observável externamente e a superfície pública suportada da API. Caminhos de namespace internos, classes auxiliares, tabelas de mecanismos, nomes de arquivos de runbook e prefixos de ticket estão fora de escopo.