💡 مثال کامل قابل اجرا در GitHub موجود است:
digital-signing-certificate-validity-dotnet

مشکلی که تا زمان حضور حسابرس هیچ‌کس نمی‌بیند

یک سرویس امضا به مدت سه سال بدون خطا اجرا می‌شود. اسناد صادر می‌شوند، گیرندگان آن‌ها را می‌پذیرند و هیچ‌چیزی در لاگ‌ها نشانگر مشکلی نیست. سپس اعتبارسنج طرف مقابل یک دسته اسناد را نامعتبر اعلام می‌کند و تحقیق دو دلیل را نشان می‌دهد: امضاها با SHA‑1 نوشته شده‌اند و در چهار ماه گذشته گواهی‌نامه منقضی شده بود.

هر دو نقص در زمان امضا به‌صورت ساکن بودند. این همان چیزی است که GroupDocs.Signature نسخه 26.9 تغییر می‌دهد.

اجرای اعتبار گواهی‌نامه به‌صورت پیش‌فرض جدید برای امضای دیجیتال در .NET است: گواهی‌نامه‌ای که خارج از بازهٔ اعتبار خود باشد رد می‌شود نه اینکه استفاده شود. این ویژگی همراه دو ویژگی دیگر می‌آید — SHA‑256 به‌عنوان الگوریتم پیش‌فرض هش PDF و LogLevel که سرانجام فیلتر می‌کند — و با هم سه دستهٔ شکست را از گیرنده به فرستنده منتقل می‌کنند، جایی که هنوز می‌توان آن‌ها را رفع کرد.

چرا موفقیت ساکن هزینه‌بر است

امضا کردن به‌گونه‌ای است که طرفی که اشتباه را می‌کند، طرف دیگری است که آن را کشف می‌کند. یک فاکتور خراب در سیستم خود شما شکست می‌خورد؛ یک امضای نامعتبر چند هفته بعد در سیستم شخص دیگری شکست می‌خورد و هیچ‌گونه تشخیصی برای شما در دسترس نیست.

این عدم تقارن دلیل این است که «API موفقیت را برگرداند» در اینجا تضمین مفیدی نیست. پیش‌فرض‌های قدیمی برای عدم قطع تماس‌کننده بهینه شده بودند و هزینه به گیرنده و در نهایت به کسی که مجبور شد صدها سند را دوباره امضا و ارسال کند، منتقل می‌شد.

تغییر 1: رد گواهی‌نامه‌های منقضی‌شده

تغییر اصلی. Sign اکنون وقتی اعتبار گواهی‌نامه به‌پایان رسیده یا هنوز شروع نشده باشد، GroupDocsSignatureException پرتاب می‌کند و هیچ‌چیزی روی دیسک نوشته نمی‌شود.

try
{
    signature.Sign(outputPath, options);
    return true;
}
catch (GroupDocsSignatureException ex)
{
    Console.WriteLine($"   Rejected: {ex.Message}");
    return false;
}

پیام، گواهی‌نامه و ویژگی‌ای که می‌توانست آن را اجازه دهد را نام می‌برد، بنابراین یک اپراتور که یک خط لاگ می‌خواند می‌تواند بدون مراجعه به مستندات اقدام کند. برای یک خط لوله که به 26.9 ارتقا می‌یابد و شروع به شکست می‌کند، این تقریباً همیشه دلیل است — و پاسخ صحیح تجدید گواهی‌نامه است، نه سرکوب.

وقتی واقعاً به رفتار قدیمی نیاز دارید، یک ویژگی کافی است:

var options = new DigitalSignOptions(certificate)
{
    Password = certificatePassword,
    AllowExpired = true
};

سند امضا می‌شود و یک هشدار به لاگر می‌رود. اعتبارسنج‌ها همچنان نتیجه را رد می‌کنند، زیرا AllowExpired فقط آنچه کتابخانه اجازه می‌دهد را کنترل می‌کند نه ارزش واقعی گواهی‌نامه. پرچم همراه AllowNotYetValid انتهای دیگر بازه را پوشش می‌دهد و عمداً مستقل است: اجازه دادن به گواهی‌نامه منقضی‌شده به‌صورت ساکن اجازهٔ گواهی‌نامه‌ای که تاریخ آینده دارد را نمی‌دهد.

تغییر 2: SHA‑256 به‌صورت پیش‌فرض

