مقال تقني

أدلة LTV في PAdES وقيم البذرة في HotPDF

مستند PDF وقّعته للتو هو توقيع B-B ولا شيء غير ذلك. فهو يثبت من وقّع وأن البايتات لم تتحرك، لكنه لا يحمل إثباتا بأن شهادة الموقّع كانت صالحة وقت التوقيع، فيضطر أداة تحقق بعد سنوات إلى البحث عن بيانات إبطال قد لا توجد بعد الآن. وسد تلك الفجوة يعني كتابة استجابات OCSP وقوائم CRL في مخزن أمان المستند على مستوى الوثيقة، وفي HotPDF ذلك نداء واحد: يجوب PopulatePAdESLTVEvidence كل توقيع محمل، ويستنتج طلبات الإبطال من مجموعة الشهادات، وينفذها عبر ناقل تزوده، ويكتب المواد المجلوبة مع سلسلة CMS في DSS. ويعيد عدد التوقيعات التي استقرت أدلتها، أو سالب واحدا عندما لا يملك المستند حقل توقيع أصلا

قرار التصميم الجدير بالفهم قبل استخدامك له أن المكتبة لا تفتح مقبسا شبكيا أبدا. كل بايت يصل من الشبكة يصل عبر دالة استدعاء عكسي كتبتها أنت. ذلك ليس حذرا لمجرد الحذر؛ إنه الطريق الوحيد لكي تعمل هذه الميزة داخل البيئات التي تشترط التحقق طويل الأمد فعلا

لماذا ترفض المكتبة القيام بـ HTTP بنفسها؟

لأن المواضع التي تشترط تواقيع B-LT هي المواضع التي لا يمكن الوثوق فيها بمكتبة تتصل بالشبكة. خدمات التوقيع تعمل خلف وكلاء مصادَق عليهم بجذور مؤسسية. وطبقات التوقيع المعزولة هوائيا لا طريق لها إلى مستجيب ويجب تغذيتها بأدلة مخزنة. وأنظمة التدقيق تشترط أن يسجل التطبيق كل طلب صادر، لا أن يدفن في اعتمادية. وأجنحة الاختبار تحتاج استجابات حتمية، وهو مستحيل إذا اتصلت المكتبة بنفسها

الناقل مرجع دالة عادي ذو شكل ثابت، فتبقى السياسة ملكك. يسلّمك HotPDF سجل طلب يصف بالضبط ما يجب جلبه، بما في ذلك نوع المحتوى وسقف حجم الاستجابة، وتعيد أنت البايتات مع حالة

مسار PopulatePAdESLTVEvidence في HotPDF: ناقل FetchEvidence المزود من المستدع، وحقول سجل الطلب، وحالات النتيجة لكل توقيع
كل بايت شبكي يمر عبر دالة FetchEvidence العكسية لديك، وكل توقيع يأخذ حالته الخاصة فلا يجهض انتهاء مهلة واحد المرور كله
function FetchEvidence(const Request: THPDFSignatureEvidenceRequest;
  Attempt: Integer; CancellationToken: THPDFCancellationToken;
  out Response: TBytes; out RetryAfterMS: Cardinal;
  out ErrorMessage: UnicodeString): THPDFSignatureEvidenceTransportStatus;
begin
  RetryAfterMS := 0;
  try
    // يبيّن Request.Kind ما إذا كان هذا OCSP POST أو CRL GET؛
    // وكلا Request.ContentType وRequest.Body محضّران سلفا،
    // وRequest.MaxResponseBytes هو السقف الذي يجب احترامه
    Response := HttpExchange(Request.URI, Request.ContentType,
      Request.Body, Request.MaxResponseBytes);
    Result := setsSucceeded;
  except
    on E: Exception do
    begin
      ErrorMessage := E.Message;
      // يتيح setsRetry لسياسة إعادة المحاولة التراجع؛ استخدم
      // setsPermanentFailure لخطأ 404 أو عنوان سيئ
      Result := setsRetry;
    end;
  end;
end;

