مقال تقني

التحقق من التوقيعات الرقمية لملفات PDF في دلفي باستخدام HotPDF

يتحقق HotPDF من التوقيعات الرقمية في مستندات PDF المحملة من خلال ثلاث طرق في THotPDF: هي GetLoadedSignatureInfo، و VerifyLoadedSignature، و VerifyLoadedSignatureEx، والتي تم تقديمها في الإصدار v2.259.0. ويقوم المكون بإعادة تجزئة قطاعات /ByteRange للملف الأصلي، والتحقق من سمة messageDigest لـ CMS، وتشغيل التحقق من RSA PKCS#1 v1.5 مقابل شهادة الموقع المضمنة، ويرجع svValid عندما تكون بايتات المستند سليمة

السيناريو عادي والرهانات ليست كذلك. يعيد الطرف الآخر عقدًا موقعًا، ويحتاج سير عملك إلى أرشفته، ويسأل شخص ما السؤال الوحيد المهم: هل هذا هو المستند الذي أرسلناه، بايت ببايت، والموقع بالشهادة التي يدعيها؟ والإجابة على ذلك برمجياً هي جانب التحقق من قصة التوقيع؛ أما جانب التوقيع، وبناء وتضمين توقيعات PAdES في المقام الأول، فيتم تغطيته في المقال المصاحا حول إنشاء توقيعات PAdES الرقمية باستخدام HotPDF. وهذا المقال يدور حول الاتجاه الآخر: يظهر ملف PDF موقعًا بالفعل، وتريد حكمًا برمجياً بدلاً من لقطة شاشة لعلامة الصح الخضراء لبرنامج Acrobat

كيف يثبت ملف PDF الموقع أنه لم يتم العبث به؟

يحمي توقيع PDF نطاقات بايت معينة من الملف، وليس مفهومًا مجردًا عن "المستند". ويحدد معيار ISO 32000-1 §12.8 الآلية: يحمل حقل نموذج التوقيع قاموسًا يحتوي إدخاله /Contents على حاوية CMS SignedData (RFC 5652)، وتحدد مصفوفة /ByteRange الخاصة به مناطق الملف الدقيقة التي يغطيها التوقيع، وفقًا للفقرة §12.8.1. والمصفوفة هي قائمة من أزواج الإزاحة والطول، وهي في الممارسة العملية قطاعان: كل شيء قبل سلسلة الست عشرية لـ /Contents، وكل شيء بعدها. ولا يمكن لقيمة التوقيع أن تغطي نفسها، لذلك يتم تجزئة الملف حول تلك الفجوة

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

قراءة البيانات الوصفية للتوقيع قبل التحقق من أي شيء

تقوم GetLoadedSignatureInfo بتحليل قاموس التوقيع وحاوية CMS الخاصة به دون لمس بايت مستند واحد، مما يجعله الاستدعاء الأول المناسب عندما تحتاج فقط إلى عرض من وقع ومتى. ويتم فهرسة حقول التوقيع من 0 بترتيب حقول النموذج، وتخبرك GetLoadedSignatureFieldCount بعدد الحقول الموجودة. ويحمل سجل THPDFSignatureInfo المرجع اسم الحقل، و /SubFilter، والاسم الشائع لشهادة الموقع، والأسماء المميزة للموضوع والمصدر، والرقم التسلسلي، وتواريخ الصلاحية، ووقت التوقيع (من السمة الموقعة عند وجودها، وإلا إدخال /M للقاموس)، واسم خوارزمية التلخيص (digest)، وسلاسل /Reason و /Location و /ContactInfo. ويظل عضو الحالة (Status) الخاص به svNotVerified، وهو تصنيف صادق لـ "تم تحليله، ولم يتم التحقق منه"

var
  Pdf: THotPDF;
  Info: THPDFSignatureInfo;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('signed-contract.pdf');
    for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
    begin
      Info := Pdf.GetLoadedSignatureInfo(I);
      Writeln('Field:     ', Info.FieldName);
      Writeln('Signer:    ', Info.SignerName);
      Writeln('Issuer:    ', Info.IssuerDN);
      Writeln('Algorithm: ', Info.HashAlgorithm);
      Writeln('SubFilter: ', Info.SubFilter);
    end;
  finally
    Pdf.Free;
  end;
