콘텐츠로 이동

기존 서명 검사 및 신뢰 경계 이해하기

이 레시피는 Core 검사기를 사용하여 PDF에 서명 딕셔너리가 들어 있는지 감지합니다. 검사기는 오프라인으로 실행되며 Spectrum 사이드카를 사용하지 않습니다. 또한 이 레시피는 경계를 분명히 합니다. 서명을 감지하는 것은 서명을 검증하는 것과 같지 않습니다. 암호화 검증, 신뢰 경로 유효성 검사, 폐기 확인은 Premium 또는 외부 영역에 속합니다.

Terminal window
composer require nextpdf/core:^3

PDF의 서명은 값이 서명 딕셔너리인 서명 필드입니다(ISO 32000-2 §12.7.4). 딕셔너리의 Contents 항목에는 DER로 인코딩된 CMS SignedData가 들어 있습니다(ISO 32000-2 §12.8.1). Inspector Quick 폴백은 서명 마커를 스캔해 이러한 구조의 존재를 감지합니다. 이 폴백은 CMS를 파싱하거나, 바이트 범위 다이제스트(서명 값을 제외함 — ISO 32000-2 §12.8.1)를 다시 계산하거나, 인증서 체인을 검증하거나, 폐기를 확인하지 않습니다.

먼저 new Inspector()를 호출한 다음 ->inspect(string $pdfData, InspectConfig $config)를 호출합니다. 오프라인 PHP 폴백에는 InspectConfig::quick()를 사용합니다. 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";

이 프로그램은 하니스에서 독립 실행형으로 실행할 수 있습니다. 예제 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 경로를 구성하고 확인하며, 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” 메시지와 함께 검사 예외를 발생시킵니다. 읽기 처리를 보호하십시오.
  • 여러 서명 / 타임스탬프. 존재 플래그는 서명을 세거나 승인 서명을 문서 타임스탬프(RFC 5652 §5.3에 따라 unsignedAttrs에도 담김)와 구별하지 않습니다. 개수나 서명별 판정이 중요할 때는 전용 검증기를 사용하십시오.

Quick 폴백은 문서 바이트에 대한 제한된 스캔입니다. 전체 객체 그래프를 파싱하지 않습니다. 더 무거운 검증기로 라우팅하기 전에 수신 파일을 빠르게 선별하는 데 적합합니다.

검사기는 선별 도구일 뿐, 신뢰 경계가 아닙니다. 양성 hasSigned만으로 신뢰 결정을 내려서는 절대 안 됩니다.

데이터 레지던시 및 PII 완화 조치

섹션 제목: “데이터 레지던시 및 PII 완화 조치”

검사는 완전히 프로세스 내부에서 수행됩니다. 문서 바이트는 호스트를 떠나지 않습니다. Quick 폴백은 문서 텍스트가 아닌 구조적 마커만 읽으므로, PII가 추출되거나 전송되지 않습니다.

안전한 텔레메트리 및 로그 스크러빙

섹션 제목: “안전한 텔레메트리 및 로그 스크러빙”

Inspector는 선택적 PSR-3 로거를 받습니다. 이 로거는 문서 콘텐츠가 아니라 선택된 실행 경로(“Spectrum unavailable, using PHP fallback”)를 기록합니다. 문서가 민감한 경우 검사된 PDF 바이트나 InspectResult를 그대로 로깅하지 마십시오.

고려 대상: 구문적으로 유효한 서명 딕셔너리를 제시하는 변조된 파일(검사기는 존재를 보고하지만, 무결성을 명시적으로 주장하지 않음)과 서명이 없는 파일(올바르게 없음으로 보고함). 주장하지 않는 것: 감지된 서명이 암호학적으로 유효하거나, 신뢰되거나, 폐기되지 않았다는 것 — 이는 검증기의 역할입니다.

Quick 폴백은 암호 연산을 수행하지 않으므로, FIPS 모드는 이 레시피와 관련이 없습니다. FIPS 공급자 체인이 중요한 곳은 암호화 검증(Premium/외부)입니다.

설명규격조항reference_id (참조 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
타임스탬프는 SignerInfo unsignedAttrs에 담깁니다.RFC 5652§5.3

이 레시피는 서명을 감지합니다. 이것은 어떤 서명이 유효하거나, 신뢰되거나, 폐기되지 않았다고 주장하지 않습니다. 그 판단은 암호화 검증기의 몫입니다.

암호화 CMS 검증, X.509 경로 유효성 검사, OCSP/CRL 폐기 확인은 Pro 및 Enterprise 에디션에서 서명 계약 뒤에 제공됩니다. Core 검사기는 존재 감지만 다룹니다.