مقاله فنی

بررسی امضاهای PDF و سطوح PAdES در دلفی

شما یک فایل PDF امضاشده دریافت کرده‌اید و می‌خواهید در نمایشگر خود نشان دهید که چه کسی آن را امضا کرده، چه زمانی امضا شده، آیا امضا کل فایل را پوشش می‌دهد، و چقدر با انطباق بلندمدت فاصله دارد. PDFium Component برای دلفی و لازاروس به هر چهار سؤال با فراخوانی‌های فقط‌خواندنی پاسخ می‌دهد: تابع FPDF_GetSignatureCount و خانواده FPDFSignatureObj_* دیکشنری امضا را ارائه می‌دهند، و متد TPdf.ValidatePades سطح پایه PAdES را مشخص می‌کند. این اولین مقاله از سه مقاله درباره امضاهای PDF با پی‌دی‌اف‌یوم است؛ دو مقاله بعدی ساخت امضای B-B و افزودن مهرهای زمانی بلندمدت را پوشش می‌دهند. با این حال، یک مرز از ابتدا وجود دارد: تمام مباحث اینجا مربوط به بررسی است، و خواندن آنچه یک امضا ادعا می‌کند کار متفاوتی از تایید رمزنگاری آن یا تصمیم‌گیری درباره اعتماد به امضاکننده است

یک امضای PDF یک دیکشنری است، نه یک پیوست نامفهوم، و دو ورودی مهم آن به شما می‌گویند که چه مقدار از فایل واقعاً محافظت شده است. استاندارد ISO 32000-1 §12.8 دیکشنری امضا را با یک ورودی /ByteRange و یک ورودی /Contents تعریف می‌کند. ورودی /Contents حاوی یک ساختار رمزگذاری‌شده با فرمت هگز CMS SignedData (RFC 5652) است، یعنی پاکت رمزنگاری که گواهینامه امضاکننده، ویژگی‌های امضاشده و خود مقدار امضا را حمل می‌کند. /ByteRange بخشی است که توسعه‌دهندگان آن را دست‌کم می‌گیرند: این آرایه‌ای از دو بازه آفست-طول (offset-length) است که با هم کل فایل را به جز رشته هگز /Contents پوشش می‌دهند. این شکاف دقیقاً همان جایی است که بایت‌های امضا در آن قرار دارند، و دو بازه در هر طرف دقیقاً همان چیزی هستند که امضا به آن متعهد است

طراحی ByteRange چیزی است که یک ذخیره‌سازی افزایشی را قابل حسابرسی می‌کند. از آنجا که یک امضاکننده نمی‌تواند بایت‌های امضایی را که هنوز وجود ندارند هش (hash) کند، فایل در اطراف فیلد نگهدارنده /Contents تقسیم می‌شود و بقیه موارد در امضا هش می‌شوند. امضایی که ByteRange آن به انتهای فایل نمی‌رسد یک نشانه هشداردهنده است: محتوای اضافه شده پس از محدوده پوشش داده شده، از طریق یک به‌روزرسانی افزایشی بعدی، امضا را خراب نمی‌کند حتی اگر چیزی را که خواننده می‌بیند تغییر دهد. بنابراین اولین چیزی که یک بازرس دقیق بررسی می‌کند این نیست که چه کسی امضا کرده است، بلکه این است که آیا امضا بایت‌هایی را که به نظر می‌رسد تأیید می‌کند، پوشش می‌دهد یا خیر

خواندن دیکشنری امضا با استفاده از API فقط‌خواندنی PDFium

کامپوننت PDFium دیکشنری امضا را از طریق دو عضو فقط‌خواندنی ارائه می‌دهد: SignatureCount و رکورد Signature[Index]. در پس‌زمینه، آنها توابع FPDF_GetSignatureCount، FPDF_GetSignatureObject و دسترسی‌های FPDFSignatureObj_* را برای ویژگی‌های /SubFilter، /ByteRange، /Contents، /Reason و زمان امضا فراخوانی می‌کنند. در اینجا عبارت کلیدی «فقط‌خواندنی» است: پی‌دی‌اف‌یوم می‌تواند امضاها را شمارش کرده و بخواند اما هیچ API برای ایجاد یا نوشتن امضا ندارد، به همین دلیل است که بخش ایجاد امضا در این سری مقالات به جای پی‌دی‌اف‌یوم توسط خود کتابخانه پیاده‌سازی می‌شود

