Перейти к содержимому

Проверка существующей подписи и границы доверия

Используйте инспектор Core, чтобы определить, содержит ли PDF словарь подписи. Инспектор работает автономно и не использует сайдкар Spectrum. Этот рецепт также проясняет границу доверия: обнаружить подпись — не значит проверить её. Криптографическая проверка, проверка пути доверия и проверка отзыва доступны в Premium или выполняются внешними средствами.

Окно терминала
composer require nextpdf/core:^3

Подпись PDF — это поле подписи, значение которого является словарём подписи (ISO 32000-2 §12.7.4). Запись Contents словаря содержит данные Cryptographic Message Syntax (CMS) SignedData в кодировке DER (ISO 32000-2 §12.8.1). Резервный режим Quick компонента Inspector обнаруживает наличие этой структуры, сканируя маркеры подписи. Он не разбирает CMS, не пересчитывает дайджест по диапазону байтов (который исключает значение подписи — ISO 32000-2 §12.8.1), не проверяет цепочку сертификатов и не проверяет отзыв.

Вызовите new Inspector(), затем ->inspect(string $pdfData, InspectConfig $config). Используйте InspectConfig::quick() для автономного резервного режима на PHP. InspectDepth::Standard/Full требуют сайдкар Spectrum и при его отсутствии завершаются с отказом (INSPECT-SIDECAR-001). Результат — объект-значение InspectResult. В этом сценарии используйте $hasSigned, чтобы определить наличие подписи, а также $isEncrypted и $pdfVersion.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Inspect\InspectConfig;
use NextPDF\Inspect\Inspector;
$pdfData = file_get_contents(__DIR__ . '/incoming.pdf');
if ($pdfData === false || $pdfData === '') {
fwrite(STDERR, "Cannot read incoming.pdf\n");
exit(1);
}
$result = (new Inspector())->inspect($pdfData, InspectConfig::quick());
// hasSigned reports the PRESENCE of a signature dictionary.
// It does NOT mean the signature verifies.
echo $result->hasSigned
? "A signature is present — NOT verified.\n"
: "No signature found.\n";

Эта самодостаточная программа выполняется в среде запуска cookbook. Она повторяет examples/37-inspect-existing-signature.php. Она проверяет заведомо подписанный образец из корпуса и только что созданный неподписанный документ, чтобы вы могли увидеть обе ветви флага наличия. Затем программа передаёт вердикт дальше. Наличие — это входные данные для маршрутизации, а не вердикт о доверии. Файл передаётся криптографическому верификатору (Pro или внешнему). Здесь ему не доверяют.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Inspect\InspectConfig;
use NextPDF\Inspect\Inspector;
$inspector = new Inspector();
// --- A known-signed input ---
// The repository corpus carries synthetic PAdES samples. In your
// application this is simply the incoming PDF you received.
$signedPath = __DIR__ . '/tests/Corpus/pades/pades-b-b-bytepattern-synthetic.pdf';
if (is_file($signedPath)) {
$signed = (string) file_get_contents($signedPath);
$r = $inspector->inspect($signed, InspectConfig::quick());
echo "Signed sample:\n";
echo ' Signature present : ' . ($r->hasSigned ? 'yes' : 'no') . "\n";
echo ' Encrypted : ' . ($r->isEncrypted ? 'yes' : 'no') . "\n";
echo ' PDF version : ' . ($r->pdfVersion ?? 'unknown') . "\n";
echo " Verdict : presence detected — NOT verified.\n";
if ($r->hasSigned) {
// Presence detected. This is routing input, not a trust verdict.
// Hand the file to a cryptographic verifier (Pro or external)
// before relying on it. (Pseudo-queue shown; wire your own.)
// $verifierQueue->enqueue($signed);
echo " Next step : run a cryptographic verifier before trusting it.\n";
}
} else {
echo "Signed corpus sample absent; skipping the signed branch.\n";
}
// --- A known-unsigned input ---
$unsigned = Document::createStandalone();
$unsigned->setTitle('Unsigned sample');
$unsigned->addPage();
$unsigned->setFont('helvetica', '', 12);
$unsigned->cell(0, 10, 'This document carries no signature.', newLine: true);
$unsignedBytes = $unsigned->getPdfData();
$ru = $inspector->inspect($unsignedBytes, InspectConfig::quick());
echo "Unsigned sample:\n";
echo ' Signature present : ' . ($ru->hasSigned ? 'yes' : 'no') . "\n";
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script under the
// semantic profile; emit the unsigned document to the side-channel.
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
file_put_contents($out !== false && $out !== '' ? $out : __DIR__ . '/inspected.pdf', $unsignedBytes);

