콘텐츠로 이동
getnextpdf.com

서명을 올바르게 검증하는 방법

Spec: RFC 5280, §6Spec: RFC 6960Spec: RFC 5652

“서명이 유효하다”는 보통 한 가지, 즉 수학적 계산이 맞아떨어졌다는 것만 확인했다는 뜻입니다. 올바른 검증은 최소한 다섯 가지 독립적인 항목을 확인하며, 그중 어느 하나라도 실패하면 초록색 체크 표시가 무의미해질 수 있습니다. 이 페이지는 전체 검사 집합을 정리하고, 부분적인 답이 왜 위험한 답인지 설명합니다.

단일 불리언 값은 이 주제 전체에서 가장 위험한 출력입니다. 그 값은 독자가 “유효함”을 “신뢰할 수 있음”으로 받아들이도록 유도하지만, “유효함”은 단지 바이트가 변경되지 않았다는 의미일 뿐일 수 있습니다 — 3년 전에 만료되었고, 지난달에 폐지되었으며, 여러분이 인정하는 어떤 기관에도 연결되지 않는 인증서의 키로 말입니다. 이들 각각은 별개의 검사입니다. 불리언 하나만 보고하는 소프트웨어는 어떤 검사가 중요한지를 조용히 결정해 버린 것이며, 그 결정을 여러분 대신 내린 것입니다. 규제 또는 계약 환경에서 도구가 가장 저렴한 속성만 검증했다면 “도구가 유효하다고 했다”는 것은 변명이 되지 않습니다.

완전한 검증은 다섯 개의 별개 질문에 답합니다. 이들은 독립적입니다 — 하나를 통과했다고 해서 나머지에 대해서는 아무것도 말해 주지 않습니다.

  1. 무결성 — 서명된 바이트가 여전히 서명이 포괄하는 값으로 해시됩니까? (바이트 범위 다이제스트를 다시 계산해 비교합니다.)
  2. 진정성 — 암호화 서명이 서명된 속성에 대해 서명 인증서의 공개 키로 검증됩니까?
  3. 인증서 경로 — 그 인증서가 여러분이 선택한 신뢰 앵커까지 연결되며, 모든 연결 고리가 유효합니까?
  4. 시간해당 시점에 인증서가 유효 기간 내에 있었으며, 그 시간이 자체 주장된 것이 아니라 신뢰된 것입니까?
  5. 폐지 — 그 시점에 인증서가 폐지되지 않았음이, 실제로 확보할 수 있거나 임베드된 증거(OCSP/CRL)로 확인됩니까?

다섯 가지를 모두 수행하지 않은 “유효함”은 완전한 답처럼 보이는 불완전한 답입니다.

NextPDF의 입장은 각 질문이 별개이며 각각에 명시적으로 답해야 한다는 것입니다. 질문은 결코 하나의 낙관적인 플래그로 뭉뚱그려지지 않으며, 검사가 번거롭다는 이유로 조용히 건너뛰는 일도 결코 없습니다. 이는 테스트로 강제됩니다. 이 페이지가 표준 기반이 아니라 테스트 기반으로 표시된 이유가 바로 이것입니다. 즉, 이 동작은 조항으로만 논증되는 것이 아니라 테스트 스위트로 계속 보장됩니다.