var
  Pdf: TPdf;
  i: Integer;
  Sig: TPdfSignature;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'contract-signed.pdf';
    Pdf.Active := True;
    for i := 0 to Pdf.SignatureCount - 1 do
    begin
      Sig := Pdf.Signature[i];
      Writeln('SubFilter : ', Sig.Encoding);        // ETSI.CAdES.detached, adbe.pkcs7.detached, ...
      Writeln('Signed at : ', Sig.Time);            // signer date string, e.g. D:20260708120000+02'00'
      Writeln('Reason    : ', Sig.Reason);
      Writeln('CMS length: ', Length(Sig.Content)); // raw DER SignedData taken from /Contents
      Writeln('DocMDP    : ', Sig.Permission);      // 0 = no certification, 1..3 = MDP level
    end;
  finally
    Pdf.Free;
  end;
end;

تفاوت بین سطوح PAdES B-B، B-T، B-LT و B-LTA چیست؟

چهار سطح پایه PAdES ساختاری پله‌ای را از یک امضای با حداقل اعتبار تا امضایی که برای دوام آوردن در دهه‌ها بایگانی ساخته شده است، تشکیل می‌دهند و هر سطح دقیقاً شامل سطح زیرین خود است. استاندارد ETSI EN 319 142-1 آنها را به عنوان B-B، B-T، B-LT و B-LTA تعریف می‌کند. سطح B-B (پایه) امضا به همراه ویژگی‌های امضاشده اجباری و نه هیچ چیز دیگر است. سطح B-T (مهر زمانی) یک مهر زمانی معتبر RFC 3161 را روی امضا اضافه می‌کند، به طوری که لحظه امضا به جای اینکه توسط ساعت امضاکننده اعلام شود، توسط یک مرجع مهر زمانی تایید می‌گردد. سطح B-LT (بلندمدت) مستندات اعتبارسنجی را — شامل زنجیره گواهینامه و به صورت اختیاری پاسخ‌های OCSP یا CRL — در داخل فایل تعبیه می‌کند، به طوری که امضا همچنان سال‌ها بعد و در زمانی که زیرساخت صادرکننده از بین رفته است، قابل اعتبارسنجی باشد. سطح B-LTA (بلندمدت با مهر زمانی بایگانی) یک مهر زمانی سند را در اطراف آن مستندات قرار می‌دهد، از خود داده‌های بلندمدت محافظت می‌کند و به شما نقطه‌ای برای تمدید مهر زمانی قبل از قدیمی شدن رمزنگاری پایه‌ای می‌دهد

برداشت عملی درباره افق زمانی است. یک امضای B-B به این سؤال پاسخ می‌دهد که «آیا کسی این را امضا کرده است». سطح B-T پاسخ می‌دهد «و چه زمانی، به طور اثبات‌پذیر». سطح B-LT پاسخ می‌دهد «و آیا همچنان می‌توانم آن را پس از انقضای گواهینامه‌ها بررسی کنم». سطح B-LTA پاسخ می‌دهد «و آیا آن بررسی همچنان در بیست سال آینده معتبر خواهد بود». چارچوب‌های مقرراتی یک پله را انتخاب می‌کنند: بسیاری از سناریوهای فاکتور الکترونیکی و eIDAS حداقل به سطح B-T نیاز دارند، و قوانین بایگانی به سطح B-LT یا B-LTA نیاز دارند. دانستن اینکه یک سند در واقع به کدام پله می‌رسد، قبل از پذیرش یا رد آن، هدف اصلی مرحله بررسی است

تشخیص سطح پایه با استفاده از TPdf.ValidatePades

