💡 مثال کامل قابل اجرا در گیت‌هاب موجود است:
pdf-signing-certificate-checks-python

مقدمه

یک سرویس هر شب فایل‌های PDF بارگذاری‌شده را امضا می‌کند. یک صبح گواهی‌نامه‌ای که استفاده می‌کند پس از تاریخ انقضای خود می‌گذرد و به نظر نمی‌رسد چیزی تغییر کرده باشد: کار اجرا می‌شود، فایل‌ها نوشته می‌شوند، لاگ به‌نظر عادی می‌آید. چند هفته بعد کسی یکی از این اسناد را در Acrobat باز می‌کند و بنر هشدار می‌بیند، زیرا امضایی که با گواهی‌نامه منقضی‌شده ساخته شده است، امضای ضعیف نیست – بلکه امضایی است که اعتبارسنج‌ها آن را نامعتبر گزارش می‌دهند. اسنادی که به‌نظر تأیید شده می‌آیند، کمتر از اسناد بدون امضا ارزش دارند، چون مردم به آن‌ها اعتماد کرده‌اند.

این رد کردن یک نام دارد. بررسی اعتبار گواهی‌نامه یک رفتار GroupDocs.Signature برای Python است که پس از پایان دورهٔ اعتبار گواهی‌نامه یا پیش از شروع آن، از امضا کردن خودداری می‌کند. این ویژگی در نسخهٔ ۲۶.۹ همراه با دو تغییر دیگر با همان شکل ظاهر شد: SHA‑256 به‌عنوان خلاصهٔ پیش‌فرض برای امضاهای PDF شد و SignatureSettings.log_level شروع به فیلتر کردن کرد به‌جای اینکه به‌سکوت نادیده گرفته شود. هر یک از این موارد، نتیجه‌ای که قبلاً به‌صورت خاموش رخ می‌داد را در مقابل شما می‌گذارند.

این مقاله این سه کنترل را همان‌طور که از Python به .NET رفتار می‌کنند مقایسه می‌کند – هر کدام چه تغییری در خروجی ایجاد می‌کند، چه زمانی باید از آن استفاده کنید و کدام دو جزئیات بایندینگ باعث هزینهٔ یک بعدازظهر برای افراد می‌شود. تمام نتایج نقل‌شده از اجرای نمونه بر روی یک PDF تک‌صفحه‌ای به‌دست آمده‌اند.

چرا این موضوع مهم‌تر از یک یادداشت نسخه است

این سه تغییر یک ویژگی مشترک دارند که شایستگی نام‌گذاری دارد: همهٔ آن‌ها یک شکست را که قبلاً بعداً کشف می‌شد، به شکستی تبدیل می‌کنند که هم‌اکنون کشف می‌شود.

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

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

پیش‌نیازها

قبل از شروع، اطمینان حاصل کنید که:

  • Python 3.9 یا بالاتر بر روی مفسری ۶۴‑بیتی نصب شده باشد – بسته یک زمان‌اجرای .NET باندل‌شده می‌آورد و چرخ‌دندهٔ ۳۲‑بیتی ندارد
  • GroupDocs.Signature برای Python از طریق .NET نسخهٔ ۲۶.۱۰.۰، همراه با یک مجوز موقت رایگان اگر می‌خواهید محدودیت‌های ارزیابی را حذف کنید
  • یک فایل PDF برای امضا داشته باشید و بستهٔ cryptography را اگر می‌خواهید گواهی‌نامه‌های آزمایشی یک‌بار مصرف بسازید همان‌طور که نمونه انجام می‌دهد، نصب کنید

نصب

pip install groupdocs-signature-net cryptography

کنترل ۱ - خلاصه‌ای که در امضا نوشته می‌شود

hash_algorithm در DigitalSignOptions خلاصه (digest) را انتخاب می‌کند. پیش‌فرض از نسخهٔ ۲۶.۹ SHA‑256 است، در قالب adbe.pkcs7.detached که اعتبارسنج‌های فعلی انتظار دارند؛ پیش از آن، امضاهای جدید SHA‑1 بودند.

