يتحقق HotPDF من توقيعات ML-DSA-44 وML-DSA-65 وML-DSA-87 وEd25519 وEd448 بنية CMS في مستندات PDF المحمَّلة، ويوقّع عبر مزوّدين قابلين للإدراج بحيث لا يضطر المفتاح الخاص للعيش داخل عملية Delphi. النصف الثاني هو الجزء الذي تحتاجه أغلب الفرق أولًا. رمز عتادي، وخدمة توقيع عن بعد، وبطاقة هوية وطنية إلكترونية ترفض جميعها تسليم مفتاح، وحتى يُفصَل خط أنابيب التوقيع عن مخزن المفاتيح، لا يمكن استخدام أيٍّ منها إطلاقًا
هذا الفصل هو غاية 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;
ناده قبل الحفظ، لا بعده. التصريح جزء من نطاق البايتات الموقَّع، والفهرس الذي يُرقَّع بعدها إما تغيير غير موقَّع على ملف موقَّع أو مراجعة ثانية سيُبلِّغ عنها المُحقِّق كتعديل
ثلاث عائلات خوارزمية، نقطة دخول تحقق واحدة
تصل العائلات الثلاث جميعها عبر VerifyLoadedSignatureWithOptions، التي تأخذ فهرس التوقيع ودفق المصدر وسجل THPDFCMSVerifyOptions ومعامل خرج لتفاصيل التوقيع. للسجل ثلاثة حقول بالضبط، وكلٌّ يجيب عن سؤال كان يتطلب إعادة بناء
يستبدل SignatureProvider مزوّدك الخاص بمزوّد المنصّة المدمج. يختار OpenSSLLibraryPath مكتبة OpenSSL 3، وهي ما يزوّد التحقق من Ed25519 وEd448 بالنمط النقي الذي لا تقدّمه 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 يعترف بهما أصلًا. تحتاجان مزوّدًا يطبّقهما، وهذا في أغلب عمليات نشر Windows يعني توجيه OpenSSLLibraryPath إلى مكتبة تُسلِّمها أنت وتتحكم بها بدلًا من أي مكتبة تصادف وجودها على الآلة
ماذا يَعِد مزوّد التوقيع فعليًا؟
يَعِد شيئًا واحدًا: باعتبار الطلب، أعد الحالة، وعند التوقيع، البايتات. يحمل THPDFSignatureProviderRequest الخوارزمية وOID الخاص بها، وOID للملخّص، وطول ملح PSS، إن كان المدخل رسالة أو ملخّصًا محسوبًا فعلًا، والمدخل نفسه، والمفتاح العام أو الشهادة، ومعرّف مفتاح، ومعرّف عملية. لا شيء في ذلك السجل خاص بـ HotPDF — هو المفردات التي يتحدّث بها محرّك رمز أو خدمة توقيع أصلًا
تُشحَن ثلاثة تنفيذات مع المكتبة. يغلّف THPDFCallbackSignatureProvider الإجراءات المجهولة، وهو أقصر طريق من روتين توقيع داخلي قائم إلى توقيع PDF عامل. يغلّف THPDFRemoteSignatureProvider استدعاء نقل بحد إعادة محاولة، وسجل إلغاء، وحدود لحجم المدخل والتوقيع، فلا يصبح 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;
لماذا لـ enum الحالة ست قيم بدلًا من قيمة منطقية
يميّز THPDFSignatureProviderStatus بين spsValid وspsInvalid وspsUnsupported وspsMalformed وspsProviderError وspsCancelled، وطيّها يكلّفك القدرة على التصرف صحيحًا. توقيع خاطئ تشفيريًا (spsInvalid) حدث أمني. خوارزمية لا يطبّقها المزوّد (spsUnsupported) فجوة نشر. فشل نقل (spsProviderError) يستحق إعادة المحاولة، ومطالبة رمز ألغاها المستخدم (spsCancelled) لا تستحق إعادة المحاولة إطلاقًا
قاعدة التوقيع ضيقة: يُعيد مزوّد التوقيع spsValid فقط مع توقيع غير فارغ. تُعيد مزوّدات التحقق spsValid أو spsInvalid، وتبقى الأربع الأخرى متميزة على كلا المسارين. إن كتبت مزوّدًا، قاوم إغواء تعيين كل ما لا تعترف عليه إلى spsInvalid — ذلك يحوّل DLL مفقودًا إلى بلاغ بأن توقيع العميل مزيّف
أين يهبط التوقيع فعلًا في الملف
تربط دالتان المزوّدين ببايتات PDF حقيقية. يبني HPDFCMSBuildSignedDataWithProvider CMS منفصل من ملخّص SHA-256 للمستند، وهي نقطة الدخول الصحيحة حين يحسب سير عملك الملخّص في مكان آخر. يوقّع HPDFCMSSignPDFStreamWithProvider على عنصر نائب للتوقيع موجود مسبقًا في دفق PDF ويحافظ على خط أنابيب /ByteRange القياسي، وهي نقطة الدخول الصحيحة حين يرتّب HotPDF العنصر النائب نفسه
الحفاظ على ذلك الخط أنابيب أهم مما يبدو. اصطلاح /ByteRange — نطاقان يتخطيان نافذة التوقيع الست عشرية — هو ما يفحصه كل مُحقِّق أولًا، ومسار مبني على المزوّد يعيد كتابته سيكسر مطابقة PAdES مهما كانت التشفيريات سليمة. يحافظ HotPDF على التخطيط مطابقًا لمسار التوقيع المدمج، فيُتحقَّق من مستند موقَّع عبر رمز PKCS#11 بنفس كود التحقق من التوقيع كمستند موقَّع من ملف PFX. لقواعد الملف التي تجلس فوق اختيار الخوارزمية، انظر شرح توقيعات PAdES الأساسية في Delphi، ولأفخاخ ترميز ECDSA الخاصة التي تسبق نموذج المزوّد هذا، ملاحظات التحقق من ECDSA في CMS وصيغ توقيع P1363
ترتيب نقل لا يترك مستنداتك معلَّقة
الجاهزية بعد الكم مشكلة جدول لا مفتاح. لا يتحقق تقريبًا أي عارض PDF منشور من ML-DSA اليوم، فمستند موقَّع بها وحدها من وجهة نظر القارئ مستند بتوقيع غير قابل للتحقق. الترتيب الذي يصمد أمام الأرشيفات الحقيقية هو: أبقِ RSA أو ECDSA كالتوقيع الذي سيحكم به المُحقِّق، وأضِف تصريح الامتداد وتوقيع ML-DSA ثانيًا حين تطلب سياسة دليلًا مقاومًا للكم، وانقل التوقيع الأساسي فقط حين تلحق الأنظمة المستهلكة
ما يمنحك إياه HotPDF اليوم هو القدرة على كتابة الاثنين والتحقق منهما، من الكود نفسه، مع الخوارزمية مسجَّلة بأمانة في الملف وفي نتيجة التحقق. HotPDF مكوّن VCL أصلي لـ PDF من أجل Delphi وC++Builder بلا مشغّل PDF خارجي، فيُشحَن مسارا التوقيع والتحقق داخل ملفك التنفيذي بدلًا من بجانبه — انظر صفحة مكوّن HotPDF لـ Delphi PDF لقائمة الميزات والنسخة التجريبية