// ترقية B-B إلى B-LT بنداء واحد لكل توقيع في الملف المحمل
var
  Pdf: THotPDF;
  Upgraded: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginIncrementalUpdate('signed.pdf');
    Upgraded := Pdf.PopulatePAdESLTVEvidence(FetchEvidence,
      THPDFSignatureEvidenceRetryPolicy.Default);
    if Upgraded > 0 then
      // حفظ بالإلحاق فقط: البايتات التي تغطيها التوقيعات القائمة
      // محفوظة حرفيا
      Pdf.SaveIncrementalUpdate('signed-lt.pdf');
  finally
    Pdf.Free;
  end;
end;

الإخفاقات لكل توقيع، لا لكل مستند. مستجيب تنتهي مهلته لموقّع واحد يتخطى مواد ذاك الموقّع ويترك بقية المرور سليما، وهو السلوك الذي تريده في دفعة: الأدلة الجزئية أفضل من تشغيلة مجهضة، والقيمة المعادة تخبرك كم توقيعا تحسن فعلا

السلسلة التي نسي CMS تضمينها

فحص الإبطال يحتاج شهادة المُصدِر، وعدد مذهل من حزم التوقيع يحذف الشهادات الوسيطة من حاوية CMS. طريق الاستعادة امتداد Authority Information Access، بطريقة وصول 1.3.6.1.5.5.7.48.2، الذي يعلن عنوان URL يمكن تنزيل شهادة المُصدِر منه. يجوب HPDFFetchAIAIntermediates تلك العناوين عبر الناقل نفسه، ويحلل DER من كل استجابة، ويعيد فقط الشهادات التي لم يكن CMS يحملها أصلا، مفهرسة بهاش DER كي لا تدور المكررات والحلقات

تفصيلان يقرران نجاح هذا مقابل جهات إصدار شهادات حقيقية. الأول الترميز: نهايات CA تقدم الشهادة كـ DER عارٍ بنفس تكرار تقديمها مدرعة PEM، ولا يوجد نوع محتوى موثوق للتفريق بينهما. المجس المتين نصي ثم بنيوي. ابحث عن العلامة -----BEGIN CERTIFICATE-----، وأزل الدرع وفك ترميز base64 إن وُجد، وفي كلا المسارين أكد أن أول بايت من النتيجة هو $30، وسم DER لـ SEQUENCE. والثاني العمق: يمكن لشهادة وسيطة مجلوبة أن تعلن بنفسها عنوان AIA لمُصدِرها، فيلحق الجوب المرشحين الجدد إلى الطابور ويكمل سلاسل تنقصها قفزتان أو ثلاث. ويجب وضع سقف لذلك، وهو ما وُضع المعامل MaxFetch من أجله

مخطط إكمال سلسلة AIA في HotPDF: جلب عنوان caIssuers، ومجس PEM مقابل DER، وإزالة تكرار بهاش DER، وسقف العمق MaxFetch
يجوب HPDFFetchAIAIntermediates عناوين caIssuers عبر الناقل نفسه، مجسّا درع PEM ويضع سقفا للطابور عبر MaxFetch

ما قيمة البذرة للتوقيع، ولماذا يفشل فحصها بصمت؟

قيمة البذرة قيد يلحقه مؤلف المستند بحقل توقيع ليخبر الموقّع أي نوع من التواقيع مقبول: أي SubFilter، وأي خوارزمية ملخص، وأي أسباب، وأي حد أدنى لإصدار PDF، وهل يجب تضمين معلومات الإبطال. وهي تعيش في قاموس /SV على الحقل ومعرّفة في ISO 32000-1 §12.7.5.5. يكتبها HotPDF بـ AttachPAdESSeedValue ويفحصها بـ CheckLoadedSignatureSeedValue، التي تعيد True عندما يكون الحقل غير مقيد أو اجتاز كل قيد حاضر، وعند False تسمي أول قيد فاشل عبر معامل خرج يمكنك وضعه مباشرة في رسالة خطأ

