Search by

dskripchenko / php-pdf-pkcs11

dskripchenko

PKCS#11 signer for dskripchenko/php-pdf: sign PDFs (PAdES: RSA, ECDSA and GOST R 34.10-2012) with a key held by an HSM, a smart card or a USB token, through the php-pkcs11 extension.

Package info

github.com/dskripchenko/php-pdf-pkcs11

pkg:composer/dskripchenko/php-pdf-pkcs11

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-10-18 16:51 UTC

This package is auto-updated.

Last update: 2026-10-18 16:52:05 UTC


README

Sign PDFs with dskripchenko/php-pdf using a key held by a PKCS#11 token: a network or PCIe HSM (Thales Luna, Entrust nShield, Utimaco, AWS CloudHSM), a smart card or a national eID card, a USB token (YubiKey, SafeNet eToken, Rutoken), or SoftHSM. The key never leaves the device: php-pdf computes the digest, the token signs it, and the result is a PAdES signature with RSA, ECDSA or GOST R 34.10-2012.

🌐 English Β· Deutsch Β· Русский Β· δΈ­ζ–‡

Tests Packagist License: MIT PHP

Usage

use Dskripchenko\PhpPdf\Pdf\Signature\PdfSigner;
use Dskripchenko\PhpPdf\Pdf\SignatureConfig;
use Dskripchenko\PhpPdfPkcs11\Pkcs11Signer;

$signer = Pkcs11Signer::open(
    '/usr/lib/softhsm/libsofthsm2.so',   // the vendor's PKCS#11 library
    pin: $pin,
    token: 'signing',                    // token label, when there are several
    keyLabel: 'pdf',                     // or keyId: '01'
);

$signed = PdfSigner::fromBytes($pdf)
    ->addField('signature', page: 0, x: 60, y: 450, width: 250, height: 75, appearance: $stamp)
    ->sign(new SignatureConfig(signer: $signer, timestampAuthority: $tsa));

The signer works wherever php-pdf takes one: PdfSigner::sign() for an existing PDF, Pdf\Document::sign() and a laid-out document's signature:.

The certificate is read from the token β€” the one with the key's CKA_ID, as tokens store them β€” unless you pass certificatePem:. Before handing the signature to php-pdf the signer verifies it against the certificate, so a key that does not belong to the certificate fails with a clear error instead of producing a PDF no reader accepts.

Options

Argument Default Meaning
module β€” Path to the vendor's PKCS#11 library: libsofthsm2.so, libeTPkcs11.so, opensc-pkcs11.so, libCryptoki2_64.so…
pin null The user PIN. Private keys are visible only after login
token the only token The token label, when the module has several
slot β€” The slot id, as an alternative to token
keyLabel β€” The private key's CKA_LABEL
keyId β€” The private key's CKA_ID, hex: 01, 0x01, a1:b2 (as pkcs11-tool -O shows it). With neither label nor id, the only private key
certificatePem from the token The signing certificate, when the token does not hold it
chainPem [] Intermediate certificates to embed β€” a PEM bundle or a list
digest by the key SHA-256 for RSA and P-256, SHA-384 for P-384, SHA-512 for P-521; Streebog-256/512 for GOST (the only choice there)
hashOnToken false GOST only: the token hashes the data itself (see below)

The digest is computed in PHP and the token signs it with CKM_RSA_PKCS (over a DigestInfo) or CKM_ECDSA. Every token that signs at all supports these two, the document is never streamed through the device, and the token's own SHA-2 support does not matter.

GOST R 34.10-2012

GOST keys on TC26-compliant tokens β€” Rutoken ECP 2.0/3.0, JaCarta GOST and other modules that implement the TC26 PKCS#11 extensions β€” sign like any other key; the digest and the certificate follow the key:

Key Digest Mechanism Token input
GOST R 34.10-2012, 256-bit Streebog-256 CKM_GOSTR3410 (0x1201) the 32-byte digest
GOST R 34.10-2012, 512-bit Streebog-512 CKM_GOSTR3410_512 (0xD4321006) the 64-byte digest

With hashOnToken: true the token hashes the signed attributes itself, with CKM_GOSTR3410_WITH_GOSTR3411_12_256 (0xD4321008) or …_512 (0xD4321009) β€” for a token that does not offer the plain mechanisms.

