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

การเซ็น PAdES ระยะไกลของ PDFium VCL: HSM และคีย์บนคลาวด์

PDFiumPas แบ่งการเซ็น PAdES ออกเป็นสองการเรียก เพื่อให้คีย์ส่วนตัวไม่จำเป็นต้องอยู่ในโพรเซสของคุณเลย PreparePadesRemoteSignature จะเขียนอัปเดตแบบเพิ่มขึ้นพร้อมพื้นที่ว่างสำรอง /Contents ที่มีความกว้างคงที่และว่างเปล่า แล้วส่งกลับเรคคอร์ดคำขอที่พก digest SHA-256 ของเอกสาร ByteRange ที่แน่นอน และลายนิ้วมือของไฟล์ที่เตรียมไว้ CompletePadesRemoteSignature จะรับ CMS แบบแยกส่วนที่บริการเซ็นของคุณส่งกลับมา แล้ววางลงในช่องที่จองไว้นั้น

ระหว่างการเรียกทั้งสองครั้งนี้ อาจผ่านไปหลายนาทีหรือหลายชั่วโมง โพรเซสอาจรีสตาร์ท และงานอาจย้ายไปยังเครื่องอื่นได้ ช่องว่างนี้เองคือเหตุผลทั้งหมดที่ API ถูกออกแบบมาในรูปแบบนี้

เหตุใดคีย์ระยะไกลจึงใช้การเรียกเซ็นแบบธรรมดาไม่ได้

เพราะ SignPadesBytes สันนิษฐานว่าการเซ็นจะเกิดขึ้นภายในการเรียกครั้งนั้น มันสร้างอัปเดตแบบเพิ่มขึ้น คำนวณ digest บน ByteRange เซ็นมัน แล้วเขียนผลลัพธ์ ทั้งหมดนี้ก่อนที่จะคืนค่ากลับมา ซึ่งถูกต้องพอดีเมื่อคีย์อยู่ใน Windows certificate store หรือในไฟล์ PKCS#12 ที่คุณโหลดไว้

แต่มันเป็นไปไม่ได้เมื่อคีย์อยู่ใน HSM บนเครือข่าย อุปกรณ์สร้างลายเซ็นที่ผ่านการรับรองซึ่งดำเนินการโดยผู้ให้บริการที่เชื่อถือได้ หรือ API เซ็นบนคลาวด์ที่ต้องการให้ผู้ใช้ยืนยันบนโทรศัพท์ ในกรณีเหล่านี้ลำดับการทำงานไม่ใช่การเรียกฟังก์ชัน แต่เป็นบทสนทนา: คุณส่ง digest ไป มีบางอย่างยืนยันตัวตนของมนุษย์ แล้ว CMS ก็ส่งกลับมาในภายหลัง API แบบซิงโครนัสไม่สามารถแสดงคำว่า “ภายหลัง” ได้ โดยไม่บล็อกเธรดไว้กับการดำเนินการที่อาจต้องใช้ปัจจัยที่สอง

โพรโทคอลสองเฟส

เฟสแรกเตรียมเอกสาร PDFiumPas จะต่อท้ายฟิลด์ลายเซ็นและดิกชันนารีค่า จองพื้นที่ที่เข้ารหัสฐานสิบหกจำนวน ContentsSize ไบต์ใน /Contents คำนวณ ByteRange รอบพื้นที่จองนั้น แล้วสร้าง TPadesRemoteSigningRequest ที่บรรจุ FormatVersion, PreparedFingerprint, DocumentDigest, ByteRange ที่มีสี่ส่วน ContentsHexOffset และ ContentsSize

ค่าเดียวที่บริการเซ็นของคุณต้องการคือ DocumentDigest ซึ่งเป็น SHA-256 ที่ CAdES SignedData ที่ส่งกลับมาต้องพกไว้เป็น message digest ของมัน สิ่งอื่นทั้งหมดในเรคคอร์ดนี้มีอยู่เพื่อให้เฟสที่สองพิสูจน์ได้ว่าไฟล์ที่กำลังทำให้เสร็จสมบูรณ์คือไฟล์เดียวกับที่ digest นั้นถูกคำนวณมาจาก

