Τεχνικό Άρθρο

Μετα-κβαντική και EdDSA υπογραφή PDF με HotPDF σε Delphi

Το HotPDF επαληθεύει υπογραφές CMS ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 και Ed448 σε φορτωμένα PDF έγγραφα, και υπογράφει μέσω συνδέσιμων providers ώστε το ιδιωτικό κλειδί να μη χρειάζεται ποτέ να ζει μέσα στη διεργασία Delphi σας. Αυτό το δεύτερο μισό είναι το κομμάτι που οι περισσότερες ομάδες χρειάζονται πρώτα. Ένα hardware token, μια υπηρεσία απομακρυσμένης υπογραφής και μια εθνική κάρτα eID αρνούνται όλες να παραδώσουν ένα κλειδί, και μέχρι η γραμμή παραγωγής υπογραφής να διαχωριστεί από το κλειδοφυλάκιο, καμία από αυτές δεν μπορεί να χρησιμοποιηθεί

Ο διαχωρισμός είναι το νόημα του THPDFSignatureProvider. Το HotPDF κρατά τα κομμάτια που πρέπει να κατέχει — την ανάλυση CMS, την κατασκευή SignedData, τη διάταξη του /ByteRange — και αναθέτει τη μία λειτουργία που δεν μπορεί να κατέχει, που είναι η μετατροπή μιας σύνοψης σε υπογραφή με ένα κλειδί που δεν επιτρέπεται να δει. Όλα όσα ακολουθούν προκύπτουν από αυτή τη διαίρεση

Γιατί μια έγκυρη ML-DSA υπογραφή αποτυγχάνει σε επαλήθευση;

Επειδή το HotPDF αρνείται ML-DSA σε ένα φορτωμένο έγγραφο που δεν δηλώνει την επέκταση για αυτό. Το ML-DSA — το σχήμα πλεγματικής υπογραφής που τυποποιήθηκε ως FIPS 204, και ο λόγος που λέμε «μετα-κβαντικό PDF» — δεν έχει ακόμα εγγραφή στο ISO 32000-2. Ένα PDF που φέρει τέτοιο χρησιμοποιεί έναν αλγόριθμο που το βασικό πρότυπο δεν ονομάζει, και ένα αρχείο που χρησιμοποιεί σιωπηλά έναν ακατονόμαστο αλγόριθμο είναι ένα αρχείο του οποίου η ετυμηγορία δεν μπορεί να αναπαραχθεί από κανέναν άλλον

Οπότε το HotPDF κάνει τη διεκδίκηση ρητή. Η EnsureMLDSAExtensions αναβαθμίζει το έγγραφο σε PDF 2.0 όπου επιτρέπεται και γράφει /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> στον Κατάλογο. Στη μεριά ανάγνωσης, η LoadedDocumentDeclaresMLDSAExtension αναφέρει αν η δήλωση αυτή επέζησε, και η VerifyLoadedSignatureWithOptions εφαρμόζει τον ίδιο έλεγχο πριν τιμήσει το Options.AllowMLDSA. Ορίστε τη σημαία σε ένα μή δηλωμένο έγγραφο και παραμένει κλειστή — η επιλογή μπορεί να χαλαρώσει την πολιτική, ποτέ όμως τη δομική απαίτηση

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;

Καλέστε την πριν την αποθήκευση, όχι μετά. Η δήλωση είναι μέρος του υπογεγραμμένου εύρους byte, και ένας Κατάλογος που διορθώνεται μετά είναι είτε μια μή υπογεγραμμένη αλλαγή σε ένα υπογεγραμμένο αρχείο είτε μια δεύτερη αναθεώρηση που ένας validator θα αναφέρει ως τροποποίηση

Τρεις οικογένειες αλγορίθμων, ένα σημείο εισόδου επαλήθευσης

Και οι τρεις οικογένειες καταλήγουν μέσω της VerifyLoadedSignatureWithOptions, που δέχεται έναν δείκτη υπογραφής, το stream πηγής, μια εγγραφή THPDFCMSVerifyOptions και μια παράμετρο out για τις λεπτομέρειες υπογραφής. Η εγγραφή έχει ακριβώς τρία πεδία, και το καθένα απαντά μια ερώτηση που παλιά απαιτούσε rebuild

