Skip to content

Security / Signing: CMS, RFC 3161 timestamp, LTV, trust

This page describes the signature surface in NextPDF Core: producing a Content Management Syntax (CMS) signature, applying a Request for Comments (RFC) 3161 timestamp, validating a certificate chain against RFC 5280, and checking revocation through the Online Certificate Status Protocol (OCSP) and a certificate revocation list (CRL). It stays at the behavior level. Core implementation classes are internal: production code consumes the SignerInterface contract, not the concrete NextPDF\Security\Signature types. The verifier and its configured trust anchors decide whether a produced signature verifies. That outcome is outside the producer’s control, and this page says so wherever it matters.

Terminal window
composer require nextpdf/core:^3

Core builds a CMS SignedData structure from the byte range, then stores it as Distinguished Encoding Rules (DER)-encoded data in the signature dictionary Contents entry — ISO 32000-2 §12.8.1. The structure carries SignerInfo signed attributes, including content-type and message-digest — RFC 5652 §5.3. A verifier recomputes the content digest and compares it with the message-digest attribute. The comparison must match for the signature to be valid — RFC 5652 §5.4. SignerInfo also carries the digest-algorithm identifier and the signed-attributes block — RFC 5652 §5. Core uses phpseclib3 for the software signing paths it ships: RSA, RSASSA-PSS, ECDSA, and Ed25519.

An RFC 3161 timestamp is a request-and-response exchange with a Time-Stamping Authority (TSA) that returns a TSTInfo structure — RFC 3161 §2.4.1. Each token carries a serialNumber unique to the issuing TSA — RFC 3161 §2.4.2 — and a genTime expressed as Coordinated Universal Time (UTC), the instant the token was created — RFC 3161 §2.4.2.

Trust validation has two checks. Path validation walks from the signer certificate to a trust anchor, checking basic constraints and the path-construction inputs — RFC 5280 §6.1. Revocation checking asks an OCSP responder or reads a CRL: an OCSP response reports good, revoked, or unknown — RFC 6960 §2.2 — and the response’s thisUpdate and nextUpdate bound how fresh that status is — RFC 6960 §4.2. The caller supplies the trust anchors and the revocation freshness policy. The engine validates against those inputs and does not ship a built-in trust list.

TypeKindRoleStabilitySince
SignerInterfaceinterface (NextPDF\Contracts)The signing contract callers depend onstable1.0.0
SignatureLevelenumPDF Advanced Electronic Signatures (PAdES) level selector and availability probestable1.0.0
Rfc5280PathValidatorinterfaceCertification-path validation entry point (validate(...))stable (frozen 3.1.0)3.1.0
RevocationStatusenumOCSP / CRL outcome: good, revoked, unknownstable3.1.0
CaTrustAnchorBundletypeCaller-supplied set of trust anchorsstable3.1.0
TstInfotypeParsed RFC 3161 timestamp fieldsstable3.2.0

SignerInterface::sign() returns a SignatureResult. Its toHex() method yields the /Contents hex string, and its cmsSignedData property holds the raw DER bytes. The concrete NextPDF\Security\Signature classes that implement this behavior are internal (stability: internal in the module manifest). They are not part of the public API and may change without a major-version bump. Depend on the contracts and enums above.

examples/contracts/signing-quickstart.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\SignerInterface;
/**
* Produce the CMS SignedData hex for a PDF /Contents field.
*
* @param SignerInterface $signer A Core or Premium signer.
* @param string $byteRange The PDF byte range to sign.
*
* @return string Hex-encoded CMS SignedData.
*/
function sign(SignerInterface $signer, string $byteRange): string
{
return $signer->sign($byteRange)->toHex();
}

The caller depends on the contract. A Core software signer and a Premium Hardware Security Module (HSM) signer both satisfy SignerInterface, so this code does not change across editions.

examples/contracts/signing-trust.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\SignerInterface;
use NextPDF\Contracts\TimestampProviderInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class TimestampedSigner
{
public function __construct(
private SignerInterface $signer,
private TimestampProviderInterface $timestamps,
private LoggerInterface $logger,
) {}
/**
* Sign a byte range, then timestamp the CMS structure.
*
* The timestamp's trust still depends on the verifier accepting the
* Time-Stamping Authority; this method only produces the structure.
*
* @param string $byteRange The PDF byte range to sign.
*
* @return array{cms: string, tst: string}
*/
public function sign(string $byteRange): array
{
try {
$signature = $this->signer->sign($byteRange);
$token = $this->timestamps->getTimestamp($signature->cmsSignedData);
return ['cms' => $signature->toHex(), 'tst' => $token];
} catch (NextPdfException $e) {
$this->logger->error('Signing failed', ['error' => $e->getMessage()]);
throw $e;
}
}
}