Ожидаемый STDOUT (подписанная ветвь пропускается, если в корпусе нет образца):

Signed sample:
Signature present : yes
Encrypted : no
PDF version : <version>
Verdict : presence detected — NOT verified.
Next step : run a cryptographic verifier before trusting it.
Unsigned sample:
Signature present : no
  • Наличие не равно достоверности. $hasSigned сообщает, что словарь подписи существует. Он не проверяет структуру CMS, дайджест по диапазону байтов, сертификат подписи, цепочку или отзыв. Подделанный файл всё равно может вернуть hasSigned = true. Никогда не рассматривайте наличие как доказательство целостности или авторства.
  • Что требуется для полной проверки. Полное решение пересчитывает дайджест по диапазону байтов (ISO 32000-2 §12.8.1), проверяет CMS SignedData, строит и проверяет путь X.509 до доверенного якоря и проверяет отзыв через Online Certificate Status Protocol (OCSP) или список отзыва сертификатов (CRL). Метка времени подписи, если она есть, проверяется по собственному отпечатку октетов значения подписи (ETSI EN 319 122-1 §5.3). Эти операции выполняются за контрактами подписи. Реализации для рабочей среды поставляются в редакциях Pro и Enterprise. Внешний валидатор — ещё один поддерживаемый путь.
  • Глубина проверки. InspectConfig::quick() — единственная глубина, которая работает без сайдкара Spectrum. Standard/Full выбрасывают INSPECT-SIDECAR-001, когда сайдкар недоступен.
  • Пустые входные данные. Пустая строка вызывает исключение проверки с сообщением “PDF data must not be empty”. Защитите операцию чтения проверкой.
  • Несколько подписей / меток времени. Флаг наличия не подсчитывает подписи и не отличает утверждающую подпись от метки времени документа (она также содержится в unsignedAttrs согласно RFC 5652 §5.3). Используйте специализированный верификатор, когда важно количество подписей или вердикт по каждой из них.

Резервный режим Quick выполняет ограниченное сканирование байтов документа. Он не разбирает полный граф объектов. Используйте его для быстрой сортировки входящих файлов, прежде чем направлять их в более тяжёлый верификатор.

Инспектор — инструмент для сортировки, а не граница доверия. Положительное значение hasSigned никогда не должно само по себе определять решение о доверии.

Проверка полностью выполняется внутри процесса. Ни один байт документа не покидает хост. Резервный режим Quick читает только структурные маркеры, а не текст документа, поэтому не извлекает и не передаёт персональные данные (PII).

Безопасная телеметрия и очистка журналов

Заголовок раздела «Безопасная телеметрия и очистка журналов»

Inspector принимает необязательный логгер PSR-3. Он записывает в журнал выбранный путь (“Spectrum unavailable, using PHP fallback”), а не содержимое документа. Не записывайте в журнал байты проверяемого PDF или дословный InspectResult, если документ содержит конфиденциальные данные.

Рассмотрено: подделанный файл, который представляет синтаксически корректный словарь подписи (инспектор сообщает о наличии; он явно не утверждает целостность), и файл без подписи (правильно сообщается как отсутствующий). Не утверждается, что какая-либо обнаруженная подпись криптографически достоверна, доверена или не отозвана — это задача верификатора.

Резервный режим Quick не выполняет криптографических операций, поэтому режим Federal Information Processing Standards (FIPS) не имеет отношения к этому рецепту. Криптографическая проверка (Premium/внешняя) — именно тот этап, где цепочка провайдеров FIPS имеет значение.

УтверждениеСпецификацияПунктИдентификатор источника (reference_id)
Значением поля подписи является словарь подписи.ISO 32000-2§12.7.4
Contents содержит DER CMS SignedData; Contents метки времени документа — TimeStampToken.ISO 32000-2§12.8.1
Проверка пересчитывает дайджест по диапазону байтов, исключая значение подписи.ISO 32000-2§12.8.1
Отпечаток метки времени подписи вычисляется по октетам значения подписи SignerInfo.ETSI EN 319 122-1§5.3
Метка времени содержится в unsignedAttrs SignerInfo.RFC 5652§5.3

Этот рецепт обнаруживает подпись. Он не утверждает, что какая-либо подпись достоверна, доверена или не отозвана. Это решение принимает криптографический верификатор.

Криптографическая проверка CMS, проверка пути X.509 и проверка отзыва через OCSP/CRL поставляются за контрактами подписи в редакциях Pro и Enterprise. Инспектор Core охватывает только обнаружение подписи.