Skip to content

Contracts / Signing

The signing domain includes six contracts. They define how you produce a Cryptographic Message Syntax (CMS) signature, apply a Request for Comments (RFC) 3161 timestamp, sign with a hardware security module (HSM) key, and enable long-term validation (LTV). Core publishes the contracts; the Pro and Enterprise editions ship the production implementations.

Terminal window
composer require nextpdf/core:^3

A Portable Document Format (PDF) digital signature is a CMS SignedData structure stored in the signature dictionary. The Contents entry holds the Distinguished Encoding Rules (DER)-encoded structure. The ByteRange entry names the byte spans covered by the digest. The digest covers the whole file and excludes the signature value itself; see International Organization for Standardization (ISO) 32000-2 §12.8.1. The CMS structure follows RFC 5652 §5.1: version, digest algorithms, encapsulated content, and signer information.

SignerInterface is the core contract. It produces CMS SignedData for a byte range, applies a timestamp to a signature value, and reports whether it supports LTV. It carries PDF Advanced Electronic Signatures (PAdES) baseline levels B-B through B-LTA as defined by the European Telecommunications Standards Institute (ETSI) EN 319 142. Each higher level adds validation material. B-B carries the base signature. B-T adds a signature timestamp. B-LT adds revocation data. B-LTA adds an archival timestamp.

HsmSignerInterface signs with a key stored in a hardware security module. The private key never leaves the hardware boundary. The contract returns the signer certificate and certificate chain in DER form, so the CMS layer can build the structure. DeferredSignerInterface extends SignerInterface for asynchronous signing. The caller submits data, receives a job identifier, polls for completion, and retrieves the result. Use it when a remote HSM or cloud key service does not return a result immediately.

TimestampProviderInterface requests an RFC 3161 timestamp token. The protocol is a request-response exchange with a Time-Stamping Authority (TSA); see RFC 3161 §2.4. The messageImprint in the token is a hash of the signature value; see RFC 3161 §2.4.2. LtvManagerInterface enables LTV. It collects the certificate chain, fetches Online Certificate Status Protocol (OCSP) and certificate revocation list (CRL) responses, builds the Document Security Store (DSS), and adds a document timestamp for B-LTA. CryptoPolicyInterface gates permitted hash, signature, and encryption algorithms, plus key strengths, before any cryptographic operation runs.

TypeKindKey membersStabilitySince
SignerInterfaceinterfacesign(string): SignatureResult, timestamp(string): string, supportsLtv(): boolstable1.0.0
HsmSignerInterfaceinterfacesign(string, string): string, getCertificateDer(), getCertificateChainDer(), getPublicKeyAlgorithm()stable1.0.0
DeferredSignerInterfaceinterfacesubmitForSigning(string): string, retrieveSignature(string), isComplete(string) (extends SignerInterface)experimental3.0.0
TimestampProviderInterfaceinterfacegetTimestamp(string): stringexperimental3.0.0
LtvManagerInterfaceinterfaceenableLtv(...), addDocumentTimestamp(...)stable1.10.0
CryptoPolicyInterfaceinterfaceisHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName()stable1.9.0

SignerInterface::sign() returns a NextPDF\Security\Signature\SignatureResult. The public $cmsSignedData property contains the DER-encoded CMS. The public $digestHex property contains the SHA-256 digest. The public $timestampToken property contains the optional TSA token. The accessors are toHex() / toHexPadded(int) / getSize() / hasTimestamp(). HsmSignerInterface::sign() returns DER-encoded bytes for Rivest-Shamir-Adleman (RSA) and raw r‖s bytes for Elliptic Curve Digital Signature Algorithm (ECDSA). The algorithm argument uses OpenSSL identifiers.

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

SignerInterface::sign() returns a SignatureResult. toHex() yields the hex string that the writer places in the /Contents field; the raw DER bytes are available on the public $cmsSignedData property. The function depends on the contract, not on a concrete class. A core test signer and a Premium HSM signer both satisfy it.

examples/contracts/signing-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\CryptoPolicyInterface;
use NextPDF\Contracts\SignerInterface;
use NextPDF\Contracts\TimestampProviderInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class TimestampedSigningService
{
public function __construct(
private SignerInterface $signer,
private TimestampProviderInterface $timestamps,
private CryptoPolicyInterface $policy,
private LoggerInterface $logger,
) {}
/**
* Sign a byte range, then timestamp the CMS structure.
*
* @param string $byteRange The PDF byte range to sign.
*
* @return array{cms: string, digest: string, tst: string}
*/
public function sign(string $byteRange): array
{
if (!$this->policy->isHashAlgorithmAllowed($this->policy->getPreferredHashAlgorithm())) {
throw new \LogicException('Preferred hash rejected by crypto policy.');
}
try {
$signature = $this->signer->sign($byteRange);
$token = $this->timestamps->getTimestamp($signature->cmsSignedData);
return [
'cms' => $signature->toHex(),
'digest' => $signature->digestHex,
'tst' => $token,
];
} catch (NextPdfException $e) {
$this->logger->error('Signing failed', [
'policy' => $this->policy->getName(),
'error' => $e->getMessage(),
]);
throw $e;
}
}
}

