บทความเทคนิค

จัดประเภทสิ่งที่เปลี่ยนใน PDF หลังถูกลงนาม

ลายเซ็นที่ครอบ PDF ไม่ได้ห้ามการเปลี่ยนแปลงในภายหลัง มันตรึงช่วงไบต์เอาไว้ และ incremental update จะแนบไบต์ใหม่ต่อท้ายเข้าไป ลายเซ็นจึงยังคง valid ในเชิงคณิตศาสตร์ทั้งที่เอกสารได้เนื้อหาใหม่เพิ่มเข้ามาแล้ว ว่าเนื้อหานั้นยอมรับได้หรือไม่เป็นคำถามเชิงนโยบาย และ DocMDP คือจุดที่ผู้เขียนประกาศนโยบายนั้น: ห้ามเปลี่ยนเด็ดขาด, อนุญาตเฉพาะการกรอกฟอร์มและการลงนาม หรือเพิ่ม annotations เข้ามาอีกชั้น การบังคับใช้มันหมายถึงการจัดประเภทว่าอะไรเปลี่ยนไปจริง ๆ ซึ่งคือหน้าที่ของ AnalyzeModifications ชี้มันไปที่ revision ก่อนหน้า แล้วอ่าน GetModificationLevel เพื่อรับคำตัดสินรวม พร้อม accessor รายข้อค้นพบสำหรับระดับ หมายเลขออบเจกต์ และคำอธิบายของความต่างแต่ละจุด

เมื่อมีชิ้นส่วนเหล่านี้พร้อม การบังคับใช้ DocMDP ยุบลงเหลือการเทียบเดียว: ระดับที่คำนวณได้อยู่ที่หรือต่ำกว่าระดับที่นโยบายอนุญาตหรือไม่

แผนภาพบันไดระดับการเปลี่ยนแปลงของ PDFlibPas จาก mlNone ถึง mlUnclassified แสดงการบังคับใช้นโยบาย DocMDP เป็นการเทียบเดียวใน Delphi
บันได TPLModificationLevel ไต่จาก mlNone ถึง mlUnclassified และการบังคับใช้ DocMDP ลดลงเหลือการเทียบระดับที่คำนวณได้กับนโยบาย

ทำไม PDF ที่ลงนามแล้วจึงเปลี่ยนได้ตามที่คาด

มีกรณีที่ชอบด้วยกฎหมายสามกรณี และพวกมันครอบคลุมสิ่งที่คุณจะเจอส่วนใหญ่ ผู้ลงนามคนที่สองเพิ่มลายเซ็นของตนเข้ามา ผู้รับกรอกฟิลด์ฟอร์มที่ผู้เขียนเปิดไว้ และเนื้อหาสำหรับ long-term validation ถูกแนบเข้ามา: คำตอบ OCSP และ CRL ถูกเขียนลง document security store เพื่อให้ลายเซ็นยังตรวจสอบได้แม้ผู้ตอบจะหายไปแล้ว กรณีสุดท้ายนี้ไม่ได้แค่ถูกอนุญาต แต่คือสิ่งที่คลังเอกสารที่จัดการดีทำกับเอกสารที่ลงนามแล้วอย่างตั้งใจ

ประโยคว่า "ไฟล์โตขึ้นหลังลงนาม" จึงไม่แบกข้อมูลใด ๆ คำถามที่แท้จริงคือมีอะไรถูกเพิ่มเข้ามา และคำตอบต้องมาจากการเทียบสถานะเอกสาร ไม่ใช่จากการจ้องไบต์ กลไกการ append เองอธิบายไว้ในบทความ incremental update

จัดประเภทจากรูปร่างของออบเจกต์ ไม่ใช่จากเส้นทางที่ผลิตมัน