end;

تشغيل الفحص التشفيري

تقوم VerifyLoadedSignatureEx بإجراء التحقق الكامل لمستند تم تحميله من ملف وتسليم سجل المعلومات المعبأ في استدعاء واحد: حيث تعيد فتح الملف المصدر، وتجزيء قطاعات /ByteRange بخوارزمية تلخيص SignerInfo، وتقارن النتيجة بصفة messageDigest الموقعة (RFC 5652 §5.4)، ثم تتحقق بطريقة RSA من التوقيع عبر إعادة ترميز DER SET للسمات الموقعة. وعندما لا يحمل التوقيع أي سمات موقعة، يتم تشغيل فحص RSA مباشرة على تلخيص المستند بدلاً من ذلك. والتوقيعات المدعومة هي RSA PKCS#1 v1.5 مع تلخيصات SHA-1 أو SHA-256 أو SHA-384 أو SHA-512، مما يغطي المرشحات الفرعية adbe.pkcs7.detached و ETSI.CAdES.detached التي تنتجها أدوات التوقيع الرئيسية

var
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  Status := Pdf.VerifyLoadedSignatureEx(0, Info);
  case Status of
    svValid:
      if Info.CoversWholeDocument then
        Writeln('Valid; signature covers the whole file')
      else
        Writeln('Valid; file was extended after signing');
    svDigestMismatch:
      Writeln('Document bytes changed after signing');
    svSignatureInvalid:
      Writeln('RSA check failed over signed attributes');
    svUnsupportedAlgorithm:
      Writeln('Non-RSA key or unknown digest algorithm');
    svMalformed:
      Writeln('CMS container could not be parsed');
    svSourceUnavailable:
      Writeln('No source bytes; use the TStream overload');
  end;
end;

هناك تفصيلان في التنفيذ جديران بالمعرفة لأنهما يفسران الإخفاقات التي تبدو غامضة من الخارج. أولاً، فحص السمات الموقعة دقيق للغاية بشأن الترميز: فداخل الملف يتم وسم السمات بـ [0] IMPLICIT، ولكن التوقيع تم حسابه على نموذج DER SET OF الخاص بها، وبالتالي يعيد المدقق الوسم قبل التجزئة، تمامًا كما يتطلب معيار RFC 5652 §5.4. والمدقق المصنوع يدويًا والذي يجزيء البايتات كما تظهر في الملف سيرفض كل مستند موقع بشكل صحيح. ثانياً، يتم حشو /Contents تقليديًا بالأصفار لميزانية بايت محجوزة، لذلك يقطع المدقق كتلة (blob) الـ DER إلى الطول الفعلي لـ SEQUENCE الخارجية قبل التحليل؛ والأصفار اللاحقة التي تبدو كبيانات تالفة هي أمر طبيعي وليست فسادًا. ونفس هذه عائلة من مخاطر تحليل ASN.1، من جانب استيراد الشهادات، هي موضوع المقال الخاص بـ PKCS#12 وتقوية أمان ASN.1 في HotPDF

ما الذي يضمنه التوقيع الصالح فعليًا؟

تعني القيمة svValid هذا بالضبط: البايتات المحددة بواسطة /ByteRange تجزيء إلى القيمة التي وقع عليها الموقع، ويتم التحقق من التوقيع بموجب المفتاح العام للشهادة المضمنة في حاوية CMS. وهذه هي سلامة البايت بالإضافة إلى ارتباط المفتاح، ولا شيء أكثر من ذلك. وتعد سلسلة الشهادات والتحقق من الثقة خارج نطاق عمل مدقق HotPDF صراحةً: فهو لا يسير في السلسلة إلى الجذر، ولا يفحص الإلغاء، ولا يستشير أي مخزن ثقة. فالشهادة الموقعة ذاتيًا من مهاجم أعاد توقيع مستند معدل سيتم التحقق منها على أنها svValid، لأن الرياضيات متسقة داخليًا. وسواء كان الموقع هو من يدعي، وما إذا كان ينبغي لأي شخص الوثوق به، فهو قرار سياسي ينتمي إلى طبقة منفصلة، سواء كان ذلك القائمة البيضاء لشهادات مؤسستك، أو مخزن شهادات Windows، أو سلطة التحقق

