Ga naar inhoud
getnextpdf.com

Ondertekenen op grote schaal, zonder compromis

Spec: ISO 32000-2, §12.8Spec: ETSI EN 319 142-1Spec: RFC 5652, §5.1

Eén document ondertekenen is een cryptografische operatie. Honderdduizend ondertekenen onder tijdsdruk is dezelfde operatie, herhaald, waarbij de gevaarlijke fout niet langer “het was traag” is maar “een ervan ging ongetekend de deur uit en niemand merkte het.” Deze pagina gaat over het tweede doen zonder het eerste op te geven: bulk- en gelijktijdige ondertekening waarbij elke handtekening nog steeds correct is, de run weigert een bestand uit te sturen dat hij niet kon ondertekenen, en een grote taak hervat in plaats van overnieuw te beginnen.

Een handtekening is een per-document-feit. De digest ervan wordt berekend over een opgegeven byte- bereik dat de handtekeningwaarde zelf uitsluit (Spec: ISO 32000-2, §12.8), dus er is geen eerlijke manier om duizend documenten “als een batch” in één klap te ondertekenen — elk draagt zijn eigen CMS SignedData over zijn eigen bytes (Spec: RFC 5652, §5.1). Schaal vermenigvuldigt daarom de kansen dat precies één ding stilzwijgend misgaat: een sleutel-handle die kortstondig faalde, een tijdstempelautoriteit die een time-out had, een worker die stierf met een half-geschreven bestand in handen.

De dure uitkomst is geen crash. Een crash is luid en je probeert het opnieuw. De dure uitkomst is een stille — een ongetekende PDF die afgerond lijkt en in een archief ligt, maanden later ontdekt door de validator van een auditor. Op volume is “grotendeels getekend” niet te onderscheiden van “getekend” tot precies aan het moment dat de ene die ertoe doet wordt gecontroleerd. Het hele punt van ondertekenen op schaal is om die uitkomst structureel onmogelijk te maken, niet statistisch zeldzaam.

  • Elk document wordt individueel ondertekend, over zijn eigen bytebereik. Batch is een planningswoord, geen cryptografisch woord. Er is geen gedeelde handtekening.
  • Het niveau is een contract, geen hint. Je benoemt een PAdES-baseline-niveau en de engine produceert precies dat niveau voor elk document, of laat dat document luid falen (Spec: ETSI EN 319 142-1).
  • De pijplijn is fail-closed. Een document dat niet correct kan worden ondertekend, gaat niet als gewone bytes door. Het wordt vastgehouden, niet doorgegeven.
  • Gelijktijdigheid is per document, en veilig door constructie. Ondertekeneenheden delen geen veranderlijke status, zodat twee workers elkaars uitvoer niet kunnen beschadigen.
  • Grote runs zijn duurzaam. Vastgelegde uitvoer wordt bij hervatten niet opnieuw uitgestuurd; een gecrashte run gaat verder vanaf zijn laatste checkpoint in plaats van alles opnieuw te ondertekenen.

Het ontwerp rust op één scheiding: de handtekening produceren is een kleine, deterministische stap per document; er duizenden veilig draaien is een orkestratiestap. Die uit elkaar houden is wat elk eenvoudig laat blijven.

De ondertekenstap is degene die nooit een compromis mag sluiten. Je vraagt om een niveau — een SignatureLevel-enumgeval, nooit een string die de engine moet interpreteren — en dat niveau wordt behandeld als een contract voor dat document. De engine produceert het gevraagde niveau of stopt met een actiegerichte fout; het ondertekent niet stilletjes op een lager niveau en laat een record een hoger niveau claimen. Correctheid verslapt niet omdat er meer documenten achter dit ene zitten. De honderdduizendste handtekening wordt precies zo zorgvuldig berekend als de eerste.

De fail-closed-regel is wat dat betrouwbaar maakt op volume. Het ondertekenpad van NextPDF weigert een plausibel-ogend-maar-ongetekend artefact uit te sturen in plaats van het exemplaar waar je om vroeg. De ondersteunde applicatieroute is de high-level Document- API: je configureert de handtekening met Document::setSignature() en vraagt vervolgens om de bytes met Document::getPdfData() (of save() / output()), en die enkele schrijfgang stuurt ofwel een correct-ondertekende PDF uit of gooit vóór het teruggeven van bytes — nooit een ongetekend bestand waarvan de aanroeper gelooft dat het getekend is. Over een batch toegepast is dit de regel die “er glipte er eentje ongetekend doorheen” omzet van een stille latente fout in een enkele gefaalde, opnieuw-uitvoerbare taak.

  1. Warm the signing material onceOn worker boot, open the key/certificate source and the timestamp client. This cost is paid once per worker, not once per document.
  2. Enqueue the documentsA queue holds the per-document jobs. The queue is the throughput dial — signing workers scale horizontally behind it.
  3. Render and sign one documentA disposable unit renders the document, then signs it over its own byte range at the requested PAdES level. Nothing is shared with the next document.
  4. Commit on success, hold on failureA correctly-signed file commits once. A document that could not be signed is failed and retried — never emitted as unsigned bytes.
  5. Checkpoint, and resume on crashA durable run records what has committed. After a crash it continues from the last checkpoint instead of re-signing the whole batch.
