Skip to content

KMS provider contract

HsmSignerInterface is the public contract a third party implements to provide key custody for NextPDF. Your provider keeps custody of the private key. The engine builds the Cryptographic Message Syntax (CMS) structure.

Terminal window
composer require nextpdf/core:^3

NextPDF separates signature assembly from key custody. The engine prepares the byte range and assembles the CMS SignedData structure. It does not hold the private key. Instead, it delegates key custody to a signing back end through a public contract.

You implement one of three public contracts:

  • SignerInterface. The base contract. It returns a SignatureResult for the supplied data, applies a timestamp, and reports long-term validation support. Use this contract when the signing logic runs in process.
  • HsmSignerInterface. The key-custody contract. The implementation must perform signing inside the hardware boundary. The private key must not leave that boundary. A key management system provider implements this contract.
  • DeferredSignerInterface. An extension of SignerInterface for asynchronous back ends. The caller submits data, receives a job identifier, and retrieves the result later.

This page specifies the public contract. It does not describe any internal NextPDF Pro or NextPDF Enterprise implementation. A paid edition can provide a supported implementation of this contract. You depend on the contract, not on the implementation.

The contract requires that the private key never leaves the secure boundary. Established key-management practice supports this requirement. National Institute of Standards and Technology (NIST) SP 800-57 Part 1 Revision 5 treats key management as lifecycle handling for secure generation, storage, distribution, use, and destruction. Public-Key Cryptography Standards #11 (PKCS#11) v3.1 makes the boundary enforceable. When a private-key object sets CKA_SENSITIVE to true or CKA_EXTRACTABLE to false, the token must not reveal the key value in plaintext outside the token.

Your implementation is responsible for honoring this requirement. The engine cannot enforce it for you. If your sign() method can read raw key bytes into process memory, the contract loses its security property.

NextPDF\Contracts\HsmSignerInterface (stable, since 1.0.0):

MethodReturnsPurpose
sign(string $data, string $algorithm)stringSign data inside the hardware boundary. Return raw signature bytes. Throw RuntimeException on failure.
getCertificateDer()stringReturn the signer X.509 certificate in Distinguished Encoding Rules (DER) form.
getCertificateChainDer()array<string>Return the intermediate certificates, issuer to root, excluding the signer certificate.
getPublicKeyAlgorithm()stringReturn the public-key algorithm identifier.

NextPDF\Contracts\SignerInterface (stable, since 1.0.0):

MethodReturnsPurpose
sign(string $data)SignatureResultReturn the DER-encoded CMS SignedData.
timestamp(string $signatureValue)stringReturn a DER-encoded Request for Comments (RFC) 3161 time-stamp token.
supportsLtv()boolReport whether long-term validation (LTV) data can be embedded.

NextPDF\Contracts\DeferredSignerInterface (experimental, since 3.0.0) extends SignerInterface with submitForSigning(), retrieveSignature(), and isComplete() for asynchronous back ends.

The signature levels the engine produces follow the PDF Advanced Electronic Signatures (PAdES) profiles. European Telecommunications Standards Institute (ETSI) EN 319 142-2 defines extended PAdES profiles that build on the baseline building blocks in EN 319 142-1. The engine maps levels B-B, B-T, B-LT, and B-LTA to those building blocks. Your signer supplies the cryptographic operation and the certificate material the engine needs.

This minimal PKCS#11-style provider delegates signing to the token. PHP never reads the key value.

<?php
declare(strict_types=1);
use NextPDF\Contracts\HsmSignerInterface;
final class TokenSigner implements HsmSignerInterface
{
public function __construct(private readonly TokenSession $session) {}
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): string
{
// The token computes the signature. The key stays inside the token.
return $this->session->c_sign($data, $algorithm);
}
public function getCertificateDer(): string
{
return $this->session->readCertificate();
}
/** @return array<string> */
public function getCertificateChainDer(): array
{
return $this->session->readChain();
}
public function getPublicKeyAlgorithm(): string
{
return 'rsaEncryption';
}
}

A production provider validates inputs, fails closed on a token error, and never logs key or signature material.