تحمي علامة CoversWholeDocument فجوة أكثر دقة. فالتوقيع يغطي فقط /ByteRange الخاصة به، وتسمح آلية التحديث التزايدي لملفات PDF بإلحاق محتوى بعد التوقيع دون إبطاله، وهو أمر مقصود بالرسم ويدعم كيفية عمل مهام سير عمل متعددة التوقيعات. ويتم حساب العلامة أثناء التحقق وتكون صحيحة فقط عندما يمتد القطاعان بالإضافة إلى فجوة /Contents على الملف بأكمله. وعندما تصل svValid مع كون CoversWholeDocument خاطئة (false)، تكون المراجعة الموقعة سليمة ولكن الملف يحتوي على إضافات لاحقة، وما غيرته تلك الإضافات هو أمر يجب أن يقرر سير عملك ما إذا كان سيتسامح معه

المستندات المحملة من دفق والمشفرة تحتاج إلى بايتات المصدر الخاصة بها

تعتمد الوظيفتان VerifyLoadedSignature و VerifyLoadedSignatureEx الخاليتان من المعلمات على تذكر المكون للملف الذي جاء منه المستند. وإذا قمت بتحميل المستند من دفق، فلن يكون هناك اسم ملف لإعادة فتحه؛ وينطبق الشيء نفسه بعد مسار إعادة تحميل كلمة المرور المستخدم للمستندات المشفرة، وهو سير العمل الموضح في المقال الخاص بتشفر PDF بترميز AES-256 باستخدام HotPDF. وفي كلتا الحالتين، ترجع الأحمال الزائدة المدعومة بالملفات svSourceUnavailable بدلاً من التخمين. والحل هو التحميل الزائد لـ TStream، والذي يتيح لك تسليم البايتات الخام الأصلية من أي مكان تحتفظ بها فيه، مثل ملف لا تزال تملكه، أو مخزن مؤقت للذاكرة، أو كتلة قاعدة بيانات (blob)

var
  Src: TFileStream;
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  // Stream-loaded document: the component holds no source
  // file name, so supply the original bytes yourself.
  Src := TFileStream.Create('signed-contract.pdf',
    fmOpenRead or fmShareDenyWrite);
  try
    Status := Pdf.VerifyLoadedSignature(0, Src, Info);
    if Status <> svValid then
      Writeln('Verification failed: ', Ord(Status));
  finally
    Src.Free;
  end;
end;

الإبلاغ عما لا يمكنك التحقق منه

المدقق الذي يعرف فقط "صالح" و "غير صالح" سوف يبلغ بشكل خاطئ عن المستندات التي لم يفهمها فحسب، وبالتالي فإن تعداد الحالات يفصل الحالات التي يجب أن تميزها واجهة المستخدم الخاصة بك. وتعني svDigestMismatch أن بايتات المستند تغيرت بعد التوقيع، وهي إشارة التلاعب الكلاسيكية. وتعني svSignatureInvalid أن البايتات تجزيء بشكل صحيح ولكن فحص RSA فشل، مما يشير إلى قيمة توقيع تالفة أو مزورة. و svUnsupportedAlgorithm هي الإجابة الصادقة لمفاتيح ECDSA والملخصات غير المعترف بها: فقد يكون التوقيع جيدًا تمامًا، ولكن HotPDF ببساطة لا يستطيع فحصه، والإبلاغ عن ذلك كـ "غير صالح" من شأنه التشهير بمستند سليم. وتشير svMalformed إلى حاوية CMS التي تعذر تحليلها على الإطلاق. وبالنسبة للفحوصات بنمط البوابة، ترجع VerifyAllLoadedSignatures القيمة true فقط عندما يوجد حقل توقيع واحد على الأقل ويتم التحقق من كل منها على أنه svValid، وهو متغير منطقي واحد مريح لخط أنابيب استيعاب الأرشيف الذي يرفض أي شيء أقل من ذلك

تشحن وظائف التحقق من التوقيع، وتوقيع PAdES، وتشفير AES-256، وواجهة برمجة تطبيقات تعديل المستندات المحملة في نفس مكتبة VCL الأصلية لدلفي و C++Builder، وبدون تبعيات DLL خارجية؛ وتوجد قائمة الميزات الكاملة وإصدارات IDE المدعومة على صفحة منتج مكون HotPDF