The timestamp provider is injected so a deployment can pin its own Time-Stamping Authority. The catch block logs and rethrows. It never swallows the failure, so the signing path stays fail-closed.

  • A produced signature is not a verified signature. Path validation and revocation run at the verifier, using that verifier’s trust anchors. The producer cannot assert the result.
  • The byte-range digest excludes the signature value. A digest that covers the Contents octets cannot verify — ISO 32000-2 §12.8.1.
  • Revocation status has a freshness window. An OCSP response is current only for its thisUpdate / nextUpdate interval — RFC 6960 §4.2. A stale response is not a substitute for a fresh check at validation time.
  • The engine ships no built-in trust list. CaTrustAnchorBundle is caller-supplied; an empty bundle means no chain validates, by design.
  • OCSP unknown is not good. Treat unknown as a non-determination, not an implicit pass — RFC 6960 §2.2.
  • HSM key custody, deferred and cloud signing, and the PAdES B-LT / B-LTA producer are not in Core. Selecting those paths in the Core distribution fails closed with a message that names the missing Enterprise component.

A software signature takes single-digit milliseconds. A timestamp adds one network round trip to the TSA. Path validation is local once the certificates are in memory; revocation adds one OCSP or CRL fetch per certificate in the chain. The 1500 ms wall budget covers one timestamped signature with a remote TSA on a warm connection. Revocation against a slow endpoint exceeds that budget and belongs outside the request path. The reproducibility profile is structural: a timestamp embeds the signing instant, so two runs differ in the timestamp bytes while the document structure is identical.

This is the engine’s primary cryptographic boundary, so the threat model is explicit. The engine computes the byte range and never accepts it from the caller. The signing path is fail-closed: a primitive failure or a capability gap raises a typed exception and never silently downgrades to a weaker algorithm. At a timestamp-required level (B-T, B-LT, B-LTA), a Time-Stamping Authority that returns an empty token is a terminal fault: the signature is refused, not emitted at a silently unstamped, down-levelled state, unless a fault handler is wired to authorise a documented degradation. Trust is caller-controlled by design: anchors and revocation policy are inputs, not engine defaults, because a producer that asserted its own trust would assert a fact only the verifier can establish. Timestamp trust reduces to trust in the Time-Stamping Authority, which is injectable so a deployment can pin its own. This page is marked export_control_class: legal-review-required because it concerns cryptographic signing; every normative source is paraphrased and none is reproduced, per citation hygiene.

ClaimStandardClauseEvidence
The CMS signature is stored DER-encoded in the signature dictionary Contents entry.ISO 32000-2§12.8.1
SignerInfo carries content-type and message-digest signed attributes.RFC 5652§5.3
The verifier recomputes the content digest and compares it with the message-digest attribute.RFC 5652§5.4
A timestamp token is produced by an RFC 3161 TSA and carries a unique serialNumber and a UTC genTime.RFC 3161§2.4.1, §2.4.2,,
Certification path validation checks basic constraints and the path inputs from the signer to a trust anchor.RFC 5280§6.1,
OCSP reports certStatus as good, revoked, or unknown, bounded by thisUpdate / nextUpdate.RFC 6960§2.2, §4.2,

All clauses are paraphrased. NextPDF does not reproduce normative text. Consult the published standards for the authoritative wording.

Core ships the software CMS signer (RSA, RSASSA-PSS, ECDSA, Ed25519), RFC 3161 timestamp consumption, RFC 5280 path validation, and OCSP / CRL revocation checking. HSM and Public-Key Cryptography Standards #11 (PKCS#11) key custody, deferred and cloud signing, the PAdES B-LT and B-LTA producer, and the Federal Information Processing Standards (FIPS) 140-3 crypto-policy profile ship in the Pro and Enterprise editions. Core resolves these at runtime against the contract, so the open-source engine carries no commercial dependency, and the API does not change on upgrade.