Een grootschalige ondertekenrun van begin tot eind: gedeeld ondertekenmateriaal wordt één keer opgewarmd; elk document wordt individueel weergegeven en ondertekend op een wegwerpeenheid; een correct-ondertekend resultaat wordt precies één keer vastgelegd, terwijl elke fout wordt vastgehouden voor herhaling, nooit als ongetekende bytes wordt doorgegeven; een gecrashte run hervat vanaf zijn checkpoint.

Core geeft je de cryptografische correctheid: software-CMS-ondertekening en PAdES B-B (met B-T via de tijdstempelclient) waarbij elk document individueel en fail-closed wordt ondertekend. De orkestratie die een grote run duurzaam, gelijktijdig en exactly-once maakt — de bijwerkingsvrije render-engine plus de committer, checkpoint, idempotentie en dead-letter-stores — is de Stream- module in de geavanceerde edities; hardware-ondersteund ondertekenen via een HSM of een cloud-KMS is eveneens een naad in de geavanceerde editie. Core bewijst dat elke handtekening klopt; de geavanceerde edities maken er een miljoen overleefbaar.

De onderstaande vorm is de ondertekeneenheid per document binnen een batchlus. Elke iteratie ondertekent één document op een benoemd niveau en levert ofwel een correct-ondertekend resultaat of laat die ene taak falen — hij geeft nooit ongetekende bytes terug die als een resultaat zijn opgedoft.

<?php
declare(strict_types=1);
use NextPDF\Contracts\DocumentFactoryInterface;
use NextPDF\Security\Signature\CertificateInfo;
use NextPDF\Security\Signature\SignatureLevel;
use NextPDF\Exception\SignatureException;
use Psr\Log\LoggerInterface;
/**
* One signing-batch iteration: render, sign at a named level, commit or fail.
*
* The factory and the certificate source ($certInfo, the warmed signing
* material) are process-lifetime singletons; the document is disposable. A
* document that cannot be signed at the requested level fails this job loudly —
* it is never committed unsigned.
*
* @param iterable<int, callable(\NextPDF\Core\Document): \NextPDF\Core\Document> $jobs
*/
function signBatch(
DocumentFactoryInterface $factory,
CertificateInfo $certInfo,
LoggerInterface $logger,
iterable $jobs,
): void {
// The level is an explicit, ordered contract — not a flag we hope is honoured.
$level = SignatureLevel::PAdES_B_T;
foreach ($jobs as $jobId => $build) {
// Fresh, disposable unit — shares the warmed signing material only.
$doc = $factory->create();
$doc = $build($doc);
try {
// Sign over this document's own byte range, at exactly $level,
// or throw. There is no "signed lower, reported higher" path.
$doc->setSignature(certInfo: $certInfo, level: $level);
$signed = $doc->getPdfData();
} catch (SignatureException $e) {
// Fail-closed: this document does NOT continue as unsigned bytes.
// The job is failed and left for retry / dead-letter handling.
$logger->error('pdf.sign.failed', ['job_id' => $jobId, 'reason' => $e->getMessage()]);
continue;
}
// Only a correctly-signed result reaches the commit step.
commitSignedOutput($jobId, $signed);
unset($doc, $signed); // release per-document state before the next iteration
$logger->info('pdf.sign.committed', ['job_id' => $jobId, 'level' => $level->value]);
}
}

De catch is de dragende regel. Het is het verschil tussen een run die de documenten vasthoudt die hij niet kon ondertekenen en een run die ze toch verstuurt. De continue doekt de fout niet weg — de taak wordt geregistreerd en achtergelaten voor herhaling, zodat de batch eindigt met een bekende, complete lijst van wat is getekend en wat niet, nooit met een stille leemte.

Het eerste misverstand is dat “batch-ondertekenen” betekent dat één handtekening op veel bestanden wordt toegepast. Dat doet het niet, en elk systeem dat dat claimt produceert geen geldige PAdES-handtekeningen — de digest van elk document is gebonden aan zijn eigen bytes (Spec: ISO 32000-2, §12.8). Batch gaat puur over hoeveel en hoe snel, nooit over het delen van de cryptografische eenheid.

Het tweede is dat gelijktijdigheid betekent dat correctheid voor snelheid wordt versoepeld — dat een snelle ondertekenaar een hoek moet afsnijden die de zorgvuldige niet afsnijdt. Dat doet het niet. Omdat ondertekeneenheden geen veranderlijke status delen, verandert het parallel draaien het schema, niet de bytes. Elke parallelle handtekening wordt met dezelfde nauwgezetheid berekend als een enkele; de parallelliteit zit in de orkestratie eromheen.

