تخطَّ إلى المحتوى
getnextpdf.com

Enterprise الإصدار

التحقّق من التوقيعات على دفعات

يتحقّق ⁨NextPDF Enterprise⁩ من التوقيعات الرقمية عبر العديد من مستندات ⁨PDF⁩ في استدعاء واحد. تأخذ الدالة NextPDF\Enterprise\Signature\BatchSignatureValidator::validate() قائمة من المستندات وتُرجع كائن BatchValidationReport. يمرّ كل توقيع عبر المسار نفسه الذي يفشل بأمان: مصادقة ⁨CMS⁩ تشفيرية على نطاق البايتات الموقَّع، والتحقّق من سلسلة الشهادات المثبّتة بمراسٍ موثوقة، وفحص الإبطال عبر ⁨OCSP/CRL⁩. يحمل التقرير تفاصيل لكل مستند ولكل توقيع — CertChainStatus وRevocationStatus وTimestampStatus — بحيث تستطيع أدوات الامتثال إعادة اشتقاق كل حُكم من أدلته المسجّلة.

نموذج الأحكام صارم عن قصد. لا يكون التوقيع Valid إلّا عندما تُثبَت كل الأدلة إثباتًا مؤكّدًا. تؤدّي أدلة الإبطال المفقودة إلى Indeterminate، وليس Valid أبدًا. تغطّي هذه الصفحة مُنسّق الدُّفعات وأنواع نتائجه. أمّا جانب التحقّق ⁨AdES⁩ للمستند المفرد فمُوثَّق في التحقّق من التوقيع. ويُوثَّق تضمين مواد التحقّق طويلة الأمد في الأرشفة.

تأتي هذه القدرة ضمن ⁨NextPDF Enterprise⁩ (nextpdf/enterprise) وتُفعَّل عبر مغلّف ترخيص من فئة ⁨Enterprise⁩. لا يُحمّل أي نشر لا يملك هذا الاستحقاق فئات هذه القدرة. قارن الإصدارات واحصل على ترخيص.

Terminal window
composer require nextpdf/enterprise

تُوفِّر الحزمة الوصفية nextpdf/premium أيضًا حزمة ⁨Enterprise⁩. يستخدم التفعيل مغلّف ترخيص ⁨Enterprise⁩ الخاص بك؛ راجع الترخيص والتفعيل. تُحمَّل أنواع الدُّفعات تلقائيًا ضمن NextPDF\Enterprise\Signature. ولا حاجة إلى أي امتداد ⁨PHP⁩ يتجاوز الحدّ الأساسي للمحرّك.

يعالج استدعاء واحد للدالة validate() قائمة من قيم DocumentSignatureInput. يحمل كل مُدخَل مُعرّف مستند، وبايتات ⁨PDF⁩ الخام، ومراسي ثقة اختيارية مُرمّزة بصيغة ⁨PEM⁩. يستخرج المُتحقّق قواميس التوقيع لكل مستند ويُشغّل ثلاث مراحل لكل توقيع.

المرحلة 1 — المصادقة التشفيرية. يُتحقَّق من كتلة ⁨CMS/PKCS#7⁩ المنفصلة المأخوذة من /Contents على البايتات التي يغطّيها /ByteRange. يعيد المُتحقّق حساب مُلخّص المحتوى بنفسه ويقارنه بالسمة الموقَّعة messageDigest. ولا يثق أبدًا بمُلخّص وفّره المُنتِج (⁨RFC 5652 §5.6⁩). يجب أن يُتحقَّق من قيمة التوقيع، وأن تكون شهادة التوقيع مرتبطة بـ⁨CMS⁩. أمّا غياب /Contents أو /ByteRange أو تشوّههما، أو تعذّر تحليل ⁨CMS⁩، أو عدم تطابق المُلخّص، أو فشل فحص التوقيع فكلّها تفشل بأمان. ويُعامَل أي توقيع يُتحقَّق منه تحت ⁨SHA-1⁩ على أنه ضعيف ولا يُعدّ نجاحًا كاملًا أبدًا.