ตัวจัดประเภทมองว่าออบเจกต์ เป็น อะไรหลังการเปลี่ยนแปลง ไม่ใช่ว่าการเรียกไลบรารีไหนสร้างมันขึ้น นั่นคือการตัดสินใจอย่างมีสติ เพราะการวิเคราะห์รันกับไฟล์ที่ผลิตโดยซอฟต์แวร์อื่น ซึ่งไม่มี call path ให้ตรวจสอบอยู่แล้ว

มีสี่รูปร่างที่ถูกจำแนก document security store และ information dictionaries ที่เกี่ยวกับ validation, ออบเจกต์ cross-reference stream, entry metadata ของ catalog และ signature dictionary ที่แบก byte range ล้วนเป็นเนื้อหาแบบ long-term-archive ออบเจกต์ที่แบกทั้ง field type และ field value คือการกรอกฟอร์ม ออบเจกต์ที่ type เป็น annotation หรือ subtype ตรงกับรายการใน ISO 32000-2 Table 168 คือการเปลี่ยนแปลง annotation และทุกอย่างนอกเหนือจากนี้เป็น unclassified

ต้นไม้การตัดสินใจที่ PDFlibPas ใช้กับออบเจกต์ PDF ที่เปลี่ยนไปแต่ละตัว จัดการอัปเดตเข้าระดับ archive, การกรอกฟอร์ม, annotation หรือ unclassified
ออบเจกต์ที่เปลี่ยนไปแต่ละตัวถูกจัดประเภทจากสิ่งที่มันเป็น ไม่ว่าจะ security store, xref stream, field หรือ annotation ไม่เคยจากการเรียกที่ผลิตมัน

การถูกลบถูกปฏิบัติเข้มกว่าการถูกเพิ่ม ออบเจกต์ที่หายไปจะเข้า whitelist ได้เมื่อออบเจกต์ฝั่งเก่าเป็นเนื้อหา archive เองเท่านั้น ซึ่งครอบคลุมกรณีปกติของ security store ที่ถูกแทนด้วยตัวใหม่ การลบอื่นทั้งหมดเป็น unclassified เพราะการลบเนื้อหาออกจากเอกสารที่ลงนามแล้วไม่ใช่สิ่งที่ระดับสิทธิ์ใดอนุญาต ความต่างระดับเอกสารเข้มกว่านั้นอีก: การเปลี่ยนจำนวนหน้าถูกส่งตรงไป unclassified ทันทีโดยไม่สำรวจออบเจกต์รายตัว เพราะไม่มีระดับ DocMDP ใดอนุญาตให้เพิ่มหรือลบหน้า

whitelist ที่เอนไปทางการปฏิเสธ

นี่คือกฎการออกแบบที่คุมการตัดสินใจทุกจุดก้ำกึ่ง การเปลี่ยนแปลงที่ถูกจัดผิดว่าอนุญาตคือลายเซ็นที่ผ่านครอบเนื้อหาที่ผู้เขียนไม่เคยอนุญาต ส่วนการเปลี่ยนแปลงที่ถูกจัดผิดว่า unclassified คือเอกสารที่ถูกตั้งใส่และถูกคนมาตรวจทาน สองความผิดพลาดนี้ไม่สมมาตรกัน whitelist จึงต้องแคบ และรูปร่างที่ไม่รู้จักจะไหลไป unclassified แทนที่จะถูกเดาให้

ผลทางปฏิบัติที่ควรคาดการณ์ไว้ก็คือไฟล์จากผู้ผลิตหายากจะบางครั้งรายงานการเปลี่ยนแปลง unclassified ที่เมื่อส่องดูแล้วบริสุทธิ์ การตอบสนองที่ถูกคือไปอ่านรายละเอียดของข้อค้นพบและหมายเลขออบเจกต์ ไม่ใช่การขยาย whitelist เพราะ whitelist ที่โตไปเพื่อเงียบรายงานรายจุดก็เลิกเป็นการควบคุมด้านความปลอดภัย

uses
  PDFlibrary, PDFlibCompare;

var
  Pdf: TPDFlib;
  I, Level: Integer;
