콘텐츠로 이동
getnextpdf.com

Enterprise 에디션

ASiC 신뢰 바인딩

ASiC 컨테이너는 서명 대상 파일을 그 파일을 보호하는 서명과 함께 묶습니다. 어려운 질문은 “서명이 계산에 맞는가?”가 아니라 “누가 서명자를 뒷받침하는가?”입니다. NextPDF\Enterprise\Security\Asic\AsicTrustBinder는 바로 그 질문에 답합니다. 컨테이너 서명에서 얻은 서명 인증서, 신뢰 목록, 검증 시각을 넘기면, AsicTrustBindingResult로 답합니다. 즉 신뢰/비신뢰 판정, 판단 근거로 삼은 앵커 번들 버전, 그리고 기계 판독 가능한 사유입니다. 모든 거부는 원인을 명시하므로 감사 증거가 저절로 작성됩니다.

한 가지 경계는 의도적이며 미리 밝혀둘 가치가 있습니다. 이 API는 ASiC 컨테이너를 파싱하지 않습니다. 사용자의 도구가 컨테이너를 열고 서명 인증서를 추출하며, NextPDF는 신뢰 결정을 담당합니다.

이 기능은 NextPDF Enterprise(nextpdf/enterprise)에 포함되며 Enterprise 등급 라이선스 봉투로 활성화됩니다. 해당 권한이 없는 배포에서는 이 기능의 클래스가 로드되지 않습니다. 에디션을 비교하고 라이선스를 받으세요.

Terminal window
composer require nextpdf/enterprise

활성화에는 Enterprise 라이선스 봉투가 필요합니다. 설치 및 인증을 참조하세요. 이 페이지의 클래스는 NextPDF\Enterprise\Security\AsicNextPDF\Enterprise\Security\Tsl 아래에 있습니다.

ASiC(Associated Signature Containers, ETSI EN 319 162-1)는 데이터 파일과 서명을 하나의 아카이브에 패키징합니다. baseline ASiC 컨테이너는 CAdES 또는 XAdES baseline 서명만 임베드합니다. CAdES baseline 서명은 자신의 서명 인증서를 SignedData.certificates 안에 담으므로, 서명이 올바른 형식이고 컨테이너 도구가 지원하는 경우 검증자는 컨테이너의 서명에서 이를 추출하도록 기대됩니다. 그렇게 추출된 인증서가 이 API의 입력입니다.

신뢰 원천은 ETSI TS 119 612 신뢰 목록(TSL)입니다. 이는 신뢰 서비스 제공자와 그들의 서비스 인증서를 열거하는 서명된 XML 문서입니다. NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider는 파싱된 TslDocument를 앵커 번들로 변환합니다. granted 상태이면서 CA/QC 서비스 유형인 서비스만 앵커 집합의 씨앗이 됩니다. 번들은 TSL 시퀀스 번호와 영토에서 파생된 버전 문자열, 그리고 SHA-256 무결성 다이제스트를 담습니다.

앵커 비교 이전에 두 개의 fail-closed 게이트가 실행됩니다.

  1. TSL 신선도. NextUpdate 시각이 지난 신뢰 목록은 만료된 것으로 폐기해야 합니다. AsicTrustBinder::verify()는 앵커를 하나라도 파생하기 전에 제공된 검증 시각에서 신선도를 단언합니다. 오래된 목록, 또는 명시적 UTC 지정자가 없는 NextUpdate 값은 TslParseException을 던집니다.
  2. 서명자 유효 기간. RFC 5280 경로 검증은 인증서 유효 기간이 검증 시각을 포함할 것을 요구합니다. 암호학적으로 온전하더라도 그 시각에 인증서가 만료되었거나 아직 유효하지 않은 서명은 정확한 사유 코드와 함께 거부됩니다.

그런 다음에야 바인더는 서명 인증서를 각 앵커에 대해 테스트합니다. 일치하면 사유 anchor_signature_match와 함께 trusted: true를 낳습니다. 일치가 없으면 사유 no_anchor_chain과 함께 trusted: false를 낳습니다.

