مقال تقني

فحوص سياسة ECDSA وEdDSA وفق ISO/TS 32002 في PDFium لـ Delphi

يفحص مكوّن PDFium لـ Delphi كل توقيع PDF من نوع ECDSA وEdDSA مقابل ملف خوارزميات ISO/TS 32002 ويبلّغ عن الحكم في TPadesSignatureValidation.AlgorithmPolicyStatus. لا تنجح إلا منحنيات P-256 وP-384 وP-521 ومنحنيات Brainpool r1 الثلاثة وEd25519 وEd448، وكل منها بمزخص موافق، وأي تباين يرفع ppeiSignatureAlgorithmMismatch. وهذه السياسة أهم مما تبدو. توقيع على brainpoolP160r1، أو مفتاح P-256 يوقّع مزخص SHA-512، يمكن أن يتحقق حسابيًا على أكمل وجه، فيبلّغ Windows CryptoAPI عن قيمة التوقيع بوصفها سليمة بينما يرفض الملف مدقق PDF 2.0 صارم. يسد فحص السياسة تلك الفجوة، وهو منفصل عمدًا عن سؤال هل بايتات التوقيع صحيحة تشفيريًا

ماذا يسمح ISO/TS 32002 فعلًا لتواقيع المنحنيات الإهليلجية؟

يسمح ISO/TS 32002 بستة منحنيات ECDSA بالضبط ومخططي EdDSA في تواقيع PDF، ويربط كل منحنى بأطوال المزخص التي يجوز أن يحملها. يرمّز مكوّن PDFium ذلك الجدول في PadesCurveDigestAllowed، مفهرسًا بـ OID للمنحنى من شهادة الموقّع. منحنيات NIST صارمة: يجب أن يكون للمزخص عرض البت نفسه كالمنحنى، من SHA-2 أو SHA-3. ومنحنيات Brainpool أيسر فتقبل عرضها الخاص أو أي أعرض منه:

  • P-256 (1.2.840.10045.3.1.7): SHA-256 أو SHA3-256 فقط
  • P-384 (1.3.132.0.34): SHA-384 أو SHA3-384 فقط
  • P-521 (1.3.132.0.35): SHA-512 أو SHA3-512 فقط
  • brainpoolP256r1 (1.3.36.3.3.2.8.1.1.7): أي مزخص SHA-2 أو SHA-3 من 256 إلى 512 بت
  • brainpoolP384r1 (1.3.36.3.3.2.8.1.1.11): مزخص SHA-2 / SHA-3 بعرض 384 أو 512 بت
  • brainpoolP512r1 (1.3.36.3.3.2.8.1.1.13): SHA-512 أو SHA3-512 فقط
مصفوفة ملف خوارزميات ISO TS 32002 في مكوّن PDFium: تقبل P-256 وP-384 وP-521 أطوال مزخص مطابقة فقط في PadesCurveDigestAllowed، ويقبل brainpoolP256r1 من 256 إلى 512 بت، ويقبل brainpoolP384r1 عرضَي 384 و512، ويصرح Ed25519 وEd448 بـ SHA-512 وSHAKE256 بطول 512، ويرفع التباين ppeiSignatureAlgorithmMismatch بينما تعيد المنحنيات خارج الملف pcsUnsupported
تنجح ستة منحنيات ECDSA ومخططا EdDSA، وكل منحنى مربوط بأطوال المزخص التي يجوز حملها؛ وكل ما عداه غير صالح أو غير مدعوم، ولا يُقبل بصمت أبدًا

لا خيار منحنى ولا خيار مزخص في EdDSA، ولهذا بالضبط تتعلق قواعده بالترميز لا بالقوة. وفق RFC 8419 يجب أن يصرح SignerInfo لـ Ed25519 بـ SHA-512 بوصفه digestAlgorithm بلا معاملات، ويجب أن يصرح SignerInfo لـ Ed448، على مسار السمات الموقعة الذي تستخدمه PAdES دائمًا، بـ id-shake256-len (2.16.840.1.101.3.4.2.18) مع معامل INTEGER بقيمة 512 بالضبط. ولكلا المخططين يجب أن يحمل AlgorithmIdentifier الخاص بالتوقيع وAlgorithmIdentifier الخاص بالمفتاح العام في الشهادة بلا معاملات إطلاقًا. منتج يكتب NULL هناك، وهي العادة التي درّبها مرمّزات RSA على مكتبات ASN.1 كثيرة، ينتج توقيعًا غير مطابق مع أن المفتاح وقيمة التوقيع سليمان

كيف يستخرج مكوّن PDFium الثلاثي الخوارزمي من CMS