ขั้นตอน remote PAdES ของ PDFium สองเฟสใน Delphi ที่ PreparePadesRemoteSignature สงวนช่อง CMS และส่ง digest ของเอกสารให้ HSM หรือบริการคลาวด์ และ CompletePadesRemoteSignature ฝัง CMS ที่ได้รับกลับมาในโปรเซสหรือเครื่องใดก็ได้ภายหลัง
เฟสแรกจองช่อง CMS และส่งต่อ digest ของเอกสาร เฟสที่สองฝัง CMS ที่ไหลกลับมาจาก HSM หรือคีย์บนคลาวด์
uses
  FPdfPades;

var
  Options: TPadesRemoteSignOptions;
  Request: TPadesRemoteSigningRequest;
  Source, Prepared, Session: TFileStream;
begin
  Options := TPadesRemoteSignOptions.Default;
  Options.Reason := 'Approved by finance';
  Options.Location := 'Lisbon';
  Options.Name := 'A. Moreira';
  Options.SigningTimeUtc := NowUtc;
  Options.ContentsSize := 16384;   // จำนวนไบต์ฐานสิบหกที่จองไว้สำหรับ CMS

  Source := TFileStream.Create('contract.pdf', fmOpenRead or fmShareDenyWrite);
  Prepared := TFileStream.Create('contract.prepared.pdf', fmCreate);
  try
    PreparePadesRemoteSignature(Source, Prepared, Options, Request);
  finally
    Prepared.Free;
    Source.Free;
  end;

  // เก็บเซสชันไว้เพื่อให้การรันครั้งถัดไป หรือเครื่องอื่น สามารถทำให้เสร็จสมบูรณ์ได้
  Session := TFileStream.Create('contract.signreq', fmCreate);
  try
    SavePadesRemoteSigningRequest(Session, Request);
  finally
    Session.Free;
  end;

  SendDigestToSigningService(Request.DocumentDigest);
end;

Complete ปฏิเสธอะไรบ้าง และแต่ละการตรวจสอบมีไว้ทำไม

ขั้นตอน Complete คือจุดที่การออกแบบการเซ็นระยะไกลมักผิดพลาด ดังนั้นการตรวจสอบจึงเข้มงวดโดยตั้งใจ CompletePadesRemoteSignature จะปฏิเสธ: PDF ที่เตรียมไว้ซึ่งลายนิ้วมือไม่ตรงกับคำขออีกต่อไป ByteRange ที่ไม่ตรงกับพิกัดพื้นที่สำรองที่บันทึกไว้ ตัวคั่น /Contents ที่ถูกแก้ไข พื้นที่สำรองที่ไม่ว่างเปล่าอีกต่อไป CMS ที่ใหญ่กว่าพื้นที่ที่จองไว้ CMS ที่ไม่ใช่ค่า DER เพียงหนึ่งค่าพอดี รูปแบบ SignedData ที่ไม่รองรับ แอตทริบิวต์ signing-certificate-v2 ที่ขาดหายไป และ CMS ที่ message digest ไม่เท่ากับ digest ของเอกสารที่เตรียมไว้

แต่ละข้อนี้เชื่อมโยงกับความล้มเหลวที่เกิดขึ้นจริง การตรวจสอบลายนิ้วมือและ ByteRange จะจับกรณีที่มีคนสร้างไฟล์ที่เตรียมไว้ขึ้นใหม่ระหว่างสองเฟส ซึ่งจะสร้างลายเซ็นที่ยืนยันได้กับไบต์ที่ไม่มีใครมีอยู่จริง การตรวจสอบพื้นที่ว่างเปล่าจะจับการทำให้เสร็จสมบูรณ์ซ้ำสอง ที่ CMS ตัวที่สองถูกเขียนทับลายเซ็นที่มีอยู่แล้ว การตรวจสอบ message digest จะจับกรณีที่อันตรายที่สุด: CMS ที่สร้างขึ้นถูกต้องแต่เซ็นบนเอกสารคนละฉบับ ซึ่งเป็นสิ่งที่คุณจะได้เมื่อคิวปนกันระหว่างสองเซสชันการเซ็นที่ทำงานพร้อมกัน ถ้าไม่มีการตรวจสอบนี้ คุณจะได้ไฟล์ที่ดูเหมือนเซ็นแล้วแต่ยืนยันไม่ผ่านทุกที่ หรือแย่กว่านั้น คือพกการอนุมัติของคนอื่นติดไปด้วย