핵심 설계 결정은 컨테이너 메커니즘과 신뢰 결정 사이의 엄격한 분리이며, 신뢰 결정은 시간에 대해 명시적일 것을 강제받습니다. 컨테이너 형식은 다양하지만(ASiC-S, ASiC-E, CAdES 또는 XAdES 페이로드), 신뢰 질문은 하나의 불변 핵심입니다. 즉 이 인증서가 명시된 시각에 신선한 신뢰 목록의 앵커까지 체인을 이루는가? 그 핵심을 ZIP 및 XML 파싱으로부터 자유롭게 유지하면 핵심이 충분히 작아져 빠짐없이 테스트할 수 있고 모든 게이트에서 fail closed 할 수 있습니다. 같은 논리로 조용한 now 기본값을 금합니다. 검증 시각이 판정을 바꾸므로, 호출자가 그것을 소유해야 합니다. 신선도는 선택적 협력자가 아니라 앵커 파생 경로 자체 안에서 단언되므로, 어떤 생산 경로도 이를 건너뛸 수 없습니다.

설계 배경: 디지털 서명이 서명자를 증명하는 방법.

생성에는 신뢰 목록을 앵커 번들로 변환하는 앵커 프로바이더가 필요합니다.

public function __construct(
private readonly TslTrustAnchorProvider $anchorProvider,
) {}

주 진입점은 서명자 인증서를 신뢰 목록에 대해 검증합니다.

public function verify(
string $signerCertPem,
TslDocument $tsl,
DateTimeInterface $validationTime,
): AsicTrustBindingResult
  • $signerCertPem — 비어 있지 않은 PEM 문자열: ASiC 서명에서 얻은 서명 인증서.
  • $tsl — 파싱되고 인증된 신뢰 목록.
  • $validationTime — 서명자 인증서의 유효 기간이 반드시 포함해야 하는 시각. 기본값은 없습니다.

던지거나 실패하는 경우: TSL이 오래되었을 때(NextUpdate 경과), NextUpdate가 정규 UTC 값이 아닐 때, 또는 목록에 활성 CA/QC 서비스가 없을 때 NextPDF\Enterprise\Security\Tsl\TslParseException. 비신뢰 서명자는 던지지 않습니다. trusted: false와 사유 코드를 담은 결과를 반환합니다.

배치 워크로드의 경우, 미리 구축된 번들에 대해 검증하세요.

public function verifyAgainstBundle(
string $signerCertPem,
EnterpriseCaTrustAnchorBundle $bundle,
DateTimeInterface $validationTime,
): AsicTrustBindingResult

던지거나 실패하는 경우: 자체 예외는 없습니다. 모든 결과는 AsicTrustBindingResult입니다. 번들은 TslTrustAnchorProvider::buildBundle()에서 얻으세요 — 직접 손으로 생성하지 마세요.

public function buildBundle(TslDocument $tsl, DateTimeImmutable $now): EnterpriseCaTrustAnchorBundle

던지거나 실패하는 경우: TSL이 오래되었거나, NextUpdate가 정규 UTC 값이 아니거나, 활성 CA/QC 서비스가 없을 때 TslParseException.

public function __construct(
public bool $trusted,
public string $anchorBundleVersion,
public array $reasons,
) {}

$reasons는 기계 판독 가능한 코드의 list<non-empty-string>입니다. $anchorBundleVersion은 사용된 앵커 집합을 tsl-<territory>-seq<N> 형태로 기록합니다(예: tsl-eu-seq42).

사유 코드의미
anchor_signature_match서명자 인증서가 TSL에서 파생된 앵커에 대해 검증됨. 신뢰됨.
no_anchor_chain번들의 어떤 앵커도 서명자 인증서를 검증하지 못함. 비신뢰.
signer_cert_expired검증 시각이 인증서의 notAfter 이후에 위치함. 비신뢰.
signer_cert_not_yet_valid검증 시각이 인증서의 notBefore 이전에 위치함. 비신뢰.
cannot_parse_signer_cert제공된 PEM이 X.509 인증서로 파싱되지 않음. 비신뢰.