لا يستطيع PDFium نفسه الإجابة عن هذا السؤال، لأن واجهته العامة للتوقيعات تقرأ قاموس التوقيع لكنها لا تتحقق من CMS ولا تكشف منحنى شهادة الموقّع. لذلك يحلل طبقة فحص PAdES المبنية على PDFium بنية CMS SignedData (RFC 5652) بنفسها. تقرأ InspectPadesSignatureAlgorithm قيمتي digestAlgorithm وsignatureAlgorithm لأول SignerInfo، ثم تجد شهادة الموقّع وتقرأ SubjectPublicKeyInfo منها لتأخذ خوارزمية المفتاح والمنحنى. وبحث الشهادة محدود عمدًا: تُفحص 64 شهادة على الأكثر من مجموعة certificates في CMS، والمطابقة مقارنة بايتات تامة للمرسل والرقم التسلسلي من issuerAndSerialNumber، ولا يرتد الكود إلى «الشهادة الوحيدة الموجودة» إلا إذا احتوت المجموعة على شهادة قابلة للتحليل واحدة بالضبط. انتقاء أول شهادة EC من مجموعة بلا ترتيب أمر ميسور، وسيجعل شهادة CA هي من يقرر أي منحنى استخدمه الموقّع زعمًا

كيف يفحص مكوّن PDFium ثلاثيَ خوارزميات توقيع PDF: تقرأ InspectPadesSignatureAlgorithm قيمتَي digestAlgorithm وsignatureAlgorithm لأول SignerInfo في CMS، ويثبت شهادة الموقّع عبر مطابقة issuerAndSerialNumber التامة بين 64 مرشحًا على الأكثر، ويقرأ SubjectPublicKeyInfo للمنحنى، ويعيد EvaluatePadesSignatureAlgorithm قيمة AlgorithmPolicyStatus
لا يتحقق PDFium نفسه من CMS ولا يكشف منحنى الموقّع، فيحلل طبقة PAdES بنية SignedData ويحفظ كل OID خام في السجل لرفض قابل للتفسير
uses
  PDFium, FPdfPades;

const
  StatusNames: array[TPadesCryptoStatus] of string =
    ('not checked', 'valid', 'invalid', 'unsupported', 'indeterminate');

var
  Pdf: TPdf;
  R: TPadesValidationResult;
  A: TPadesSignatureAlgorithmInfo;
  I: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'invoice-ecdsa-signed.pdf';
    Pdf.Active := True;
    R := Pdf.ValidatePades;
    for I := 0 to Length(R.Signatures) - 1 do
    begin
      A := R.Signatures[I].AlgorithmInfo;
      Writeln('Signature ', I);
      Writeln('  digestAlgorithm    : ', string(A.DigestAlgorithmOid));
      Writeln('  signatureAlgorithm : ', string(A.SignatureAlgorithmOid));
      Writeln('  public key / curve : ', string(A.PublicKeyAlgorithmOid),
        ' / ', string(A.CurveOid));
      Writeln('  policy             : ',
        StatusNames[R.Signatures[I].AlgorithmPolicyStatus]);
    end;
    if ppeiSignatureAlgorithmMismatch in R.Issues then
      Writeln('At least one signature violates the ISO/TS 32002 profile');
  finally
    Pdf.Free;
  end;
end;

يطبق TPdf.ValidatePades السياسة ضمن جولة الامتثال، انطلاقًا من الشهادة التي يعثر عليها داخل CMS، وتعيد TPdf.ValidatePadesTrust تشغيلها مقابل شهادة الموقّع التي استخدمها Windows CryptoAPI فعلًا للتحقق، فشهادة CryptoAPI المبلَّغ عنها هي صاحبة الكلمة الأخيرة. وكل مدخل خام يهبط في TPadesSignatureAlgorithmInfo، بما فيه DigestParametersPresent وDigestParameterBits وSignatureParametersPresent وPublicKeyParametersAreNamedCurve، فيكون كل رفض قابلًا للتفسير من السجل لا من سطر سجل أحداث

لماذا يفشل توقيع P-256 بمزخص SHA3-256 في السياسة؟

يفشل توقيع P-256 في سياسة مكوّن PDFium كلما اختلفت قيمة digestAlgorithm في CMS عن المزخص المستتبع في signatureAlgorithm الخاص بـ ECDSA، حتى لو كان كلاهما مقبولًا على حدة للمنحنى. تطابق EvaluatePadesSignatureAlgorithm أولًا ecdsa-with-SHA256 وecdsa-with-SHA3-256 وإخوتهما إلى مزخص، وتقارنه بالمزخص المصرَّح به في digestAlgorithm، وتعيد pcsInvalid عند أي اختلاف قبل الرجوع إلى جدول المنحنيات. والحالة حقيقية: أداة توقيع تحوّل مزخصها إلى SHA3-256 لكنها تبقي معرف ecdsa-with-SHA256 مبرمجًا ثابتًا، والناتج ملف لا يستطيع أي مدقق مطابق تفسيره بانسجام. الدالة عامة، فيمكن تثبيت المصفوفة في اختبار وحدة دون بناء PDF:

