HotPDF verifieert ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 en Ed448 CMS-handtekeningen in geladen PDF-documenten, en het ondertekent via pluggable providers zodat de privésleutel nooit in je Delphi-proces hoeft te leven. Die tweede helft is het deel dat de meeste teams het eerst nodig hebben. Een hardware-token, een externe ondertekeningsdienst en een nationale eID-kaart weigeren allemaal een sleutel af te geven, en totdat de ondertekeningspijplijn is gescheiden van de sleutelopslag, kan geen van hen worden gebruikt
Die splitsing is het punt van THPDFSignatureProvider. HotPDF behoudt de delen die het zou moeten bezitten — CMS parsen, SignedData opbouwen, de /ByteRange uitleggen — en delegeert de ene operatie die het niet kan bezitten: een digest omzetten in een handtekening met een sleutel die het niet mag zien. Alles wat volgt vloeit voort uit die verdeling
Waarom faalt een geldige ML-DSA-handtekening de verificatie?
Omdat HotPDF ML-DSA weigert op een geladen document dat de extensie daarvoor niet declareert. ML-DSA — het roostersignatuurschema dat is gestandaardiseerd als FIPS 204, en de reden dat mensen "post-kwantum PDF" zeggen — heeft nog geen ISO 32000-2-registratie. Een PDF die er een meedraagt, gebruikt een algoritme dat het basisstandaard niet noemt, en een bestand dat stilzwijgend een ongenaamd algoritme gebruikt, is een bestand waarvan het verdict door niemand anders is te reproduceren
Dus HotPDF maakt de claim expliciet. EnsureMLDSAExtensions brengt het document waar toegestaan naar PDF 2.0 en schrijft /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> in de Catalog. Aan de leeskant rapporteert LoadedDocumentDeclaresMLDSAExtension of die declaratie heeft overleefd, en VerifyLoadedSignatureWithOptions past dezelfde test toe voordat het Options.AllowMLDSA honoreert. Zet de flag op een ongedeclareerd document en hij blijft uit — de optie kan beleid versoepelen, nooit de structurele vereiste
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'contract-pq.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 720, 0, 'Supply agreement 2026-114');
Pdf.EnsureMLDSAExtensions; // declare before the signature is written
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Roep het aan vóór het opslaan, niet erna. De declaratie is onderdeel van het ondertekende bytebereik, en een Catalog die achteraf wordt gepatcht is ofwel een niet-ondertekende wijziging van een ondertekend bestand ofwel een tweede revisie die een validator als wijziging rapporteert
Drie algoritmefamilies, één verificatietoegang
Alle drie de families komen binnen via VerifyLoadedSignatureWithOptions, die een handtekeningindex nodig heeft, de bronstream, een THPDFCMSVerifyOptions-record en een out-parameter voor de handtekeningdetails. Het record heeft precies drie velden, en elk beantwoordt een vraag die vroeger een rebuild vereiste
SignatureProvider substitueert je eigen provider voor de ingebouwde platformprovider. OpenSSLLibraryPath selecteert een OpenSSL 3-bibliotheek, wat de pure-mode Ed25519- en Ed448-verificatie levert die Windows CNG niet overal biedt. AllowMLDSA kiest de roosteralgoritmen in, onderworpen aan de extensiecontrole hierboven. De exacte algoritme-OID die werd herkend komt terug in THPDFSignatureInfo.SignatureAlgorithmOID, zodat een auditlog kan vastleggen wat is geverifieerd in plaats van wat werd verzocht
var
Opts: THPDFCMSVerifyOptions;
Info: THPDFSignatureInfo;
Status: THPDFSignatureVerifyStatus;
Src: TFileStream;
begin
Opts := THPDFCMSVerifyOptions.Default;
Opts.OpenSSLLibraryPath := 'C:\openssl3\libcrypto-3-x64.dll';
Opts.AllowMLDSA := Pdf.LoadedDocumentDeclaresMLDSAExtension;
Src := TFileStream.Create('contract-pq.pdf', fmOpenRead or fmShareDenyWrite);
try
Status := Pdf.VerifyLoadedSignatureWithOptions(0, Src, Opts, Info);
if Status = svValid then
Memo1.Lines.Add('signed with OID ' + string(Info.SignatureAlgorithmOID));
finally
Src.Free;
end;
end;
Ed25519 en Ed448 hebben geen extensiedeclaratie nodig, want ISO 32000-2 admisseert ze al. Ze hebben wel een provider nodig die ze implementeert, wat op de meeste Windows-implementaties betekent dat je OpenSSLLibraryPath wijst naar een bibliotheek die je zelf levert en beheert in plaats van naar wat er toevallig op de machine staat
Wat belooft een ondertekeningsprovider werkelijk?
Een provider belooft één ding: gegeven een verzoek, retourneer een status en, bij ondertekening, bytes. THPDFSignatureProviderRequest draagt het algoritme en zijn OID, de digest-OID, de PSS-zoutlengte, of de invoer een boodschap of een al berekende digest is, de invoer zelf, de publieke sleutel of het certificaat, een sleutelidentificator en een operatie-identificator. Niets in dat record is HotPDF-specifiek — het is de woordenschat die een tokendriver of een ondertekeningsdienst al spreekt
Drie implementaties worden met de bibliotheek geleverd. THPDFCallbackSignatureProvider wikkelt anonieme methoden, wat het kortste pad is van een bestaande interne ondertekeningsroutine naar een werkende PDF-handtekening. THPDFRemoteSignatureProvider wikkelt een transport-callback met een herproeflimiet, een annuleringsregister en grenzen aan invoer- en handtekeninggrootte, zodat een hangende HSM geen hangende applicatie wordt. THPDFPKCS11SignatureProvider serialiseert RSA-operaties tegen een aanroeper-eigene, reeds-geauthenticeerde PKCS#11-sessie en private-key-handle — HotPDF logt nooit in, ziet nooit een PIN en sluit nooit een sessie die het niet heeft geopend
var
Provider: THPDFRemoteSignatureProvider;
begin
Provider := THPDFRemoteSignatureProvider.Create(
function(const Req: THPDFSignatureProviderRequest; Attempt: Integer;
out Signature: TBytes): THPDFSignatureProviderStatus
begin
// POST Req.Input to the signing service; Req.KeyIdentifier selects the key
if PostToSigningService(Req.KeyIdentifier, Req.Input, Signature) then
Result := spsValid
else
Result := spsProviderError;
end,
3, // RetryLimit
1048576, // MaxInputBytes
65536); // MaxSignatureBytes
try
// hand Provider to the signing call
finally
Provider.Free;
end;
end;
Waarom de status-enum zes waarden heeft in plaats van een boolean
THPDFSignatureProviderStatus onderscheidt spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError en spsCancelled, en ze samenvouwen kost je de mogelijkheid om correct te handelen. Een handtekening die cryptografisch verkeerd is (spsInvalid) is een beveiligingsgebeurtenis. Een algoritme dat de provider niet implementeert (spsUnsupported) is een implementatiekloof. Een transportstoring (spsProviderError) is het waard om opnieuw te proberen, en een door de gebruiker geannuleerde tokenprompt (spsCancelled) is het helemaal niet waard om opnieuw te proberen
De regel voor ondertekening is nauw: een ondertekeningsprovider retourneert spsValid alleen met een niet-lege handtekening. Verificatieproviders retourneren spsValid of spsInvalid, en de andere vier blijven op beide paden gescheiden. Als je een provider schrijft, weersta de verleiding om alles wat je niet herkent op spsInvalid af te beelden — dat verandert een ontbrekende DLL in een rapport dat de handtekening van de klant is vervalst
Waar de handtekening daadwerkelijk in het bestand belandt
Twee functies verbinden providers met echte PDF-bytes. HPDFCMSBuildSignedDataWithProvider bouwt detached CMS uit een document-SHA-256-digest, wat het juiste ingangspunt is wanneer je workflow de digest ergens anders berekent. HPDFCMSSignPDFStreamWithProvider ondertekent een bestaande handtekening-placeholder in een PDF-stream en behoudt de standaard /ByteRange-pijplijn, wat het juiste ingangspunt is wanneer HotPDF de placeholder zelf heeft uitgelegd
Die pijplijn behouden talt zwaarder dan het klinkt. De /ByteRange-conventie — twee bereiken die het hexadecimale handtekeningvenster overslaan — is wat elke validator het eerst controleert, en een op providers gebaseerd pad dat het herschreef zou PAdES-conformiteit breken hoe dan ook hoe stevig de cryptografie was. HotPDF houdt de lay-out identiek aan het ingebouwde ondertekeningspad, zodat een document dat via een PKCS#11-token is ondertekend verifieert met dezelfde handtekeningsverificatiecode als een dat is ondertekend vanuit een PFX-bestand. Voor de profielregels die boven de algoritmekenuze staan, zie de doorloop van PAdES-baseline-handtekeningen in Delphi, en voor de ECDSA-specifieke coderingsvallen die aan dit providermodel voorafgaan, de notities over ECDSA CMS-verificatie en P1363-handtekeningformaten
Een migratievolgorde die je documenten niet in de steek laat
Post-kwantum-gereedheid is een planningsprobleem, geen schakelaar. Vrijwel geen uitgebrachte PDF-viewer valideert vandaag ML-DSA, dus een document dat er alleen mee is ondertekend, is vanuit het oogpunt van de lezer een document met een onverifieerbare handtekening. De volgorde die contact met echte archieven overleeft is: behoud RSA of ECDSA als de handtekening die een validator beoordeelt, voeg de extensiedeclaratie en een tweede ML-DSA-handtekening toe waar een beleid kwantumbestendig bewijs eist, en verplaats de primaire handtekening pas wanneer de verbruikende systemen zijn bijgetrokken
Wat HotPDF je vandaag geeft, is de mogelijkheid om beide te schrijven en te verifiëren, vanuit dezelfde code, met het algoritme eerlijk vastgelegd in het bestand en in het verificatieresultaat. HotPDF is een native VCL PDF-component voor Delphi en C++Builder zonder externe PDF-runtime, zodat de ondertekenings- en verificatiepaden binnen je executable worden geleverd in plaats van ernaast — zie de HotPDF Delphi PDF-componentpagina voor de volledige functielijst en trial-download