컨테이너 도구가 이미 서명 인증서를 추출했습니다. 이를 가져와 인증한 회원국 신뢰 목록에 바인딩하세요(Trusted lists 참조).

asic-trust-binding-quickstart.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Security\Asic\AsicTrustBinder;
use NextPDF\Enterprise\Security\Tsl\TslParseException;
use NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider;
use NextPDF\Enterprise\Security\Tsl\TslXmlParser;
// Extracted by YOUR tooling from META-INF/signature.p7s or signatures.xml.
$signerCertPem = (string) file_get_contents(__DIR__ . '/asic-signer.pem');
// A trusted list you have already fetched and authenticated.
$tslXml = (string) file_get_contents(__DIR__ . '/member-state-tsl.xml');
$binder = new AsicTrustBinder(new TslTrustAnchorProvider());
try {
$tsl = (new TslXmlParser())->parse($tslXml);
$result = $binder->verify(
signerCertPem: $signerCertPem,
tsl: $tsl,
validationTime: new DateTimeImmutable('2026-07-03T12:00:00Z'),
);
} catch (TslParseException $e) {
// Fail closed: stale TSL, malformed NextUpdate, or no active CA/QC services.
fwrite(STDERR, 'Trusted list rejected: ' . $e->getMessage() . PHP_EOL);
exit(1);
}
echo $result->trusted ? "TRUSTED\n" : "NOT TRUSTED\n";
echo 'Anchors: ' . $result->anchorBundleVersion . "\n";
echo 'Reasons: ' . implode(', ', $result->reasons) . "\n";

목록에 등재된 CA/QC 서비스가 발급한 서명자에 대한 예상 출력:

TRUSTED
Anchors: tsl-eu-seq42
Reasons: anchor_signature_match

신뢰 목록당 한 번 앵커 번들을 파생한 다음, 많은 컨테이너 서명자를 그에 대해 검증하세요. 오래되었거나 사용할 수 없는 TSL 하나는 전체 배치를 fail closed 시킵니다. 개별 서명자 문제는 컨테이너별로 드러납니다.

asic-trust-binding-batch.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Security\Asic\AsicTrustBinder;
use NextPDF\Enterprise\Security\Asic\AsicTrustBindingResult;
use NextPDF\Enterprise\Security\Tsl\TslDocument;
use NextPDF\Enterprise\Security\Tsl\TslParseException;
use NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider;
use NextPDF\Enterprise\Security\Tsl\TslXmlParser;
/**
* @param array<string, non-empty-string> $signerPemsByContainer PEM per container path.
* @return array<string, AsicTrustBindingResult>
* @throws TslParseException When no anchor set can be derived from the TSL.
*/
function bindBatch(
TslDocument $tsl,
array $signerPemsByContainer,
DateTimeImmutable $validationTime,
): array {
$provider = new TslTrustAnchorProvider();
// Derive the anchor set ONCE; a throw here means the trusted list itself
// is unusable at this validation time.
$bundle = $provider->buildBundle($tsl, $validationTime);
$binder = new AsicTrustBinder($provider);
$results = [];
foreach ($signerPemsByContainer as $container => $signerPem) {
$results[$container] = $binder->verifyAgainstBundle(
signerCertPem: $signerPem,
bundle: $bundle,
validationTime: $validationTime,
);
}
return $results;
}
$tsl = (new TslXmlParser())->parse(
(string) file_get_contents(__DIR__ . '/member-state-tsl.xml'),
);
$signerPems = [
'invoice-2026-06.asice' => (string) file_get_contents(__DIR__ . '/signer-a.pem'),
'tender-2019.asice' => (string) file_get_contents(__DIR__ . '/signer-b.pem'),
];
try {
$results = bindBatch(
tsl: $tsl,
signerPemsByContainer: $signerPems,
validationTime: new DateTimeImmutable('now', new DateTimeZone('UTC')),
);
} catch (TslParseException $e) {
// Fail closed for the WHOLE batch: no trustworthy anchor set exists.
fwrite(STDERR, 'Anchor derivation failed: ' . $e->getMessage() . PHP_EOL);
exit(1);
}
foreach ($results as $container => $result) {
printf(
"%s => %s (%s; anchors %s)\n",
$container,
$result->trusted ? 'trusted' : 'rejected',
implode(',', $result->reasons),
$result->anchorBundleVersion,
);
}