The service injects three contracts. It checks the crypto policy before signing. SignatureResult exposes the CMS bytes on the public $cmsSignedData property, the SHA-256 digest on $digestHex, and the /Contents hex string through toHex(). The timestamp provider receives the CMS bytes and returns a DER-encoded token. The catch block logs the policy name and rethrows the exception. It never swallows the failure.

  • The byte-range digest must exclude the signature value. A digest that covers the Contents entry produces a signature that can never verify; see ISO 32000-2 §12.8.1.
  • SignerInterface::supportsLtv() reports capability, not state. A signer can support LTV and still produce a B-B signature when no timestamp service is configured.
  • DeferredSignerInterface::retrieveSignature() returns null until the job completes. Poll isComplete() first so you do not transfer the payload on every check. Retrieval is idempotent once a result exists.
  • LtvManagerInterface::addDocumentTimestamp() must run after the Document Security Store is written. Calling it first produces an invalid B-LTA structure.
  • CryptoPolicyInterface returns true for every algorithm when no policy is set. In a regulated environment, set an explicit policy; do not rely on the open default.
  • HsmSignerInterface::getCertificateChainDer() excludes the signer certificate. Use getCertificateDer() for the signer leaf and the chain method for intermediate certificates.

Signing cost comes mainly from the cryptographic operation and any network round trip, not from the contract. A local software signature typically takes single-digit milliseconds. An HSM signature adds the device round trip. A timestamp adds a network round trip to the Time-Stamping Authority. Long-term validation adds one OCSP or CRL fetch per certificate in the chain. The performance_budget of 1500 ms wall covers a single timestamped signature with a remote TSA on a warm connection. Long-term validation against a slow revocation endpoint exceeds it and should run outside the request path. The reproducibility profile is structural, not bitwise. A timestamp embeds the signing instant, so two runs differ in timestamp bytes while the document structure remains identical.

The signing contracts are the engine’s primary cryptographic boundary, so the threat model is explicit. Key custody is the first concern: HsmSignerInterface keeps the private key inside the hardware boundary, and the contract never exposes key material. Algorithm downgrade is the second: CryptoPolicyInterface blocks weak hashes and short keys before the operation, letting a deployment enforce a Federal Information Processing Standards (FIPS) 140-3 or electronic identification, authentication, and trust services (eIDAS) profile without forking the engine. Timestamp trust is the third: an RFC 3161 token is only as trustworthy as the Time-Stamping Authority, so the provider contract is injectable and a deployment pins its own authority. Long-term validation is the fourth: revocation material is fetched at signing time and stored in the Document Security Store so verification survives certificate expiry. Treat every signer input as untrusted. The engine computes the byte range; it is never accepted from the caller. This page is marked export_control_class: legal-review-required because the contracts govern cryptographic signing. The prose paraphrases all normative sources and quotes none, per citation hygiene.

ClaimStandardClauseEvidence
The signature dictionary Contents entry stores the signature value as DER-encoded CMS SignedData or a TimeStampToken.ISO 32000-2§12.8.1
The digest is computed over the byte range defined by the ByteRange array and excludes the signature value.ISO 32000-2§12.8.1,
The Document Security Store carries long-term validation material with validation-related information (VRI), OCSP, CRL, and certificate entries.ISO 32000-2§12.8.4.3,
PAdES long-term validation places validation data in the DSS and VRI dictionaries.ETSI EN 319 142-2§6.3,
An RFC 3161 timestamp token binds a hash of the signature value through messageImprint in a TSA request-response exchange.RFC 3161§2.4.2, §2.4,
The CMS SignedData sequence carries version, digest algorithms, encapsulated content, and signer information.RFC 5652§5.1

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

Core publishes and freezes the signing contracts. The Pro and Enterprise editions ship the production implementations behind HsmSignerInterface, LtvManagerInterface, and the deferred signer, including Public-Key Cryptography Standards #11 (PKCS#11) hardware integration and PAdES B-LT and B-LTA. Core resolves these implementations at runtime with class_exists() and casts them to the contract, so the open-source engine carries no commercial dependency and the API does not change on upgrade.