무결성과 진정성은 종단 간 방식으로 테스트됩니다. 알려진 인증서가 실제 서명된 속성 구조에 서명하고, 테스트 스위트가 여러 시간 벡터에 걸쳐 일치하는 공개 키로 서명을 검증합니다. 따라서 정규 구조를 깨뜨리는 변경은 테스트를 깨뜨립니다. 인증서 경로 검증은 서명 바이트를 의도적으로 조작해, 그 결과가 구조화된 이유와 함께 유효하지 않다고 단언하는 테스트로 유지됩니다 — 삼켜진 예외가 아니라 명시적으로 기록된 실패로 말입니다. 타임스탬프 토큰 검증은 개별 단계 — 디코드, 서명자 정보, 서명된 속성, 메시지 다이제스트, 인증서 바인딩, 키 사용, 서명, produced-at — 로 나뉘며, 각 단계가 개별적으로 테스트되므로 “타임스탬프가 검증됨”은 모든 단계가 검증되었음을 의미합니다. 서명 타임스탬프의 경우, 그 단계들 중 하나는 선택 사항도 아니고 일반적인 것도 아닙니다. 토큰의 메시지 임프린트는 그것이 포괄한다고 주장하는 서명에 반드시 바인딩되어야 합니다 — 즉, 토큰 내부의 해시는 서명자 자신의 서명 값의 해시와 같아야 하며, 그렇지 않으면 토큰은 다른 바이트를 증명하고 있는 것입니다. NextPDF는 이를 서명 값을 필수 입력으로 받는 전용 verifySignatureTimestamp 검사로 노출하므로 이를 건너뛸 수 없으며, 빈 입력, 인식되지 않거나 취약한(SHA-1) 임프린트 알고리즘, 잘못된 형식의 임프린트, 또는 일치하지 않는 해시에 대해서는 안전하게 실패(fail-closed)합니다. 폐지 소프트 실패(응답자에게 도달할 수 없음)는 코드와 테스트 모두에서 확정적인 “폐지됨”과 구분됩니다. 이 둘은 결코 같은 답으로 혼동되지 않습니다.

  1. IntegrityRecompute the byte-range digest and compare it to the value the signature covers.
  2. AuthenticityVerify the cryptographic signature against the certificate’s public key, over the signed attributes — not the raw content.
  3. Certificate pathBuild and validate the chain to a trust anchor you chose; every link’s signature, validity, and constraints must hold.
  4. TimeConfirm the certificate was valid at the relevant instant, and that the instant is trusted time, not the signer’s clock.
  5. RevocationConfirm the certificate was not revoked at that time, using obtainable or embedded OCSP/CRL evidence.
올바른 PDF 서명 검증이 답하는 다섯 개의 독립적인 질문을 순서대로 나타냅니다. 각각은 별개입니다. 하나를 통과해도 나머지에 대해서는 아무것도 말해 주지 않으며, 어느 하나라도 건너뛰면 전체 결과가 불완전해집니다.

핵심은 API 호출이 아닙니다. 결과에 따라 행동하기 전에 답할 수 있어야 하는 질문들입니다. 이를 검토 과정에서 여러분이 책임져야 할 체크리스트로 다루십시오.

<?php
declare(strict_types=1);
// A correct validation produces a structured outcome, not one boolean.
// Before you trust a signature, you must be able to answer ALL of these:
//
// integrity : Does the byte-range digest still match? (tamper check)
// authenticity: Does the signature verify over the SIGNED ATTRIBUTES,
// not just the content?
// path : Does the certificate chain to a trust anchor YOU chose,
// with every link valid at the relevant time?
// time : Is the relevant time TRUSTED (a timestamp), or merely the
// signer's self-asserted clock?
// revocation : Was the certificate not revoked at that time, by evidence
// you obtained or that the document embedded?
//
// "valid: true" without an answer to every line above is an incomplete
// result. A path-validation outcome carries a `valid` flag AND a structured
// `reasons` list precisely so a failure says WHY — never a bare false.

어느 항목이라도 “모르겠다”라면, 정직한 상태는 “유효함”이 아닙니다. 그것은 “아직 결정되지 않음”이며 — 이 둘을 같은 것으로 취급하는 것이야말로 이 페이지가 방지하고자 하는 오류입니다.

