Optional module · pdftoolkit/pdf-reader 0.7.0

English | Français

PublicKeyAuthentication implements the Adobe.PubSec handler using an explicitly supplied core RecipientDecrypter. It receives PDF bytes from the application, recovers the seed from a matching CMS envelope and authenticates default document filters before the catalog is interpreted. It does not discover keys, fetch certificates or establish PKI trust.

use PDF\Reading\DocumentReader;
use PDF\Reading\FileBytes;
use PDF\Reading\Security\PublicKeyAuthentication;
use PDF\Reading\Security\PublicKeyAuthenticationLimits;
use PDF\Security\PublicKeyPermissionByteOrder;
use PDF\Security\RecipientDecrypter;

/** @var RecipientDecrypter $recipientDecrypter Application-selected identity. */
$authentication = new PublicKeyAuthentication(
    $recipientDecrypter,
    PublicKeyPermissionByteOrder::MostSignificantFirst,
    new PublicKeyAuthenticationLimits(maxCredentialAttempts: 64),
);
$document = (new DocumentReader(authentication: $authentication))->read(new FileBytes($bytes));
$permissions = $document->permissions();
$effectiveMask = $permissions?->effectiveMask();

For local PEM credentials, construct core OpenSslIdentity from an explicit CertificateChain, private-key PEM bytes and optional passphrase, then pass it to OpenSslRecipientDecrypter. That backend requires ext-openssl and temporary files in an application-selected directory; it invokes no executable. Alternative CMS capabilities remain application choices. A nonempty list of decrypters may be supplied for separate document and attachment identities. Recipients are tried in original source order, then identities in application order. A malformed matching payload fails instead of falling back to a more permissive recipient.

Run the application-level example with an explicit PDF, DER certificate and unencrypted PEM private key (or adapt it to supply a passphrase securely):

php examples/certificate.php document.pdf certificate.der private-key.pem

It reports page count, effective permissions and the verified MAC location.

Source mechanism Supported operation
s3/s4, V1/V2 Historical RC4; s3 and V1 use 40 bits.
s5, V4 Named V2/RC4 and AESV2/AES-128 filters.
s5, V5/V6 AESV3/AES-256 CBC and AESV4/GCM respectively, including source PDF MAC.
Identity Standard pass-through filter; source definitions cannot override it.

Public-key CF Length is measured in bits, unlike Standard CF byte lengths. Absent lengths use historical defaults (AESV2 128, AESV3/AESV4 256, RC4 the global Length or 40); this is compatible reading, not an ISO2 conformance claim. Custom handlers, unknown cipher methods and CFM None remain unsupported. None delegates decryption to a handler; it is not silently treated as Identity.

Recipient facts and permissions

Document-scope CMS plaintext is exactly 20 seed bytes plus four permission bytes. An individual-stream envelope contains exactly the 20 seed bytes. Keys are derived from that recovered seed and all original CMS packets in their original order, including packets for other recipients. A historical compatibility Encrypt.P does not grant rights.

The permission byte order is a required constructor argument: MostSignificantFirst follows deployed/corrected ISO2 semantics; IsoLeastSignificantFirst selects the literal ISO2008 wording. No heuristic switches between them. Public-key bit 2 grants all usage rights but does not represent a Standard password-owner login.

ReadDocument::permissions() now returns ?DocumentPermissions, a common effectiveMask() contract. Standard authentication still returns the existing ReadPermissions with mask, access and revision. Certificate authentication returns PublicKeyReadPermissions; its filters map exposes PublicKeyRecipientAccess objects containing the authenticated permission word and first matching recipientIndex, without key material. Where default filters have different permission words, effectiveMask() conservatively intersects their rights. Individual-stream access does not grant extra document rights. Applications needing password-specific fields must check instanceof ReadPermissions. The typed subFilter preserves s3/s4/s5 identity. For s3, only the historical R2 usage bits 3–6 are meaningful; reserved bits must not be interpreted as the later granular permissions. Starting with 0.5.2, both document and recipient effectiveMask() translate these four broad R2 rights into the common mask: form filling follows bit 6, accessibility extraction follows bit 5, assembly follows bit 4 and unrestricted printing follows bit 3. Source bits 9–12 never grant those rights for s3. The original authenticated word remains available in PublicKeyRecipientAccess::permissions; its subFilter also remains explicit. Bit 2 still grants all PubSec permissions. For s4/s5, the granular source bits retain their normal meanings. Applications should enforce the effective mask, not reinterpret a raw s3 permission word as an s4/s5 word.

Independent filters and deferred access

Each named filter has its own recovered key and recipient facts. DocOpen filters authenticate when opening. EFOpen filters authenticate only when their protected stream is accessed; missing attachment credentials do not prevent an otherwise accessible document from opening. If an EFOpen filter is selected by StrF or StmF, it is treated as DocOpen as required by table 25. There is no fallback to another filter key or to Identity. Explicit Crypt uses the direct filter key; ordinary historical encryption uses the owning object number and generation.

Catalog metadata ownership, terminal reference identities, signature/XRef exemptions and single decryption of object-stream members use the same bounded pipeline as Standard authentication. Metadata flags come from the stream filter, and only actual catalog metadata can receive the implicit cleartext exemption.

For modern PubSec, any indicated PDF MAC is verified before catalog interpretation using the authenticated DefaultCryptFilter key. A document-scope DefaultCryptFilter is authenticated at open even if declared EFOpen, so its required-MAC bit cannot be hidden behind deferred access. Other selectors cannot substitute their keys. Authenticated bit 13, direct KDFSalt, latest physical AuthCode and the required-integrity policy retain their existing enforcement. Use SourceIntegrityPolicy::RequirePdfMac when integrity is expected; see the source MAC guide. Neither CMS decryption nor a valid MAC grants signature trust, revocation status or authorization to change protected revisions.

Bounds and validation

PublicKeyAuthenticationLimits defaults to 64 filters, 1,024 total recipient packets, 1 MiB per packet, 16 MiB total recipient bytes, 4,096 credential attempts and 32 application identities. Counts, bytes and attempts are cumulative across filters, including deferred access, and reset for each document read. General syntax, source, retention and decoding limits also apply. PublicKeyAuthenticationFailed reports no matching credential without exposing certificate, key or source data. Malformed dictionaries/payloads and exhausted budgets raise ParseException.

The independent oracle tools/validate_public_key_authentication.py generates real sources with unmodified pyHanko and checks PHP results against independent CMS/key/cipher operations. Its 47 sources include 14 s3 cases; ten pair each broad R2 right with source bits 9–12 clear or set. pyHanko supplies the real CMS and encrypted bytes, while the oracle applies the normative s3 permission interpretation independently of pyHanko's common flag enum. Native adverse tests separately cover packet order, scope, first matching rights, unknown filters, delayed failures and limits. The existing Standard, metadata, hybrid and MAC oracles remain required.

Normative sources retained unchanged in the core: ISO 32000-2:2020 §§7.6.5–7.6.6 (physical pages 101–108), ISO 32000-1:2008 §7.6.4 (pages 72–77), Adobe supplement AESV3 (page 21), ISO/TS 32004:2024 §6.4 (page 15). The source map records their SHA-256 identities; this implementation reuses the core's documented derivation and cipher primitives rather than adding another cryptographic implementation.

Guide source at the documented commit ↗