PDFiumPas divide la firma PAdES in due chiamate così la chiave privata non deve mai trovarsi nel tuo processo. PreparePadesRemoteSignature scrive un incremental update con un placeholder /Contents vuoto a larghezza fissa e restituisce un record di richiesta che porta il digest SHA-256 del documento, il ByteRange esatto e un fingerprint del file preparato. CompletePadesRemoteSignature prende il CMS distaccato che il tuo servizio di firma restituisce e lo colloca in quello slot riservato
Tra queste due chiamate possono passare minuti o ore, il processo può riavviarsi, e il lavoro può spostarsi su un'altra macchina. Quel divario è l'intera ragione per cui l'API è progettata in questo modo
Perché una chiave remota non può usare la chiamata di firma ordinaria?
Perché SignPadesBytes presuppone che l'operazione di firma avvenga dentro la chiamata. Costruisce l'incremental update, calcola il digest sul ByteRange, lo firma, e scrive il risultato, tutto prima di ritornare. Questo è esattamente corretto quando la chiave vive nel certificate store di Windows o in un file PKCS#12 che hai caricato
È impossibile quando la chiave vive in un HSM di rete, in un dispositivo qualificato di creazione della firma gestito da un trust service provider, o in un'API di firma cloud che richiede all'utente di confermare su un telefono. In questi casi la sequenza non è una chiamata di funzione, è una conversazione: invii un digest, qualcos'altro autentica un essere umano, e un CMS torna più tardi. Un'API sincrona non può esprimere «più tardi» senza bloccare un thread su un'operazione che potrebbe richiedere un secondo fattore
Il protocollo a due fasi
La fase uno prepara il documento. PDFiumPas aggiunge il signature field e il value dictionary, riserva ContentsSize byte di spazio codificato in hex dentro /Contents, calcola il ByteRange attorno a quella riserva, e produce un TPadesRemoteSigningRequest contenente FormatVersion, PreparedFingerprint, DocumentDigest, il ByteRange a quattro elementi, ContentsHexOffset e ContentsSize
L'unico valore di cui il tuo servizio di firma ha bisogno è DocumentDigest: lo SHA-256 che il SignedData CAdES restituito deve portare come proprio message digest. Tutto il resto nel record esiste perché la fase due possa dimostrare che il file che sta completando è il file da cui è stato calcolato quel digest
uses
FPdfPades;
var
Options: TPadesRemoteSignOptions;
Request: TPadesRemoteSigningRequest;
Source, Prepared, Session: TFileStream;
begin
Options := TPadesRemoteSignOptions.Default;
Options.Reason := 'Approved by finance';
Options.Location := 'Lisbon';
Options.Name := 'A. Moreira';
Options.SigningTimeUtc := NowUtc;
Options.ContentsSize := 16384; // hex bytes reserved for the CMS
Source := TFileStream.Create('contract.pdf', fmOpenRead or fmShareDenyWrite);
Prepared := TFileStream.Create('contract.prepared.pdf', fmCreate);
try
PreparePadesRemoteSignature(Source, Prepared, Options, Request);
finally
Prepared.Free;
Source.Free;
end;
// Persisti la sessione così un'esecuzione successiva - o un'altra macchina - possa completarla
Session := TFileStream.Create('contract.signreq', fmCreate);
try
SavePadesRemoteSigningRequest(Session, Request);
finally
Session.Free;
end;
SendDigestToSigningService(Request.DocumentDigest);
end;
Cosa rifiuta Complete, e perché esiste ogni controllo?
Il completamento è il punto in cui un design di firma remota di solito va storto, quindi la validazione è deliberatamente inflessibile. CompletePadesRemoteSignature rifiuta un PDF preparato il cui fingerprint non corrisponde più alla richiesta, un ByteRange che non corrisponde alle coordinate registrate del placeholder, delimitatori /Contents modificati, un placeholder che non è più vuoto, un CMS più grande della riserva, un CMS che non è esattamente un valore DER, una forma SignedData non supportata, un attributo signing-certificate-v2 mancante, e un CMS il cui message digest non è uguale al digest del documento preparato
Ognuno di questi corrisponde a un fallimento reale. I controlli su fingerprint e ByteRange catturano il caso in cui qualcuno ha rigenerato il file preparato tra le due fasi, il che produrrebbe una firma che valida contro byte che nessuno possiede. Il controllo del placeholder vuoto cattura il doppio completamento, dove un secondo CMS viene scritto sopra una firma già esistente. Il controllo del message digest cattura il caso più pericoloso di tutti: un CMS correttamente formato ma firmato su un documento diverso, che è ciò che si ottiene quando una coda confonde due sessioni di firma concorrenti. Senza di esso produrresti un file che sembra firmato e fallisce la validazione ovunque, o peggio, che porta l'approvazione di qualcun altro
Il requisito signing-certificate-v2 è una questione di conformità PAdES più che di integrità. ETSI EN 319 142 richiede che il certificato di firma sia legato agli attributi firmati, e un CMS privo di quell'attributo non è una firma PAdES anche se verifica crittograficamente. Rifiutarlo al completamento significa scoprirlo qui, non in un report di un validatore da parte di un cliente, un argomento approfondito in perché i validatori rifiutano le firme PAdES
var
Request: TPadesRemoteSigningRequest;
Session, Prepared, Dest: TFileStream;
CmsDer: TBytes;
begin
Session := TFileStream.Create('contract.signreq', fmOpenRead);
try
Request := LoadPadesRemoteSigningRequest(Session);
finally
Session.Free;
end;
CmsDer := FetchDetachedCmsFromService; // returned by the HSM or TSP
Prepared := TFileStream.Create('contract.prepared.pdf', fmOpenRead);
Dest := TFileStream.Create('contract.signed.pdf', fmCreate);
try
try
CompletePadesRemoteSignature(Prepared, Dest, Request, CmsDer);
except
on E: EPadesCrypto do
// Ogni rifiuto porta una ragione specifica; registrala testualmente
FailSession(E.Message);
end;
finally
Dest.Free;
Prepared.Free;
end;
end;
Attraversare i confini di processo e di macchina
SavePadesRemoteSigningRequest e LoadPadesRemoteSigningRequest serializzano la sessione tramite un formato binario versionato e stabile, ed è ciò che rende il design pratico e non solo corretto. Un'applicazione web può preparare un documento in una richiesta, memorizzare il PDF preparato e il blob della sessione, restituire un digest al browser per una firma con smart card, e completare il file in un gestore di richiesta completamente diverso
Il campo FormatVersion è ciò che mantiene tutto questo sicuro attraverso gli upgrade. Una sessione scritta da una build più vecchia e caricata da una più recente viene riconosciuta o rifiutata esplicitamente, invece di essere interpretata erroneamente come un record di forma diversa. Se la tua coda può mantenere sessioni per giorni, tratta la format version come un fatto operativo che vale la pena registrare, non un dettaglio implementativo
Dimensionare il placeholder
ContentsSize è l'unico parametro a cui devi pensare, perché viene fissato prima che il CMS esista. Conta la riserva codificata in hex, quindi un CMS DER di 6 KB richiede almeno 12 KB di spazio, e l'implementazione limita la riserva a 64 MiB
Riserva troppo poco e il completamento fallisce con un errore di CMS sovradimensionato dopo che il tuo servizio di firma ha già svolto il proprio lavoro, il che su un servizio di firma qualificata a consumo significa un'operazione sprecata. Riserva troppo e ogni documento firmato porta per sempre quel padding. L'approccio sensato è misurare: firma un documento con la tua vera catena di certificati, guarda la lunghezza DER, raddoppiala per l'hex, poi aggiungi un margine generoso per il timestamp token se intendi passare a una firma di livello T. Le catene con più intermediari e una risposta OCSP lunga crescono più rapidamente di quanto ci si aspetti
Cosa viene dopo la firma
Una firma remota completata è PAdES B-B. La validazione a lungo termine richiede un timestamp e il materiale di validazione, che è un incremental update separato che aggiunge un DSS e i suoi dictionary VRI per singola firma, descritto in firme a lungo termine con timestamp RFC 3161 e DSS. Quel passo è locale: aggiunge certificati, risposte OCSP e CRL, nessuno dei quali richiede la chiave privata
Prima di rilasciare, verifica ciò che hai prodotto con lo stesso percorso di codice che userebbe una relying party, trattato in ispezionare le firme digitali e i livelli PAdES. Firma e verifica sono codice diverso, e una pipeline di firma remota è esattamente il punto in cui i due possono divergere senza che nessuno se ne accorga finché non lo dice un validatore esterno
PDFiumPas è un componente Delphi e Lazarus attorno al motore PDFium con uno stack PAdES nativo in Pascal, così firma, timestamping e validazione funzionano senza strumenti a riga di comando esterni. La documentazione API completa e una build di prova sono sulla pagina del componente Delphi PDFium