کامپوننت PDFium کل مسئله سطح را به یک فراخوانی محدود می‌کند. متد TPdf.ValidatePades یک رکورد TPadesValidationResult برمی‌گرداند که فیلد Level آن از نوع شمارشی TPadesLevel است — شامل plNone، plUnknown، plB_B، plB_T، plB_LT یا plB_LTA — در کنار مجموعه‌ای از مشکلات، تعداد امضا و تعداد مهرهای زمانی سند. سطح به صورت صعودی مشخص می‌شود: ارزیاب ابتدا سطح B-B را بررسی می‌کند، سپس در صورت وجود مهر زمانی امضا یا مهر زمانی سند به سطح B-T ارتقا می‌دهد، در صورتی که کاتالوگ حاوی یک /DSS با گواهینامه‌ها و نشانگر /Extensions /ESIC سطح ۱ باشد به سطح B-LT ارتقا می‌دهد، و در صورتی که مهر زمانی سند و نشانگر ESIC سطح ۲ هر دو وجود داشته باشند به سطح B-LTA ارتقا می‌دهد. دو متد کمکی نتیجه را کاربردی می‌کنند: IsCompliant تنها زمانی True است که سطح حداقل به B-B برسد و مجموعه مشکلات خالی باشد، و IsCompliantAt به شما اجازه می‌دهد حداقل سطح سیاست‌گذاری مانند plB_T را بررسی کنید

var
  Pdf: TPdf;
  R: TPadesValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'contract-signed.pdf';
    Pdf.Active := True;
    R := Pdf.ValidatePades;
    case R.Level of
      plNone:    Writeln('No PAdES signature present');
      plUnknown: Writeln('Signature present but level undeterminable');
      plB_B:     Writeln('PAdES B-B   (basic)');
      plB_T:     Writeln('PAdES B-T   (trusted timestamp)');
      plB_LT:    Writeln('PAdES B-LT  (long-term material embedded)');
      plB_LTA:   Writeln('PAdES B-LTA (archive timestamp)');
    end;
    Writeln('Signatures   : ', R.SignatureCount);
    Writeln('DocTimeStamps: ', R.DocTimeStampCount);
    if R.IsCompliantAt(plB_T) then
      Writeln('Meets the B-T policy floor')
    else
      Writeln('Below the required B-T level');
  finally
    Pdf.Free;
  end;
end;

چرا adbe.pkcs7.sha1 یک SubFilter ممنوع است؟

زیرا الگوریتم SHA-1 آسیب‌پذیر است و هندلر adbe.pkcs7.sha1 آن را در خود دارد. این SubFilter سند را قبل از قرار دادن در PKCS#7 با SHA-1 پیش-هش (pre-hashes) می‌کند، و SHA-1 سال‌هاست که در برابر تصادم (collision) آسیب‌پذیر است، بنابراین بند ۶.۳ استاندارد EN 319 142-1 آن را به طور کامل برای یک امضای پایه ممنوع کرده است. متد ValidatePades در زمان مشاهده adbe.pkcs7.sha1 یا adbe.x509.rsa_sha1 خطای ppeiForbiddenSubFilter را ایجاد می‌کند، و در صورتی که خود CMS از MD5 یا SHA-1 به عنوان خلاصه پیام (message digest) استفاده کند، خطای ppeiBadDigestAlgorithm را ایجاد می‌نماید (بند ۶.۲.۱). این‌ها دو بررسی مجزا هستند که کلاس یکسانی از ضعف را در دو لایه مختلف شناسایی می‌کنند

مجموعه مشکلات در مجموع دارای ۲۶ عضو است، و مواردی که بیشتر با آنها مواجه می‌شوید پیرامون ساختار و پوشش هستند. ppeiByteRangeNotCoveringFile همان بررسی پوشش است که قبلاً توضیح داده شد. خطای ppeiForbiddenCertKey زمانی فعال می‌شود که دیکشنری امضا حاوی یک ورودی /Cert باشد، که استاندارد PAdES آن را ممنوع کرده است زیرا زنجیره باید در داخل بخش SignedData.certificates مربوط به CMS قرار داشته باشد. خطاهای ppeiMissingSigningCertificate، ppeiMissingContentType و ppeiMissingMessageDigest نشان‌دهنده نبود ویژگی‌های امضاشده اجباری هستند، و ppeiDetachedContentViolation امضایی را شناسایی می‌کند که به اشتباه محتوای امضاشده را در خود تعبیه کرده است به جای اینکه آن را جدا کند. شمارش این مجموعه، یک رد شدن ساده را به یک تشخیص دقیق تبدیل می‌کند که می‌توانید آن را ثبت (log) کنید

