Incremental PFX Signing Jobs

The sign operation supports a validated incremental profile that preserves the exact original PDF revision and each existing cryptographic signature, whilst returning a separately signed artefact

Select a profile

{"schemaVersion":1,"type":"sign","profile":"pfx-incremental-document",
 "pfxFile":"C:/keys/signer.pfx","pfxPassword":"password",
 "fieldName":"ApprovalTwo","existingField":true,
 "budget":{"memoryBytes":268435456,"outputBytes":134217728,
           "resultBytes":16777216,"timeMilliseconds":60000,
           "objectCount":1000000,"pageCount":1024}}

The default pfx-unsigned-document profile retains the original behaviour and accepts sources without any signature fields; it creates a new field using page, x1, y1, x2, and y2 and restores the source graph before publication

pfx-incremental-document selects the new profile; incremental: true is an equivalent selector when profile is omitted, and conflicts with an explicitly different profile

existingField: true fills the named empty signature field without adding another field; otherwise the operation creates a new field, and page and rectangle identify its widget

The fieldName default is Signature1 and accepts 1 through 1024 UTF-8 bytes; existingField requires an incremental profile, a matching signature field, and an absent or null V value

Read-only Ff bit 1 and malformed or out-of-range integer flags are rejected before preparation; inherited SV and Lock dictionaries are resolved with unique semantic PDF names and bounded parent traversal

Seed values and field locks

digestMethod selects the actual SHA256, SHA384, or SHA512 CMS digest; SHA256 is the default, whilst cades: true selects ETSI.CAdES.detached and includes the signing-certificate-v2 attribute that binds the actual signing certificate

reason, location, and contactName are UTF-8 JSON strings written as PDF Unicode text strings; required seed reasons compare their Unicode values, including the special single dot value that requires an omitted reason

Required SV constraints check the actual handler, CMS SubFilter, supported signing version, digest, reason, certification permission, legal attestation, and LockDocument mode; a required timestamp uses the application transport and explicit trust workflow described in Windows signing timestamps, required signed revocation evidence uses authenticated CRL and OCSP signing, and required named appearances use actual generated signature appearance presets

Required SVCert properties validate the actual PFX leaf certificate and included issuer chain, permitted certificate bytes, certificate policy OIDs, key usage bits, and Unicode subject DN attributes before signing

credentialSourceURL supplies application-asserted credential provenance for a required SVCert URL; the library compares the declared URL and supported URLType without opening that endpoint or authenticating a transport identity

All, Include, and Exclude field locks are written as an indirect SigFieldLock dictionary and a matching FieldMDP reference bound to the document Catalog; field names use PDF Unicode text strings and remain encrypted in encrypted documents

A signed Lock P value limits later revisions using the original signed field policy, independently of any DocMDP permission; permitted second signatures preserve the exact previous revision and CMS, whilst a locked field or a P1 change is rejected before publication

Policy lookup decodes PDF name escapes exactly once, including inherited Parent names; dictionaries with duplicate semantic names and invalid or cyclic parents are rejected before publication, whilst valid escaped names retain their source bytes

contentsBytes defaults to 16384 and accepts 1024 through 1048576; reason, location, and contactName populate the new signature dictionary

Certification and legal attestations

certificationPermission defaults to 0 for approval signing; 1, 2, or 3 creates a certification signature with the requested DocMDP permission, binds Catalog Perms DocMDP to the actual indirect signature dictionary, and retains any independent FieldMDP transform

Certification must be the first signature, and a document can contain only one certification signature; malformed Catalog permissions, unresolved references, and DocMDP values without an original signed field binding are rejected

legalAttestation is an optional Unicode string for certification signing and is written to Catalog Legal Attestation; a required SV LegalAttestation list must contain the selected text, whilst an empty required list requires omission

Existing P2 and P3 certification signatures permit later approval signing when field locks and revision policy allow it; P1 signatures and locked fields reject changes before binary publication

Source contract

The incremental job profile accepts a retained original source, including supported encrypted PDFs, and an unmodified loaded object graph; provide password in the sign operation for an encrypted source, separately from pfxPassword

The source password authenticates an independent raw snapshot, initialises the original encryption context, and reopens the signed candidate; the resulting incremental revision preserves the original ciphertext prefix, security dictionary, and permanent document ID

Decryption can internally mark objects dirty or remove a security dictionary from the decrypted graph; the operation compares these states and bounded object serialisations against a freshly authenticated source baseline, whilst rejecting actual graph changes and all flushed object states

