PDFlibPas risponde alla domanda FieldMDP attraverso TPDFlib.GetSignatureModificationVerdict. Dato un PDF firmato, la sua password e il nome di un campo firma, il metodo attraversa ogni aggiornamento incrementale accodato dopo quella firma, risolve il nome completo di ogni campo modulo toccato da quegli aggiornamenti e restituisce 3 quando è stato modificato un campo bloccato da FieldMDP e 2 quando sono stati toccati solo campi non bloccati. GetSignatureModificationDetail indica poi la revisione e il campo
Il flusso di lavoro che ne ha bisogno è il modulo multi-approvatore. Un richiedente compila Order.Customer e Order.Total su una richiesta d'acquisto e firma Approval.Requester, bloccando quei due campi. Un responsabile aggiunge un centro di costo e firma Approval.Manager. Due settimane dopo qualcuno apre il file in un editor desktop e alza Order.Total in un terzo aggiornamento incrementale. Ogni firma continua a verificare, perché ciascuna copre solo il proprio byte range. Le domande che un revisore si pone davvero sono quale campo è cambiato dopo quale firma, e se quel cambiamento era consentito
Perché il solo DocMDP non individua un campo bloccato modificato?
DocMDP non riesce a individuarlo perché classifica tipi di cambiamento, non i singoli campi. I livelli di permesso di certificazione in ISO 32000-1 §12.8.2.2 stabiliscono se gli aggiornamenti successivi possono compilare moduli e firmare (P 2) o anche annotare (P 3), e quel permesso si applica a ogni campo del documento allo stesso modo. Modificare Order.Total è compilazione di un modulo, quindi una certificazione P 2 è soddisfatta proprio dalla modifica che il richiedente intendeva vietare. La classificazione delle modifiche a livello di documento in PDFlibPas copre quel livello, e questo articolo scende a una granularità più fine
FieldMDP è il livello con granularità di campo. Un campo firma può portare un dizionario /Lock (ISO 32000-1 §12.7.4.5), e la firma che contiene può portare una transform FieldMDP nel suo signature reference (ISO 32000-1 §12.8.2.4). Entrambi usano lo stesso vocabolario: /Action /All blocca ogni campo, /Action /Include blocca i nomi elencati in /Fields, e /Action /Exclude blocca tutto tranne i nomi in /Fields. Prima della v3.586.0 GetSignatureModificationVerdict sapeva che una revisione successiva aveva modificato un campo modulo, ma non quale, quindi qualsiasi documento con un blocco di campo i cui aggiornamenti successivi andavano oltre il materiale di validazione tornava come 4, indeterminato. La revision map ora registra un nome di campo risolto per ogni oggetto legato ai campi che classifica, ed è questo che trasforma quel 4 in un vero 2 o 3
Scrivere il blocco di campo quando firma ogni approvatore
TPDFlib.SetSignProcessFieldLock imposta il blocco per la prossima firma prodotta da un processo di firma. L'azione 1 è All, 2 è Include, 3 è Exclude e 0 cancella il blocco; Include ed Exclude prendono un elenco di nomi completi di campo separati da punto e virgola. Il campo firmato finisce con un dizionario /Lock e una transform FieldMDP corrispondente, che è l'abbinamento che i validatori rigorosi si aspettano
var
Pdf: TPDFlib;
SignId: Integer;
begin
Pdf := TPDFlib.Create;
try
SignId := Pdf.NewSignProcessFromFile('purchase-request.pdf', '');
if SignId = 0 then
raise Exception.Create('cannot open the form');
try
Pdf.SetSignProcessField(SignId, 'Approval.Requester');
Pdf.SetSignProcessPFXFromFile(SignId, 'requester.pfx', PfxPassword);
// 2 = Include: solo questi campi vengono congelati da questa firma
if Pdf.SetSignProcessFieldLock(SignId, 2,
'Order.Customer;Order.Total') <> 1 then
raise Exception.Create('field lock rejected');
Pdf.EndSignProcessToFile(SignId, 'purchase-request-signed.pdf');
if Pdf.GetSignProcessResult(SignId) <> 1 then
raise Exception.CreateFmt('signing failed, code %d',
[Pdf.GetSignProcessResult(SignId)]);
finally
Pdf.ReleaseSignProcess(SignId);
end;
finally
Pdf.Free;
end;
end;
SetSignProcessFieldLock restituisce 0 per una chiamata Include o Exclude senza nomi, e anche per un elenco che contiene una voce vuota come 'Order.Customer;;Order.Total'; una chiamata rifiutata lascia al suo posto il blocco configurato in precedenza, quindi controllate il valore di ritorno invece di dare per vinta l'ultima chiamata. Il resto della configurazione della firma, scelta del digest, riservazione del /Contents e codici di risultato, è trattato nella guida a firma e validazione PAdES per PDFlibPas. Un punto di progettazione è specifico delle catene di approvazione: un blocco /All sulla firma del richiedente congela anche Approval.Manager, perché un campo firma è a sua volta un campo modulo. Usate Include per i campi dati di proprietà di ciascun approvatore, oppure Exclude elencando i campi firma successivi
Eseguire il verdetto lungo una catena di approvazione
GetSignatureModificationVerdict giudica una firma alla volta, quindi una catena di approvazione si controlla chiamandolo una volta per campo firma. Il valore di ritorno è un insieme piccolo e chiuso, e ogni valore significa qualcosa di diverso per chi revisiona:
- 0: non è stato possibile leggere il file o il campo firma indicato
- 1: dopo la firma non è stato accodato nulla
- 2: sono stati accodati solo cambiamenti consentiti dai permessi effettivi, e nessun campo bloccato è stato toccato
- 3: una revisione successiva ha introdotto un cambiamento non consentito, compresa una modifica a un campo bloccato da FieldMDP
- 4: non è stato possibile determinare la risposta, e non va mai letto come approvazione
- 5: la firma non termina su un confine di revisione, quindi ci sono byte dentro l'intervallo che dichiara di coprire
const
Approvals: array[0..2] of WideString =
('Approval.Requester', 'Approval.Manager', 'Approval.Finance');
var
Pdf: TPDFlib;
I, Verdict: Integer;
begin
Pdf := TPDFlib.Create;
try
for I := Low(Approvals) to High(Approvals) do
begin
Verdict := Pdf.GetSignatureModificationVerdict(
'purchase-request-final.pdf', FormPassword, Approvals[I]);
// entrambi i getter descrivono la chiamata appena fatta, quindi leggeteli prima della successiva
Log.Add(Format('%s: verdict %d, MDP level %d, %s',
[Approvals[I], Verdict, Pdf.GetSignatureEffectiveMDPLevel,
Pdf.GetSignatureModificationDetail]));
case Verdict of
1, 2: ; // accettata
3: RejectForm(Approvals[I]); // il dettaglio indica campo e revisione
else
QueueForManualReview(Approvals[I]); // 0, 4, 5
end;
end;
finally
Pdf.Free;
end;
end;
Prendete la richiesta d'acquisto manomessa, dove la revisione 0 è il modulo in bianco, le revisioni 1 e 2 sono le due firme e la revisione 3 è la modifica. Poiché le definizioni di blocco vengono messe in comune da ogni campo firmato, il verdetto per Approval.Manager fa rispettare già il blocco del richiedente, e il suo unico aggiornamento successivo è la modifica stessa. Quando quell'aggiornamento ha riscritto solo il dizionario del campo, il verdetto è 3 e il dettaglio recita Revision 3 changed locked form field "Order.Total", which FieldMDP does not allow. Quella riga risponde a entrambe le domande della revisione contabile. Il verdetto per Approval.Requester deve prima superare la revisione della firma del responsabile, e per i motivi nella sezione limiti qui sotto spesso riporta 4, quindi la firma più recente di solito dà la risposta più netta. GetSignatureEffectiveMDPLevel riporta il valore P DocMDP più basso trovato tra le firme, o 3 quando nessuno ha certificato il documento, e entrambi i getter restano validi solo fino alla successiva chiamata di verdetto
Come fa PDFlibPas a sapere quale campo ha modificato una revisione?
PDFlibPas ricostruisce il nome del campo dal grafo di oggetti di ogni revisione, perché un oggetto modificato raramente porta con sé il nome completo. Un dizionario di campo con /FT o /V, o un'annotazione widget, contiene solo il proprio nome parziale in /T. Il nome completo descritto in ISO 32000-1 §12.7.3.2 è la catena di nomi parziali dalla radice, uniti da punti, quindi la revision map risale i collegamenti /Parent e antepone ogni /T che trova. Ogni padre viene letto dalla sua definizione più recente alla revisione da classificare o precedente, il che impedisce a una rinomina successiva di un nodo padre di riscrivere la storia delle modifiche precedenti
Il confronto del nome risolto con i blocchi è volutamente semplice. Un nome corrisponde a una voce /Fields quando è identico alla voce o comincia con la voce seguita da un punto, quindi bloccare Order blocca anche Order.Total e ogni altro discendente. All blocca ogni nome, Include blocca i nomi corrispondenti, e Exclude blocca ogni nome che non corrisponde. Le definizioni di blocco provengono da ogni campo firma firmato presente nel file; quando un campo ha sia un dizionario /Lock sia una transform FieldMDP, si usano i valori del /Lock. Per vedere esattamente cosa ha bloccato ogni firma, la classe di livello più basso TPDFlibSignDoc dell'unità PDFlibDigitalSign espone il rapporto sui permessi che il verdetto legge:
uses
PDFlibDigitalSign;
var
SignDoc: TPDFlibSignDoc;
Report: TPDFlibSignaturePermissionReport;
Names: TStringList;
I, J: Integer;
Line: string;
begin
SignDoc := TPDFlibSignDoc.Create;
Names := TStringList.Create;
try
if not SignDoc.Open('purchase-request-final.pdf', FormPassword, False) then
Exit;
try
SignDoc.GetSignatureFieldNames(Names);
for I := 0 to Names.Count - 1 do
begin
SignDoc.GetSignaturePermissionInfoByName(Names[I], Report);
if not (Report.HasFieldLock or Report.HasFieldMDP) then
Continue;
Line := Names[I] + ': ' + string(Report.FieldLockAction);
for J := 0 to High(Report.FieldNames) do
Line := Line + ' ' + string(Report.FieldNames[J]);
if Report.HasFieldLock and Report.HasFieldMDP and
not Report.FieldMDPMatches then
Line := Line + ' (lock and FieldMDP transform disagree)';
Log.Add(Line);
end;
finally
SignDoc.Close;
end;
finally
Names.Free;
SignDoc.Free;
end;
end;
Moduli cifrati: passate la password o i nomi sono illeggibili
Un modulo cifrato va decifrato prima di poter risolvere qualunque nome di campo, quindi GetSignatureModificationVerdict ha bisogno dell'argomento Password per qualcosa di più dell'apertura del file. Stringhe come /T sono cifrate per oggetto, e i campi scritti in object stream compressi non si possono leggere affatto finché lo stream che li contiene non è decifrato e decompresso. Dalla v3.586.0 la revision map interna accetta la password, aggancia un contesto di decifratura mentre analizza object stream e dizionari di revisione, e decifra ogni oggetto che decodifica, comprese le versioni precedenti con cui fa il diff e i padri che risale
La decifratura per oggetto nella revision map si attiva solo quando Password non è vuota. Un verdetto calcolato su un file cifrato con password vuota va quindi trattato come non verificato, anche se il documento si è aperto senza chiedere nulla. Conservate la password del modulo accanto al record del flusso di lavoro e passatela a ogni chiamata
Cosa non copre il verdetto FieldMDP
Il controllo dei campi è una risposta strutturale a una domanda ristretta, e diversi casi restano fuori dal suo perimetro. Conoscerli in anticipo è ciò che impedisce a un 4 di trasformarsi in una escalation al supporto:
- Il confronto dei campi parte solo dopo che il controllo a livello di documento è uscito pulito e almeno una revisione successiva ha cambiato più che materiale di validazione o timestamp; una violazione DocMDP viene riportata come 3 prima di guardare qualunque campo
- Gli oggetti classificati come legati alla firma ma senza nome di campo, come il dizionario del valore di firma, un dizionario AcroForm riscritto, o un cambiamento di catalogo limitato a
/AcroFormo/Perms, fanno restituire 4 al verdetto invece di tirare a indovinare, così una revisione che aggiunge una firma di approvazione successiva sotto un blocco attivo spesso finisce su 4 - Solo i dizionari di campo e le annotazioni widget ricevono un nome di campo; la revision map non ha una classe dedicata agli appearance stream, quindi un editor che rigenera gli aspetti insieme al valore può essere fermato dal controllo a livello di documento come cambiamento di contenuto prima che parta il controllo dei campi, e la stringa di dettaglio distingue i due casi
- La scansione si ferma al primo campo bloccato o alla prima voce senza nome che incontra, quindi un 3 nomina un campo colpevole, non tutti
- I blocchi vengono messi in comune da ogni campo firma firmato senza ordinarli nel tempo, quindi un blocco scritto da un firmatario successivo si applica anche agli aggiornamenti venuti prima; questo può solo spingere un risultato verso 3, mai verso 2
- I nomi si confrontano in modo esatto e distinguendo le maiuscole, e la risalita dei
/Parentsi ferma dopo otto livelli, quindi una gerarchia insolitamente profonda si risolve in un nome troncato - Una voce FieldMDP la cui struttura non risulta valida e la cui azione non è leggibile fa restituire 4 al verdetto
Quando un verdetto torna 4, GetSignatureRevisionIndex dà la revisione a cui la firma termina e GetDocumentRevisionChanges elenca ogni oggetto che ogni revisione successiva ha aggiunto, modificato o liberato, con la sua classe di cambiamento. Basta a una persona per decidere in un minuto. La meccanica con cui quelle revisioni vengono accodate è trattata nella guida agli aggiornamenti incrementali per PDFlibPas
FAQ
Un verdetto 2 significa che le firme sono valide?
No. GetSignatureModificationVerdict esamina cosa è stato accodato dopo una firma e non controlla mai il valore della firma CMS, la catena di certificati o la revoca. Un 2 dice che gli aggiornamenti successivi sono rimasti dentro i permessi; la validazione crittografica è un passo a parte e servono entrambi
Perché la firma di un secondo approvatore produce 4 invece di 2?
Firmare accoda un dizionario del valore di firma e di solito riscrive la contabilità dei moduli, come il dizionario AcroForm. Quegli oggetti non appartengono a nessun campo nominato, e quando un blocco di campo è attivo il verdetto rifiuta di dare il via libera a un cambiamento che non sa attribuire a un campo. Usate GetDocumentRevisionChanges per confermare che la revisione contiene solo gli oggetti di firma attesi
Un solo nome può bloccare un intero gruppo di campi?
Sì. Una voce di blocco corrisponde al campo nominato e a ogni discendente il cui nome completo comincia con esso seguito da un punto, quindi elencare Order in un blocco Include congela Order.Customer, Order.Total e qualunque altro campo sotto quel padre
Verdetti FieldMDP, firma con blocco di campo e ispezione delle revisioni sono tutti presenti nella PDFlibPas Delphi PDF Library, insieme alle funzionalità di certificazione DocMDP e di validazione PAdES di lungo termine su cui si appoggia il flusso di approvazione descritto sopra