Το PDFiumPas διαχωρίζει την υπογραφή PAdES σε δύο κλήσεις ώστε το ιδιωτικό κλειδί να μη χρειάζεται ποτέ να βρίσκεται στη διεργασία σας. Η PreparePadesRemoteSignature γράφει μια incremental update με ένα κενό δεσμευτικό /Contents σταθερού πλάτους και επιστρέφει μια εγγραφή αιτήματος που φέρει το digest SHA-256 του εγγράφου, το ακριβές ByteRange και ένα αποτύπωμα του προετοιμασμένου αρχείου. Η CompletePadesRemoteSignature παίρνει το detached CMS που επιστρέφει η υπηρεσία υπογραφής σας και το τοποθετεί σε αυτή τη δεσμευμένη θέση
Ανάμεσα σε αυτές τις δύο κλήσεις μπορούν να περάσουν λεπτά ή ώρες, η διεργασία μπορεί να επανεκκινήσει, και η δουλειά μπορεί να μετακινηθεί σε άλλη μηχανή. Αυτό το κενό είναι ολόκληρος ο λόγος που το API έχει αυτή τη μορφή
Γιατί ένα απομακρυσμένο κλειδί δεν μπορεί να χρησιμοποιήσει την κοινή κλήση υπογραφής;
Επειδή η SignPadesBytes υποθέτει ότι η λειτουργία υπογραφής συμβαίνει μέσα στην κλήση. Χτίζει την incremental update, υπολογίζει το digest πάνω στο ByteRange, το υπογράφει, και γράφει το αποτέλεσμα, όλα πριν επιστρέψει. Αυτό είναι ακριβώς σωστό όταν το κλειδί βρίσκεται στο Windows certificate store ή σε ένα αρχείο PKCS#12 που φορτώσατε
Είναι αδύνατο όταν το κλειδί βρίσκεται σε ένα HSM δικτύου, μια εξειδικευμένη συσκευή δημιουργίας υπογραφής που λειτουργεί ένας πάροχος υπηρεσιών εμπιστοσύνης, ή ένα cloud API υπογραφής που απαιτεί από τον χρήστη να επιβεβαιώσει στο τηλέφωνο. Σε αυτές τις περιπτώσεις η ακολουθία δεν είναι μια κλήση συνάρτησης, είναι μια συνομιλία: στέλνετε ένα digest, κάτι άλλο πιστοποιεί έναν άνθρωπο, και ένα CMS επιστρέφει αργότερα. Ένα σύγχρονο API δεν μπορεί να εκφράσει το "αργότερα" χωρίς να μπλοκάρει ένα thread σε μια λειτουργία που μπορεί να χρειάζεται δεύτερο παράγοντα
Το πρωτόκολλο δύο φάσεων
Η πρώτη φάση προετοιμάζει το έγγραφο. Το PDFiumPas προσαρτά το πεδίο και το dictionary τιμής υπογραφής, δεσμεύει ContentsSize bytes hex-encoded χώρου στο /Contents, υπολογίζει το ByteRange γύρω από αυτή τη δέσμευση, και παράγει ένα TPadesRemoteSigningRequest που περιέχει FormatVersion, PreparedFingerprint, DocumentDigest, το τετραμελές ByteRange, ContentsHexOffset και ContentsSize
Η μόνη τιμή που χρειάζεται η υπηρεσία υπογραφής σας είναι το DocumentDigest: το SHA-256 που το επιστρεφόμενο CAdES SignedData πρέπει να φέρει ως message digest του. Όλα τα υπόλοιπα στην εγγραφή υπάρχουν ώστε η δεύτερη φάση να μπορεί να αποδείξει ότι το αρχείο που ολοκληρώνει είναι το αρχείο από το οποίο υπολογίστηκε εκείνο το 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 δεσμευμένα για το 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;
// Αποθηκεύστε τη συνεδρία ώστε μια μεταγενέστερη εκτέλεση - ή άλλη μηχανή - να μπορεί να την ολοκληρώσει
Session := TFileStream.Create('contract.signreq', fmCreate);
try
SavePadesRemoteSigningRequest(Session, Request);
finally
Session.Free;
end;
SendDigestToSigningService(Request.DocumentDigest);
end;
Τι απορρίπτει το Complete, και γιατί υπάρχει κάθε έλεγχος;
Η ολοκλήρωση είναι εκεί όπου ένας σχεδιασμός απομακρυσμένης υπογραφής συνήθως πάει στραβά, οπότε η επικύρωση είναι σκόπιμα ανένδοτη. Η CompletePadesRemoteSignature απορρίπτει ένα προετοιμασμένο PDF του οποίου το αποτύπωμα δεν ταιριάζει πια με το αίτημα, ένα ByteRange που δεν ταιριάζει με τις καταγεγραμμένες συντεταγμένες placeholder, τροποποιημένους οριοθέτες /Contents, ένα placeholder που δεν είναι πια κενό, ένα CMS μεγαλύτερο από τη δέσμευση, ένα CMS που δεν είναι ακριβώς μία τιμή DER, ένα ανυποστήρικτο σχήμα SignedData, ένα attribute signing-certificate-v2 που λείπει, και ένα CMS του οποίου το message digest δεν ισούται με το προετοιμασμένο digest εγγράφου
Κάθε ένα από αυτά αντιστοιχεί σε πραγματική αποτυχία. Οι έλεγχοι αποτυπώματος και ByteRange πιάνουν την περίπτωση όπου κάποιος αναδημιούργησε το προετοιμασμένο αρχείο ανάμεσα στις φάσεις, κάτι που θα παρήγαγε μια υπογραφή που επικυρώνεται έναντι bytes που δεν υπάρχουν πουθενά. Ο έλεγχος κενού placeholder πιάνει τη διπλή ολοκλήρωση, όπου ένα δεύτερο CMS γράφεται πάνω σε μια υπογραφή που ήδη υπάρχει. Ο έλεγχος message digest πιάνει την πιο επικίνδυνη περίπτωση απ' όλες: ένα σωστά σχηματισμένο CMS υπογεγραμμένο πάνω σε διαφορετικό έγγραφο, κάτι που παίρνετε όταν μια ουρά μπερδεύει δύο ταυτόχρονες συνεδρίες υπογραφής. Χωρίς αυτό θα παράγατε ένα αρχείο που φαίνεται υπογεγραμμένο και αποτυγχάνει την επικύρωση παντού, ή χειρότερα, που φέρει την έγκριση κάποιου άλλου
Η απαίτηση signing-certificate-v2 είναι ζήτημα συμμόρφωσης PAdES και όχι ακεραιότητας. Το ETSI EN 319 142 απαιτεί το πιστοποιητικό υπογραφής να δεσμεύεται μέσα στα υπογεγραμμένα attributes, και ένα CMS που στερείται αυτό το attribute δεν είναι υπογραφή PAdES ακόμη κι αν επαληθεύεται κρυπτογραφικά. Η απόρριψή του κατά την ολοκλήρωση σημαίνει ότι το μαθαίνετε εδώ, όχι σε αναφορά validator από πελάτη, θέμα που εξετάζεται περαιτέρω στο γιατί οι validators απορρίπτουν υπογραφές 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; // επιστρέφεται από το HSM ή τον 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
// Κάθε απόρριψη φέρει έναν συγκεκριμένο λόγο· καταγράψτε τον αυτούσιο
FailSession(E.Message);
end;
finally
Dest.Free;
Prepared.Free;
end;
end;
Διάσχιση ορίων διεργασίας και μηχανής
Οι SavePadesRemoteSigningRequest και LoadPadesRemoteSigningRequest σειριοποιούν τη συνεδρία μέσω μιας σταθερής, εκδοσιοποιημένης δυαδικής μορφής, κάτι που κάνει τον σχεδιασμό πρακτικό και όχι απλώς σωστό. Μια εφαρμογή web μπορεί να προετοιμάσει ένα έγγραφο σε ένα request, να αποθηκεύσει το προετοιμασμένο PDF και το blob της συνεδρίας, να επιστρέψει ένα digest στον browser για υπογραφή με έξυπνη κάρτα, και να ολοκληρώσει το αρχείο σε έναν εντελώς διαφορετικό handler request
Το πεδίο FormatVersion είναι αυτό που το κρατά ασφαλές μέσα από αναβαθμίσεις. Μια συνεδρία γραμμένη από παλαιότερη έκδοση και φορτωμένη από νεότερη αναγνωρίζεται ή απορρίπτεται ρητά, αντί να παρερμηνεύεται ως εγγραφή διαφορετικού σχήματος. Αν η ουρά σας μπορεί να κρατά συνεδρίες για ημέρες, αντιμετωπίστε την έκδοση μορφής ως λειτουργικό γεγονός άξιο καταγραφής, όχι ως λεπτομέρεια υλοποίησης
Καθορισμός μεγέθους του placeholder
Το ContentsSize είναι η μία παράμετρος που πρέπει να σκεφτείτε, επειδή καθορίζεται πριν υπάρξει το CMS. Μετρά τη δέσμευση σε hex-encoded μορφή, οπότε ένα CMS DER 6 KB χρειάζεται τουλάχιστον 12 KB χώρο, και η υλοποίηση περιορίζει τη δέσμευση στα 64 MiB
Δεσμεύστε πολύ λίγο και η ολοκλήρωση αποτυγχάνει με σφάλμα υπερμεγέθους CMS αφού η υπηρεσία υπογραφής σας έχει ήδη κάνει τη δουλειά της, κάτι που σε μια χρεώσιμη υπηρεσία qualified signature σημαίνει χαμένη λειτουργία. Δεσμεύστε πολύ πολύ και κάθε υπογεγραμμένο έγγραφο κουβαλά το padding για πάντα. Η λογική προσέγγιση είναι να μετρήσετε: υπογράψτε ένα έγγραφο με την πραγματική σας αλυσίδα πιστοποιητικών, δείτε το μήκος DER, διπλασιάστε το για hex, και προσθέστε γενναιόδωρο περιθώριο για το token χρονοσφράγισης αν σκοπεύετε να αναβαθμιστείτε σε υπογραφή επιπέδου T. Αλυσίδες με αρκετά ενδιάμεσα και μια μεγάλη απόκριση OCSP μεγαλώνουν γρηγορότερα απ' όσο περιμένει κανείς
Τι έπεται μετά την υπογραφή
Μια ολοκληρωμένη απομακρυσμένη υπογραφή είναι PAdES B-B. Η μακροπρόθεσμη επικύρωση χρειάζεται μια χρονοσφραγίδα και το υλικό επικύρωσης, κάτι που είναι ξεχωριστή incremental update που προσθέτει ένα DSS και τα per-signature VRI dictionaries του, όπως περιγράφεται στο μακροπρόθεσμες υπογραφές με χρονοσφραγίδες RFC 3161 και DSS. Αυτό το βήμα είναι τοπικό: προσθέτει πιστοποιητικά, αποκρίσεις OCSP και CRL, τίποτα από τα οποία δεν χρειάζεται το ιδιωτικό κλειδί
Πριν την παράδοση, επαληθεύστε ό,τι παράξατε με τον ίδιο κώδικα που θα χρησιμοποιούσε ένας εξωτερικός επικυρωτής, κάτι που καλύπτεται στο επιθεώρηση ψηφιακών υπογραφών και επιπέδων PAdES. Η υπογραφή και η επικύρωση είναι διαφορετικός κώδικας, και ένα pipeline απομακρυσμένης υπογραφής είναι ακριβώς το σημείο όπου τα δύο μπορούν να αποκλίνουν χωρίς κανείς να το προσέξει μέχρι να το πει ένας εξωτερικός validator
Το PDFiumPas είναι ένα component Delphi και Lazarus γύρω από τη μηχανή PDFium με μια εγγενή στοίβα Pascal PAdES, οπότε η υπογραφή, η χρονοσφράγιση και η επικύρωση λειτουργούν χωρίς εξωτερικά εργαλεία γραμμής εντολών. Πλήρης τεκμηρίωση API και δοκιμαστική έκδοση βρίσκονται στη σελίδα του PDFium Delphi component