Η SignatureProvider αντικαθιστά τον δικό σας provider για τον ενσωματωμένο της πλατφόρμας. Το OpenSSLLibraryPath επιλέγει μια βιβλιοθήκη OpenSSL 3, που είναι αυτή που παρέχει την επαλήθευση Ed25519 και Ed448 σε pure-mode που το Windows CNG δεν προσφέρει παντού. Το AllowMLDSA εντάσσει τους πλεγματικούς αλγορίθμους, υπό τον έλεγχο επέκτασης παραπάνω. Το ακριβές OID του αλγορίθμου που αναγνωρίστηκε επιστρέφει στο THPDFSignatureInfo.SignatureAlgorithmOID, ώστε ένα αρχείο καταγραφής ελέγχου να μπορεί να καταγράψει τι επαληθεύτηκε αντί για τι ζητήθηκε

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 και Ed448 δεν χρειάζονται δήλωση επέκτασης, επειδή το ISO 32000-2 ήδη τις παραδέχεται. Χρειάζονται όμως έναν provider που τις υλοποιεί, που στις περισσότερες αναπτύξεις Windows σημαίνει να κατευθύνετε το OpenSSLLibraryPath σε μια βιβλιοθήκη που διανέμετε και ελέγχετε εσείς παρά σε ό,τι τυχαίνει να υπάρχει στο μηχάνημα

Τι υπόσχεται πραγματικά ένας signing provider;

Ένας provider υπόσχεται ένα πράγμα: δεδομένου ενός αιτήματος, να επιστρέψει μια κατάσταση και, κατά την υπογραφή, bytes. Η THPDFSignatureProviderRequest μεταφέρει τον αλγόριθμο και το OID του, το OID της σύνοψης, το μήκος άλατος PSS, αν η είσοδος είναι μήνυμα ή ήδη υπολογισμένη σύνοψη, την ίδια την είσοδο, το δημόσιο κλειδί ή πιστοποιητικό, ένα αναγνωριστικό κλειδιού και ένα αναγνωριστικό λειτουργίας. Τίποτα σε αυτή την εγγραφή δεν είναι HotPDF-συγκεκριμένο — είναι το λεξιλόγιο που ήδη μιλά ένας οδηγός token ή μια υπηρεσία υπογραφής

Τρεις υλοποιήσεις έρχονται με τη βιβλιοθήκη. Ο THPDFCallbackSignatureProvider τυλίγει ανώνυμες μεθόδους, που είναι η πιο σύντομη διαδρομή από μια υπάρχουσα εσωτερική ρουτίνα υπογραφής σε μια λειτουργική PDF υπογραφή. Ο THPDFRemoteSignatureProvider τυλίγει ένα callback μεταφοράς με όριο επανάληψης, μητρώο ακύρωσης και όρια στο μέγεθος εισόδου και υπογραφής, ώστε ένα κολλημένο HSM να μην μπορεί να γίνει μια κολλημένη εφαρμογή. Ο THPDFPKCS11SignatureProvider σειριοποιεί λειτουργίες RSA έναντι μιας περιόδου σύνδεσης PKCS#11 σε ιδιοκτησία του καλούντος, ήδη πιστοποιημένης, και ενός χειριστή ιδιωτικού κλειδιού — το HotPDF ποτέ δεν συνδέεται, ποτέ δεν βλέπει PIN, και ποτέ δεν κλείνει περίοδο σύνδεσης που δεν άνοιξε

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;

Γιατί η απαρίθμηση κατάστασης έχει έξι τιμές αντί για ένα boolean

Η THPDFSignatureProviderStatus διακρίνει spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError και spsCancelled, και η κατάρρευση τους στο ίδιο σας στερεί την ικανότητα να δράσετε σωστά. Μια υπογραφή που είναι κρυπτογραφικά λάθος (spsInvalid) είναι ένα περιστατικό ασφαλείας. Ένας αλγόριθμος που δεν υλοποιεί ο provider (spsUnsupported) είναι ένα κενό ανάπτυξης. Μια αποτυχία μεταφοράς (spsProviderError) αξίζει επανάληψης, και ένα prompt token που ακυρώθηκε από τον χρήστη (spsCancelled) δεν αξίζει επανάληψης καθόλου

