Prima che un viewer mostri il segno di spunta verde su un PDF firmato, compie tre operazioni meccaniche: legge l'array /ByteRange dal dizionario della firma, calcola l'hash esattamente sui due intervalli di byte descritti da quell'array e verifica la firma CMS memorizzata — in esadecimale — nella voce /Contents che si trova tra quei due intervalli. Quasi ogni fallimento di firma in produzione risale a uno di questi tre passaggi sistemati male: un placeholder troppo piccolo per il blob di firma finale, un hash calcolato sui byte sbagliati o un salvataggio post-firma che ha riscritto byte già coperti dai range. La crittografia quasi mai fallisce. La contabilità dei byte sì.
HotPDF offre alle applicazioni Delphi e C++Builder tre livelli di supporto alla firma: firma PFX in una sola chiamata, workflow con firmatario esterno per HSM e servizi di firma, e campi profilo PAdES con marche temporali del documento. Sono presentati in quest'ordine perché ogni livello esiste per gestire una modalità di fallimento del precedente.
Il contratto ByteRange in ISO 32000-1 §12.8
Una firma PDF deve vivere dentro il file che firma, e questo crea un problema di tipo uovo-e-gallina: il valore della firma non può coprire se stesso. Il formato lo risolve con un buco. Il writer riserva una voce /Contents di dimensione fissa riempita di zeri, e /ByteRange registra due intervalli: tutto ciò che precede il buco e tutto ciò che lo segue. Il firmatario calcola l'hash su quei due intervalli, e la struttura CMS risultante viene scritta nel buco come esadecimale. La conseguenza che fa inciampare gli ingegneri: la dimensione della riserva è congelata prima della firma, quindi la firma finale, certificati inclusi, deve stare in un buco la cui dimensione è stata scelta prima. Circa 8 KB bastano per una firma CMS detached con una catena certificati breve.
HotPDF espone direttamente la distinzione. AddSignatureField crea un campo visibile vuoto che qualcuno firmerà più tardi in un viewer; AddSignedSignatureField crea il campo e riserva il buco /Contents per il completamento programmatico. Scegliere quello sbagliato è un errore classico della prima settimana: un campo vuoto non offre nulla da riempire a un firmatario esterno.
Una chiamata quando la chiave è un file PFX
Quando certificato di firma e chiave privata vivono in un file PFX/PKCS#12, l'intera pipeline si riduce a una class function:
if THotPDF.SignPDFWithPFX('invoice-unsigned.pdf', 'invoice-signed.pdf',
'company-cert.pfx', 'pfx-password') then
Writeln('Signed: invoice-signed.pdf')
else
raise Exception.Create('PFX signing failed');
Il fallimento che domina il traffico di supporto qui non riguarda affatto il PDF: riguarda il contenitore PFX. HotPDF legge file PFX protetti con PBES2 — derivazione chiave PBKDF2 con AES-256-CBC. I contenitori esportati da vecchi wizard certificati di Windows, o da release OpenSSL precedenti alla 3.0, usano di default protezione legacy RC2/3DES e non vengono analizzati. Il rimedio è una riesportazione una tantum del contenitore con parametri moderni (OpenSSL attuale lo fa di default), non una modifica al codice. Controlla questo punto per primo quando la firma fallisce immediatamente su un certificato che "funziona ovunque".
Firma esterna: riserva, hash, inserimento
Il percorso in una chiamata presume che la chiave privata sia un file leggibile dal processo. In produzione sempre più spesso non lo è: sta in un HSM, in un token USB o in un servizio di firma remoto, e nessuna libreria può invocarla direttamente. Per questa topologia HotPDF divide il workflow in passaggi a livello byte: scrivere un documento placeholder, calcolare i range di hash, consegnare l'input dell'hash a ciò che possiede la chiave e innestare il CMS restituito nel file.
var
Doc: THotPDF;
Fs: TFileStream;
PdfBytes, HashInput, SigHex: AnsiString;
R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
// 1. Write the document with a reserved /Contents hole
Doc := THotPDF.Create(nil);
try
Doc.FileName := 'placeholder.pdf';
Doc.BeginDoc;
Doc.CurrentPage.AddSignedSignatureField('Sig1',
Rect(50, 100, 350, 150), 8192, 'adbe.pkcs7.detached',
'Contract approval', 'Boston, MA', 'legal@example.com');
Doc.EndDoc;
finally
Doc.Free;
end;
// 2. Load the saved bytes; the returned offsets are 0-based
Fs := TFileStream.Create('placeholder.pdf', fmOpenRead);
try
SetLength(PdfBytes, Fs.Size);
Fs.ReadBuffer(PdfBytes[1], Fs.Size);
finally
Fs.Free;
end;
THotPDF.PreparePDFForSigning(PdfBytes, R1Start, R1Len, R2Start, R2Len,
CStart, CLen);
// 3. Hash both spans and sign externally (HSM, token, service)
HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
Copy(PdfBytes, R2Start + 1, R2Len);
SigHex := SignWithHsm(HashInput); // your integration: returns CMS as hex
// 4. Splice the signature into the reserved hole
THotPDF.InsertSignatureHex(PdfBytes, SigHex);
Fs := TFileStream.Create('signed.pdf', fmCreate);
try
Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
finally
Fs.Free;
end;
end;
Due vincoli in questa sequenza causano fallimenti intermittenti in produzione quando vengono ignorati. Primo, PreparePDFForSigning opera sui byte di un file completamente salvato: il documento placeholder deve essere scritto fino in fondo prima che i range abbiano senso; calcolare i range su uno stream ancora in corso produce offset che non corrispondono più alla serializzazione finale. Secondo, la riserva da 8192 byte deve contenere il CMS finale. Una firma che incorpora certificati intermedi, o una restituita da un servizio che aggiunge attributi firmati, può superarla, e InsertSignatureHex non può allargare il buco. Il sintomo è un job che riesce con un certificato e fallisce con un altro; la correzione è rigenerare il placeholder con una riserva più grande, dimensionata da una firma reale prodotta dal firmatario effettivo.
Baseline PAdES e marche temporali del documento
La regolamentazione europea sulle firme si basa su ETSI EN 319 142-1, che definisce quattro livelli baseline PAdES: B-B è la firma di base; B-T aggiunge una marca temporale fidata che prova quando è stata fatta; B-LT incorpora nel documento il materiale di validazione — certificati e dati di revoca; B-LTA aggiunge marche temporali periodiche del documento affinché l'evidenza sopravviva all'invecchiamento degli algoritmi. HotPDF crea le strutture lato documento per questo ciclo di vita:
// PAdES baseline signature field (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
'Contract approval', 'Boston, MA', 'legal@example.com');
// Document timestamp: larger reservation for the TSA token and chain
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);
Nota la riserva da 16384 byte sulla marca temporale: il token di una timestamp authority porta la propria catena certificati, quindi supera regolarmente gli 8 KB sufficienti per una firma semplice. Le marche temporali del documento sono anche il meccanismo alla base della manutenzione B-LTA: rimarcare temporalmente un archivio firmato ogni pochi anni con algoritmi correnti è ciò che mantiene verificabile nel 2040 una firma del 2026.
Le stringhe reason, location e contact accettate da entrambe le chiamate ai campi meritano una nota di policy: sono memorizzate come voci di dizionario semplici e renderizzate nell'aspetto visibile, ma nulla le verifica. Documentano l'intento per lettori umani — "Contract approval", una città, una mailbox — e gli auditor le leggeranno, quindi popolale in modo coerente dai dati di workflow. Non trattare però mai il testo visibile come evidenza: l'affermazione crittografica vive interamente nel CMS e nella sua catena certificati, e un validatore ignora completamente l'appearance.
Perché i file firmati devono solo crescere
Una volta che esiste una firma, i byte dentro i suoi range sono congelati per sempre. Gli incremental update di ISO 32000-1 §7.5.6 sono l'unico modo legittimo di modificare il file dopo: oggetti nuovi e modificati vengono accodati dopo i byte originali, con una nuova sezione cross-reference che si collega alla precedente. La firma resta valida per la sua revisione, e i viewer riportano lo stato onesto: "revisione firmata integra, documento modificato dopo". Una riserializzazione completa, al contrario, riscrive gli intervalli firmati e distrugge del tutto la firma, anche se non è cambiato un solo elemento visibile. La meccanica dei salvataggi append-only, e quando compattarli, è trattata nell'articolo su object stream e incremental update.
Un vincolo a livello libreria completa il quadro di pianificazione: la modalità di output PDF/A di HotPDF rifiuta i campi firma, quindi conformità archivistica e firme incorporate devono essere separate in deliverable distinti invece che combinate in un solo file. Inoltre la firma è ortogonale alla riservatezza: una firma prova origine e integrità ma non nasconde nulla, terreno che appartiene a crittografia AES-256 e policy dei permessi.
I test di accettazione per una pipeline di firma devono essere indipendenti dal codice che ha prodotto il file. Apri l'output nel pannello firme di Acrobat e conferma tre stati: la firma è valida, l'identità concatena fino alla root attesa e il pannello non riporta modifiche dopo la firma. Poi corrompi un byte dentro il range firmato di una copia e conferma che lo stesso pannello segnali il documento come alterato: una pipeline che non è mai stata vista fallire la validazione è una pipeline la cui validazione non è stata davvero testata.
Domande ricorrenti nelle code review di firma
Quanto deve essere grande la riserva /Contents?
8192 byte per un CMS detached con una catena breve; 16384 quando sono coinvolte marche temporali o intermedi incorporati. Misura il CMS prodotto dal tuo firmatario reale e aggiungi margine: la riserva non può crescere più tardi.
Un documento può contenere due firme?
Sì. Ogni firma vive nella propria revisione incrementale, e i range della seconda firma coprono la prima: è esattamente così che si costruiscono i workflow di controfirma.
La firma protegge il contenuto del documento?
No. Una firma fornisce evidenza di integrità e origine; chiunque può comunque leggere il file. La riservatezza richiede crittografia, configurata separatamente.
Tutti e tre i livelli di firma sono inclusi in HotPDF Component per Delphi e C++Builder; la pagina prodotto collega il riferimento completo dell'API di firma.