begin
  Pdf := TPDFlib.Create(nil);
  try
    Pdf.LoadFromFile('contract-countersigned.pdf', '');
    if Pdf.AnalyzeModifications('contract-as-signed.pdf', '') < 0 then
      raise Exception.Create('the earlier revision could not be loaded');

    // TPLModificationLevel เรียงจาก mlNone, mlLTAUpdates, mlFormFilling,
    // mlAnnotations, mlUnclassified; getter คืนค่า ordinal ของมัน
    Level := Pdf.GetModificationLevel;
    // การบังคับใช้ DocMDP ตอนนี้เหลือเทียบเดียวกับนโยบาย
    if Level > Ord(mlFormFilling) then
      for I := 0 to Pdf.GetModificationFindingCount - 1 do
        Report.Add(Format('object %d, level %d: %s',
          [Pdf.GetModificationFindingObjNum(I),
           Pdf.GetModificationFindingLevel(I),
           Pdf.GetModificationFindingDetail(I)]));
  finally
    Pdf.Free;
  end;
end;

ระดับรวมคือค่าสูงสุดเหนือข้อค้นพบทั้งหมด ซึ่งเป็นการรวมที่ป้องกันตัวได้ทางเดียว: เอกสารที่มีการเพิ่มแบบ archive เก้าสิบเก้าจุดกับการเปลี่ยนแปลง unclassified หนึ่งจุด คือเอกสารที่มีการเปลี่ยนแปลง unclassified

เบื้องล่าง: fingerprint ไม่ใช่ cryptographic hash

เครื่องมือเทียบที่ CompareWith เปิดให้ใช้ และที่การวิเคราะห์การเปลี่ยนแปลงสร้างบนมัน ระบุออบเจกต์ด้วย fingerprint ของ body ที่ผ่านการ normalize โดยใช้ hash 64 บิตแบบไม่ใช่ cryptography แทน SHA-256 นั่นคือทางเลือกที่คิดมาแล้ว สิ่งที่การเทียบโครงสร้างต้องการคือความ deterministic: body ออบเจกต์ชุดเดียวกันต้องให้ fingerprint เดิมเสมอภายในการรันเดียวกัน มันไม่ต้องการความต้านทานการชน เพราะผู้โจมตีที่คุมทั้งสองฝั่งของการเทียบชนะไปแล้วด้วยวิธีอื่น และการจ่าย hash เชิง cryptography เต็มรูปแบบกับออบเจกต์ทุกตัวในเอกสารล้านออบเจกต์คือต้นทุนจริงที่ไม่มีผลตอบแทน

กฎ normalize สองข้อสำคัญกว่าการเลือก hash การอ้างแบบ indirect พับเป็น token แทนตำแหน่ง แทนการขยายออกเป็นเนื้อหาที่ถูกอ้าง: การขยายจะก๊อป body ของออบเจกต์ที่ถูกแชร์ลงทุกผู้อ้าง การแก้เล็ก ๆ ครั้งเดียวกับ font descriptor ที่ถูกแชร์จึงทำให้ fingerprint ของทุกออบเจกต์ที่ไปถึงมันพัง และรายงานจะอ่านไม่ออก และหมายเลขออบเจกต์เองถูกตัดออกจาก fingerprint เพราะการเขียนใหม่สามารถเรียงเลขออบเจกต์ใหม่ได้โดยไม่เปลี่ยนอะไรเชิงความหมาย

การจับคู่แล้ววิ่งสองรอบ จัดแนวด้วย fingerprint ก่อน แล้วจับคู่ที่เหลือด้วยหมายเลขออบเจกต์เพื่อระบุการเปลี่ยนแปลง แทนที่จะได้การเพิ่มบวกการลบมาหนึ่งคู่ การเช็กถูกๆ มาก่อนตลอด: ความต่างของจำนวนหน้าถูกรายงานก่อนการเดินออบเจกต์จะเริ่ม