امضاهای دیجیتال PDF اکنون با SHA‑256 در قالب adbe.pkcs7.detached نوشته می‌شوند که اعتبارسنج‌های فعلی انتظار دارند. نسخه‌های قبلی SHA‑1 می‌نوشتند.

var options = new DigitalSignOptions(certificate)
{
    Password = certificatePassword,
    HashAlgorithm = HashAlgorithm.Sha256,
    Reason = "Approved",
    Location = "Head office"
};

تنظیم صریح این ویژگی فقط زمانی لازم است که بخواهید به‌سوی الگوریتم‌های دیگر پیش بروید — Sha384 یا Sha512 وقتی سیاست آن‌ها را می‌طلبد — یا برای ماندن روی Sha1 برای اعتبارسنجی که قادر به پردازش چیز دیگری نیست. یک مهر زمان‌دار که به امضا اضافه می‌شود از همان هش استفاده می‌کند.

تأیید در همان نسخه و به همان جهت تغییر کرد: DigitalVerifyOptions بدون معیار قبلاً تقریباً کاری انجام نمی‌داد و اکنون یک بررسی کامل رمزنگاری انجام می‌دهد، بنابراین سندی که پس از امضا تغییر یافته باشد به‌عنوان نامعتبر گزارش می‌شود.

تغییر 3: LogLevel واقعاً فیلتر می‌کند

SignatureSettings مدت‌هاست که یک لاگر می‌پذیرد. قبل از 26.9 سطح نادیده گرفته می‌شد، بنابراین هر پیام بدون در نظر گرفتن سطح می‌آمد و اکثر سرویس‌ها به‌جای غرق شدن در ردپاها، لاگ‌گذاری را خاموش می‌کردند.

نمونه با استفاده از یک لاگر شمارشی تفاوت را قابل‌اندازه‌گیری می‌کند؛ همان سند را سه بار با لاگرهای مختلف امضا می‌کند:

var levels = new Dictionary<string, LogLevel>
{
    ["None"] = LogLevel.None,
    ["Warning | Error"] = LogLevel.Warning | LogLevel.Error,
    ["All"] = LogLevel.All
};

None هیچ پیامی تولید نمی‌کند، Warning | Error تنها هشدار واحدی که توسط گواهی‌نامه منقضی‌شدهٔ مجاز ایجاد می‌شود را نگه می‌دارد و All برای هر مرحله یک ردپا اضافه می‌کند. لاگر شمارشی خود نقطهٔ یکپارچه‌سازی برای استک شماست:

public void Warning(string message)
{
    Warnings++;
    WarningMessages.Add(message);
}

این سه متد را برای Serilog، NLog یا Application Insights پیاده‌سازی کنید و تشخیص‌های کتابخانه در هر جایی که لاگ‌های سرویس شما می‌ریزند، ظاهر می‌شوند.

آیا سطح لاگ تغییر می‌دهد که چه استثناهایی دریافت می‌کنم؟

نه، و ارزش این را دارد که صریح بگوییم چون دو مورد به‌نظر مرتبط می‌آیند. LogLevel آنچه به ILogger می‌رسد را فیلتر می‌کند. استثناها صرف‌نظر از سطح لاگ به کد شما پرتاب می‌شوند: گواهی‌نامه منقضی‌شده بدون AllowExpired حتی در LogLevel.None نیز استثنا می‌اندازد و بلوک catch شما به‌طور یکسان رفتار می‌کند. تشخیص‌ها و جریان کنترل کانال‌های جداگانه‌ای هستند، به همین دلیل است که اجرای تولیدی با Warning | Error ایمن است.

رد شدن هزینهٔ کمتری نسبت به ظاهر دارد

اعتراض به توقف سخت، عملیاتی است: یک دستهٔ شبانه که قبلاً به‌پایان می‌رسید، اکنون در ساعت 02:00 شکست می‌خورد و کسی صفحه‌نمایش می‌شود. این هزینهٔ واقعی است و هنوز هم هزینهٔ کوچکتری است. یک دستهٔ رد شده یک هشدار، یک تجدید و یک اجرای مجدد است، همه در داخل سیستم‌های خود شما. یک دستهٔ امضا شده با گواهی‌نامه منقضی‌شده توسط گیرنده کشف می‌شود، که به معنای یک رشتهٔ پشتیبانی، صدور مجدد هر سند تحت تأثیر و گفت‌وگوی ناخوشایند دربارهٔ مدت زمان وقوع مشکل است.

