Skip to content

Signature verification: AdES / PAdES cryptographic verify-side

NextPDF Enterprise cryptographically verifies digital signatures. The verify-side reads Cryptographic Message Syntax (CMS) SignedData or a Request for Comments (RFC) 3161 timestamp token from the PDF, recomputes the digests, checks the signatures, binds each signature to its signing certificate, and validates the certificate chain, including certificate-policy processing, against a caller-supplied trust anchor store. This page describes behavior. It states what is verified, what is rejected fail-closed, which algorithms are supported, and where the trust boundary sits.

This is separate from Validation, which runs read-only structural policy checks and deliberately performs no cryptography. It is also the verify-side counterpart to the Signature producer, which writes PDF Advanced Electronic Signatures (PAdES) B-LT / B-LTA structures.

Terminal window
composer require nextpdf/enterprise:^3

Verification is evidence-based and fail-closed: a check that cannot be positively established does not produce a positive result. The pieces below combine into one outcome carried by a ValidationReport.

CMS / timestamp-token cryptographic verification. A self-contained, strict, definite-length DER stack parses the CMS SignedData and the RFC 3161 timestamp token. A token is accepted only when it carries exactly one SignerInfo, its signed attributes parse strictly, the signer certificate resolves, the message-digest attribute matches the recomputed digest, the mandatory signing-certificate (ESS signing-certificate-v2) binding holds, and the SignerInfo signature verifies over the re-tagged signed attributes. The digest-matching rule follows the RFC 5652 §5.6 / §5.4 verification model: the recipient recomputes the content message digest, and the signature is valid only when that value equals the messageDigest signed attribute. A verifier never trusts a digest supplied by the producer.

TSA-certificate validation at genTime. The timestamp’s genTime is the UTC instant the token was created (RFC 3161 §2.4.2). The TSA signing certificate is validated at that instant, not at “now”: its extended key usage must be a single, critical id-kp-timeStamping (RFC 3161 §2.3), and its validity window, chain, trust-anchor provenance, and revocation are all evaluated against genTime. A structurally well-formed chain that does not reach a configured trust anchor can never support a positive outcome.

Detached PAdES basic document signatures. A concrete extractor performs real Advanced Electronic Signatures (AdES) / PAdES basic verification for a detached document signature: the message digest is recomputed over the signed byte range and compared, the signature is verified, and the signing-certificate binding is mandatory. This replaces the earlier structural-only fallback. The timestamp verifier and the document-signature extractor share one ESS issuer-and-serial validator, so certificate-binding parsing cannot drift.

Archival DocTimeStamp coverage chain. validateArchivalTimestampChain() validates a chain of trusted DocTimeStamp tokens over the PDF byte ranges as B-LTA archival evidence. Each token’s imprint is bound to the actual ByteRange bytes it covers, the chain follows strict ETSI ordering, every token’s TSA chain is trust-anchored, and the chain must cover the file to its end-of-file marker. It reaches a fully passed outcome only for a complete, trust-anchored, EOF-covering chain.

Certificate-policy processing (RFC 5280 §6.1.4). Path validation applies full certificate-policy processing: a node-structured policy tree, policy mapping with the anyPolicy fallback, and policyConstraints / inhibitAnyPolicy folding, backed by a fail-closed policy-extension DER reader. The path-processing state variables explicit_policy and inhibit_anyPolicy (RFC 5280 §6.1.2) determine whether a non-empty valid-policy tree is required; the wrap-up step intersects the valid-policy tree with the user-initial-policy-set (RFC 5280 §6.1.4). With no required policies and anyPolicy accepted, processing is unconstrained, which is the default and unchanged from earlier behavior.

The verify-side accepts the algorithm set below and fails closed on anything outside it: an unsupported algorithm is a rejected verification, never a silent pass.

