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 นั้นถูกคำนวณมาจาก
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
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