Het derde is dat duurzaamheid iets is wat je erop schroeft na de eerste gefaalde nachtelijke run. Tegen die tijd ben je de run al kwijt. Een hervatbare pijplijn moet per document weten wat is vastgelegd en wat niet vóór de crash — wat precies is wat de checkpoint- en idempotentie-stores bestaan om vast te leggen.

  • Elke handtekening is per document en standaardgebonden; er is geen batch- sluiproute. Volume verandert de planning, niet de cryptografische eenheid. NextPDF ondertekent elk document over zijn eigen bytebereik.
  • Core doet software-CMS-ondertekening en PAdES B-B (B-T via een tijdstempelclient). De duurzame, gelijktijdige, exactly-once render-en-onderteken-engine is de Stream-module in de geavanceerde edities; HSM/KMS-ondersteunde sleutelbewaring is een naad in de geavanceerde editie. Deze pagina claimt die orkestratie niet als Core.
  • Fail-closed is het gedrag van de engine, geen garantie over jouw bedrading. NextPDF weigert een ongetekend-maar-gewaand-getekend bestand uit te sturen en brengt de ondersteunde ondertekenroute naar voren. Een pijplijn die de resulterende fout vangt en toch vastlegt, heeft ervoor gekozen de garantie te verslaan — de kadering waarvoor de catch/continue in het voorbeeld bestaat om te voorkomen.
  • Het PAdES-niveau wordt per document afgedwongen, niet gecertificeerd voor de run. De engine produceert het gevraagde baseline-niveau of faalt; dat is een structurele afdwinging, geen conformiteitsoordeel van een derde partij voor de geproduceerde bestanden. De niveauprogressie zelf wordt behandeld in PAdES-baseline-profielen.
  • De wachtrij, de sleutelbewaring, de tijdstempelautoriteit en de objectopslag zijn van jou. NextPDF levert de ondertekencorrectheid per document en, in de geavanceerde edities, de duurzame orkestratieprimitieven. Het draait je infrastructuur niet en staat niet garant voor je TSA.
High-volume and concurrent signing — edition availability
EditionAvailability
Core

Software-CMS-ondertekening per document, PAdES B-B (B-T met een tijdstempelclient), individueel ondertekend over het eigen bytebereik van elk document, fail-closed tegen stilzwijgend-ongetekende uitvoer. Gewone ondertekening per document vereist geen commerciële laag.

Pro

Voegt de Stream-module toe: een bijwerkingsvrije render-engine plus duurzame committer, checkpoint, idempotentie en dead-letter-stores — gelijktijdige, crash-veilige, exactly-once batchruns die hervatten in plaats van opnieuw te beginnen.

Enterprise

Voegt hardware-ondersteunde sleutelbewaring toe (HSM via PKCS#11, of een cloud-KMS) zodat de private sleutel het apparaat nooit verlaat, en de langetermijn-PAdES-niveaus (B-LT, B-LTA) die een grootschalig archief decennialang verifieerbaar houden.

  • Documentgeneratie op grote schaal — het begrensde-geheugen, wachtrijgebaseerde batchmodel waarop deze pagina ondertekent; lees het eerst voor de discipline van doorvoer en meting.
  • PAdES-baseline-profielen — wat elk niveau (B-B tot B-LTA) toevoegt, zodat je ondertekent op het niveau dat de verplichting nodig heeft.
  • Hoe handtekeningen in een PDF zitten — de bytebereik- en dictionary-fundering die een handtekening per document maakt.
  • HSM-ondersteund ondertekenen — waar de private-sleutel- grens ligt wanneer ondertekenmateriaal in hardware leeft.
  • Stream (Pro) — de duurzame, gelijktijdige, exactly-once render-engine die een enkele ondertekeneenheid omzet in een hervatbare run.
  • Batch-ondertekenen — veel documenten op een schema ondertekenen. Een planningsconcept; elk document wordt nog steeds individueel ondertekend over zijn eigen bytes.
  • Fail-closed — bij een fout die anders een ongetekende of verkeerde uitvoer zou produceren, houdt de pijplijn het document vast en rapporteert, in plaats van het door te geven als gewone bytes.
  • Exactly-once-commit — een eigenschap van een duurzame pijplijn waarbij een correct-ondertekende uitvoer één keer wordt gepubliceerd en niet opnieuw wordt uitgestuurd wanneer een gecrashte run hervat.
  • Checkpoint — duurzaam record per document van wat is vastgelegd, zodat een run kan doorgaan vanaf waar hij stopte in plaats van alles opnieuw te ondertekenen.
  • CMS SignedData — de cryptografische container voor handtekeningen over inhoud (hij kan meerdere ondertekenaars dragen); deze pijplijn produceert één ondertekenaars-PDF- handtekening per document, de eenheid per document die een batch produceert.
  • PAdES — PDF Advanced Electronic Signatures, de ETSI EN 319 142-profiel- familie voor PDF-ondertekening; de baseline-niveaus lopen van B-B tot B-LTA.