Enterprise รุ่น
การตรวจสอบลายเซ็นแบบกลุ่ม
ภาพรวมโดยสังเขป
หัวข้อที่มีชื่อว่า “ภาพรวมโดยสังเขป”NextPDF Enterprise ตรวจสอบลายเซ็นดิจิทัลของเอกสาร PDF จำนวนมากได้ในการเรียกใช้เพียงครั้งเดียว NextPDF\Enterprise\Signature\BatchSignatureValidator::validate() รับรายการเอกสารและคืนค่า BatchValidationReport ทุกลายเซ็นจะผ่านไปป์ไลน์แบบ fail-closed เดียวกัน ได้แก่ การยืนยันตัวตนเชิงวิทยาการเข้ารหัสลับ CMS เหนือช่วงไบต์ที่ลงนาม การตรวจสอบห่วงโซ่ใบรับรองที่ยึดกับ trust anchor และการตรวจสอบการเพิกถอนผ่าน OCSP/CRL รายงานจะให้รายละเอียดในระดับแต่ละเอกสารและแต่ละลายเซ็น — CertChainStatus, RevocationStatus, TimestampStatus — เพื่อให้เครื่องมือด้านการปฏิบัติตามข้อกำหนดสามารถอนุมานผลตัดสินทุกรายการใหม่ได้จากหลักฐานที่บันทึกไว้
แบบจำลองผลตัดสินถูกออกแบบให้เข้มงวดโดยเจตนา ลายเซ็นจะเป็น Valid ก็ต่อเมื่อหลักฐานทั้งหมดได้รับการยืนยันอย่างชัดเจนเท่านั้น หลักฐานการเพิกถอนที่ขาดหายไปจะให้ผลเป็น Indeterminate ไม่ใช่ Valid หน้านี้ครอบคลุมตัวจัดการแบบกลุ่มและชนิดผลลัพธ์ของมัน ส่วนการตรวจสอบ AdES ฝั่งเอกสารเดี่ยวมีการบันทึกไว้ใน การตรวจสอบลายเซ็น การฝังวัสดุสำหรับการตรวจสอบระยะยาวมีการบันทึกไว้ใน การจัดเก็บถาวร
ความพร้อมใช้งานและการอนุญาต
หัวข้อที่มีชื่อว่า “ความพร้อมใช้งานและการอนุญาต”ความสามารถนี้มาพร้อมกับ NextPDF Enterprise (nextpdf/enterprise) และเปิดใช้งานด้วยซองใบอนุญาตระดับ Enterprise การติดตั้งที่ไม่มีสิทธิ์ดังกล่าวจะไม่โหลดคลาสของความสามารถนี้ เปรียบเทียบรุ่นและรับใบอนุญาต
การติดตั้ง
หัวข้อที่มีชื่อว่า “การติดตั้ง”composer require nextpdf/enterpriseเมทาแพ็กเกจ nextpdf/premium ก็จะเรียกใช้แพ็กเกจ Enterprise ให้เช่นกัน การเปิดใช้งานจะใช้ซองใบอนุญาต Enterprise ของคุณ ดูที่ การอนุญาตและการเปิดใช้งาน ชนิดข้อมูลแบบกลุ่มจะโหลดอัตโนมัติภายใต้ NextPDF\Enterprise\Signature ไม่จำเป็นต้องใช้ส่วนขยาย PHP อื่นใดนอกเหนือจากพื้นฐานของเอนจิน
ภาพรวมเชิงแนวคิด
หัวข้อที่มีชื่อว่า “ภาพรวมเชิงแนวคิด”การเรียก validate() เพียงครั้งเดียวจะประมวลผลรายการค่า DocumentSignatureInput แต่ละอินพุตประกอบด้วยตัวระบุเอกสาร ไบต์ PDF ดิบ และ trust anchor ที่เข้ารหัสแบบ PEM ซึ่งเป็นทางเลือก ตัวตรวจสอบจะสกัดพจนานุกรมลายเซ็นของแต่ละเอกสารและดำเนินการสามขั้นตอนต่อหนึ่งลายเซ็น
ขั้นตอนที่ 1 — การยืนยันตัวตนเชิงวิทยาการเข้ารหัสลับ บล็อบ CMS/PKCS#7 แบบแยกจาก /Contents จะถูกตรวจสอบเหนือไบต์ที่ /ByteRange ครอบคลุม ตัวตรวจสอบจะคำนวณไดเจสต์ของเนื้อหาขึ้นใหม่เองและเปรียบเทียบกับแอตทริบิวต์ที่ลงนาม messageDigest โดยไม่เชื่อถือไดเจสต์ที่ผู้สร้างจัดหามาเลย (RFC 5652 §5.6) ค่าลายเซ็นต้องผ่านการตรวจสอบ และใบรับรองสำหรับลงนามต้องผูกกับ CMS หาก /Contents หรือ /ByteRange ขาดหายไปหรือมีรูปแบบผิด CMS ที่แจงไม่ได้ ไดเจสต์ไม่ตรงกัน หรือการตรวจสอบลายเซ็นล้มเหลว ทั้งหมดนี้จะ fail closed ลายเซ็นที่ผ่านการตรวจสอบด้วย SHA-1 จะถือว่าอ่อนแอและไม่ถือเป็นการผ่านแบบเต็มรูปแบบ
ขั้นตอนที่ 2 — การตรวจสอบห่วงโซ่และการยึด trust anchor ห่วงโซ่ของผู้ลงนามที่กู้คืนจาก CMS จะถูกตรวจสอบในฐานะเส้นทางการรับรองที่คาดหวัง trustedCerts ที่คุณจัดหาคืออินพุตของ trust anchor ตามความหมายใน RFC 5280 §6.1.1 กล่าวคือ จุดปลายของห่วงโซ่ต้องตรงกับ anchor ที่จัดหามาด้วยลายนิ้วมือ DER SHA-256 ห่วงโซ่ที่มีโครงสร้างสอดคล้องกันแต่จุดปลายไม่ใช่ anchor ที่กำหนดค่าไว้จะไม่ถูกรายงานว่าเชื่อถือได้ หากไม่มี anchor ที่ใช้งานได้ ระบบจะรายงานเฉพาะผลตัดสินเชิงโครงสร้างเท่านั้น และ CertChainStatus::$trusted จะยังคงเป็น false
ขั้นตอนที่ 3 — การเพิกถอน การเพิกถอนจะทำงานเหนือห่วงโซ่ที่กู้คืนหลังจากการยืนยันตัวตน โดยสะท้อนแบบจำลอง ETSI EN 319 102-1 ที่การตรวจสอบการเพิกถอนตามมาหลังการตรวจสอบเส้นทางสำเร็จ (ข้อ 5.2.6.2) OCSP เป็นวิธีหลัก โดยนับเฉพาะการตอบสนองที่ผ่านการตรวจสอบเชิงวิทยาการเข้ารหัสลับเท่านั้น ว่าเป็น Good หรือ Revoked เส้นทาง CRL เป็นทางเลือกสำรองและยืนยันความสดใหม่ของรายการ เมื่อไม่ได้กำหนดค่าไคลเอนต์ใดเลย สถานะจะเป็น unavailable
ผลตัดสินในระดับแต่ละลายเซ็นคือ SignatureValidationStatus การจัดหมวดหมู่นี้สะท้อนแบบจำลองสถานะของ ETSI EN 319 102-1 (TOTAL-PASSED / TOTAL-FAILED / INDETERMINATE) ในระดับแต่ละลายเซ็น:
| หลักฐาน | ผลตัดสิน |
|---|---|
| ใบรับรองได้รับการยืนยันว่าถูกเพิกถอน | Invalid (ชี้ขาด โดยไม่คำนึงถึงการตรวจสอบอื่น) |
| การยืนยันตัวตน CMS ล้มเหลว ไม่มีการกู้คืนวัสดุของผู้ลงนาม | Error |
| การยืนยันตัวตน CMS ล้มเหลว มีวัสดุของผู้ลงนามอยู่ | Invalid |
| ยืนยันตัวตนได้ แต่ห่วงโซ่ไม่ผ่านการตรวจสอบ | Invalid (หรือ Error เมื่อไม่มีห่วงโซ่) |
| ยืนยันตัวตนได้และห่วงโซ่ถูกต้อง แต่ไม่มี trust anchor ที่ยืนยันแล้ว | Indeterminate |
| ยืนยันตัวตนได้ ห่วงโซ่ถูกต้อง เชื่อถือได้ แต่ไม่มีการยืนยันการไม่ถูกเพิกถอนที่ชี้ขาด | Indeterminate |
| ทั้งหมดข้างต้นได้รับการยืนยันอย่างชัดเจน | Valid |
กฎการไม่ถูกเพิกถอนแบบชี้ขาด “ยังไม่พิสูจน์ว่าถูกเพิกถอน” ไม่เหมือนกับ “พิสูจน์แล้วว่าไม่ถูกเพิกถอน” ผลตัดสิน Valid ต้องมีผลการเพิกถอน Good อย่างน้อยหนึ่งรายการ การตอบสนอง OCSP ที่ยืนยันว่าดีคือรูปแบบที่ชี้ขาด เพราะมันยืนยันสถานะของใบรับรองผู้ลงนามเอง CRL ที่ผ่านการยอมรับเชิงวิทยาการเข้ารหัสลับและสดใหม่ก็ผ่านเกณฑ์นี้ในการนำไปใช้งานนี้เช่นกัน แต่เป็นเพียงการรับรองความสดใหม่และความสมบูรณ์เท่านั้น — เส้นทางนี้ไม่ได้แจงรายการตามหมายเลขซีเรียลแต่ละรายการ จึงไม่ให้การรับประกันการเพิกถอนในระดับซีเรียล และไม่เคยให้ผลตัดสิน revoked เชิงบวก ควรกำหนดค่า OCSP ทุกที่ที่การตรวจจับการเพิกถอนเชิงบวกมีความสำคัญ การติดตั้งที่ใช้เพียง CRL จะไม่แสดงใบรับรองที่ถูกเพิกถอนเป็น Invalid เมื่อผลลัพธ์ทั้ง OCSP และ CRL เป็น Unknown หรือ Unavailable สถานะการเพิกถอนจะไม่ถูกกำหนด และผลตัดสินจะเป็น Indeterminate ซึ่งเป็นไปตาม ETSI EN 319 102-1 คือ ข้อมูลสถานะการเพิกถอนที่ไม่พร้อมใช้งานจะให้ผลเป็น INDETERMINATE ไม่ใช่การผ่าน (ข้อ 5.1.3, TRY_LATER) นี่คือการเพิ่มความเข้มงวดของพฤติกรรมในเวอร์ชัน 3.1.0 ที่มีผลต่อความเข้ากันได้ย้อนหลัง: รุ่นก่อนหน้าอาจรายงาน Valid โดยไม่มีหลักฐานการเพิกถอนที่ชี้ขาด การติดตั้งที่ไม่ได้กำหนดค่าไคลเอนต์ OCSP หรือ CRL ตอนนี้จะพบ Indeterminate โดยทั่วไปในจุดที่ก่อนหน้านี้เคยเห็น Valid
มีสองขอบเขตที่กำหนดกรอบความสามารถนี้อย่างตรงไปตรงมา ประการแรก ตัวตรวจสอบแบบกลุ่มไม่ได้ประเมินโทเคนการประทับเวลาที่ฝังอยู่ TimestampStatus ในผลลัพธ์แบบกลุ่มจะเป็นสถานะไม่ปรากฏเสมอ การประเมินการประทับเวลา RFC 3161 เป็นของฝั่งตรวจสอบเอกสารเดี่ยว ดูที่ การตรวจสอบลายเซ็น ประการที่สอง หน้านี้เป็นการตรวจสอบแบบอ่านอย่างเดียว การฝังวัสดุ DSS/VRI เพื่อความถูกต้องระยะยาวเป็นความสามารถของ การจัดเก็บถาวร
เหตุใดจึงทำงานเช่นนี้
หัวข้อที่มีชื่อว่า “เหตุใดจึงทำงานเช่นนี้”การตัดสินใจที่สำคัญคือตัวผลิตผลตัดสินแบบ fail-closed Valid จะถูกสร้างขึ้นจากหลักฐานเชิงยืนยันบนทั้งสามแกนเท่านั้น ได้แก่ การยืนยันตัวตนเชิงวิทยาการเข้ารหัสลับ ห่วงโซ่ที่ยึดกับ trust anchor และการไม่ถูกเพิกถอนที่ชี้ขาด สิ่งใดที่ยังไม่ได้รับการยืนยันจะลดระดับลงเป็น Indeterminate แทนที่จะกำหนดค่าเริ่มต้นเป็นการผ่าน ซึ่งเป็นท่าทีของ EN 319 102-1 สำหรับวัสดุการเพิกถอนที่ขาดหายไป ปริมาณงานแบบกลุ่มไม่เคยแลกความเข้มงวดกลับมา ชั้นแบบกลุ่มเป็นการจัดการเหนือตัวตรวจสอบ CMS ที่ผ่านการตรวจสอบเดียวกันกับที่ใช้กับเอกสารเดี่ยว ดังนั้นการทำงานกับเอกสาร 1,000 ฉบับจึงใช้วิทยาการเข้ารหัสลับที่เหมือนกัน รายงานยังแยกหลักฐานออกจากผลตัดสิน — CertChainStatus และ RevocationStatus บันทึกอินพุตที่ผลตัดสินแต่ละรายการตั้งอยู่ เพื่อให้ผู้ตรวจสอบสามารถอนุมานใหม่ได้ในภายหลัง
ที่มาของการออกแบบ: การลงนามในปริมาณมากโดยไม่ลดทอนคุณภาพ
พื้นผิว API
หัวข้อที่มีชื่อว่า “พื้นผิว API”สัญลักษณ์ทั้งหมดด้านล่างเป็น public API ใน nextpdf/enterprise 3.1.0
BatchSignatureValidator
หัวข้อที่มีชื่อว่า “BatchSignatureValidator”final class BatchSignatureValidator{ public function __construct( ?SignatureExtractor $extractor = null, ?CertificateChainValidator $chainValidator = null, private readonly ?OcspClient $ocspClient = null, private readonly ?CrlFetcher $crlFetcher = null, ?CmsSignatureDataExtractor $cmsExtractor = null, private readonly ClockInterface $clock = new SystemClock(), )
public function validate(array $inputs): BatchValidationReport}ส่งข้อยกเว้นหรือล้มเหลวด้วย: validate() ส่ง \InvalidArgumentException หากรายการอินพุตว่างเปล่า และ \OverflowException เมื่อกลุ่มเกิน 1,000 เอกสาร เอกสารที่ไม่ใช่ PDF ที่แจงได้จะไม่ส่งข้อยกเว้น แต่จะกลายเป็นผลลัพธ์ Error ในระดับเอกสาร $clock เป็น Psr\Clock\ClockInterface แบบ PSR-20 ที่ใช้สำหรับการตัดสินความสดใหม่ของ CRL ดังนั้นผลตัดสินจึงกำหนดได้แน่นอนภายใต้นาฬิกาทดสอบที่ตรึงไว้
DocumentSignatureInput
หัวข้อที่มีชื่อว่า “DocumentSignatureInput”final readonly class DocumentSignatureInput{ public string $documentId;
public function __construct( string $documentId, public string $pdfData, public array $trustedCerts = [], )}ส่งข้อยกเว้นหรือล้มเหลวด้วย: \InvalidArgumentException หาก $documentId เป็นสตริงว่าง $trustedCerts เป็นรายการใบรับรอง trust anchor ที่เข้ารหัสแบบ PEM
BatchValidationReport
หัวข้อที่มีชื่อว่า “BatchValidationReport”final readonly class BatchValidationReport{ public function __construct( public array $documents, public int $totalDocuments, public int $totalSignatures, public int $totalValid, public int $totalInvalid, public float $durationMs, )
public function allValid(): bool
public function hasDocumentsWithoutSignatures(): bool
public function toJson(?CertPiiGuard $piiGuard = null): string}ส่งข้อยกเว้นหรือล้มเหลวด้วย: toJson() ส่ง \JsonException หากการเข้ารหัสล้มเหลว allValid() จะเป็น true ก็ต่อเมื่อมีลายเซ็นและไม่มีรายการใดที่ไม่ถูกต้องเท่านั้น โดยค่าเริ่มต้น toJson() จะใช้ NextPDF\Enterprise\Signature\Eidas\CertPiiGuard ที่ปกป้องความเป็นส่วนตัวโดยค่าเริ่มต้น ซึ่งจะปิดบังชื่อผู้ลงนาม ผู้ออกใบรับรองราก ชื่อ TSA และการวินิจฉัยปัญหาของห่วงโซ่ ดูที่ ระดับการรับประกัน eIDAS สำหรับ API ของตัวป้องกันนี้
DocumentValidationResult และ DocumentValidationStatus
หัวข้อที่มีชื่อว่า “DocumentValidationResult และ DocumentValidationStatus”final readonly class DocumentValidationResult{ public function __construct( public string $documentId, public DocumentValidationStatus $status, public array $signatures, public int $validCount, public int $invalidCount, )
public function hasSignatures(): bool
public function totalSignatures(): int}enum DocumentValidationStatus: string{ case AllValid = 'all_valid'; case SomeInvalid = 'some_invalid'; case AllInvalid = 'all_invalid'; case NoSignatures = 'no_signatures'; case Error = 'error';}ส่งข้อยกเว้นหรือล้มเหลวด้วย: ไม่มี วัตถุค่าที่ไม่เปลี่ยนแปลงและ enum แบบ backed
SignatureValidationResult และ SignatureValidationStatus
หัวข้อที่มีชื่อว่า “SignatureValidationResult และ SignatureValidationStatus”final readonly class SignatureValidationResult{ public function __construct( public SignatureValidationStatus $status, public CertChainStatus $certChain, public TimestampStatus $timestamp, public RevocationStatus $revocation, public string $signer, public string $level = '', public string $subFilter = '', public string $reason = '', )
public function isValid(): bool}enum SignatureValidationStatus: string{ case Valid = 'valid'; case Invalid = 'invalid'; case Indeterminate = 'indeterminate'; case Error = 'error';}ส่งข้อยกเว้นหรือล้มเหลวด้วย: ไม่มี $signer คือ subject ของใบรับรองที่ผ่านการตรวจสอบ CMS เมื่อการยืนยันตัวตนสำเร็จ มิฉะนั้นจะเป็นสตริงว่าง $level เป็นป้ายกำกับที่ได้มาจาก SubFilter (ตัวอย่างเช่น B-B สำหรับ ETSI.CAdES.detached) ไม่ใช่การพิจารณาความสอดคล้อง AdES
CertChainStatus
หัวข้อที่มีชื่อว่า “CertChainStatus”final readonly class CertChainStatus{ public function __construct( public bool $valid, public bool $trusted, public int $chainLength, public string $rootIssuer, public array $issues = [], )
public function hasIssues(): bool}ส่งข้อยกเว้นหรือล้มเหลวด้วย: ไม่มี $trusted จะถูกตั้งค่าเฉพาะเมื่อมีการยืนยันการเป็นสมาชิกของ trust anchor เท่านั้น ไม่เคยมาจากการที่รายการ anchor ไม่ว่างเปล่า
RevocationStatus และ RevocationCheckResult
หัวข้อที่มีชื่อว่า “RevocationStatus และ RevocationCheckResult”final readonly class RevocationStatus{ public function __construct( public RevocationCheckResult $ocspStatus, public RevocationCheckResult $crlStatus, public bool $isRevoked, public ?DateTimeImmutable $revocationDate = null, )
public static function unavailable(): self
public function hasConclusiveGood(): bool}enum RevocationCheckResult: string{ case Good = 'good'; case Revoked = 'revoked'; case Unknown = 'unknown'; case Unavailable = 'unavailable';}ส่งข้อยกเว้นหรือล้มเหลวด้วย: ไม่มีจากสมาชิกที่แสดง คลาสนี้ยังเปิดเผยแฟกทอรีแบบสแตติกที่ตรวจสอบหลักฐาน (good(), revoked(), fromResults()) ซึ่งจะส่ง \InvalidArgumentException เมื่อสถานะที่อ้างขัดแย้งกับหลักฐาน OCSP/CRL — ผลลัพธ์ที่ถูกเพิกถอนไม่มีทางถูกสร้างเป็นไม่ถูกเพิกถอนได้ หรือในทางกลับกัน hasConclusiveGood() จะเป็น true เฉพาะสำหรับสถานะที่ไม่ถูกเพิกถอนซึ่งมีการตรวจสอบอย่างน้อยหนึ่งรายการเป็น Good เท่านั้น
TimestampStatus
หัวข้อที่มีชื่อว่า “TimestampStatus”final readonly class TimestampStatus{ public function __construct( public bool $present, public bool $valid, public ?DateTimeImmutable $timestampTime = null, public string $tsaName = '', public array $issues = [], )
public static function absent(): self}ส่งข้อยกเว้นหรือล้มเหลวด้วย: ไม่มี ในผลลัพธ์แบบกลุ่มนี่จะเป็นสถานะ absent() เสมอ ดูที่ กรณีขอบและข้อควรระวัง
ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว”ตรวจสอบเอกสารหนึ่งฉบับและอ่านรายงาน ตัวอย่างนี้ใช้ PDF ที่ไม่ได้ลงนาม ผลลัพธ์จึงกำหนดได้แน่นอน
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Signature\BatchSignatureValidator;use NextPDF\Enterprise\Signature\DocumentSignatureInput;
// A minimal, unsigned PDF: the validator reports it as no_signatures.$unsigned = "%PDF-1.7\n1 0 obj\n<< /Type /Catalog >>\nendobj\ntrailer\n<< /Root 1 0 R >>\n%%EOF\n";
$validator = new BatchSignatureValidator();
try { $report = $validator->validate([ new DocumentSignatureInput(documentId: 'doc-001', pdfData: $unsigned), ]);} catch (\InvalidArgumentException $e) { // Empty input list, or an empty documentId. echo 'Rejected: ' . $e->getMessage() . "\n"; exit(1);}
echo 'Documents: ' . $report->totalDocuments . "\n";echo 'Signatures: ' . $report->totalSignatures . "\n";
foreach ($report->documents as $doc) { echo $doc->documentId . ': ' . $doc->status->value . "\n";}
echo 'All valid: ' . ($report->allValid() ? 'yes' : 'no') . "\n";echo 'Unsigned documents: ' . ($report->hasDocumentsWithoutSignatures() ? 'yes' : 'no') . "\n";ผลลัพธ์ที่คาดหวัง:
Documents: 1Signatures: 0doc-001: no_signaturesAll valid: noUnsigned documents: yesโปรดสังเกตว่า allValid() รายงาน no ที่นี่ เพราะมันต้องการลายเซ็นอย่างน้อยหนึ่งรายการและไม่มีผลลัพธ์ที่ไม่ถูกต้อง ดังนั้นชุดลายเซ็นที่ว่างเปล่าจึงไม่มีทางผ่านอย่างเงียบๆ
ตัวอย่างโค้ด — การใช้งานจริง
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — การใช้งานจริง”ตรวจสอบไดเรกทอรีของสัญญาที่ลงนามแล้วด้วยไคลเอนต์การเพิกถอน trust anchor การแบ่งกลุ่มเป็นชิ้น และรายงาน JSON ที่ปกป้องข้อมูล PII
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Security\Ltv\CrlFetcher;use NextPDF\Enterprise\Security\Ltv\OcspClient;use NextPDF\Enterprise\Security\Ltv\OcspResponseCache;use NextPDF\Enterprise\Signature\BatchSignatureValidator;use NextPDF\Enterprise\Signature\DocumentSignatureInput;use NextPDF\Enterprise\Signature\SignatureValidationStatus;
// Any PSR-18 client works; Guzzle shown here.$httpClient = new \GuzzleHttp\Client(['timeout' => 10]);
// Revocation clients make a conclusive non-revoked (Good) result reachable.// Without them, every verdict tops out at Indeterminate. The response cache// lets repeat signers across the batch resolve without extra network calls.$validator = new BatchSignatureValidator( ocspClient: new OcspClient($httpClient, cache: new OcspResponseCache()), crlFetcher: new CrlFetcher($httpClient),);
// Trust anchors are an input: the chain terminus must match one of these.$anchors = [(string) file_get_contents('/etc/nextpdf/trust/enterprise-root.pem')];
$inputs = [];foreach (glob('/var/contracts/signed/*.pdf') ?: [] as $path) { $inputs[] = new DocumentSignatureInput( documentId: basename($path), pdfData: (string) file_get_contents($path), trustedCerts: $anchors, );}
$exit = 0;
// One call is capped at 1,000 documents; chunk larger runs.foreach (array_chunk($inputs, 1000) as $batch) { try { $report = $validator->validate($batch); // Signer PII is redacted by default in the serialized report. file_put_contents('/var/log/nextpdf/batch-report.jsonl', $report->toJson() . PHP_EOL, FILE_APPEND); // one JSON document per line } catch (\InvalidArgumentException | \OverflowException $e) { fwrite(STDERR, 'Batch rejected: ' . $e->getMessage() . "\n"); exit(2); } catch (\JsonException $e) { fwrite(STDERR, 'Report encoding failed: ' . $e->getMessage() . "\n"); exit(3); }
foreach ($report->documents as $doc) { foreach ($doc->signatures as $sig) { if ($sig->status !== SignatureValidationStatus::Valid) { $exit = 1; fwrite(STDERR, sprintf( "%s: %s (chain trusted: %s, revoked: %s)\n", $doc->documentId, $sig->status->value, $sig->certChain->trusted ? 'yes' : 'no', $sig->revocation->isRevoked ? 'yes' : 'no', )); } } }}
exit($exit);ผลลัพธ์ที่คาดหวัง (stderr สำหรับเอกสารหนึ่งฉบับที่หลักฐานการเพิกถอนไม่พร้อมใช้งาน บรรทัดอื่นจะแตกต่างกันไปตามอินพุตของคุณ):
contract-0042.pdf: indeterminate (chain trusted: yes, revoked: no)รายงาน JSON จะทำให้เขตข้อมูลตัวตนของผู้ลงนามเป็นอนุกรมผ่าน CertPiiGuard เริ่มต้น ดังนั้นรายการในระดับแต่ละลายเซ็นจะมีลักษณะเช่นนี้ (ตัดตอนมาเพื่อประกอบการอธิบาย):
{ "status": "indeterminate", "signer": "[REDACTED]", "level": "B-B", "subFilter": "ETSI.CAdES.detached"}กรณีขอบและข้อควรระวัง
หัวข้อที่มีชื่อว่า “กรณีขอบและข้อควรระวัง”- รายการอินพุตที่ว่างเปล่าจะส่ง
\InvalidArgumentExceptionเอกสารมากกว่า 1,000 ฉบับในการเรียกครั้งเดียวจะส่ง\OverflowExceptionให้แบ่งการทำงานที่ใหญ่กว่าเป็นชิ้น ดังในตัวอย่างการใช้งานจริง - การอัปเกรดจากรุ่นก่อนหน้า: เมื่อไม่ได้กำหนดค่าไคลเอนต์ OCSP หรือ CRL การเพิกถอนจะเป็น
unavailableดังนั้นจึงไม่มีลายเซ็นใดที่ถึงระดับValidได้ รุ่นก่อนหน้ารายงานValidที่นี่ ส่วน 3.1.0 รายงานIndeterminate(ดูที่ ภาพรวมเชิงแนวคิด) - ตัวนับในระดับเอกสารมีความเข้มงวด เฉพาะ
Validเท่านั้นที่เพิ่มvalidCountส่วนInvalid,Indeterminate, และErrorล้วนเพิ่มinvalidCountดังนั้นเอกสารที่มีลายเซ็นเดียวเป็นIndeterminateจึงรายงานเป็นall_invalidให้ตรวจสอบที่statusในระดับแต่ละลายเซ็นเมื่อความแตกต่างนี้มีความสำคัญ - การตรวจสอบ OCSP จะทำงานเฉพาะเมื่อห่วงโซ่ที่กู้คืนมีใบรับรองอย่างน้อยสองใบ เพราะการสอบถามต้องใช้ผู้ออกใบรับรอง ห่วงโซ่ที่มีใบรับรองเดียวจะตกไปยังเส้นทาง CRL หรือ
unavailable crlStatusไม่เคยรายงานrevokedในผลลัพธ์แบบกลุ่ม ทางเลือกสำรอง CRL ยืนยันเพียงความสดใหม่ของรายการเท่านั้น ผลการเพิกถอนที่เชื่อถือได้มาจาก OCSPtimestampจะเป็นabsent()เสมอในผลลัพธ์แบบกลุ่ม ตัวตรวจสอบแบบกลุ่มไม่ได้ประเมินโทเคน RFC 3161 ที่ฝังอยู่ ให้ใช้ การตรวจสอบลายเซ็น สำหรับการประเมินการประทับเวลาsignerจะว่างเปล่าเมื่อการยืนยันตัวตนล้มเหลว เมื่อมีการตั้งค่า มันคือ subject CN (หรือ O) ของใบรับรองที่ผ่านการตรวจสอบ CMS — ไม่เคยเป็นสตริง/Nameที่ไม่ผ่านการยืนยันจากพจนานุกรมลายเซ็น- รายการ
trustedCertsต้องเป็นใบรับรอง PEM รายการ anchor ที่ว่างเปล่าหรือมีรูปแบบผิดจะให้ผลตัดสินห่วงโซ่เชิงโครงสร้างเท่านั้นด้วยtrusted: falseซึ่งจำกัดผลตัดสินไว้ที่Indeterminate - ไบต์ที่ไม่ได้ขึ้นต้นด้วยส่วนหัว PDF จะสร้างสถานะ
errorในระดับเอกสารพร้อมลายเซ็นศูนย์รายการ — โดยไม่มีข้อยกเว้น toJson()จะปกปิด PII โดยค่าเริ่มต้น ให้ส่งnew CertPiiGuard(disclosePii: true)เฉพาะในกรณีที่คุณมีฐานทางกฎหมายที่มีการบันทึกไว้สำหรับการประมวลผลตัวตนของผู้ลงนามเท่านั้น
หมายเหตุด้านความปลอดภัย
หัวข้อที่มีชื่อว่า “หมายเหตุด้านความปลอดภัย”- ตัวผลิตผลตัดสินแบบ fail-closed
Validต้องการทั้งหมดต่อไปนี้ คือ การยืนยันตัวตน CMS ที่ตรวจสอบแล้วเหนือไดเจสต์/ByteRangeห่วงโซ่ที่ถูกต้อง การยืนยันการเป็นสมาชิกของ trust anchor และสถานะไม่ถูกเพิกถอนที่ชี้ขาด การตรวจสอบทุกรายการที่ยังไม่ได้รับการยืนยันจะลดระดับผลตัดสิน ไม่มีสิ่งใดกำหนดค่าเริ่มต้นเป็นการผ่าน - ไม่มีการฟอกตัวตน ผู้ลงนามที่รายงานคือ subject ของใบรับรองที่ผูกไว้เชิงวิทยาการเข้ารหัสลับ รายการ
/Nameเป็นข้อมูลเมทาดาทาที่ผู้โจมตีควบคุมได้ และไม่เคยถูกแสดงเป็นผู้ลงนาม - อัลกอริทึมที่อ่อนแอไม่มีทางผ่าน ลายเซ็น SHA-1 ที่ผ่านการตรวจสอบยังคงถูกรายงานว่าไม่ถูกต้อง ความถูกต้องเชิงวิทยาการเข้ารหัสลับภายใต้ไดเจสต์ที่อ่อนแอจะไม่ถูกฟอกให้กลายเป็นการผ่านแบบเต็มรูปแบบ
- ความเชื่อถือเป็นอินพุต ไม่ใช่การอนุมาน anchor ที่คุณจัดหาจะถูกจับคู่กับจุดปลายของห่วงโซ่ด้วยลายนิ้วมือ DER SHA-256 (RFC 5280 §6.1.1) ความสอดคล้องในตัวเองของห่วงโซ่ หรือรายการ anchor ที่ไม่ว่างเปล่าเพียงอย่างเดียว ไม่เคยสร้างความเชื่อถือ
- การเพิกถอนเป็นสิ่งชี้ขาด คำแถลงการเพิกถอนที่ตรวจสอบแล้วจะบังคับให้เป็น
Invalidโดยไม่คำนึงถึงการตรวจสอบอื่นทุกรายการ ส่วนหลักฐานที่ไม่พร้อมใช้งานจะบังคับให้เป็นIndeterminate - ความเป็นส่วนตัวโดยค่าเริ่มต้นในผลลัพธ์ที่เป็นอนุกรม
toJson()จะปิดบัง CN ของผู้ลงนาม DN ของผู้ออกใบรับรองราก ชื่อ TSA และการวินิจฉัยปัญหาของห่วงโซ่ เว้นแต่คุณจะเลือกไม่ใช้ โดยนำหลักการลดข้อมูลให้น้อยที่สุดตาม GDPR Article 5(1)(c) มาใช้ที่ขอบเขตการทำให้เป็นอนุกรม - เวลาที่กำหนดได้แน่นอน การตัดสินความสดใหม่ของ CRL จะอ่านนาฬิกา PSR-20 ที่ฉีดเข้ามา ไม่ใช่นาฬิกาของโฮสต์ ดังนั้นผลตัดสินการเพิกถอนจึงทำซ้ำได้ภายใต้การทดสอบ
ความสอดคล้อง
หัวข้อที่มีชื่อว่า “ความสอดคล้อง”NextPDF Enterprise นำพฤติกรรมที่อ้างอิงจาก ETSI EN 319 102-1 (แบบจำลองสถานะการตรวจสอบสามค่าและกฎที่ว่าข้อมูลการเพิกถอนที่ไม่พร้อมใช้งานจะให้ผลเป็น INDETERMINATE) RFC 5652 §5.6 (การคำนวณไดเจสต์ใหม่ฝั่งผู้ตรวจสอบ) และ RFC 5280 §6.1 (trust anchor ในฐานะอินพุตของฝ่ายที่พึ่งพาสำหรับการตรวจสอบเส้นทาง) มาใช้ การรองรับไม่ใช่ความสอดคล้อง และความสอดคล้องไม่ใช่การรับรอง NextPDF ไม่ถือครองการรับรองใดและไม่มอบการรับรองใด ตัวตรวจสอบแบบกลุ่มไม่ใช่บริการตรวจสอบที่ผ่านการรับรอง และสถานะของมันเป็นผลตัดสินเชิงวิศวกรรมที่สอดคล้องกับการจัดหมวดหมู่ของ EN 319 102-1 — ไม่ใช่การบ่งชี้ TOTAL-PASSED/TOTAL-FAILED/INDETERMINATE จากกระบวนการตรวจสอบตามข้อ 5 แบบเต็มรูปแบบ โดยเฉพาะอย่างยิ่ง โหมดกลุ่มไม่ได้ดำเนินการพิสูจน์การมีอยู่หรือการประมวลผลการประทับเวลา ฝั่งตรวจสอบเอกสารเดี่ยวครอบคลุมส่วนนั้น
พฤติกรรมในโหมด FIPS
หัวข้อที่มีชื่อว่า “พฤติกรรมในโหมด FIPS”ตัวตรวจสอบแบบกลุ่มไม่ได้อ้างอิงนโยบายโหมด FIPS ใด และการเปิดใช้งานโหมด FIPS ไม่เปลี่ยนแปลงผลตัดสินแบบกลุ่ม การจัดการอัลกอริทึมฝั่งตรวจสอบของมันถูกกำหนดตายตัวและเป็นแบบ fail-closed คือ ลายเซ็นที่อ่อนแอ (SHA-1) จะไม่เคยถูกรายงานเป็น Valid ไม่ว่าจะเปิดหรือปิดโหมด FIPS นโยบายโหมด FIPS ของ Enterprise ควบคุมฝั่งการลงนาม/การสร้าง ซึ่งมีการบันทึกไว้ใน FIPS 140 — เอกสารอ้างอิงเชิงลึก การรองรับ FIPS 140 เป็นคำแถลงความสามารถ ไม่ใช่การอ้างการตรวจสอบหรือการรับรอง
สัญญาของพฤติกรรม
หัวข้อที่มีชื่อว่า “สัญญาของพฤติกรรม”validate()ส่ง\InvalidArgumentExceptionสำหรับรายการที่ว่างเปล่า และ\OverflowExceptionเมื่อเกิน 1,000 เอกสาร เอกสารที่มีรูปแบบผิดจะไม่เคยส่งข้อยกเว้น แต่จะสร้างผลลัพธ์errorในระดับเอกสารValidต้องการเงื่อนไขร่วมทั้งหมด คือ CMS ที่ตรวจสอบเชิงวิทยาการเข้ารหัสลับแล้ว ห่วงโซ่ที่ถูกต้อง การยืนยันการเป็นสมาชิกของ trust anchor และRevocationStatus::hasConclusiveGood()เป็นจริง- ใบรับรองที่ยืนยันแล้วว่าถูกเพิกถอนเป็นสิ่งชี้ขาด คือ ผลตัดสินจะเป็น
Invalidโดยไม่คำนึงถึงหลักฐานอื่นทั้งหมด - การตรวจสอบการเพิกถอนทั้งสองเป็น
Unknown/UnavailableหมายถึงIndeterminateไม่ใช่Valid(การเพิ่มความเข้มงวดในเวอร์ชัน 3.1.0 มีผลต่อความเข้ากันได้ย้อนหลัง) - ลายเซ็นที่ยืนยันตัวตนได้และห่วงโซ่ถูกต้องแต่ไม่มี trust anchor ที่ยืนยันแล้วจะเป็น
Indeterminate— เป็นของแท้ แต่ยังไม่ได้สร้างความเชื่อถือ signerคือ subject ที่ผ่านการตรวจสอบ CMS หรือสตริงว่าง รายการ/Nameจะไม่เคยถูกใช้timestampจะเป็นสถานะไม่ปรากฏเสมอในผลลัพธ์แบบกลุ่มvalidCountนับเฉพาะValidเท่านั้น สถานะอื่นทั้งหมดจะนับเข้าinvalidCountและสถานะเอกสารจะรวมมาจากตัวนับเหล่านั้นtoJson()จะใช้CertPiiGuardที่ปกป้องความเป็นส่วนตัวโดยค่าเริ่มต้น เว้นแต่มีการส่งตัวป้องกันมาอย่างชัดเจน- ยอดรวมของรายงานเป็นผลรวมที่แม่นยำเหนือผลลัพธ์ในระดับเอกสาร
durationMsคือเวลาจริงที่วัดได้สำหรับกลุ่ม
ทางเลือกสำรองของ Core
หัวข้อที่มีชื่อว่า “ทางเลือกสำรองของ Core”โมดูล ความปลอดภัย / การลงนาม ของ NextPDF Core เป็นฝั่งผู้ผลิต คือ มันสร้างลายเซ็น CMS ใช้การประทับเวลา RFC 3161 และตรวจสอบห่วงโซ่และการเพิกถอนสำหรับวัสดุที่มันฝังในเวลาที่ลงนาม Core ไม่มีตัวจัดการแบบกลุ่มฝั่งตรวจสอบ คือ ไม่มีรายงานหลายเอกสาร ไม่มีการจัดหมวดหมู่สถานะแบบรวม ไม่มีผลตัดสินการเพิกถอน OCSP/CRL สำหรับเอกสารของบุคคลที่สาม และไม่มีการทำให้รายงานเป็นอนุกรมที่ปกป้องข้อมูล PII หากใช้ Core เพียงอย่างเดียว คุณจะต้องสกัดและตรวจสอบแต่ละลายเซ็นด้วยตนเองและสร้างระบบรายงานของคุณเอง ฝั่งตรวจสอบเอกสารเดี่ยวของ Enterprise (การตรวจสอบลายเซ็น) และตัวจัดการแบบกลุ่มนี้ให้ชั้นดังกล่าว
ขอบเขตการเผยแพร่
หัวข้อที่มีชื่อว่า “ขอบเขตการเผยแพร่”หน้านี้บันทึกเฉพาะพฤติกรรมที่สังเกตได้จากภายนอกและพื้นผิว public API ที่รองรับเท่านั้น เส้นทาง namespace ภายใน คลาสตัวช่วย ตารางกลไก ชื่อไฟล์ runbook และคำนำหน้าตั๋วอยู่นอกขอบเขต
ดูเพิ่มเติม
หัวข้อที่มีชื่อว่า “ดูเพิ่มเติม”- การตรวจสอบลายเซ็น — ฝั่งตรวจสอบเชิงวิทยาการเข้ารหัสลับ AdES/PAdES สำหรับเอกสารเดี่ยว รวมถึงการตรวจสอบการประทับเวลาและห่วงโซ่การจัดเก็บถาวร
- การจัดเก็บถาวร — การฝังวัสดุ DSS/VRI และการประทับเวลาเอกสารเพื่อความถูกต้องระยะยาว
- การตรวจสอบความถูกต้อง — การตรวจสอบนโยบายเชิงโครงสร้างแบบอ่านอย่างเดียว ไม่มีวิทยาการเข้ารหัสลับ
- Signature — เอกสารอ้างอิงเชิงลึก — เอกสารอ้างอิงเชิงลึกของโมดูล Signature
- ระดับการรับประกัน eIDAS — API
CertPiiGuardและการแมประดับการรับประกัน - การลงนามในปริมาณมากโดยไม่ลดทอนคุณภาพ — บทความ Insider ว่าด้วยการออกแบบการลงนามและการตรวจสอบปริมาณสูง
- การตรวจสอบลายเซ็นอย่างถูกต้อง — บทความ Insider ว่าด้วยเหตุผลที่การตรวจสอบแบบ fail-closed มีความสำคัญ