الآلية التي تجعل قيم البذرة سهلة الخطأ هي مدخلة الأعلام /Ff الموصوفة في §12.7.5.5.3. فالبت المضبوط يعلم قيده كإلزامي: التطابق الخاطئ خطأ ويجب على الموقّع الرفض. والبت المصدَّر يعلم القيد نفسه كتفضيل: القيمة ترشح ما يجب أن تعرضه الواجهة ولا شيء أكثر. يترتب على ذلك فخان. أولا أن /Ff تعيش داخل قاموس /SV، لا على التعليقة التوضيحية للودجت، فيحصل الكود الذي يقرأ /Ff على مستوى الحقل على جواب فارغ إلى الأبد ويستنتج أنه لا شيء مفروض. ثانيا أن إسنادات البتات ليست تتابعا بسيطا من واحد واثنين وأربعة وثمانية؛ ففي HotPDF يصدر الكاتب 2 لـ SubFilter، و4 لـ MinVersion، و32 لـ AddRevInfo، و64 لـ DigestMethod. القارئ الذي يفترض بتاتا متتالية يفك كل قيد كاختياري ويجتاز كل اختبار عدا الاختبار المهم

جدول بتات علم قيمة البذرة لتوقيع PAdES في HotPDF يظهر بتات Ff بقيم 2 و4 و32 و64 ومعالجة القيد الإلزامي مقابل المفضل
مدخلة /Ff تعيش داخل /SV، وكل موضع بت يقرر هل عدم التطابق رفض قاطع أم تفضيل لواجهة
var
  Violation: AnsiString;
begin
  // اسأل الحقل هل الملف الشخصي الذي سوف نوقّع به مسموح
  if not Pdf.CheckLoadedSignatureSeedValue(0, 'ETSI.CAdES.detached',
       'SHA256', 'Approved for payment', 1, Violation) then
    raise Exception.Create('Signature field rejects this profile: ' +
      String(Violation));
  // اجتيز القيد: تابع مرور التوقيع
end;

الاختبار الذي كشف عيب الفك الأصلي لم يكن اختبارا إيجابيا. كان التوكيد بأن عدم تطابق مفروض يجب رفضه، وهو النوع الوحيد من الاختبارات القادر على التقاط هذا الصنف من العيوب: فالمفكك الذي يقرأ القاموس الخاطئ أو مواضع البت الخاطئة ينتج «لا قيود مخالفة» لكل مدخل، وهو ما يبدو تماما كسلوك صحيح حتى تخالف قيدا عمدا

موضع ذلك على سلم LTV

أربع درجات، وكل واحدة تحتاج التي تحتها. B-B هو التوقيع المجرد. وB-T يضيف طابعا زمنيا موثوقا يثبت وقت التوقيع فيعرف أداة التحقق أي لحظة تقيس الإبطال ضدها. وB-LT يضيف أدلة الإبطال إلى DSS، وهو ما يؤتمته PopulatePAdESLTVEvidence. وB-LTA يضيف أختاما زمنية للمستند تجدد قبل أن تضعف السابقة، ممتدا الصلاحية إلى ما لا نهاية؛ ويكشفه HotPDF كـ RenewPAdESLTATimestamp، الذي يلحق ختاما زمنيا جديدا كمراجعة تزايدية ويحفظ كل توقيع سابق وختام ومدخلة DSS دون مساس

نموذج التحديث التزايدي هو الطريقة الصحيحة الوحيدة لإضافة أدلة إلى مستند موقع، لأن إعادة كتابة الملف ستكسر مدايات البايتات التي تغطيها التوقيعات القائمة. وإن كنت تحتاج إلى التفكير فيما تغير بين المراجعات، وهل تلك التغييرات من الصنف الذي يسمح به توقيع، فذلك التحليل مشمول على حدة في تحليل المراجعات DocMDP وFieldMDP. ومسار التوقيع ذاته، بما فيه مصادر الشهادات ومزالق ترتيب البايتات، في جولة توقيع PAdES، ووجهة التحقق في التحقق من التواقيع على المستندات المحملة

تحذير عملي واحد عن الترتيب. اجمع الأدلة في أقرب وقت بعد التوقيع، ويستحب في المهمة نفسها. المستجيبون القادرون على الإجابة عن شهادة متصلون بينما الشهادة سارية ويختفون بعد سنوات، فالمستند الذي يغادر مسارك بصيغة B-B قد لا يُرقّى مرة أخرى أبدا. يعمل HotPDF كمكون VCL أصلي لـ Delphi وC++Builder، ومرور الأدلة كله داخل العملية عدا ناقلك الخاص؛ والملفات الشخصية المدعومة مدرجة في صفحة منتج HotPDF Delphi PDF component