Ο κανόνας για την υπογραφή είναι στενός: ένας signing provider επιστρέφει spsValid μόνο με μια μη κενή υπογραφή. Οι providers επαλήθευσης επιστρέφουν spsValid ή spsInvalid, και οι άλλες τέσσερις παραμένουν διακριτές και στις δύο διαδρομές. Αν γράφετε έναν provider, αντισταθείτε στον πειρασμό να χαρτογραφήσετε ό,τι δεν αναγνωρίζετε στο spsInvalid — αυτό μετατρέπει ένα λείπον DLL σε αναφορά ότι η υπογραφή του πελάτη είναι πλαστή

Πού προσγειώνεται η υπογραφή στο αρχείο

Δύο συναρτήσεις συνδέουν providers με πραγματικά PDF bytes. Η HPDFCMSBuildSignedDataWithProvider κατασκευάζει detached CMS από μια σύνοψη SHA-256 του εγγράφου, που είναι το σωστό σημείο εισόδου όταν η ροή εργασίας σας υπολογίζει τη σύνοψη αλλού. Η HPDFCMSSignPDFStreamWithProvider υπογράφει ένα υπάρχον placeholder υπογραφής σε ένα PDF stream και διατηρεί την τυπική διαδικασία /ByteRange, που είναι το σωστό σημείο εισόδου όταν το HotPDF διέθεσε μόνο του το placeholder

Η διατήρηση αυτής της διαδικασίας έχει μεγαλύτερη σημασία από όσο ακούγεται. Η σύμβαση /ByteRange — δύο εύρη που παραλείπουν το δεκαεξαδικό παράθυρο υπογραφής — είναι αυτό που κάθε validator ελέγχει πρώτο, και μια διαδρομή βάσει provider που θα την ξαναέγραφε θα έσπαγε τη συμμόρφωση PAdES όσο υγιής κι αν ήταν η κρυπτογραφία. Το HotPDF κρατά τη διάταξη πανομοιότυπη με την ενσωματωμένη διαδρομή υπογραφής, οπότε ένα έγγραφο υπογεγραμμένο μέσω token PKCS#11 επαληθεύεται με τον ίδιο κώδικα επαλήθευσης υπογραφών με ένα που υπογράφηκε από αρχείο PFX. Για τους κανόνες προφίλ που κάθονται πάνω από την επιλογή αλγορίθμου, δείτε την ανάλυση των υπογραφών PAdES baseline σε Delphi, και για τις παγίδες κωδικοποίησης ECDSA-συγκεκριμένες που προηγούνται αυτού του μοντέλου provider, τις σημειώσεις για την επαλήθευση ECDSA CMS και τις μορφές υπογραφής P1363

Μια σειρά μετάβασης που δεν αφήνει στάσιμα τα έγγραφά σας

Η μετα-κβαντική ετοιμότητα είναι ένα πρόβλημα προγραμματισμού, όχι ένας διακόπτης. Σχεδόν κανένας αναπτυγμένος PDF αναγνώστης δεν επικυρώνει ML-DSA σήμερα, οπότε ένα έγγραφο υπογεγραμμένο μόνο με αυτό είναι, από την οπτική γωνία του αναγνώστη, ένα έγγραφο με μια μη επαληθεύσιμη υπογραφή. Η σειρά που επιζεί σε επαφή με πραγματικά αρχεία είναι: κρατήστε RSA ή ECDSA ως την υπογραφή που θα κρίνει ένας validator, προσθέστε τη δήλωση επέκτασης και μια δεύτερη ML-DSA υπογραφή όπου μια πολιτική απαιτεί κβαντο-ανθεκτικά αποδεικτικά στοιχεία, και μετακινήστε την κύρια υπογραφή μόνο όταν τα συστήματα κατανάλωσης έχουν προφτάσει

Αυτό που σας δίνει σήμερα το HotPDF είναι η ικανότητα να γράφετε και να επαληθεύετε και τα δύο, από τον ίδιο κώδικα, με τον αλγόριθμο καταγεγραμμένο ειλικρινά στο αρχείο και στο αποτέλεσμα επαλήθευσης. Το HotPDF είναι ένα native VCL PDF συστατικό για Delphi και C++Builder χωρίς εξωτερικό PDF runtime, οπότε οι διαδρομές υπογραφής και επαλήθευσης έρχονται μέσα στο εκτελέσιμό σας παρά δίπλα του — δείτε τη σελίδα του HotPDF Delphi PDF συστατικού για την πλήρη λίστα χαρακτηριστικών και τη δοκιμαστική λήψη