PDF ที่คุณเพิ่งลงนามคือลายเซ็นระดับ B-B และไม่มีมากกว่านั้น มันพิสูจน์ว่าใครลงนามและไบต์ไม่ถูกแตะต้อง แต่ไม่ได้แบกหลักฐานว่าใบรับรองของผู้ลงนามถูกต้อง ณ เวลาลงนาม ผู้ตรวจสอบอีกหลายปีข้างหน้าจึงต้องออกไปหาข้อมูล revocation ที่อาจไม่มีเหลืออยู่แล้ว การปิดช่องว่างนี้หมายถึงการเขียนคำตอบ OCSP และ CRL ลง Document Security Store ระดับเอกสาร และใน HotPDF มันคือการเรียกครั้งเดียว: PopulatePAdESLTVEvidence เดินผ่านลายเซ็นที่โหลดไว้ทุกตัว สร้างคำขอ revocation จากชุดใบรับรอง รันมันผ่าน transport ที่คุณส่งเข้ามา และเขียนเนื้อหาที่ดึงมาได้พร้อมห่วงโซ่ CMS ลง DSS มันคืนจำนวนลายเซ็นที่หลักฐานลงทะเบียนสำเร็จ หรือคืนลบหนึ่งเมื่อเอกสารไม่มี signature field เลย
การตัดสินใจเชิงออกแบบที่ควรเข้าใจก่อนใช้คือไลบรารีไม่เคยเปิด socket เอง ทุกไบต์ที่มาจากเครือข่ายมาถึงผ่าน callback ที่คุณเขียน นั่นไม่ใช่ความระมัดระวังเพื่อความระมัดระวัง แต่เป็นหนทางเดียวที่ฟีเจอร์นี้จะทำงานได้ในสภาพแวดล้อมที่ต้องการ long-term validation จริง ๆ
ทำไมไลบรารีจึงปฏิเสธที่จะทำ HTTP เอง
เพราะสถานที่ที่บังคับใช้ลายเซ็น B-LT คือสถานที่ที่ไม่อาจไว้ใจให้ไลบรารีแตะเครือข่ายได้ บริการลงนามวิ่งอยู่หลัง proxy ที่ยืนยันตัวตนพร้อม root ขององค์กร ชั้นลงนามแบบ air-gapped ไม่มีเส้นทางไปหาผู้ตอบและต้องได้รับหลักฐานจาก cache ระบบ audit บังคับว่าคำขอขาออกทุกคำถูกบันทึกโดยแอปพลิเคชัน ไม่ใช่ฝังอยู่ใน dependency และชุดทดสอบต้องการคำตอบที่ deterministic ซึ่งเป็นไปไม่ได้ถ้าไลบรารีโทรออกเอง
transport เป็น function reference ธรรมดาที่มีรูปร่างคงที่ นโยบายจึงยังเป็นของคุณ HotPDF ส่ง request record ให้คุณที่อธิบายว่าจะดึงอะไรอย่างชัดเจน รวมถึง content type และเพดานขนาดคำตอบ และคุณคืนไบต์พร้อมสถานะกลับมา
function FetchEvidence(const Request: THPDFSignatureEvidenceRequest;
Attempt: Integer; CancellationToken: THPDFCancellationToken;
out Response: TBytes; out RetryAfterMS: Cardinal;
out ErrorMessage: UnicodeString): THPDFSignatureEvidenceTransportStatus;
begin
RetryAfterMS := 0;
try
// Request.Kind บอกว่านี่คือ OCSP POST หรือ CRL GET;
// Request.ContentType กับ Request.Body ถูกเตรียมไว้แล้ว
// และ Request.MaxResponseBytes คือเพดานที่คุณต้องเคารพ
Response := HttpExchange(Request.URI, Request.ContentType,
Request.Body, Request.MaxResponseBytes);
Result := setsSucceeded;
except
on E: Exception do
begin
ErrorMessage := E.Message;
// setsRetry ให้นโยบาย retry ถอยห่างได้ ส่วน 404 หรือ URL เสีย
// ให้ใช้ setsPermanentFailure
Result := setsRetry;
end;
end;
end;
// การอัปเกรด B-B เป็น B-LT ในการเรียกเดียว สำหรับลายเซ็นทุกตัวในไฟล์ที่โหลดไว้
var
Pdf: THotPDF;
Upgraded: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginIncrementalUpdate('signed.pdf');
Upgraded := Pdf.PopulatePAdESLTVEvidence(FetchEvidence,
THPDFSignatureEvidenceRetryPolicy.Default);
if Upgraded > 0 then
// บันทึกแบบ append-only: ไบต์ที่ลายเซ็นเดิมครอบอยู่ถูกเก็บ
// ไว้ครบถ้วนตามเดิมทุกไบต์
Pdf.SaveIncrementalUpdate('signed-lt.pdf');
finally
Pdf.Free;
end;
end;
ความล้มเหลวเป็นรายลายเซ็น ไม่ใช่รายเอกสาร ผู้ตอบที่ timeout กับผู้ลงนามหนึ่งรายจะข้ามเนื้อหาของรายนั้นและเก็บส่วนที่เหลือของรอบไว้ครบถ้วน ซึ่งเป็นพฤติกรรมที่คุณต้องการในการประมวลผลเป็นชุด: หลักฐานบางส่วนดีกว่าการรันที่ถูกยกเลิก และค่าคืนกลับบอกคุณว่าลายเซ็นกี่ตัวที่ดีขึ้นจริง
ห่วงโซ่ที่ CMS ลืมใส่มาด้วย
การตรวจสอบ revocation ต้องใช้ใบรับรอง issuer และมีกองซอฟต์แวร์ลงนามจำนวนน่าประหลาดใจที่ลืมใส่ intermediate ในคอนเทนเนอร์ CMS เส้นทางกู้คืนคือ extension Authority Information Access, access method 1.3.6.1.5.5.7.48.2 ซึ่งโฆษณา URL ที่ใบรับรอง issuer ดาวน์โหลดได้ HPDFFetchAIAIntermediates เดินผ่าน URL เหล่านั้นด้วย transport เดียวกัน parse DER ออกจากคำตอบแต่ละชุด และคืนเฉพาะใบรับรองที่ CMS ยังไม่มี โดยใช้ DER hash เป็น key เพื่อให้ข้อมูลซ้ำและวงวนเดินหมุนไม่ได้
รายละเอียดสองข้อตัดสินว่ามันใช้กับหน่วยออกใบรับรองจริงได้หรือไม่ ข้อแรกคือ encoding: ปลายทาง CA ส่งใบรับรองเป็น DER เปล่า ๆ บ่อยพอ ๆ กับการส่งเป็น PEM ที่มีเกราะหุ้ม และไม่มี content type ที่เชื่อถือได้ในการแยกสองอย่าง การ probe ที่ทนทานคือเชิงข้อความก่อนแล้วจึงเชิงโครงสร้าง มองหาเครื่องหมาย -----BEGIN CERTIFICATE----- ถ้ามีให้ถอดเกราะและ decode base64 และในทั้งสองเส้นทางให้ยืนยันว่าไบต์แรกของผลลัพธ์คือ $30 ซึ่งเป็น DER tag ของ SEQUENCE ข้อที่สองคือความลึก: intermediate ที่ดึงมาอาจโฆษณา URL แบบ AIA สำหรับ issuer ของมันเองต่อ การเดินจึงแนบผู้สมัครใหม่เข้าคิวและเติมห่วงโซ่ที่ขาดสองสามจังหวะให้สมบูรณ์ ซึ่งต้องมีเพดาน นั่นคือหน้าที่ของพารามิเตอร์ MaxFetch
seed value ของลายเซ็นคืออะไร และทำไมจึงล้มเหลวแบบเงียบ ๆ
seed value คือข้อจำกัดที่ผู้เขียนเอกสารแนบไปกับ signature field เพื่อบอกผู้ลงนามว่าลายเซ็นแบบใดยอมรับได้: SubFilter ใด อัลกอริทึม digest ใด เหตุผลใด เวอร์ชัน PDF ขั้นต่ำเท่าไร และต้องฝังข้อมูล revocation หรือไม่ มันอยู่ใน dictionary /SV บนฟิลด์ และถูกนิยามไว้ใน ISO 32000-1 §12.7.5.5 HotPDF เขียนมันด้วย AttachPAdESSeedValue และตรวจมันด้วย CheckLoadedSignatureSeedValue ซึ่งคืน True เมื่อฟิลด์ไร้ข้อจำกัดหรือข้อจำกัดที่มีอยู่ทุกข้อผ่าน และเมื่อเป็น False จะระบุข้อจำกัดแรกที่ล้มเหลวผ่านพารามิเตอร์ output ที่คุณนำไปใส่ข้อความ error ได้ทันที
กลไกที่ทำให้ seed value ทำผิดง่ายคือ entry ของ flag /Ff ตามที่ §12.7.5.5.3 อธิบาย บิตที่ตั้งไว้หมายถึงข้อจำกัดนั้นเป็นข้อบังคับ: ความไม่ตรงกันคือ error และผู้ลงนามต้องปฏิเสธ บิตที่เคลียร์ไว้หมายถึงข้อจำกัดเดียวกันเป็นเพียงความชอบ: ค่านั้นกรองสิ่งที่ UI ควรเสนอ และไม่มีมากกว่านั้น มีกับดักสองข้อที่ตามมา หนึ่ง /Ff อยู่ข้างใน dictionary /SV ไม่ใช่บน widget annotation โค้ดที่อ่าน /Ff ระดับฟิลด์จึงได้คำตอบว่างตลอดไปและสรุปว่าไม่มีอะไรถูกบังคับ สอง การจัดบิตไม่ใช่แถวเรียงหนึ่ง สอง สี่ แปด ใน HotPDF ฝั่งเขียนปล่อยค่า 2 สำหรับ SubFilter, 4 สำหรับ MinVersion, 32 สำหรับ AddRevInfo และ 64 สำหรับ DigestMethod ตัวอ่านที่สมมติบิตเรียงต่อกันจะ decode ข้อจำกัดทุกข้อเป็นแบบเสริม และผ่านการทดสอบทุกข้อ ยกเว้นข้อเดียวที่สำคัญที่สุด
var
Violation: AnsiString;
begin
// ถามฟิลด์ว่า profile ที่เรากำลังจะลงนามด้วยอนุญาตหรือไม่
if not Pdf.CheckLoadedSignatureSeedValue(0, 'ETSI.CAdES.detached',
'SHA256', 'Approved for payment', 1, Violation) then
raise Exception.Create('Signature field rejects this profile: ' +
String(Violation));
// ข้อจำกัดผ่าน: ดำเนินการลงนามต่อไป
end;
การทดสอบที่เผยบั๊ก decoding ดั้งเดิมไม่ใช่การทดสอบเชิงบวก แต่เป็น assertion ว่าความไม่ตรงกันแบบบังคับต้องถูกปฏิเสธ ซึ่งเป็นชนิดทดสอบเดียวที่จับข้อบกพร่องชั้นนี้ได้: decoder ที่อ่าน dictionary ผิดหรือตำแหน่งบิตผิดจะให้ผล "ไม่มีข้อจำกัดถูกละเมิด" กับทุกอินพุต ซึ่งดูเหมือนพฤติกรรมที่ถูกต้องพอดี จนกว่าคุณจะละเมิดหนึ่งข้ออย่างตั้งใจ
จุดที่เรื่องนี้อยู่บนบันได LTV
มีสี่ขั้น และแต่ละขั้นต้องใช้ขั้นที่ต่ำกว่า B-B คือลายเซ็นเปลือย B-T เพิ่ม trusted timestamp ซึ่งตรึงเวลาลงนามไว้ ผู้ตรวจสอบจึงรู้ว่าควรประเมิน revocation ที่ช่วงเวลาใด B-LT เพิ่มหลักฐาน revocation เข้า DSS ซึ่งคือสิ่งที่ PopulatePAdESLTVEvidence ทำให้อัตโนมัติ B-LTA เพิ่ม document timestamp ที่ถูกต่ออายุก่อนตัวก่อนหน้าจะอ่อนแรง ทำให้ความถูกต้องยืดออกไปได้ไม่จำกัด HotPDF เปิดให้ใช้เป็น RenewPAdESLTATimestamp ซึ่งแนบ timestamp ใหม่เข้ามาเป็น revision แบบ incremental และเก็บลายเซ็นก่อนหน้า timestamp ทุกตัวและ entry ของ DSS ไว้ไม่ถูกแตะต้อง
โมเดล incremental-update เป็นหนทางที่ถูกต้องเพียงหนทางเดียวในการเพิ่มหลักฐานเข้าเอกสารที่ลงนามแล้ว เพราะการเขียนไฟล์ใหม่ทั้งหมดจะทำลาย byte range ที่ลายเซ็นเดิมครอบอยู่ ถ้าคุณต้องเหตุผลกับว่าอะไรเปลี่ยนระหว่าง revision และว่าการเปลี่ยนเหล่านั้นเป็นชนิดที่ลายเซ็นยอมรับหรือไม่ การวิเคราะห์นั้นอยู่ในการวิเคราะห์ revision แบบ DocMDP และ FieldMDP ตัว pipeline การลงนามเอง รวมถึงแหล่งใบรับรองและกับดักลำดับไบต์ อยู่ในคู่มือเดินลงนาม PAdES และฝั่งการตรวจสอบอยู่ในการตรวจสอบลายเซ็นบนเอกสารที่โหลดไว้
คำเตือนเชิงปฏิบัติเรื่องลำดับหนึ่งประเด็น รวบรวมหลักฐานโดยเร็วที่สุดหลังลงนาม ในงานเดียวกันถ้าเป็นไปได้ ผู้ตอบที่ตอบแทนใบรับรองได้จะออนไลน์ระหว่างที่ใบรับรองยังมีอายุ และหายไปอีกหลายปีให้หลัง เอกสารที่ออกจาก pipeline ของคุณในสถานะ B-B จึงอาจไม่มีวันถูกอัปเกรดได้อีก HotPDF วิ่งเป็น VCL component แบบ native สำหรับ Delphi และ C++Builder และรอบการรวบหลักฐานทั้งหมดอยู่ใน process เดียว ยกเว้น transport ของคุณเอง profile ที่รองรับระบุไว้บนหน้าผลิตภัณฑ์HotPDF Delphi PDF component