المرحلة 2 — التحقّق من السلسلة وتثبيت الثقة. يُتحقَّق من سلسلة الموقِّع المستردّة من ⁨CMS⁩ باعتبارها مسار الاعتماد المُحتمَل. أمّا trustedCerts التي توفّرها فهي مُدخَل مرساة الثقة، بالمعنى الوارد في ⁨RFC 5280 §6.1.1⁩: يجب أن تتطابق نهاية السلسلة مع مرساة موفَّرة عبر بصمة ⁨DER SHA-256⁩. ولا يُبلَّغ أبدًا عن ثقة سلسلة متّسقة بنيويًا لا تتطابق نهايتها مع مرساة مُهيّأة. وفي غياب مراسٍ صالحة للاستخدام، يُبلَّغ عن الحُكم البنيوي فقط، ويبقى 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 عند غياب السلسلة)
مُصادَق عليه والسلسلة صالحة، لكن لا مرساة ثقة مؤكّدة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⁩ من أجل الصلاحية طويلة الأمد فهو من قدرة الأرشفة.

القرار الحامل للثقل هو مُنتِج أحكام يفشل بأمان. لا يُصدَر Valid إلّا من دليل مؤكّد على المحاور الثلاثة جميعها: المصادقة التشفيرية، وسلسلة مثبّتة بمرساة ثقة، وعدم إبطال قاطع. وأي شيء غير مُثبَت ينحدر إلى Indeterminate بدلًا من أن يؤول تلقائيًا إلى نجاح، وهذا هو موقف ⁨EN 319 102-1⁩ إزاء مواد الإبطال المفقودة. ولا تشتري الإنتاجية الدُّفعية الصرامة أبدًا: فطبقة الدُّفعات ما هي إلّا تنسيق فوق مُتحقّق ⁨CMS⁩ المُدقَّق نفسه المستخدَم لمستند مفرد، ولذا فإن تشغيلًا يضمّ 1,000 مستند يطبّق التشفير ذاته. كما يفصل التقرير الدليل عن الحُكم — إذ يسجّل CertChainStatus وRevocationStatus المُدخَلات التي يستند إليها كل حُكم، بحيث يستطيع مُدقّق إعادة اشتقاقه لاحقًا.

خلفية التصميم: التوقيع على نطاق واسع، دون تنازل.

واجهة برمجة التطبيقات (⁨API⁩)

قسم بعنوان «واجهة برمجة التطبيقات (⁨API⁩)»

جميع الرموز أدناه هي واجهة ⁨API⁩ عامة في nextpdf/enterprise 3.1.0.

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-20⁩ من نوع Psr\Clock\ClockInterface تُستخدَم لقرار حداثة ⁨CRL⁩، ولذا تكون الأحكام حتمية تحت ساعة اختبار مُجمّدة.

final readonly class DocumentSignatureInput
{
public string $documentId;
public function __construct(
string $documentId,
public string $pdfData,
public array $trustedCerts = [],
)
}

يرمي أو يفشل بما يلي: \InvalidArgumentException إذا كان $documentId سلسلة نصية فارغة. و$trustedCerts هي قائمة من شهادات مراسي الثقة المُرمّزة بصيغة ⁨PEM⁩.

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';
}

يرمي أو يفشل بما يلي: لا شيء. كائن قيمة غير قابل للتغيير وتعداد مدعوم.

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 هو موضوع الشهادة المُتحقَّق منه عبر ⁨CMS⁩ عند نجاح المصادقة، وإلّا فهو السلسلة النصية الفارغة. و$level هو تسمية مُشتقّة من SubFilter (على سبيل المثال B-B مقابل ETSI.CAdES.detached)، وليست تحديدًا لمطابقة ⁨AdES⁩.

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 إلّا عند تأكّد تطابق العضوية مع مرساة ثقة، ولا تُضبَط أبدًا من مجرّد كون قائمة المراسي غير فارغة.

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.

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⁩ غير موقَّع، ولذا يكون الخرج حتميًا.

batch-quick-start.php
<?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: 1
Signatures: 0
doc-001: no_signatures
All valid: no
Unsigned documents: yes

لاحظ أن allValid() تُبلّغ هنا بـno: فهي تتطلّب توقيعًا واحدًا على الأقل وعدم وجود نتائج غير صالحة، ولذا لا تنجح مجموعة توقيعات فارغة بصمت أبدًا.

تحقّق من دليل يضمّ عقودًا موقَّعة مع عملاء إبطال، ومراسي ثقة، وتقسيم دُفعي، وتقرير ⁨JSON⁩ محمي من الكشف عن البيانات الشخصية.