<?php
declare(strict_types=1);
use NextPDF\Contracts\HsmSignerInterface;
use Psr\Log\LoggerInterface;
use RuntimeException;
final class KmsSigner implements HsmSignerInterface
{
public function __construct(
private readonly RemoteKmsClient $kms,
private readonly string $keyId,
private readonly LoggerInterface $logger,
) {}
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): string
{
if ($data === '') {
throw new RuntimeException('Refusing to sign empty data');
}
try {
// The KMS performs the operation. The key never reaches this process.
return $this->kms->sign($this->keyId, $data, $algorithm);
} catch (\Throwable $error) {
// Fail closed. Do not log key material or signature bytes.
$this->logger->error('kms.sign.failed', ['key' => $this->keyId]);
throw new RuntimeException('KMS signing failed', previous: $error);
}
}
public function getCertificateDer(): string
{
return $this->kms->certificate($this->keyId);
}
/** @return array<string> */
public function getCertificateChainDer(): array
{
return $this->kms->chain($this->keyId);
}
public function getPublicKeyAlgorithm(): string
{
return $this->kms->algorithm($this->keyId);
}
}
  • Signature byte format. Return raw signature bytes. For Rivest-Shamir-Adleman (RSA), the bytes are DER. For Elliptic Curve Digital Signature Algorithm (ECDSA), the bytes are the raw r value followed by the s value.
  • Chain order. getCertificateChainDer() excludes the signer certificate. Order the intermediates from issuer to root.
  • Algorithm identifiers. sign() uses OpenSSL-style identifiers. getPublicKeyAlgorithm() returns the X.509 algorithm name.
  • Failure is fail-closed. Throw RuntimeException on any token or KMS error. Do not return a partial or empty signature.
  • Key destruction. When a key reaches the end of its cryptoperiod, destroy it according to your key-management procedure. NIST SP 800-57 Part 1 Revision 5 §8.2.1.2 states that a key should be destroyed as soon as it is no longer needed. The destruction itself follows §8.3.4.

The contract transfers only the data to sign and the certificate material. It does not transfer document content or personal data to the signing back end. Keep the byte range minimal. Do not place personal data in the signing reason or location fields. Those fields remain observable in the signed file.

Never log the data passed to sign(), the returned signature bytes, the key identifier in a recoverable form, or any certificate private component. Log only the operation outcome and a non-reversible reference. The SignatureAppliedEvent hook is the supported audit anchor. See Action triggers.

AssetThreatMitigation
Private keyEgress to process memoryContract requires in-boundary signing; PKCS#11 CKA_SENSITIVE / CKA_EXTRACTABLE enforce non-extractability
Signing oracleUnbounded signing requestsThe implementation rate-limits and authenticates callers
Certificate chainSubstitutionThe implementation returns a chain that builds to a trusted root
Signature bytesDisclosure through logsFail-closed error path; no signature material in telemetry
Key past cryptoperiodContinued useKey destruction per NIST SP 800-57 Part 1 Revision 5 §8.2.1.2

The signature levels conform to the PAdES profiles. ETSI EN 319 142-2 §5.1 defines extended PAdES profiles on the EN 319 142-1 baseline building blocks. The engine assembles those building blocks from the material your signer supplies. Key management aligns with the NIST SP 800-57 Part 1 Revision 5 lifecycle, including the §8.2.1.2 destruction requirement. Hardware key custody aligns with the PKCS#11 v3.1 non-extractable-key attributes. Citations are recorded in the page front matter.

This page covers PKCS#11 and hardware security module (HSM) key custody, so its front matter sets export_control_class: legal-review-required. Under the documentation review policy (plan §17 gate 6), any page with a security mode, path, or content matching PAdES, FIPS, HSM, or PKCS#11 requires sign-off from the @nextpdf-labs/crypto-reviewers GitHub team before it can publish. That CODEOWNERS approval is a hard merge gate: the page stays publish: false until both the legal export-control review and the @nextpdf-labs/crypto-reviewers review complete.

NextPDF Enterprise provides a supported implementation of this contract with key management system custody, certificate-chain assembly, and audit integration. You can implement HsmSignerInterface yourself in Core or consume the Enterprise implementation through the same public contract without code changes.

The glossary defines key management system, cryptoperiod, and HSM; see the published glossary for each canonical definition.