نمونه شکست را به‌جای نظریه‌پردازی، به‌صورت عملی نشان می‌دهد: عمداً با گواهی‌نامه منقضی‌شده امضا می‌کند، استثنا را می‌گیرد و پیام را چاپ می‌کند، بنابراین می‌توانید دقیقاً ببینید لاگ‌های شما قبل از ارتقا به تولید چه محتوایی خواهند داشت. توصیه می‌کنم این متد را روی فروشگاه گواهی‌نامهٔ خود اجرا کنید قبل از برنامه‌ریزی برای ارتقای نسخه.

قبل از ارتقا چه کاری باید انجام داد

سه بررسی، به ترتیب احتمال بایت شدن:

  1. تاریخ انقضای گواهی‌نامه‌ها را در تمام مسیرهای امضا بررسی کنید، از جمله مسیرهایی که ماهانه یا فصلی اجرا می‌شوند — این‌ها جایی هستند که گواهی‌نامه منقضی‌شده طولانی‌ترین زمان مخفی می‌ماند.
  2. برای HashAlgorithm جستجو کنید: اگر هیچ‌کسی آن را تنظیم نکرده باشد، هشت‌های شما در ارتقا از SHA‑1 به SHA‑256 تغییر می‌کند، که بهبود است و باید در یادداشت نسخه ذکر شود.
  3. سطح لاگ را به‌صورت عمدی انتخاب کنید. پیش‌فرض صادقانه برای یک سرویس Warning | Error است؛ All برای بازتولید یک مشکل خاص است و None به معنای از دست دادن سیگنال واحدی است که می‌گوید امضا تحت یک معافیت انجام شده است.

تأیید در همان جهت تغییر کرد

از دست دادن این نکته آسان است، چون کد فراخوانی نیازی به تغییر ندارد. DigitalVerifyOptions بدون معیار قبلاً تقریباً کاری انجام نمی‌داد: معیارهای داده‌شده را مقایسه می‌کرد و اگر هیچ‌کدام نبود، چیز زیادی برای گفتن نداشت. از نسخه 26.9 به‌علاوه همان فراخوانی یک بررسی کامل رمزنگاری برای هر امضای دیجیتال PDF انجام می‌دهد.

برای سرویسی که اسناد ورودی را تأیید می‌کند، این یک ارتقای ساکن از «یک امضا اینجا وجود دارد» به «این امضا با این محتوا مطابقت دارد» است. دانستن این پیش از این که سندی شروع به شکست در تأیید کند که ماه گذشته پاس می‌خورد، مهم است: احتمالاً سند تغییر یافته و بررسی قبلی صرفاً به آن نگاه نکرده بود.

گواهی‌نامه‌های موجود در نمونه

یک جزئیات که ارزش کپی کردن را دارد نه کد: نمونه هیچ کلید خصوصی‌ای را شامل نمی‌شود. TestCertificates.cs سه فایل PFX خودامضا را در حافظه در زمان اجرا می‌سازد — معتبر، منقضی‌شدهٔ سال گذشته، معتبر از سال آینده — بنابراین نمایش به‌هر تاریخی که امروز باشد کار می‌کند و هیچ‌چیز حساسی در مخزن وجود ندارد.

این الگو برای مجموعه‌های تست خودتان ارزش پذیرش دارد. یک گواهی‌نامهٔ تست متعهد شده در نهایت منقضی می‌شود و وقتی این اتفاق می‌افتد، شکست دقیقاً شبیه به باگی است که این نسخه برای آشکار کردن آن ساخته شد.

نتیجه‌گیری

سه تغییر، یک جهت: شکست‌هایی که قبلاً در گیرنده ظاهر می‌شدند، اکنون در فرستنده ظاهر می‌شوند. به‌جای استفاده از AllowExpired گواهی‌نامه را تجدید کنید، SHA‑256 را به‌عنوان پیش‌فرض بگذارید، اسناد ورودی را به‌صورت رمزنگاری تأیید کنید و قبل از نیاز به آن، سطح لاگ را انتخاب کنید. نمونه تمام شش رفتار را در یک عبور اجرا می‌کند، از جمله رد، بنابراین ارتقا می‌تواند در چند دقیقه تمرین شود.

منابع تکمیلی