batch-validate-contracts.php
<?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⁩ الاحتياطي يشهد على حداثة القائمة فقط؛ أمّا نتيجة الإبطال الموثوقة فتأتي من ⁨OCSP⁩.
  • timestamp تكون دائمًا absent() في نتائج الدُّفعة. لا يُقيّم المُتحقّق الدُّفعي رموز ⁨RFC 3161⁩ المُضمَّنة؛ استخدم التحقّق من التوقيع لتقييم الطابع الزمني.
  • signer تكون فارغة عند فشل المصادقة. وعند ضبطها، تكون هي ⁨CN⁩ (أو ⁨O⁩) لموضوع الشهادة المُتحقَّق منها عبر ⁨CMS⁩ — وليست أبدًا سلسلة /Name غير المُصادَق عليها من قاموس التوقيع.
  • يجب أن تكون إدخالات trustedCerts شهادات بصيغة ⁨PEM⁩. وتؤدّي قائمة مراسٍ فارغة أو مشوّهة إلى حُكم سلسلة بنيوي فقط بقيمة trusted: false، ما يحدّ الحُكم عند Indeterminate.
  • تُنتج البايتات التي لا تبدأ بترويسة ⁨PDF⁩ حالة error على مستوى المستند بصفر توقيعات — دون استثناء.
  • تحجب toJson() البيانات الشخصية بشكل افتراضي. مرِّر new CertPiiGuard(disclosePii: true) فقط حيثما تملك أساسًا قانونيًا موثَّقًا لمعالجة هوية الموقِّع.
  • مُنتِج أحكام يفشل بأمان. يتطلّب Valid كل ما يلي: مصادقة ⁨CMS⁩ مُتحقَّق منها على مُلخّص /ByteRange، وسلسلة صالحة، وعضوية مؤكّدة في مرساة ثقة، وحالة عدم إبطال قاطعة. وكل فحص غير مُثبَت يُنزِل مستوى الحُكم؛ ولا شيء يؤول تلقائيًا إلى نجاح.
  • لا تبييض للهوية. الموقِّع المُبلَّغ عنه هو موضوع الشهادة المرتبط تشفيريًا. أمّا إدخال /Name فهو بيانات وصفية يتحكّم بها المهاجم ولا يُظهَر أبدًا بوصفه الموقِّع.
  • الخوارزميات الضعيفة لا تنجح أبدًا. يظلّ توقيع ⁨SHA-1⁩ الذي يُتحقَّق منه مُبلَّغًا عنه بأنه غير صالح؛ فالصلاحية التشفيرية تحت مُلخّص ضعيف لا تُبيَّض لتصبح نجاحًا كاملًا.
  • الثقة مُدخَل لا استنتاج. تُطابَق المراسي التي توفّرها مع نهاية السلسلة عبر بصمة ⁨DER SHA-256⁩ (⁨RFC 5280 §6.1.1⁩). ولا يُثبِت الاتساق الذاتي لسلسلة، أو مجرّد قائمة مراسٍ غير فارغة، الثقة أبدًا.
  • الإبطال قاطع. يفرض بيان إبطال مُتحقَّق منه الحُكم Invalid بغضّ النظر عن كل فحص آخر؛ وتفرض الأدلة غير المتوفّرة الحُكم Indeterminate.
  • الخصوصية افتراضية في الخرج المُسلسَل. تُخفي toJson() ⁨CN⁩ الموقِّع، و⁨DN⁩ المُصدِر الجذر، واسم ⁨TSA⁩، وتشخيصات مشكلات السلسلة ما لم تختر إلغاء ذلك، مُطبِّقةً تقليل البيانات وفق ⁨GDPR⁩ المادة 5(1)(c) عند حدّ السَّلسلة.
  • زمن حتمي. يقرأ قرار حداثة ⁨CRL⁩ ساعة ⁨PSR-20⁩ المحقونة، لا ساعة المضيف الفعلية، ولذا تكون أحكام الإبطال قابلة لإعادة الإنتاج تحت الاختبار.

يُنفّذ ⁨NextPDF Enterprise⁩ سلوكًا مُستَرشِدًا بـ⁨ETSI EN 319 102-1⁩ (نموذج حالة التحقّق ثلاثي القيم والقاعدة القائلة بأن معلومات الإبطال غير المتوفّرة تؤدّي إلى ⁨INDETERMINATE⁩)، و⁨RFC 5652 §5.6⁩ (إعادة حساب المُلخّص في جانب المُتحقّق)، و⁨RFC 5280 §6.1⁩ (مراسي الثقة بوصفها مُدخَلات الطرف المُعتمِد للتحقّق من المسار). الدعم ليس مطابقة، والمطابقة ليست اعتمادًا. ولا يملك ⁨NextPDF⁩ أي اعتماد ولا يمنح أيًّا منه. والمُتحقّق الدُّفعي ليس خدمة تحقّق مؤهَّلة، وحالاته أحكام هندسية مُتوائمة مع تصنيف ⁨EN 319 102-1⁩ — وليست دلالات ⁨TOTAL-PASSED/TOTAL-FAILED/INDETERMINATE⁩ من عملية تحقّق كاملة وفق البند 5. وعلى وجه الخصوص، لا يُجري وضع الدُّفعات أي إثبات وجود أو معالجة طابع زمني؛ إذ يغطّي جانب التحقّق للمستند المفرد ذلك المجال.

