Optional module · pdftoolkit/pdf-reader 0.7.0

English | Français

Reader 0.5 uses the core 0.9 verifier for ISO/TS 32004:2024 PDF MACs. An explicitly selected StandardPasswordAuthentication authenticates the password and encrypted permissions, verifies any indicated MAC against the original file bytes, then permits the catalog and protected content to be read. A corrupt, missing required, unsupported or stale MAC is an error, with no fallback to unauthenticated decryption.

use PDF\Reading\DocumentReader;
use PDF\Reading\FileBytes;
use PDF\Reading\Security\SourceIntegrityPolicy;
use PDF\Reading\Security\SourcePdfMacVerification;
use PDF\Reading\Security\StandardPasswordAuthentication;
use PDF\Security\PreparedPassword;

$authentication = new StandardPasswordAuthentication(
    new PreparedPassword($preparedUtf8Password),
    mac: new SourcePdfMacVerification(maximumTokenBytes: 256 * 1024),
);
$reader = new DocumentReader(
    authentication: $authentication,
    sourceIntegrity: SourceIntegrityPolicy::RequirePdfMac,
);
$document = $reader->read(new FileBytes($bytes));
// Standalone or AttachedToSignature; this is not a signature-trust result.
$location = $document->mac();
$permissions = $document->permissions();

AcceptUnprotected, the compatible default policy, also reads unprotected PDFs. It still verifies every MAC indicated by AuthCode, KDFSalt or authenticated permission bit 13. RequirePdfMac additionally rejects an unprotected source, including one from which all encryption and MAC indications have been removed. An ordinary clear PDF cannot reveal that such indications once existed. Choose the required policy whenever the application expects protected input.

Optional AuthCode and KDFSalt values resolving to PDF null are absent, including bounded indirect-reference chains and unavailable generations. A latest null AuthCode never revives an older MAC. Real AuthCode dictionaries and salt strings must still occur directly where TS 32004 requires them; required permission bit 13 and RequirePdfMac remain enforced.

DocumentReader::sourceIntegrityPolicy() exposes its configured policy. requiringSourceIntegrity($policy) composes an immutable reader with the same limits and credentials, preserving or strengthening the requirement. Passing AcceptUnprotected cannot relax a reader that already requires a PDF MAC.

Exact source coverage

The parser records the lexical source span of the actual security string, including its delimiters. Two equal strings in different objects or revisions remain distinct. Decoded object-stream values do not acquire a span in the original file. No search for similar token bytes is used as proof.

Location Verified source binding
Standalone The latest physical trailer contains a direct AuthCode dictionary. Its direct four-integer ByteRange covers the whole current file except its actual direct hexadecimal MAC token. Only complete hexadecimal bytes are accepted: no internal whitespace, odd final nibble, extra excluded bytes or CMS padding. SigObjRef must be absent.
AttachedToSig AuthCode contains an indirect SigObjRef and no MAC or ByteRange. The referenced signature dictionary's direct ByteRange excludes exactly its actual direct hexadecimal Contents token. One DER CMS object is accepted with only trailing zero signature padding. Exactly one PDF MAC unsigned attribute and value bind both the file bytes and the raw SignerInfo signature octets. Digital signatures and document timestamps are supported.

An older trailer's inherited AuthCode cannot authenticate a new revision. The latest MAC is checked against the entire current source, including older revisions; this follows Annex B. The reader does not independently validate each historical revision's former MAC. Appending even whitespace makes a MAC stale. Table, stream and hybrid cross-reference handling retains its ordinary bounds. MAC verification occurs before any encrypted catalog or content is interpreted.

Cryptographic boundary

Native password authentication supports Standard R5/V5, R6/V5 and R7/V6, with a direct 32-byte KDFSalt. The MAC verifier uses the actual authenticated file key: HKDF-SHA256 with PDFMAC, AES-256 key unwrapping and HMAC-SHA256. It checks CMS AuthenticatedData, its recipient, required attributes, optional algorithm protection and document/signature digests. SHA-256/384/512 and SHA3-256/384/512 are supported. SHA-2 algorithm identifiers accept absent or NULL parameters according to RFC 5754; other parameters remain constrained.

This authenticates the file using its encryption key. It does not verify the underlying signature mathematically, establish certificate trust, check a TSA, fetch certificates or revocation data, or grant additional document permissions. Anyone with the file encryption key, including a valid user-password holder, can produce a new MAC. An empty user password offers no additional protection against someone who can derive that key. Successful MAC authentication does not make PDF actions or attachments safe to execute.

Adobe.PubSec s5 V5/V6 uses the authenticated 256-bit DefaultCryptFilter key, even when StmF, StrF or EFF selects another filter. Permissions are recovered from CMS envelopes, never the legacy P entry. See public-key authentication. Unknown MAC locations and other CMS wrapping or MAC algorithms are explicitly unsupported. Attached payloads currently require direct hexadecimal Contents and ByteRange, with an uncompressed source signature object. This is a bounded source reader, not a complete PDF conformance validator. A custom DocumentAuthentication is a trusted application capability; its supplied decryption context must preserve the same verification boundary.

Limits and independent checks

SourcePdfMacVerification defaults to a 1 MiB CMS limit, including the allocated Contents and its padding for attached signatures. The source token must also fit ReaderLimits::maxTokenBytes; hexadecimal encoding uses two characters per decoded byte. Source spans, the covered-byte buffer, plaintext and decoded streams consume the document's cumulative retained/decoded budgets. The source file limit is checked before authentication. These limits do not promise an exact maximum PHP process-memory size.

The checked-in fixtures and tools/validate_source_mac.py cover 15 real files: six digest algorithms, both MAC locations, a document timestamp, object streams, incremental updates and R5–R7 encryption. pyHanko and cryptography independently validate CMS/HKDF/AES key wrapping/HMAC/digests; qpdf checks the 13 non-R7 files without warnings. qpdf 12.4.2 does not support AESV4 and pyHanko does not authenticate historical R5; R7 uses unchanged pyHanko/cryptography, and R5 uses pypdf/qpdf credentials plus cryptography and pyHanko's standalone token validator. No oracle is patched.

Fourteen independently generated adversarial files include authentic MACs with incorrect source framing or semantic claims, duplicate/missing CMS attributes, wrong signature binding and inherited stale MACs. Native unit tests also modify source comments, ciphertext-independent bytes, required markers, permissions, signature padding and final file length. Their refusal is tested before a ReadDocument is returned.

Sources: ISO/TS 32004:2024, Tables 2–6 (physical pages 10–12), §§6.2–6.6 (pages 13–17), Annex B (pages 19–20); the original is retained in the core reference documents. RFC 5754 §2 and RFC 6211 §3.2 define algorithm-identifier compatibility.

Password authentication · Security controls

Guide source at the documented commit ↗