한 서명자 인증서가 만료된 경우의 예상 출력:

invoice-2026-06.asice => trusted (anchor_signature_match; anchors tsl-eu-seq42)
tender-2019.asice => rejected (signer_cert_expired; anchors tsl-eu-seq42)
  • 검증 시각은 필수이며 결정적입니다. 조용한 now 기본값은 없습니다. 2019년에 검증되었던 서명은 notAfter를 지난 2026년 시각에서 검증하면 signer_cert_expired를 보고합니다. 과거 자료의 경우, 벽시계 시각이 아니라 증거가 뒷받침하는 시각(예: 존재 증명 시각)을 넘기세요.
  • 오래된 TSL은 던지며, “비신뢰” 판정이 아닙니다. verify() 또는 buildBundle()에서 나온 TslParseException은 신뢰 원천이 사용 불가함을 뜻합니다. 이를 운영 실패로 취급하세요. 목록을 갱신하고, 서명자 거부로 기록하지 마세요.
  • 앵커는 직접 발급자로 테스트됩니다. 각 앵커는 서명자 인증서에 서명한 인증서로 시도됩니다. EU 회원국 TSL은 발급 CA/QC 서비스 인증서를 등재하므로, 최종 개체 적격 인증서는 보통 직접 일치합니다. 그 자체가 등재된 활성 CA/QC 서비스가 아닌 중간 CA가 발급한 서명자는 no_anchor_chain을 낳습니다.
  • 앵커 파생은 엄격하게 필터링합니다. 철회되었거나 CA/QC 이외의 유형인 서비스는 결코 앵커가 되지 않습니다. 활성 CA/QC 집합이 비어 있는 목록은 빈 번들을 생성하는 대신 던집니다.
  • NextUpdate는 반드시 정규 UTC여야 합니다. 명시적 Z 또는 숫자 오프셋 지정자가 없는 값은 fail-closed로 거부되며, 서버의 로컬 시간대로 결코 재해석되지 않습니다.
  • 잘못된 입력은 정밀하게 저하됩니다. 파싱되지 않는 PEM은 cannot_parse_signer_cert를 반환하며, 아직 유효하지 않은 인증서는 만료된 인증서와 구분됩니다.
  • anchorBundleVersion을 기록하세요. 이는 각 판정 뒤의 정확한 앵커 집합(tsl-<territory>-seq<N>)을 명시하며, 이것이 감사관이 요구할 대상입니다.
  • 설계상 fail-closed. 신선도는 앵커를 파생하기 전에 단언됩니다. 서명자 유효성 게이트는 어떤 앵커 비교보다 먼저 실행됩니다. 사용할 수 없는 신뢰 자료는 던지고, 의심스러운 서명자는 사유와 함께 거부됩니다. 어떤 경로도 조용한 통과로 저하되지 않습니다.
  • 신뢰 바인딩은 한 계층일 뿐, 검증 전체가 아닙니다. 이 API는 컨테이너 콘텐츠에 대한 CAdES 서명 값을 검증하지 않고, 폐기 여부를 확인하지 않으며(CRL 또는 OCSP 조회 없음), TSL 문서 자체를 인증하지 않습니다. 먼저 신뢰 목록 파이프라인을 통해 목록을 인증하고(Trusted lists 참조), 서명 도구로 서명을 암호학적으로 검증하고, 정책에 따라 폐기 확인을 추가하세요.
  • 검증 시각을 신중하게 선택하세요. 판정은 넘긴 시각의 함수입니다. 이를 공격자가 영향을 줄 수 있는 시계가 아니라 신뢰할 수 있는 증거(적격 타임스탬프, 보관 기록)에서 도출하세요.
  • 증거 출력은 결정론적입니다. trusted, anchorBundleVersion, reasons는 안정적이고 기계 판독 가능한 값으로, 서명된 감사 로그에 적합합니다.