FamilySupportedNotes
RSA (PKCS#1 v1.5)rsaEncryption with SHA-2, and the sha*WithRSAEncryption OIDsAccepted
ECDSAcurves P-256, P-384, P-521Accepted
RSASSA-PSSUnsupported → fail-closed
EdDSAUnsupported → fail-closed
SHA-3 digestsUnsupported → fail-closed
SHA-1Weak: a basic signature that verifies under SHA-1 is degraded to a crypto-constraints failure, not a pass

You use the verify-side through the public engine and the Core / Pki contracts. Concrete strategy classes are internal.

TypeKindRole
AdESValidationEngineclassThe verify-side entry point: signature and archival-chain validation.
AdESValidationEngine::validateArchivalTimestampChain()methodValidate a trusted DocTimeStamp coverage chain over the PDF byte ranges.
ValidationReportresultThe structured outcome: overall status plus per-check findings.
PathValidatorInterfaceinterface (NextPDF\…\Pki)The certification-path-validation SPI the engine depends on.
PathValidationOptionsvalue objectPolicy-processing controls: requireExplicitPolicy, inhibitAnyPolicy, inhibitPolicyMapping, maxPolicyFanout.
TrustAnchorStoreInterfaceinterfaceThe caller-supplied set of trusted anchors the chain is evaluated against.

The archival-chain method has this signature:

public function validateArchivalTimestampChain(
string $pdfBytes,
array $dssData = [],
?TrustAnchorStoreInterface $anchors = null,
): ValidationReport;

It reaches a fully passed outcome only when the DocTimeStamp chain is completely verified, trust-anchored, and covers the file to its EOF marker.

CertificateChainValidator::validate() takes an initial-policy set (the RFC 5280 user-initial-policy-set). The default is anyPolicy, which is unconstrained, so an ordinary chain is unaffected. Pass an explicit set, or set requireExplicitPolicy, to demand a non-empty valid-policy tree.

use NextPDF\Enterprise\Security\Validation\AdESValidationEngine;
$report = $engine->validateArchivalTimestampChain($pdfBytes, [], $trustAnchors);
if ($report->isTotalPassed()) {
// A complete, trust-anchored, EOF-covering DocTimeStamp chain.
}
use NextPDF\Enterprise\Security\Validation\AdESValidationEngine;
use Psr\Log\LoggerInterface;
final readonly class ArchivalEvidenceCheck
{
public function __construct(
private AdESValidationEngine $engine,
private LoggerInterface $logger,
) {}
public function check(string $pdfBytes, TrustAnchorStoreInterface $anchors): bool
{
$report = $this->engine->validateArchivalTimestampChain($pdfBytes, [], $anchors);
foreach ($report->findings as $finding) {
$this->logger->info('archival.finding', [
'check' => $finding->checkId,
'status' => $finding->status->value,
]);
}
// A positive result proves byte-range coverage by a trusted timestamp
// chain — it is one input to your decision, not a legal conclusion.
return $report->isTotalPassed();
}
}

The verify-side moved from structural / temporal acceptance to full cryptographic verification. If you rely on the older, more lenient behavior, review these changes.

  • Fail-closed on a recognisable-but-unverifiable timestamp. A DocTimeStamp or archival token that previously passed on structural and temporal grounds now requires full cryptographic verification: signature, message-digest, and the signing-certificate binding. A token that does not verify no longer yields a positive proof-of-existence; it maps to an indeterminate or failed outcome.
  • SHA-1 basic signatures are degraded, not passed. A basic document signature that verifies but uses SHA-1 is reported as a crypto-constraints failure rather than a full pass.
  • RFC 5280 §6.1.4 certificate-policy processing is enforced. A chain whose explicit_policy reaches zero with an empty valid-policy tree now fails, including when a chain-internal policyConstraints requireExplicitPolicy drives it. Default, unconstrained chains (no required policies, anyPolicy accepted) are unaffected.
  • Constructor signature change (BC break). AdESValidationEngine::__construct() now types its $chainValidator parameter as the Pki\PathValidatorInterface SPI, lazily defaulted to Pki\CertificateChainValidator::withDefaults(). It was previously the concrete Ltv\CertificateChainValidator. A caller that injected the concrete LTV validator must inject the Pki SPI implementation instead. The Pki validator wraps the same structural path engine and adds name constraints and policy processing; it is a no-op for conformant default inputs.
  • Unsupported algorithm ≠ inconclusive. RSASSA-PSS, EdDSA, and SHA-3 are rejected fail-closed, not deferred. If your signers use them, this verify-side will not return a positive result.
  • Trust is anchor-relative. Verification is always relative to the trust anchor store you supply. An empty or wrong anchor set yields a not-trusted outcome regardless of cryptographic correctness.
  • genTime, not now. The TSA certificate is judged at the token’s genTime. A TSA certificate that has since expired can still support a token created while it was valid; a certificate not yet valid at genTime cannot.
  • EOF coverage. The archival chain must cover the document to its EOF marker. A timestamp that covers only a prefix of the file does not establish whole-document coverage.
  • Not-proven-revoked is not Good. A Valid verdict needs a conclusively non-revoked status. If both OCSP and CRL come back Unknown or Unavailable, even a cryptographically sound, chain-valid, trust-anchored signature resolves to Indeterminate. Supply embedded OCSP/CRL Good material for the signer certificate to reach Valid.

Verification runs in process over the supplied PDF bytes and the embedded validation material; cost scales with chain length and the number of timestamps. Verification makes no network round trips. Revocation and trust material come from what the caller supplies or what is embedded in the document. Verification is deterministic: the same inputs and the same trust anchors produce the same report.

  • Fail-closed is the default. Every acceptance step refuses material it cannot positively verify. This prevents a document from advertising validity that the engine never established.
  • Append-after-timestamp is defeated by EOF coverage. Because each archival timestamp’s imprint is bound to the actual ByteRange bytes and the chain must reach EOF, content appended after a timestamp is not covered by it and does not gain its evidentiary weight.
  • Treat input as hostile. PDF bytes, embedded CMS, and timestamp tokens from untrusted sources are parsed by a strict definite-length DER reader that rejects malformed or indefinite-length encodings.

Verification is in process and local, with no network I/O. Certificates and signatures carry subject identity; apply your own retention and minimization controls to reports and any extracted certificate data.

Findings carry check identifiers and statuses. Some diagnostics can echo certificate subject names or serial numbers; scrub or redact those fields before you forward logs to shared sinks.

State these boundaries in user-facing output so a positive result is not over-read.

  • validateArchivalTimestampChain() proves byte-range coverage, not xref reachability. It establishes that a trusted timestamp chain cryptographically covers the document’s byte ranges to EOF. It does not perform xref-level or startxref object-reachability analysis; that is out of scope by design. The EOF-coverage rule, together with trust anchoring, defeats append-after-timestamp attacks, not object-graph analysis.
  • Out of scope by design. Evidence Records (RFC 4998 / RFC 6283); verification of RSASSA-PSS, EdDSA, or SHA-3 timestamp tokens; trusted-list (TSL) and qualified-TSA integration; and online TSA-revocation fetching are not provided by this verify-side. Revocation is evaluated from material already available to the verifier.
BehaviorReferenceStatus
Signature value / timestamp token stored DER-encoded in /ContentsISO 32000-2 §12.8.1Parsed and verified
Document timestamp dictionaryISO 32000-2 §12.8.5Read for the archival chain
Recipient recomputes the content digest; it must equal the messageDigest attributeRFC 5652 §5.6 / §5.4Enforced
TSA certificate carries a single critical id-kp-timeStamping EKURFC 3161 §2.3Checked at genTime
genTime is the UTC creation instantRFC 3161 §2.4.2Used as the validation instant
Policy-processing state variables (explicit_policy, inhibit_anyPolicy)RFC 5280 §6.1.2Processed
Wrap-up intersects the valid-policy tree with the user-initial-policy-setRFC 5280 §6.1.4Enforced
DSS entries and document time-stamps for long-term signaturesETSI EN 319 142-2 §5.5Validated as archival evidence

All clauses are paraphrased. NextPDF does not reproduce normative text; consult the published standards for the authoritative wording. NextPDF makes no AdES / PAdES certification claim. The verify-side implements the cryptographic checks described by the cited specifications; it is not a certified validation service and produces no third-party attestation.

When the Enterprise FIPS profile is active, the constraint applies to the digest and signature algorithms the verifier accepts. The verification logic, including digest recomputation, signature checks, certificate-binding, and path validation, is unchanged.

AssetAdversaryRiskMitigation
Timestamp tokenForged or altered tokenA false proof-of-existenceFull cryptographic verification; fail-closed on anything unverifiable
TSA certificateUntrusted issuerApparent trust the verifier should not assertChain validated to a caller-supplied anchor at genTime; untrusted chain never passes
Document bytesAppend-after-timestampContent gains a timestamp’s weight without coverageImprint bound to actual ByteRange bytes + EOF-coverage rule
Weak algorithmDowngrade to SHA-1 / unsupported schemeA weak signature read as validSHA-1 degraded; RSASSA-PSS / EdDSA / SHA-3 fail-closed

This verify-side is an Enterprise capability and ships in the nextpdf/enterprise package. NextPDF Core detects signature presence and produces B-B / B-T signatures, but it does not provide this cryptographic verify-side. Get a license.

The verify-side is gated by the Enterprise edition (license_feature_flag: enterprise). It resolves through the Core and Pki contracts; the public application programming interface (API) does not change on an edition upgrade.

  • A CMS or timestamp token is accepted only with exactly one SignerInfo, strictly-parsed signed attributes, a resolved signer certificate, a matching message-digest, the mandatory signing-certificate binding, and a verifying SignerInfo signature.
  • The TSA certificate is validated at the token’s genTime: a single critical id-kp-timeStamping EKU, validity window, trust-anchored chain, and revocation.
  • A Valid verdict additionally requires a conclusively non-revoked status: at least one authoritative Good (a verified-good OCSP response, or a fresh CRL placing the serial outside an unexpired list). A status that is merely not-proven-revoked, where OCSP and CRL are both Unknown or Unavailable, resolves to Indeterminate, never Valid (ETSI EN 319 102-1: revocation status-unavailable → INDETERMINATE). Because the CRL-fallback path attests only list freshness and not per-serial revocation, a CRL-only chain without a Good OCSP is commonly Indeterminate.
  • validateArchivalTimestampChain() reaches a fully passed outcome only for a complete, trust-anchored, EOF-covering DocTimeStamp chain; it proves byte-range coverage, not xref reachability.
  • Certificate-policy processing follows RFC 5280 §6.1.4; default unconstrained chains are unaffected.
  • Supported algorithms are RSA (PKCS#1 v1.5 with SHA-2) and ECDSA (P-256/384/521); RSASSA-PSS, EdDSA, and SHA-3 fail closed; SHA-1 is degraded.

This public page describes externally observable verify-side behavior only. It names the public engine, the Pki / trust-anchor contracts, and the validateArchivalTimestampChain() method through the supported surface. The concrete DER, CMS, policy-processor, and path-validator internals are in the gated deep reference under the nondisclosure agreement (NDA).

NextPDF Core detects the presence of a signature and produces B-B / B-T signatures, but it does not cryptographically verify CMS or timestamp tokens, validate an archival chain, or run RFC 5280 §6.1.4 policy processing. A Core-only deployment has no equivalent verify-side; see Security / Signing (Core).

NextPDF Pro adds signing strategies and e-invoice handling, but it does not provide this cryptographic verify-side; archival-chain validation and certificate-policy processing ship in nextpdf/enterprise only.

The verify-side is described at the behavior level. The strict DER stack, the CMS parser, the policy processor, and the path-validator internals are out of scope for the public surface and are not reproduced here.

Verification depends on the trust anchor store and any revocation material the caller supplies or that is embedded in the document. NextPDF Enterprise evaluates that material; it does not operate a trust list, a TSA, or a revocation service. The operator owns anchor selection and the freshness of embedded revocation material.

This page is marked export_control_class: legal-review-required; it concerns cryptographic verification. Legal sign-off is required before the publish flag is set. A positive verification result is a cryptographic statement about the document’s signatures and timestamps. It is not a legal opinion, an eIDAS qualification determination, or a certification. NextPDF makes no AdES / PAdES certification claim. Consult your own compliance and legal advisers for your regulatory obligations.