For a user password, an existing signature field requires form-fill or annotation permission, and a new signature widget requires annotation permission; an authenticated owner password authorises either operation, subject to existing signature policies

SaveLoadedDocumentToStream can change serialisation state; when signing a saved or edited graph, reload the saved bytes into a new handle and sign that revision

Keep original source storage stable for the handle lifetime; file jobs deny write sharing whilst loading and preparing their snapshot

The original handle retains its field counts, existing field identity, form values, source bytes, and prior launch and decode settings; every signing success returns documentUpdated: false, and signing failures leave the handle available for querying, saving, or a later valid request

File jobs reject an output path that equals or aliases the input file, including hard links; direct stream APIs reject recognisable source-stream and source-file aliases, and callers must also keep opaque callback output storage distinct from input storage

Preparation and validation

The operation copies raw source bytes into an exclusively created temporary snapshot, opens an independent incremental clone, appends the placeholder through bounded SaveIncrementalUpdate, and fills the detached CMS through the native PFX stream signer

The PFX remains open with write sharing denied from size preflight through signing; temporary files use CREATE_NEW and are removed only when the operation owns their creation

The signed candidate must contain the exact source byte prefix and expected form and signature field counts; the new signature must verify in its selected field, every previously populated signature must still verify, and every old signature's revision analysis must accept DocMDP, FieldMDP, usage-rights, and identity constraints

Unpopulated old fields are retained without being counted as preservedSignatures; malformed, invalid, or policy-violating existing signatures cause rejection before binary publication

Existing signature verification checks CMS integrity against signed bytes and native revision policy; when timestamp is requested for the new signature, its response separately validates explicit TSA trust and configured revocation evidence before publication

Budgets and publication

The effective output cap is min(outputBytes, memoryBytes / 8) and includes the whole source prefix, appended objects, xref, and signature reservation; effectiveOutputLimit reports the applied cap

The unsigned candidate and signed output are separately bounded by that cap, PFX bytes are capped at memoryBytes / 8, and source-baseline, clone, and candidate decode budgets are separately capped at memoryBytes / 8 with file spilling thresholds no higher than 1 MiB

These are operation and buffer budgets rather than a strict process RSS guarantee; crypto, object metadata, revision analysis, and the caller's already-loaded source can require additional resident memory

Cancellation and elapsed-time checks apply to source copy, delta serialisation and copy, candidate reads, signature verification reads, revision analysis reads, and callback publication; sticky native stream failures retain their budget or cancellation category at the job boundary

Binary output is staged and validated before the first output callback, and the result JSON size is checked before binary publication; callback output itself is not atomic, so a failed binary or result callback can leave bytes that the caller must discard

File jobs write to an owned same-directory candidate, flush it, close source and output handles, and atomically replace a distinct target after successful validation; failures preserve an existing destination

Success returns mediaType: application/pdf, fieldName, profile, incremental, existingField, certificationPermission, revocationInfo, appearanceProfile, preservedSignatures, effectiveOutputLimit, outputBytes, and documentUpdated: false

Replay acceptance

$env:HOTPDF_SUPPRESS_AUTO_LAUNCH = '1'
./Tests/CABI/Run-IncrementalSigningAcceptance.ps1 -BuildDLL -CheckFPC

The runner discovers RAD Studio through its registry RootDir, accepts RADStudioRoot and PythonExecutable overrides, and uses the configured FPC toolchain when CheckFPC is selected; FPCOnly replays only those native targets, whilst OpenSSLWin32Library and OpenSSLWin64Library select matching runtime libraries for each architecture

The non-GUI runner checks both Win32 and Win64 native APIs and actual C ABI DLLs using partial callbacks, existing empty fields, prior signatures, required seed values, actual SHA384 and SHA512 CAdES, Unicode metadata, permitted and locked second signatures, actual PFX certificate properties, AES-128 and AES-256 field lock transforms, permission restrictions and owner credentials, incorrect passwords, dirty decrypted graphs, exact encrypted source snapshots, encrypted in-place writer recovery, real DocMDP P1 and P2 CMS fixtures, budgets, cancellation, callback failures, and source reuse after failures

Independent pypdf, MuPDF, and OpenSSL checks prove exact prior prefixes and byte ranges, unchanged encryption and permanent IDs, searchable original text, valid old and new CMS values, the signature Contents encryption exception, and rejection of modified signed content; independent-proof.json records the byte ranges and proof results

Related topics

Document operations, CopyLoadedSourceToStream, SaveIncrementalUpdate, EHPDFIncrementalOutputBudget