var
  R: TPadesValidationResult;
  Issue: TPadesValidationIssue;
begin
  R := Pdf.ValidatePades;
  if R.Issues <> [] then
    for Issue := Low(TPadesValidationIssue) to High(TPadesValidationIssue) do
      if Issue in R.Issues then
        Writeln('Issue: ',
          GetEnumName(TypeInfo(TPadesValidationIssue), Ord(Issue)));
end;

آنچه ValidatePades بررسی نمی‌کند

متد ValidatePades ساختار را تایید می‌کند، نه اعتماد را، و اشتباه گرفتن این دو با هم یک خطای خطرناک است. نتیجه plB_LTA به این معنی است که سند حاوی یک امضای B-LTA با ساختار صحیح به همراه تمام ویژگی‌ها، مستندات و مهرهای زمانی الزامی در جای مناسب خود است — به این معنی نیست که امضا از نظر رمزنگاری معتبر است، گواهینامه به ریشه‌ای که به آن اعتماد دارید متصل است، یا هیچ گواهینامه‌ای در زنجیره باطل نشده است. ارزیاب عمداً هیچ تایید رمزنگاری انجام نمی‌دهد: امضا را روی ByteRange دوباره محاسبه نمی‌کند، زنجیره اعتماد را نمی‌سازد یا ارزیابی نمی‌کند، و وضعیت ابطال OCSP یا CRL را بررسی نمی‌نماید. این تفکیک آگاهانه و مفید است، زیرا بررسی ساختاری سریع، کاملاً قطعی و بدون نیاز به کلید، شبکه و رمزنگاری پلتفرم است، بنابراین ValidatePades به طور یکسان روی ویندوز، لینوکس و مک به صورت کدهای پاسکال خالص روی بایت‌های فایل اجرا می‌شود. در مقابل، اعتبارسنجی زنجیره اعتماد از سیاست‌گذاری‌ها جدا نیست — اینکه به کدام ریشه‌ها اعتماد دارید، چگونه اطلاعات ابطال را دریافت می‌کنید، چقدر تلورانس مهر زمانی شما سخت‌گیرانه است — و این کار متعلق به مرحله بعدی است که به مخزن گواهینامه پلتفرم بستگی دارد، بنابراین با قبولی در ValidatePades به عنوان دروازه ضروری برای درستی شکل امضا رفتار کنید، سپس یک امضا با ساختار صحیح را قبل از تکیه بر آن، به بخش تایید رمزنگاری واقعی بسپارید

این بررسی ساختاری نقطه شروع درستی است، و به طور طبیعی با بررسی‌های فقط‌خواندنی گسترده‌تر در بخش ممیزی ریسک‌های امنیتی PDF با کامپوننت PDFium و با کارهای انطباق قالب مانند اعتبار‌سنجی اسناد PDF/X آماده چاپ هماهنگ است. پس از اینکه توانستید یک امضا را بخوانید و دسته‌بندی کنید، مرحله بعدی تولید آن است: مقاله دوم در این سری مقالات امضا کردن فایل‌های PDF با PAdES B-B را پوشش می‌دهد، و مقاله سوم آن را به مهرهای زمانی معتبر و امضاهای بلندمدت B-LT و B-LTA تعمیم می‌دهد. سیستم بررسی فقط‌خواندنی امضا و ارزیاب ValidatePades نشان داده شده در اینجا بخشی از PDFium Component برای دلفی، سی‌پلاس‌پلاس‌بیلدر و لازاروس هستند