ข้อกำหนด signing-certificate-v2 เป็นเรื่องความสอดคล้องกับ PAdES มากกว่าเรื่องความสมบูรณ์ ETSI EN 319 142 กำหนดให้ใบรับรองผู้เซ็นต้องถูกผูกเข้ากับแอตทริบิวต์ที่เซ็นไว้ และ CMS ที่ขาดแอตทริบิวต์นั้นไม่ถือเป็นลายเซ็น PAdES แม้จะยืนยันได้ถูกต้องทางการเข้ารหัสก็ตาม การปฏิเสธมันตั้งแต่ขั้นตอน Complete หมายความว่าคุณจะพบปัญหานี้ที่นี่ ไม่ใช่ในรายงานของตัวตรวจสอบจากลูกค้า หัวข้อนี้อธิบายเพิ่มเติมไว้ใน เหตุใดตัวตรวจสอบจึงปฏิเสธลายเซ็น PAdES

แผนภาพประตูปิดงานสำหรับการลงลายเซ็น remote PAdES ของ PDFium ใน Delphi ที่ CompletePadesRemoteSignature ตรวจความสมบูรณ์ของไฟล์, รูปทรงของ CMS และความสอดคล้อง PAdES โดยจะเขียน PDF ที่ลงลายเซ็นก็ต่อเมื่อการตรวจทุกข้อผ่าน
CompletePadesRemoteSignature ตรวจความสมบูรณ์ รูปร่างของ CMS และความสอดคล้อง PAdES ก่อนไบต์ใดจะถูกเขียน
var
  Request: TPadesRemoteSigningRequest;
  Session, Prepared, Dest: TFileStream;
  CmsDer: TBytes;
begin
  Session := TFileStream.Create('contract.signreq', fmOpenRead);
  try
    Request := LoadPadesRemoteSigningRequest(Session);
  finally
    Session.Free;
  end;

  CmsDer := FetchDetachedCmsFromService;   // ส่งกลับมาโดย HSM หรือ TSP

  Prepared := TFileStream.Create('contract.prepared.pdf', fmOpenRead);
  Dest := TFileStream.Create('contract.signed.pdf', fmCreate);
  try
    try
      CompletePadesRemoteSignature(Prepared, Dest, Request, CmsDer);
    except
      on E: EPadesCrypto do
        // การปฏิเสธทุกครั้งพกเหตุผลที่เจาะจงมาด้วย ให้บันทึกไว้ตามที่เป็น
        FailSession(E.Message);
    end;
  finally
    Dest.Free;
    Prepared.Free;
  end;
end;

ข้ามขอบเขตโพรเซสและเครื่อง

SavePadesRemoteSigningRequest และ LoadPadesRemoteSigningRequest ซีเรียลไลซ์เซสชันผ่านรูปแบบไบนารีที่มีเวอร์ชันกำกับและเสถียร ซึ่งเป็นสิ่งที่ทำให้การออกแบบนี้ใช้งานได้จริง ไม่ใช่แค่ถูกต้องตามทฤษฎีเท่านั้น เว็บแอปพลิเคชันสามารถเตรียมเอกสารในคำขอหนึ่ง เก็บ PDF ที่เตรียมไว้และ blob เซสชัน ส่ง digest กลับไปยังเบราว์เซอร์เพื่อเซ็นด้วยสมาร์ตการ์ด แล้วทำให้ไฟล์เสร็จสมบูรณ์ในตัวจัดการคำขออีกตัวที่ต่างออกไปโดยสิ้นเชิง

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