with signature.Signature(source_path) as sign:
    options = DigitalSignOptions()
    options.certificate_stream = io.BytesIO(pfx)
    options.password = PASSWORD
    options.hash_algorithm = HashAlgorithm.SHA512
    options.reason = "Approved"

    result = sign.sign(output_path, options)
    return len(result.succeeded)

دو نکته شایستگی بیان دارند. گواهی‌نامه از طریق certificate_stream به‌صورت io.BytesIO می‌آید نه به‌صورت مسیر فایل، که همان‌طور است که یک PKCS#12 ساخته‌شده در حافظه به کتابخانه می‌رسد بدون اینکه هرگز روی دیسک نوشته شود – نمونه برای این است که هیچ کلید خصوصی‌ای را ارسال نکند. و HashAlgorithm گزینه‌های AUTO، SHA1، SHA256، SHA384 و SHA512 را ارائه می‌دهد؛ اگر یک مهر زمان اضافه کنید، همان خلاصه‌ای که امضا استفاده کرده است به کار می‌رود.

در عمل این کنترلی است که کم‌ترین دست‌کاری را انجام می‌دهید. پیش‌فرض هم‌اکنون پاسخ صحیح است، SHA384 و SHA512 برای مواقعی که سیاست امضا آن‌ها را نام می‌برد وجود دارند و SHA1 یک تنظیم سازگاری برای اعتبارسنج‌هایی است که نمی‌توانید تغییرشان دهید.

کنترل ۲ - آیا گواهی‌نامه منقضی‌شده مانع شما می‌شود

بدون هر گونه بازنویسی، امضا با گواهی‌نامه‌ای که دورهٔ اعتبارش به‌پایان رسیده یا هنوز شروع نشده است، GroupDocsSignatureException را پرتاب می‌کند و هیچ‌چیزی نمی‌نویسد.

try:
    sign.sign(output_path, options)
    return True
except signature.GroupDocsSignatureException as error:
    print(f"Rejected: {str(error).splitlines()[0]}")
    return False

پیام نام گواهی‌نامه، تاریخ انقضا، اثر انگشت و ویژگی‌ای که اجازهٔ آن را می‌داد را می‌گوید؛ این برای یک برنامه کافی است تا به اپراتور بگوید چه چیزی را باید تجدید کند. گرفتن فقط خط اول در Python اهمیت دارد: متن استثنا با ردیابی پشتهٔ .NET از پشت بایند ادامه می‌یابد و این چیزی نیست که باید به کاربر نشان داده شود.

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

settings = signature.SignatureSettings(ConsoleLogger())
settings.log_level = LogLevel.WARNING | LogLevel.ERROR

with signature.Signature(source_path, settings=settings) as sign:
    options.allow_expired = True
    result = sign.sign(output_path, options)

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

هر دو بازنویسی یک هشدار صادر می‌کنند به‌جای اینکه به‌سکوت عبور کنند؛ این همان بخشی است که به کنترل سوم متصل می‌شود.

کنترل ۳ - آیا کسی متوجه می‌شود

SignatureSettings.log_level یک مقدار پرچمی است. نمونه همان سند را سه بار امضا می‌کند، تحت LogLevel.NONE، LogLevel.WARNING | LogLevel.ERROR و LogLevel.ALL و تعداد پیام‌های دریافت‌شده را می‌شمارد:

logger = CollectingLogger()
settings = signature.SignatureSettings(logger)
settings.log_level = level

with signature.Signature(source_path, settings=settings) as sign:
    options.allow_expired = True
    sign.sign(output_path, options)

نتیجه شمارش به ترتیب هیچ‌چیز، سپس یک هشدار، سپس آن هشدار به‌علاوه ده ردیابی می‌شود. پیش از نسخهٔ ۲۶.۹ هر سه ردیف یکسان بودند، چون سطح پذیرفته می‌شد و نادیده گرفته می‌شد – این نکته‌ای است که اگر تا به‌حال یکی را تنظیم کرده‌اید، تغییری ندیده‌اید و فکر کرده‌اید کد خود را اشتباه خوانده‌اید، باید بدانید.