การเทียบ revision PDF แบบสองรอบใน PDFlibPas: เช็กจำนวนหน้าก่อน, fingerprint 64 บิต, จัดแนวด้วย fingerprint แล้วจับคู่ด้วยหมายเลขออบเจกต์
เครื่องมือเทียบ fingerprint body ออบเจกต์ที่ normalize แล้ว รายงานความต่างของจำนวนหน้าก่อน แล้วจึงจับคู่ด้วย fingerprint และหมายเลขออบเจกต์

กับดัก: การเทียบกับตัวเองไม่ได้รับประกันว่าตรงเป๊ะ

การทดสอบแรกตามสัญชาตญาณของเครื่องมือ diff คือเทียบไฟล์กับตัวมันเองแล้ว assert ว่าผลเหมือนกันเป๊ะ assertion นั้นไม่คงอยู่ที่นี่ และเหตุผลมีคุณค่าต่อการเรียนรู้ เส้นทางโหลดระดับ public กับเส้นทางโหลดเอกสารระดับต่ำกว่าไม่ได้ตั้งค่าการ decode ให้เหมือนกัน ไฟล์เดียวกันที่โหลดผ่านสองเส้นทางจึงอาจให้ fingerprint ที่ต่างกันในบางออบเจกต์ ตัวเครื่องมือไม่ได้ผิด สองการโหลดผลิตสถานะในหน่วยความจำที่ต่างกันจริง ๆ

แทนที่จะบังคับสองเส้นทางให้เป็นอันหนึ่งอันเดียวกัน ความหมายของการเทียบถูกประกาศไว้แคบ ๆ: การวิเคราะห์เทียบสถานะเอกสารปัจจุบันกับ revision ก่อนหน้า และรายงานว่าเหมือนกันเมื่อชุด fingerprint ทั้งสองตรงกันเป๊ะเท่านั้น นั่นคือคำถามที่ผู้ใช้ถามกันจริง และมันไม่ต้องการให้ loader ทั้งสองใช้แทนกันได้ ตอนคุณออกแบบฟีเจอร์การเทียบ การนิยามว่า "เหมือนกัน" หมายถึงอะไรกินแรงงานมากกว่าการคำนวณมันเสียอีก

ใช้ที่ไหน

สองที่ ในรายงาน validation เคียงข้างการเช็กลายเซ็น เพื่อให้ผู้ทวนสอบเห็นไม่ใช่แค่ว่าลายเซ็นยังคงเดิมเชิง cryptography หรือไม่ แต่เห็นด้วยว่าเกิดอะไรขึ้นกับเอกสารหลังจากนั้น ฝั่งลายเซ็นอยู่ในการลงนามและตรวจสอบ PAdES และอีกที่คือประตูรับเอกสาร ที่ที่เอกสารที่มาจากภายนอกถูกเช็กกับสำเนาที่คุณส่งออกไป สัญญาที่ส่งกลับมาพร้อม annotation เพิ่มจึงถูกปฏิบัติต่างจากใบที่หน้าถูกแก้ไข

มีข้อควรระวังเรื่องขอบเขตหนึ่งประเด็น การวิเคราะห์นี้บอกคุณว่าอะไรเปลี่ยนระหว่างสอง revision ของสายพันธุ์เอกสารเดียวกัน มันไม่ได้บอกว่าเนื้อหาที่มองเห็นทำให้เข้าใจผิดหรือไม่ ว่า appearance stream ของฟิลด์ฟอร์มตรงกับค่าของมันหรือไม่ หรือว่าข้อความที่ถูกซ่อนใต้ overlay ยังอยู่ใน content stream หรือไม่ สิ่งเหล่านี้ต้องการการจัดการแยกต่างหาก และฝั่งการลบเนื้อหาออกจริงอยู่ในบทความ true redaction entry point การวิเคราะห์และการเทียบถูกจัดทำเป็นเอกสารไว้บนหน้าผลิตภัณฑ์losLab PDF Developer Library