การกำหนดขนาดพื้นที่สำรอง

ContentsSize คือพารามิเตอร์หนึ่งเดียวที่คุณต้องคิดให้ดี เพราะมันถูกกำหนดไว้ตายตัวก่อนที่ CMS จะมีอยู่จริง มันนับพื้นที่จองที่เข้ารหัสฐานสิบหก ดังนั้น CMS แบบ DER ขนาด 6 KB จึงต้องการพื้นที่อย่างน้อย 12 KB และการใช้งานนี้จำกัดพื้นที่จองไว้สูงสุดที่ 64 MiB

จองน้อยเกินไป และการทำให้เสร็จสมบูรณ์จะล้มเหลวด้วยข้อผิดพลาด CMS ใหญ่เกินไป หลังจากบริการเซ็นของคุณทำงานเสร็จไปแล้ว ซึ่งบนบริการลายเซ็นที่ผ่านการรับรองแบบคิดตามปริมาณการใช้งาน หมายถึงการดำเนินการที่สูญเปล่า จองมากเกินไป และเอกสารที่เซ็นแล้วทุกฉบับก็จะพก padding นั้นติดตัวไปตลอดกาล วิธีที่สมเหตุสมผลคือการวัดจริง: เซ็นเอกสารหนึ่งฉบับด้วยเชนใบรับรองจริงของคุณ ดูความยาว DER คูณสองสำหรับการเข้ารหัสฐานสิบหก แล้วเผื่อพื้นที่เพิ่มอีกมากพอสำหรับโทเคนไทม์สแตมป์ ถ้าคุณตั้งใจจะอัปเกรดเป็นลายเซ็นระดับ T เชนที่มีตัวกลางหลายชั้นและการตอบสนอง OCSP ที่ยาวจะเติบโตเร็วกว่าที่คนส่วนใหญ่คาดไว้

สิ่งที่เกิดขึ้นหลังจากมีลายเซ็นแล้ว

ลายเซ็นระยะไกลที่ทำสำเร็จแล้วคือ PAdES B-B การตรวจสอบระยะยาวต้องการไทม์สแตมป์และวัสดุการตรวจสอบ ซึ่งเป็นอัปเดตแบบเพิ่มขึ้นแยกต่างหากที่เพิ่ม DSS และดิกชันนารี VRI ต่อลายเซ็น อธิบายไว้ใน ลายเซ็นระยะยาวด้วยไทม์สแตมป์ RFC 3161 และ DSS ขั้นตอนนั้นทำในเครื่องล้วน ๆ: เพิ่มใบรับรอง การตอบสนอง OCSP และ CRL ซึ่งไม่มีตัวใดต้องการคีย์ส่วนตัวเลย

ก่อนส่งมอบ ให้ตรวจสอบสิ่งที่คุณสร้างขึ้นด้วยเส้นทางโค้ดเดียวกับที่ฝ่ายที่พึ่งพาลายเซ็นจะใช้ อธิบายไว้ใน การตรวจสอบลายเซ็นดิจิทัลและระดับ PAdES การเซ็นและการตรวจสอบเป็นโค้ดคนละส่วนกัน และไปป์ไลน์การเซ็นระยะไกลก็เป็นจุดพอดีที่ทั้งสองอาจเบี่ยงเบนออกจากกันโดยไม่มีใครสังเกตเห็น จนกว่าตัวตรวจสอบภายนอกจะบอกออกมา

PDFiumPas เป็นคอมโพเนนต์สำหรับ Delphi และ Lazarus ที่ห่อหุ้มเอนจิน PDFium ไว้ พร้อมสแตก PAdES แบบเนทีฟที่เขียนด้วย Pascal ทำให้การเซ็น การประทับเวลา และการตรวจสอบทำงานได้โดยไม่ต้องพึ่งเครื่องมือบรรทัดคำสั่งภายนอก เอกสาร API ฉบับเต็มและรุ่นทดลองใช้งานอยู่ที่ หน้าคอมโพเนนต์ PDFium สำหรับ Delphi