دو جزئیات بایندینگ برای من یک بعدازظهر هزینه کردند، بنابراین شفاف بیان می‌شوند. SignatureSettings.logger فقط‑خواندنی است، بنابراین لاگر باید به‌عنوان آرگومان سازنده داده شود و اختصاص به آن AttributeError می‌اندازد؛ log_level به‌صورت معمول پس از ساخت تنظیم می‌شود. همچنین لاگر سفارشی نباید از groupdocs.signature.logging.ILogger ارث‌بری کند – این کلاس پایه یک شی بومی را می‌پیچد که سازندهٔ آن به یک دستگیرهٔ متعلق به کتابخانه نیاز دارد، بنابراین ارث‌بری TypeError می‌دهد. بایند هر شی ساده‌ای که سه متد زیر را فراهم کند، می‌پذیرد:

class StdlibLogger:
    def error(self, message, exception=None):
        logging.getLogger("groupdocs").error(message, exc_info=exception)

    def warning(self, message, exception=None):
        logging.getLogger("groupdocs").warning(message)

    def trace(self, message):
        logging.getLogger("groupdocs").debug(message)

به error و warning یک پارامتر اختیاری exception بدهید. کتابخانه همیشه یک استثنا نمی‌فرستد و لاگری که به‌طور اجباری آن را می‌خواهد، در پیام‌هایی که آن را ندارند شکست می‌خورد.

مقایسهٔ سه مورد: چه زمانی از هر کدام استفاده کنیم

کنترل مناسب‌ترین حالت مزایای کلیدی محدودیت‌ها
hash_algorithm برآورده کردن سیاستی که یک خلاصه را نام می‌برد یک بار تخصیص؛ همان اندازهٔ خروجی بی‌فایده اگر خود گواهی‌نامه مورد اعتماد نباشد
بررسی اعتبار و بازنویسی‌ها هر امضایی که برای دیگران انجام می‌شود شکست در جایی می‌افتد که می‌توان آن را رفع کرد بازنویسی فایلی تولید می‌کند، نه فایلی قابل اعتماد
log_level سرویس‌هایی که لاگ‌هایشان از قبل شلوغ است یازده پیام به یک تبدیل می‌شود فقط لاگ را فیلتر می‌کند، هرگز استثناها را نیست

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

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

خیر. سطح لاگ فقط تعیین می‌کند چه پیام‌هایی به لاگر شما می‌رسند و هیچ چیز دیگر. گواهی‌نامهٔ منقضی‌شده همچنان تحت LogLevel.NONE GroupDocsSignatureException را پرتاب می‌کند و allow_expired همچنان تحت LogLevel.ALL امضا می‌کند؛ مقادیر بازگشتی و استثناها در تمام سطوح یکسان هستند. چیزی که تغییر می‌کند این است که آیا هشدار توضیح‌دهندهٔ امضای مشکوک توسط شخصی خوانده می‌شود یا نه.

تأیید به همان جهت پیش رفت

ارزش ذکر دارد چون نیمهٔ دیگر همان انتشار است. verify با یک DigitalVerifyOptions خالی اکنون هر امضای دیجیتال PDF را به‌صورت cryptographic بررسی می‌کند، بنابراین سندی که پس از امضا تغییر یافته باشد، به‌جای اینکه فقط «توضیح‌ناپذیر» باشد، نامعتبر برمی‌گردد:

with signature.Signature(signed_path) as sign:
    result = sign.verify(DigitalVerifyOptions())
    return result.is_valid

دو خط، و افزودن به هر خط لوله‌ای که امضا می‌کند و سپس ذخیره می‌کند، ارزش دارد. توجه کنید که یک مقدار True چه چیزی را تضمین نمی‌کند: می‌گوید امضا با سند مطابقت دارد، نه اینکه صادرکننده مورد اعتماد باشد. گواهی‌نامه‌های خودامضا در اینجا تأیید می‌شوند اما همچنان توسط یک خواننده PDF رد می‌شوند، که سؤال اعتماد را به‌صورت جداگانه پاسخ می‌دهد.

بهترین روش‌ها و نکات

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

نتیجه‌گیری

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

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

منابع اضافی