var
  Info: TPadesSignatureAlgorithmInfo;
begin
  Info := Default(TPadesSignatureAlgorithmInfo);
  Info.Family := psafEcdsa;
  Info.PublicKeyAlgorithmOid := '1.2.840.10045.2.1';      // id-ecPublicKey
  Info.PublicKeyParametersPresent := True;
  Info.PublicKeyParametersAreNamedCurve := True;
  Info.CurveOid := '1.2.840.10045.3.1.7';                 // P-256
  Info.DigestAlgorithmOid := '2.16.840.1.101.3.4.2.8';    // SHA3-256
  Info.SignatureAlgorithmOid := '2.16.840.1.101.3.4.3.10'; // ecdsa-with-SHA3-256
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsValid);

  Info.SignatureAlgorithmOid := '1.2.840.10045.4.3.2';    // ecdsa-with-SHA256
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsInvalid); // تباين المزخص

  Info.CurveOid := '1.3.36.3.3.2.8.1.1.1';                // brainpoolP160r1
  Info.DigestAlgorithmOid := '2.16.840.1.101.3.4.2.1';    // SHA-256، متطابق الآن
  Assert(EvaluatePadesSignatureAlgorithm(Info) = pcsUnsupported); // المنحنى ليس في الملف
end;

يحظى ترميز المنحنى بالصرامة نفسها. يجيز RFC 5480 §2.1.1 أن تكون ECParameters منحنى مسمى بـ OID، أو منحنى ضمني (NULL)، أو مجموعة معاملات صريحة كاملة، وتشترط ملفات PKIX الصيغة المسمّاة. يعيد مكوّن PDFium قيمة pcsInvalid حين تحمل شهادة id-ecPublicKey بلا معاملات أو بمعاملات ضمنية أو بمعاملات صريحة، لأن المعاملات الصريحة تتيح للمهاجم وصف منحنى يشبه المنحنى القياسي شبهًا فقط. أما المنحنى المسمى تسمية صحيحة الغائب ببساطة عن قائمة ISO/TS 32002، مثل brainpoolP160r1 أعلاه أو secp256k1، فينال pcsUnsupported بدلًا منها

غير صالح أم غير مدعوم أم غير محسوم: قراءة الحالة بصدق

الحالات الثلاث غير الناجحة في AlgorithmPolicyStatus تعني أشياء مختلفة، ودمجها في سلّم «فاشل» واحد يرمي المعلومات التي يحتاجها المدققون. تعني pcsInvalid أن تركيبة خوارزمية معروفة مشوهة أو متباعدة؛ تضيف ppeiSignatureAlgorithmMismatch إلى TPadesValidationResult.Issues وتدفع IntegrityStatus الإجمالي إلى pcsInvalid، فتعيد IsCryptographicallyValid القيمة False حتى لو اجتازت قيمة توقيع CMS الفحص. وتعني pcsUnsupported أن المنحنى أو المزخص خارج ما يسمي الملف، وهي نتيجة قدرة لا دليل عبث. وتعني pcsIndeterminate أن شهادة الموقّع تعذر تثبيتها، عادةً CertificateSet بعدة مرشحين بلا مطابقة تامة لـ issuerAndSerialNumber، فيرفض الكود تخمين المنحنى؛ ومنذ v3.124.0 تُوسم أيضًا بصمة RSA فوق SHA-1 أو مزخص 112 بت مثل SHA-224، وهو ما لم يعد متفقًا عليه للتحقق الحالي. وينطبق التقسيم نفسه على EdDSA على جهاز لا تستطيع CryptoAPI فيه التحقق من Ed25519 أو Ed448: تبقى CmsSignatureStatus عند pcsUnsupported بينما يمكن أن تكون AlgorithmPolicyStatus عند pcsValid، لأن الترميز كان صحيحًا والناقص هو المدقق فقط. وإن كنت تتعقب رفضًا من Adobe أو من مدقق قائم على DSS، فتغطي قصة أسباب رفض المدققين لتواقيع PAdES الأسباب الشائعة الأخرى