함정은 “암호학적으로 유효함”을 “신뢰할 수 있음”과 동일시하는 것입니다. 무결성과 진정성은 함께 이 바이트가 이 키의 소유자에 의해 서명되었다는 것만 증명합니다. 이들은 그 키의 인증서가 신뢰되었는지, 유효 기간 내였는지, 폐지되지 않았는지에 대해서는 아무것도 말해 주지 않습니다. 자체 생성한 인증서로 서명된 문서는 “암호학적으로 유효함”이면서도 아무런 가치가 없을 수 있습니다. 반대 함정은 불확정 폐지 검사(응답자 오프라인)를 통과 — 또는 실패 — 로 취급하는 것입니다. 그것은 둘 중 어느 것도 아닙니다. 그것은 알 수 없음이며, 올바른 검증기는 어느 방향으로든 추측하기보다 이를 알 수 없음으로 보고합니다. 다섯 가지 검사 중 어느 것이 실제로 실행되었는지를 숨기는 초록색 체크 표시는 검증 결과가 아닙니다. 그것은 다른 누군가가 여러분을 대신해 내린 결정입니다.

NextPDF는 구조적 검사와 암호학적 검사를 수행하고 테스트합니다. 하지만 여러분의 신뢰 앵커를 선택하거나 그 위에 적용되는 정책을 보장하지는 않습니다. 어떤 인증서를 신뢰할지는 엔진이 내릴 수 없는 배포 결정입니다. 신뢰하지 말았어야 할 앵커까지 검증되는 체인은 여전히 여러분이 의존할 수 없는 검증입니다. 폐지 증거는 확보할 수 있거나 임베드되어 있을 때만 확인할 수 있습니다. 오프라인 응답자는 “불확정”을 산출하며, 그것을 판정으로 바꾸는 것은 엔진의 선택이 아니라 정책의 선택입니다. 이 페이지는 법적 충분성이 아니라 검사 집합을 설명합니다. 검증된 서명이 특정한 법적 효력을 갖는지 여부는 인증서, 서명자, 관할권, 그리고 의무에 따라 달라집니다. 임베드된 증거가 시간이 지나도 이러한 검사에 답할 수 있는 상태로 유지하는 방법은 장기 검증에서 다룹니다. 무결성 검사 이면의 바이트 범위 메커니즘은 서명이 PDF에 자리 잡는 방식에 있습니다.

검증 영역의 티어별 제공 범위:

Signature validation checks — edition availability
EditionAvailability
Core

서명된 속성에 대한 무결성과 진정성, 그리고 공급된 신뢰 앵커에 대한 RFC 5280 §6 인증서 경로 검증.

Pro

RFC 3161 타임스탬프 토큰 검증을 추가하여, 신뢰된 시간 질문을 독립적으로 검사되는 단계들로 분해하여 다룹니다.

Enterprise

폐지 평가(OCSP/CRL)와 임베드된 장기 자료에 대한 검증을 추가하며, 불확정 결과를 확정적 결과와 구분합니다.

  • 무결성 검사 — 바이트 범위 다이제스트를 다시 계산하여 서명이 포괄하는 값과 비교하는 것.
  • 진정성 검사 — 서명된 속성에 대해 암호화 서명을 서명 인증서의 공개 키로 검증하는 것.
  • 서명된 속성 — 서명이 실제로 계산되는 대상인 인증된 CMS 속성(content-type, message-digest, signing-time, signing-certificate-v2).
  • 인증서 경로 검증 — 서명 인증서에서 선택된 신뢰 앵커까지의 체인을 구성하고 검사하는 것(RFC 5280 §6).
  • 신뢰 앵커 — 여러분이 신뢰하기로 결정한 인증 기관. 허용 가능한 경로의 루트.
  • 폐지 검사 — OCSP 또는 CRL을 통해 해당 시점에 인증서가 폐지되었는지 여부를 판정하는 것.
  • 불확정 — 증거를 얻을 수 없어 “양호”도 “폐지됨”도 아닌 폐지 결과. 통과도 실패도 아닙니다.
  • 임프린트 바인딩(서명 타임스탬프) — 서명 타임스탬프의 메시지 임프린트가 그것이 포괄하는 서명자의 SignerInfo 서명 값의 해시와 같은지 확인하는 검사. 이를 통해 타임스탬프가 다른 바이트가 아니라 서명에 증명 가능하게 적용됨을 보장합니다.