PKCS#11 (v2.40, 2.45.7) returns the signature as s β€– r, big-endian β€” the encoding CMS carries (RFC 4491, 2.2.2) and OpenSSL's gost engine writes β€” so the value goes into the PDF as is. Since tokens have been known to differ, the signer verifies the value against the certificate and, if it does not verify, tries the halves swapped, the whole value byte-reversed and each half reversed, and takes the one that verifies; if none does, the key does not belong to the certificate and you get a Pkcs11Exception.

GOST needs OpenSSL's gost engine loaded into PHP (the engines section of openssl.cnf): php-pdf computes Streebog and verifies with it, whether the token hashes or not. docker/gost/ builds such a PHP; its enable-gost-engine.sh switches the engine on in the stock config.

Verification status. The GOST path is tested end to end β€” signing, the PDF and php-pdf's validator β€” against an emulated token that signs with a real GOST R 34.10-2012 key (256 and 512) through OpenSSL's gost engine and returns the value in each of the encodings above; CI runs it in the docker/gost image. It has not yet been run on a physical Rutoken or JaCarta, and there is no software token to run it live: the SoftHSM2 in Ubuntu 24.04 is built without GOST, and SoftHSM2 knows only GOST R 34.10-2001 at best. Reports from a real device are welcome.

Long-running workers

Loading the module and logging in cost more than a signature. In a queue worker, Octane or RoadRunner, open the token once and reuse it:

$token = new PhpPkcs11Token($module, $pin, tokenLabel: 'signing');

// per document
$signer = Pkcs11Signer::fromToken($token, keyLabel: 'pdf');

PKCS#11 allows one initialization of a module per process, so all PhpPkcs11Token instances share it. A login also holds for the whole token within the process: once one instance has logged in, another one for the same token works with any PIN. Keep the PIN check where you decide who may sign, not in the token.

Errors

Failures are Pkcs11Exception; $e->returnValue is the PKCS#11 CKR_* code when the module reported one, and the message explains the common ones: a wrong, expired or locked PIN, no token in the slot, a removed device, a key not allowed to sign, a mechanism the token does not support. Ambiguous lookups list what they found β€” "2 private keys match, pass keyLabel: or keyId: β€” label "rsa", id 01; label "ec", id 02".

Requirements

  • PHP 8.2+ with dskripchenko/php-pdf 1.19 or later (Composer installs it).
  • The php-pkcs11 extension β€” gamringer/php-pkcs11, built for your PHP version (phpize && ./configure && make install). docker/Dockerfile here does it on Ubuntu 24.04 and pins the commit this package is verified with.
  • The vendor's PKCS#11 library for the device, and access to it for the user PHP runs as.
  • RSA, EC or GOST R 34.10-2012 keys. GOST also needs OpenSSL's gost engine in PHP (see above). For keys in CryptoPro CSP rather than on a PKCS#11 token, use dskripchenko/php-pdf-cryptopro.

Trying it on SoftHSM

docker/ builds a container with PHP 8.3, php-pkcs11 and SoftHSM2, creates a token with an RSA and an EC key and certificates from a throwaway root, signs PDFs and validates them with php-pdf and poppler's pdfsig β€” see docker/README.md. CI runs it on every change, and runs the GOST tests in docker/gost/ (PHP with the gost engine).

Testing without a token

Pkcs11Signer talks to the device through the small Token interface; PhpPkcs11Token is its implementation on top of the extension. Pass your own Token to Pkcs11Signer::fromToken() to run the code without a device β€” this package's tests do so with an in-memory token backed by OpenSSL.

The php-pdf family

Package What it adds
php-pdf The core: HTML with CSS or a builder to PDF, read, merge, fill forms, sign and validate.
laravel-php-pdf Laravel bridge: a Blade view straight to PDF, response()->pdf(), signing from config.
php-pdf-fonts-liberation Liberation Sans, Serif and Mono, metric-compatible with Arial, Times New Roman and Courier New (OFL, kept out of the MIT core).
php-pdf-cryptopro Signs with a key in CryptoPro CSP on the server: GOST R 34.10-2012, Russian qualified signatures.
php-pdf-pkcs11 (this package) Signs with a key on an HSM, a smart card or a USB token, through PKCS#11.
php-pdf-cloud-kms Signs with a key in AWS KMS, Google Cloud KMS or Azure Key Vault.

License

MIT β€” see LICENSE.