AsicTrustBinder는 ETSI EN 319 162-1(ASiC baseline 컨테이너), ETSI EN 319 122-1(CAdES baseline 서명), ETSI TS 119 612(신뢰 목록)에 부합하는 워크플로를 지원하며, 제공된 검증 시각에서 RFC 5280 유효 기간 게이트를 적용합니다.

지원은 적합성이 아니며, 적합성은 인증이 아닙니다. NextPDF는 이 페이지가 설명하는 검사를 구현하지만, 어떤 기관으로부터도 이 표준에 대해 인증받지 않았으며, 이 API를 사용한다고 해서 그 자체로 사용자의 출력이 eIDAS 또는 다른 어떤 체제 아래에서 “적격”이거나 법적으로 효력을 갖게 되지는 않습니다. NextPDF는 어떤 인증도 보유하지 않으며 어떤 인증도 부여하지 않습니다. 완전한 검증 프로세스가 주어진 법적 또는 조달 요구사항을 충족하는지 여부는 사용자의 평가자가 판단할 사항입니다.

신뢰 바인딩은 인프로세스로 X.509 인증서 서명 검사를 수행합니다. 이는 Enterprise FIPS 모드 런타임 가드를 거치지 않으며, FIPS 모드를 활성화해도 그 동작이 바뀌지 않습니다. 이는 FIPS 검증된 암호 서비스가 아니며, 어떤 FIPS 140 인증도 주장하지 않습니다. FIPS 의무가 있는 배포는 이 API의 범위를 그에 맞게 설정하고 FIPS 140-2/3 암호 정책을 참조해야 합니다.

  • verify()는 제공된 검증 시각에 신선한 TSL에서만 앵커를 파생합니다. 오래되었거나 잘못된 목록은 앵커가 존재하기 전에 TslParseException을 던집니다.
  • 앵커는 granted 상태이면서 CA/QC 서비스 유형인 TSL 서비스에서만 파생됩니다. 활성 집합이 비어 있으면 던집니다.
  • 서명자 인증서의 유효 기간은 반드시 검증 시각을 포함해야 합니다. 위반 시 signer_cert_expired 또는 signer_cert_not_yet_valid를 반환합니다.
  • 모든 결과는 trusted, anchorBundleVersion, 그리고 최소 하나의 사유 코드를 담은 AsicTrustBindingResult입니다. 사유 없는 판정은 없습니다.
  • 비신뢰 서명자는 반환되며 결코 던져지지 않습니다. 사용할 수 없는 신뢰 자료는 던져지며 결코 판정으로 반환되지 않습니다.
  • 컨테이너 파싱은 이 API 안에서 결코 일어나지 않습니다. 입력은 추출된 PEM, 신뢰 목록, 검증 시각입니다.

NextPDF Core는 자신의 CaTrustAnchorBundle 계약을 통해 명시적으로 고정한 신뢰 앵커에 대해 PDF(CMS/PAdES) 서명을 검증합니다 — Core security 참조. Core에는 신뢰 목록(TSL) 인제스트도 ASiC 특유의 신뢰 바인딩도 없습니다. Core만으로는 PDF 서명 검증을 위한 자체 앵커 집합을 유지할 수 있지만, ETSI TS 119 612 신뢰 목록에서 앵커를 파생하고 ASiC 컨테이너 서명자를 그에 바인딩하려면 NextPDF Enterprise가 필요합니다.

이 페이지는 외부에서 관찰 가능한 동작과 지원되는 공개 API 표면만 문서화합니다. 내부 네임스페이스 경로, 헬퍼 클래스, 메커니즘 테이블, 런북 파일명, 티켓 접두사는 범위 밖입니다.