لا يستشير المُتحقّق الدُّفعي أي سياسة لوضع ⁨FIPS⁩، ولا يُغيّر تفعيل وضع ⁨FIPS⁩ أحكام الدُّفعة. ومعالجته للخوارزميات في جانب التحقّق ثابتة وتفشل بأمان: فالتوقيعات الضعيفة (⁨SHA-1⁩) لا يُبلَّغ عنها أبدًا بأنها Valid، بوضع ⁨FIPS⁩ أو دونه. أمّا سياسة وضع ⁨FIPS⁩ في ⁨Enterprise⁩ فتحكم جانب التوقيع/التوليد، وهي مُوثَّقة في FIPS 140 — المرجع المتعمّق. ودعم ⁨FIPS 140⁩ بيان قدرة، لا ادّعاء تحقّق أو اعتماد.

  • ترمي validate() استثناء \InvalidArgumentException عند قائمة فارغة، و\OverflowException عند تجاوز 1,000 مستند. أمّا المستندات المشوّهة فلا ترمي أبدًا؛ بل تُنتج نتائج error على مستوى المستند.
  • يتطلّب Valid اقتران ما يلي: تحقّق ⁨CMS⁩ تشفيريًا، وصلاحية السلسلة، وتأكّد العضوية في مرساة ثقة، وأن تكون RevocationStatus::hasConclusiveGood() بقيمة true.
  • الشهادة المؤكّد إبطالها قاطعة: يكون الحُكم Invalid بغضّ النظر عن كل دليل آخر.
  • كون فحصَي الإبطال كليهما Unknown/Unavailable يعني Indeterminate، وليس Valid أبدًا (تشديد ⁨3.1.0⁩، بأثر على التوافق مع الإصدارات السابقة).
  • التوقيع المُصادَق عليه والصالح السلسلة دون مرساة ثقة مؤكّدة يكون Indeterminate — أصيل، لكن الثقة غير مُثبَتة.
  • signer هي الموضوع المُتحقَّق منه عبر ⁨CMS⁩ أو السلسلة النصية الفارغة؛ ولا يُستخدَم إدخال /Name أبدًا.
  • timestamp تكون دائمًا في حالة الغياب في نتائج الدُّفعة.
  • validCount تعدّ Valid فقط؛ وكل الحالات الأخرى تُحتسَب في invalidCount، وتتجمّع حالة المستند من تلك العدّادات.
  • تطبّق toJson() حارس الخصوصية الافتراضي CertPiiGuard ما لم يُمرَّر حارس صراحةً.
  • إجماليات التقرير هي مجاميع دقيقة على نتائج كل مستند؛ وdurationMs هي الزمن الفعلي المقيس للدُّفعة.

وحدة الأمان / التوقيع في ⁨NextPDF Core⁩ هي جانب الإنتاج: فهي تُنشئ توقيعات ⁨CMS⁩، وتطبّق طوابع ⁨RFC 3161⁩ الزمنية، وتتحقّق من السلاسل والإبطال للمواد التي تُضمّنها وقت التوقيع. لكن ⁨Core⁩ لا يأتي بأي مُنسّق دُفعي في جانب التحقّق: لا تقرير متعدّد المستندات، ولا تصنيف حالة إجمالي، ولا أحكام إبطال ⁨OCSP/CRL⁩ لمستندات أطراف ثالثة، ولا سَلسلة تقارير محمية من الكشف عن البيانات الشخصية. وعلى ⁨Core⁩ وحده، سيتعيّن عليك استخراج كل توقيع والتحقّق منه بنفسك وبناء تقاريرك الخاصة. أمّا جانب التحقّق للمستند المفرد في ⁨Enterprise⁩ (التحقّق من التوقيع) وهذا المُنسّق الدُّفعي فيوفّران تلك الطبقة.

تُوثّق هذه الصفحة السلوك القابل للملاحظة خارجيًا وسطح ⁨API⁩ العام المدعوم فقط. أمّا مسارات مساحات الأسماء الداخلية، والفئات المساعِدة، وجداول الآليات، وأسماء ملفات كتيّبات التشغيل، وبادئات التذاكر فخارج النطاق.