💡 ตัวอย่างการทำงานเต็มที่มีบน 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 เป็นค่าเริ่มต้น, ตรวจสอบเอกสารที่เข้ามาเชิงคริปโตกราฟี, และเลือกระดับบันทึกก่อนที่คุณจะต้องใช้ ตัวอย่างรันพฤติกรรมทั้งหกในหนึ่งรอบรวมถึงการปฏิเสธ ทำให้การอัปเกรดสามารถฝึกซ้อมได้ในไม่กี่นาที

แหล่งข้อมูลเพิ่มเติม