การตัดสินว่าลายเซ็น PDF เป็น qualified ตาม eIDAS หมายถึงการตอบคำถามที่ไม่เกี่ยวกับ cryptography เลย: ใบรับรองถูกออกโดย trust service ที่ประเทศสมาชิกจดทะเบียนไว้ว่า qualified หรือไม่ ณ ขณะที่ลายเซ็นเกิดขึ้น คำตอบอยู่ใน trusted list เอกสาร XML ที่เผยแพร่แยกรายดินแดน และคุณค่าทั้งหมดของเอกสารนั้นพึ่งความจริงแท้ของมัน component PDFium จึงปฏิเสธที่จะมองข้างในจนกว่าจะมีใครออกใบรับรองมัน TPdfEuropeanTrustedList.ParseAuthenticated ส่งไบต์ดิบทั้งหมดให้ IPdfTrustedListAuthenticator ที่ผู้เรียกส่งเข้ามาก่อนมันจะ parse service แม้แต่ตัวเดียว และมันสร้าง snapshot ขึ้นก็ต่อเมื่อ authenticator นั้นผ่านอย่างชัดเจน
ลำดับนั้นคือการออกแบบ ทุกอย่างอื่นในฟีเจอร์นี้ไหลตามมา รวมถึงส่วนที่ดูไม่สะดวกด้วย
parse ผ่านไม่ได้แปลว่าเชื่อถือได้
trusted list ที่ parse ผ่านสะอาดบอกคุณแค่ว่า XML สร้างถูกหลัก มันไม่บอกว่าใครเขียนมัน เมื่อรายการนี้คือฐานที่การตัดสินสถานะ qualified ทั้งหมดของคุณยืนอยู่ การยอมรับมันเพราะมัน parse ผ่านจะทำให้การตัดสินไร้ความหมาย: ผู้โจมตีที่แทนรายการได้ก็ประกาศ certificate authority ของตัวเองให้เป็น qualified ได้
การเหตุผลชุดเดียวกันใช้กับ cache และนี่คือกับดักที่สมควรตั้งชื่อ cache snapshot เก็บ XML ต้นฉบับพร้อม digest SHA-256 และมันจะง่ายมากถ้าจะถือ digest ที่ตรงกันตอนโหลดเป็นหลักฐานว่ารายการแท้ มันไม่ใช่ digest ที่ถูกคำนวณโดย process เดียวกับที่เก็บไฟล์ โดยไม่มี key เข้ามาเกี่ยวข้อง พิสูจน์ได้แค่ว่าไบต์ไม่เปลี่ยนนับแต่คุณเขียนมัน ถ้ารายการเป็นของปลอมอยู่แล้วตอนถูก cache digest ก็ยืนยันว่ามันคือรายการปลอมชุดเดิม การโหลด snapshot จาก cache จึงวิ่งผ่าน authenticator ตัวเดียวกับการ parse รายการสด ความคงเดิมกับความแท้เป็นคุณสมบัติต่างกัน และมีเพียงตัวเดียวที่ต้องใช้ key
uses
FPdfTrustedList;
type
TListAuthenticator = class(TInterfacedObject, IPdfTrustedListAuthenticator)
public
function Authenticate(const XmlData: TBytes;
out AuthenticationDetails: string): Boolean;
end;
function TListAuthenticator.Authenticate(const XmlData: TBytes;
out AuthenticationDetails: string): Boolean;
begin
// นโยบายของคุณอยู่ตรงนี้: ตรวจลายเซ็น XMLDSIG แบบ enveloped
// กับใบรับรองผู้ลงนามรายการที่คุณ pin ไว้นอกช่องทางไฟล์
// และบรรยายสิ่งที่คุณเช็กไว้สำหรับ audit trail
Result := VerifyEnvelopedXmlSignature(XmlData, FPinnedListSigner);
if Result then
AuthenticationDetails := 'XMLDSIG verified against pinned LOTL signer';
end;
var
List: TPdfEuropeanTrustedList;
Cache: TFileStream;
begin
List := TPdfEuropeanTrustedList.ParseAuthenticated(RawXml,
TListAuthenticator.Create, TPdfTrustedListOptions.Default);
// snapshot มีอยู่เพราะ authenticator ตอบตกลงเท่านั้น
Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
try
List.SaveCache(Cache);
finally
Cache.Free;
end;
end;
validator ไม่ใช่เจ้าของนโยบายเครือข่าย
PAdES validator ไม่มีหน้าที่ตัดสินว่าจะไปถึงรายการ trusted lists อย่างไร จะผ่าน proxy หรือไม่ จะลองซ้ำกี่ครั้ง หรือจะทำอย่างไรเมื่อดินแดนหนึ่งติดต่อไม่ได้ พวกนั้นเป็นการตัดสินของแอปพลิเคชันและ deployment และในสภาพแวดล้อมที่มีกฎเกณฑ์กำกับ พวกมันมักถูก audit การอัปเดตจึงมาถึงผ่าน IPdfTrustedListSource ซึ่งได้รับ URI กับเพดานขนาดไบต์แล้วคืนไบต์กลับมา
สิ่งที่ component บังคับจริงคือ invariant ชุดที่ทำให้การอัปเดตเป็นการอัปเดต ไม่ใช่การแทนที่ Update เรียกใช้เงื่อนไขว่าดินแดนไม่เปลี่ยน เลขลำดับเพิ่มขึ้นอย่างเข้มงวด และเวลาออกไม่ไหลย้อนกลับ สามการเช็กนี้ทำลายการโจมตี downgrade ที่ชัดที่สุด: การเล่นซ้ำรายการเก่าที่ยังระบุ service ที่ถูกถอนไปแล้ว หรือการสลับเอารายการของดินแดนอื่นที่ service ของมันคุณไม่เคยตั้งใจจะเชื่อถือ
ขีดจำกัดของ parser และการห้าม DTD เด็ดขาด
TPdfTrustedListOptions เพดานขนาด XML, จำนวน token, ความลึกการซ้อน, จำนวน service, จำนวนใบรับรอง และขนาดใบรับรองแต่ละใบ โดยมี class function Default ให้ค่าที่ใช้งานได้ trusted list เป็นเอกสารที่เผยแพร่และมีขนาดคาดเดาได้ การตั้งขอบเขตจึงแทบไม่มีต้นทุน และไม่มีรายการที่ชอบด้วยกฎหมายตัวใดที่ต้องการเกินขอบเหล่านั้น
แยกออกมาและไม่มีเงื่อนไข ตัว parser ปฏิเสธการประกาศ DTD และ entity สิ่งนี้ปิดทั้ง denial of service แบบ entity-expansion และเส้นทางเปิดเผยผ่าน external entity ในการปฏิเสธเดียว โดยไม่มีต้นทุน เพราะ trusted list ไม่ใช้ entity XML parser ตัวใดที่เข้าถึงได้จากอินพุตที่ไม่น่าเชื่อถือสมควรถูกตั้งค่าแบบนี้ ความต่างที่นี่คือการปฏิเสธไม่สามารถตั้งค่าได้ มันจึงถูกปิดด้วยการเปลี่ยน option ที่ตั้งใจดีแต่ทำให้ความปลอดภัยหมดไปไม่ได้
สถานะ qualified ถูกบันทึกเคียงข้างความเชื่อถือห่วงโซ่ ไม่ได้ถูกหลอมรวม
ฝั่งการประเมินถูกแยกไว้โดยตั้งใจ TPadesTrustValidationOptions.QualifiedTrustEvaluator รับ IPdfQualifiedTrustEvaluator ซึ่ง snapshot ของ trusted list เป็นผู้ implement ระหว่าง validation evaluator ได้รับใบรับรองใบปลาย ห่วงโซ่ และเวลาประเมิน จับคู่ใบรับรอง service ด้วยการเทียบ DER เป๊ะ ๆ กับผู้ลงนามและห่วงโซ่ รวมสถานะ service, identifier ชนิด service และ URI ของ qualifier ณ ช่วงเวลานั้น แล้วคืน record การประเมิน
ผลลัพธ์ลงสองที่บนลายเซ็นแต่ละตัว: QualifiedTrustStatus เป็นสถานะหยาบ และ QualifiedTrust เป็นการประเมินฉบับเต็มพร้อมดินแดน ชื่อ provider, ชื่อ service, identifier ชนิด, สถานะและเวลาเริ่มของสถานะ สิ่งที่มันไม่ทำคือการเปลี่ยน CertificateTrustStatus ความเชื่อถือห่วงโซ่ระดับระบบกับสถานะ qualified ตอบคนละคำถาม รายงานที่หลอมรวมสองอย่างนี้แยก "เชื่อถือได้แต่ไม่ qualified" ออกจาก "qualified แต่ห่วงโซ่ตรวจไม่ผ่าน" ไม่ได้ ทั้งคู่เป็นกรณีจริงและทั้งคู่ต้องการการจัดการต่างกัน
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
I: Integer;
begin
Options := TPadesTrustValidationOptions.Default;
Options.CheckRevocation := True;
Options.QualifiedTrustEvaluator := List; // snapshot ที่พิสูจน์แท้แล้ว
Options.QualifiedValidationTime := SigningTime; // ไม่ใช่ Now
Report := Pdf.ValidatePadesTrust(Options);
for I := 0 to High(Report.Signatures) do
if Report.Signatures[I].QualifiedTrustStatus = pcsValid then
Writeln(Format('signature %d qualified by %s / %s (%s)',
[I, Report.Signatures[I].QualifiedTrust.Territory,
Report.Signatures[I].QualifiedTrust.ProviderName,
Report.Signatures[I].QualifiedTrust.ServiceName]))
else if Report.Signatures[I].QualifiedTrustStatus = pcsIndeterminate then
// ไม่มี service ตรงกัน หรือ snapshot ตอบไม่ได้กับช่วงเวลานี้
Writeln(Format('signature %d: qualified status undetermined', [I]));
end;
ทำไมเวลาประเมินไม่ใช่ตอนนี้
เพราะสถานะ qualified เป็นคุณสมบัติของช่วงเวลา trust service หนึ่งอาจได้รับสถานะ qualified แล้วถูกถอนคืนในภายหลัง และถูกฟื้นคืนในภายหลังอีก การเปลี่ยนสถานะแต่ละครั้งแบกเวลาเริ่มมาด้วยในรายการ ลายเซ็นที่เกิดขึ้นขณะ service ยัง qualified ยังเป็น qualified ต่อไปหลังจากนั้น ส่วนลายเซ็นที่เกิดก่อนการมอบอำนาจไม่ถูกย้อนให้เป็น qualified การประเมินกับเวลาปัจจุบันจึงให้คำตอบผิดทั้งสองทิศ
รายการแบกข้อมูลที่จำเป็นสำหรับเรื่องนี้: record service แต่ละตัวมีเวลาเริ่มสถานะและ flag ที่แยก entry ย้อนเวลาออกจาก entry ปัจจุบัน และ evaluator รวมพวกมันเทียบกับเวลาที่คุณส่งเข้าไป ในทางปฏิบัติเวลานั้นมาจาก trusted timestamp บนลายเซ็น ไม่ใช่เวลาลงนามที่อ้างไว้ใน CMS เหตุผลที่เนื้อหา long-term validation สำคัญแม้กับคำถามที่ดูเหมือนการค้นข้อมูลนโยบายธรรมดา ฝั่ง timestamp และ DSS อธิบายไว้ในบทความลายเซ็นระยะยาว
สิ่งที่คุณยังต้องสร้างเอง
สามอย่าง และไม่มีข้อใดสมควรอยู่ในไลบรารี PDF authenticator หมายถึงการตรวจ XMLDSIG จริง ๆ กับใบรับรองผู้ลงนามรายการที่คุณได้มาผ่านช่องทางที่คุณเชื่อถือ นโยบายการดึงข้อมูล หมายถึงวิธีและความถี่ที่คุณ refresh และที่แอปพลิเคชันทำเมื่อการ refresh ล้มเหลว และขอบเขตตามดินแดน หมายถึงรายการใดที่คุณแบกมาเลย ซึ่งเป็นการตัดสินเชิงธุรกิจว่าคู่ค้าของคุณลงนามในประเทศสมาชิกใด
สิ่งที่คุณได้จาก component คือส่วนที่ทำผิดแบบละเอียดอ่อนได้ง่าย: ลำดับพิสูจน์แท้ก่อน parse, การ parse XML ที่มีขอบเขตและไร้ entity, invariant การอัปเดตแบบ monotonic, การจับคู่ service ด้วย DER เป๊ะ, การประเมินสถานะย้อนเวลา และผลลัพธ์ที่แยกจากความเชื่อถือห่วงโซ่ธรรมดา ถ้าปัญหาเร่งด่วนของคุณพื้นฐานกว่านั้น คือ validator ปฏิเสธลายเซ็นที่คุณเชื่อว่าปกติ สาเหตุทั่วไปถูกจัดหมวดไว้ในทำไม validator ปฏิเสธลายเซ็น PAdES และพื้นผิวการตรวจลายเซ็นอธิบายไว้ในการส่องลายเซ็นและระดับ PAdES ความสามารถของ component ระบุไว้บนหน้าผลิตภัณฑ์PDFium Delphi component