💡 דוגמה מלאה עובדת זמינה ב‑GitHub:
digital-signing-certificate-validity-dotnet

בעיית הציות שאף אחד לא רואה עד שמבקר מגלה אותה

שירות חתימה פועל שלוש שנים ללא שגיאה. המסמכים נשלחים, הנמענים מקבלים אותם, ולא נראה שום רמז ב‑logs לבעיה. ואז מאמת של צד שני מסמן אצווה כבלתי תקפה, והחקירה מגלה שני גורמים: החתימות נכתבו עם 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 לברירת מחדל, אמת מסמכים נכנסים קריפטוגרפית, ובחר רמת לוג לפני שתזדקק לה. הדוגמה מריצה את כל שש ההתנהגויות בפעם אחת, כולל הדחייה, כך שהשדרוג ניתן לתרגל במרחק של כמה דקות.

משאבים נוספים