مسار قرار EvaluatePadesSignatureAlgorithm في مكوّن PDFium: يضبط تباين المزخص قيمة pcsInvalid وppeiSignatureAlgorithmMismatch، ويضبط المنحنى المسمى خارج ملف ISO TS 32002 مثل brainpoolP160r1 قيمة pcsUnsupported، ويعطي شهادة موقّع تعذر تثبيتها قيمة pcsIndeterminate، ومنذ v3.124.0 تطبق فرعة RSA مجموعات مزخص ETSI TS 119 312 مع بقاء حجم المفتاح على عاتق التطبيق
الحالات الثلاث غير الناجحة تعني أشياء مختلفة: غير صالح دليل على تركيبة معطوبة، وغير مدعوم نتيجة قدرة، وغير محسوم يعني أن الكود رفض التخمين
var
  Pdf: TPdf;
  Options: TPadesTrustValidationOptions;
  R: TPadesValidationResult;
  S: TPadesSignatureValidation;
  I: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'invoice-ecdsa-signed.pdf';
    Pdf.Active := True;
    Options := TPadesTrustValidationOptions.Default; // دون اتصال، بلا تحقق إبطال
    R := Pdf.ValidatePadesTrust(Options);
    for I := 0 to Length(R.Signatures) - 1 do
    begin
      S := R.Signatures[I];
      if S.IsDocumentTimeStamp then
        Continue;
      case S.AlgorithmPolicyStatus of
        pcsInvalid:       Writeln(I, ': reject, algorithm combination is invalid');
        pcsUnsupported:   Writeln(I, ': manual review, curve or digest outside the profile');
        pcsIndeterminate: Writeln(I, ': manual review, signer not identified or digest no longer agreed');
        pcsValid:
          if S.AlgorithmInfo.Family = psafRsa then
            Writeln(I, ': RSA digest suite accepted, check key size yourself')
          else
            Writeln(I, ': EC/EdDSA profile satisfied');
      end;
    end;
    Writeln('Cryptographically valid: ', R.IsCryptographicallyValid);
  finally
    Pdf.Free;
  end;
end;

ما الذي لا يضمنه pcsValid؟

AlgorithmPolicyStatus = pcsValid يشهد فقط على أن توقيع ECDSA أو EdDSA يستخدم منحنى معتمدًا بمزخص متطابق ومُقرَّن بشكل صحيح، وأن توقيع RSA يستخدم مزخصًا من مجموعة حالية؛ ولا يقول شيئًا عن صحة قيمة التوقيع ذاتها. قبل v3.124.0 كانت فرعة RSA في EvaluatePadesSignatureAlgorithm واسعة عمدًا: أي signatureAlgorithm تحت قوس PKCS #1 أي 1.2.840.113549.1.1.* كان يعيد pcsValid، بما في ذلك sha1WithRSAEncryption القديمة. ومنذ PDFiumPas v3.124.0 تطبق فرعة RSA مجموعات تواقيع ETSI TS 119 312. مزخصات MD2 وMD4 وMD5 هي pcsInvalid. وSHA-1 ومزخصات 112 بت مثل SHA-224 هي pcsIndeterminate، فيحتفظ توقيع SHA-1 بنتيجته الإجمالية للسلامة ويُوسم للمراجعة بدل الرفض. أما digestAlgorithm المخالفة للمزخص المثبت في خوارزمية التوقيع، مثل sha256WithRSAEncryption فوق مزخص SHA-1، أو خوارزمية توقيع RSA على مفتاح موقّع غير RSA، فهي pcsInvalid، تُبلَّغ بوصفها ppeiSignatureAlgorithmMismatch وتُسقط السلامة. شهادة موقّع تعذر العثور عليها تعطي pcsIndeterminate كما كان الحال سلفًا مع ECDSA، والمزخصات غير المعروفة أو معرفات RSA غير التوقيعية تعطي pcsUnsupported. طول المعامل ما يزال بلا فحص، ومعاملات PSS لا تُتحقق هنا (تغطي مقالة معاملات RSASSA-PSS في RFC 4055 كيف تُرمَّز على جانب التوقيع)، وترفع مزخصات SHA-1 أو MD5 إضافيًا مسألة ppeiBadDigestAlgorithm المنفصلة. وكذلك يبقى حساب التوقيع وسلسلة الشهادات والتحقق من الإبطال من عمل CmsSignatureStatus وCertificateTrustStatus وRevocationStatus، وهي قادمة من Windows CryptoAPI. عامل pcsValid بوصفها «ملف الخوارزمية قائم»، لا بوصفها «هذا المفتاح قوي بما يكفي» أبدًا

لتطبيق Delphi يقبل فواتير PDF موقعة أو عقودًا أو حزم أرشفة، فإن الضبط العملي قصير: شغّل ValidatePadesTrust، وارفض عند ppeiSignatureAlgorithmMismatch، ووجّه pcsUnsupported وpcsIndeterminate إلى إنسان، وفرض حدك الأدنى الخاص لحجم مفاتيح RSA لأن السياسة لن تفرضه. تُشحن PDFium Component لـ Delphi وLazarus مع مدقق PAdES وباني تقارير الأدلة وخط أنابيب التوقيع، فيستطيع المكتبة نفسها إنتاج هذه التواقيع وفحصها من طرف إلى طرف