PDF 서명이 eIDAS 아래에서 적격이라고 판정한다는 것은 암호학과는 무관한 질문에 답하는 일입니다. 서명이 만들어진 순간에 그 인증서가 회원국이 적격으로 목록에 올린 신뢰 서비스가 발급한 것인가입니다. 답은 신뢰 목록, 즉 영역별로 발행되는 XML 문서에 살며, 그 문서의 전체 가치는 진정성에 달려 있습니다. 그래서 PDFium 컴포넌트는 누군가 보증하기 전에는 그 안을 들여다보기를 거부합니다. TPdfEuropeanTrustedList.ParseAuthenticated는 서비스 하나를 파싱하기도 전에 완전한 원본 바이트를 호출자가 공급한 IPdfTrustedListAuthenticator에 넘기고, 그 인증자가 명시적으로 통과할 때만 스냅샷을 만듭니다
그 순서가 곧 설계입니다. 이 기능의 다른 모든 것, 불편해 보이는 부분까지도 여기서 따라 나옵니다
파싱되는 것은 신뢰되는 것이 아닙니다
깨끗하게 파싱되는 신뢰 목록은 XML이 잘 형성되었음을 말해 줄 뿐입니다. 누가 썼는지는 아무것도 말해 주지 않습니다. 목록이 바로 여러분의 전체 적격 상태 판정이 기대는 것이므로, 파싱되었다는 이유로 받아들이면 판정은 무의미해집니다. 목록을 바꿔치기할 수 있는 공격자는 자기 인증 기관을 적격으로 선언할 수 있습니다
같은 논리가 캐싱에도 적용되며, 이름 붙일 가치가 있는 함정이 바로 여기입니다. 스냅샷 캐시는 원본 XML을 SHA-256 다이제스트와 함께 저장하며, 로드 시 일치하는 다이제스트를 목록이 진짜라는 증거로 다루기 쉽습니다. 그렇지 않습니다. 키가 전혀 관여하지 않은 채 파일을 저장한 같은 프로세스가 계산한 다이제스트는 여러분이 쓴 뒤 바이트가 바뀌지 않았다는 것만 검증합니다. 목록이 캐시될 때 사기였다면 다이제스트는 같은 사기 목록임을 확인해 줄 뿐입니다. 따라서 캐시된 스냅샷 로드는 새 목록 파싱과 같은 인증자를 거칩니다. 무결성과 진정성은 다른 성질이며, 둘 중 키가 필요한 것은 하나뿐입니다
uses
FPdfTrustedList;
type
TListAuthenticator = class(TInterfacedObject, IPdfTrustedListAuthenticator)
public
function Authenticate(const XmlData: TBytes;
out AuthenticationDetails: string): Boolean;
end;
function TListAuthenticator.Authenticate(const XmlData: TBytes;
out AuthenticationDetails: string): Boolean;
begin
// 정책이 사는 곳입니다. 밴드 밖에서 고정해 둔 목록 서명
// 인증서로 XMLDSIG enveloped 서명을 검증하고, 감사 추적을 위해
// 무엇을 확인했는지 기술합니다
Result := VerifyEnvelopedXmlSignature(XmlData, FPinnedListSigner);
if Result then
AuthenticationDetails := 'XMLDSIG verified against pinned LOTL signer';
end;
var
List: TPdfEuropeanTrustedList;
Cache: TFileStream;
begin
List := TPdfEuropeanTrustedList.ParseAuthenticated(RawXml,
TListAuthenticator.Create, TPdfTrustedListOptions.Default);
// 인증자가 예라고 말했기 때문에만 스냅샷이 존재합니다
Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
try
List.SaveCache(Cache);
finally
Cache.Free;
end;
end;
검증기는 네트워크 정책을 소유하지 않습니다
PAdES 검증기는 신뢰 목록들의 목록에 어떻게 닿을지, 프록시를 거칠지, 얼마나 자주 재시도할지, 영역에 닿을 수 없을 때 무엇을 할지를 결정할 일이 없습니다. 그것들은 애플리케이션과 배포의 결정이며, 규제 환경에서는 자주 감사를 받습니다. 그래서 갱신은 IPdfTrustedListSource를 통해 도착합니다. URI와 바이트 상한을 넘겨받고 바이트를 돌려줍니다
컴포넌트가 강제하는 것은 갱신을 대체가 아니라 갱신으로 만드는 불변식입니다. Update는 영역이 변하지 않았고, 시퀀스 번호가 엄격히 증가하고, 발행 시각이 뒤로 움직이지 않는 것을 요구합니다. 이 세 검사가 가장 뻔한 다운그레이드 공격을 무력화합니다. 이후 철수된 서비스를 여전히 실은 더 오래된 목록을 재생하거나, 여러분이 신뢰할 의도가 없던 다른 영역의 목록으로 바꿔치기하는 것입니다
파서 한계, 그리고 DTD는 아예 없습니다
TPdfTrustedListOptions는 XML 크기, 토큰 수, 중첩 깊이, 서비스 수, 인증서 수, 개별 인증서 크기에 상한을 두며, Default 클래스 함수가 쓸 만한 값을 제공합니다. 신뢰 목록은 크기가 예측 가능한 공개 문서이므로 상한을 두는 비용은 쌉니다. 그것을 넘어야 할 정당한 목록은 없습니다
별도로, 그리고 무조건적으로 파서는 DTD와 엔티티 선언을 거부합니다. 이는 엔티티 확장 서비스 거부와 외부 엔티티 노출 경로를 한 번의 거부로 닫으며, 신뢰 목록은 엔티티를 쓰지 않으므로 비용이 없습니다. 신뢰할 수 없는 입력에서 닿을 수 있는 어떤 XML 파서든 이렇게 구성해야 합니다. 여기서의 차이는 거부가 설정 불가능하다는 것이고, 그래서 선의의 옵션 변경으로 끌 수 없습니다
적격 상태는 체인 신뢰 옆에 기록되지 그 안으로 합쳐지지 않습니다
평가 쪽은 의도적으로 분리되어 있습니다. TPadesTrustValidationOptions.QualifiedTrustEvaluator는 IPdfQualifiedTrustEvaluator를 받으며, 신뢰 목록 스냅샷이 이를 구현합니다. 검증 중 평가자는 리프 인증서, 체인, 검증 시각을 받고, 서명자와 체인에 대해 정확한 DER 비교로 서비스 인증서를 맞추며, 그 시점의 서비스 상태, 서비스 타입 식별자, 한정자 URI를 결합해 평가 레코드를 돌려줍니다
결과는 각 서명의 두 자리에 착지합니다. 대략적 상태인 QualifiedTrustStatus, 그리고 영역, 제공자 이름, 서비스 이름, 타입 식별자, 상태, 상태 시작 시각을 갖춘 완전한 평가인 QualifiedTrust입니다. 하지 않는 것은 CertificateTrustStatus를 바꾸는 일입니다. 시스템 체인 신뢰와 적격 상태는 다른 질문에 답하며, 둘을 뭉개버린 보고서는 "신뢰되지만 적격이 아님"과 "적격이지만 체인이 검증되지 않음"을 구분할 수 없습니다. 둘 다 실재하며 둘 다 다른 처리가 필요합니다
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
I: Integer;
begin
Options := TPadesTrustValidationOptions.Default;
Options.CheckRevocation := True;
Options.QualifiedTrustEvaluator := List; // 인증된 스냅샷
Options.QualifiedValidationTime := SigningTime; // 지금이 아니라
Report := Pdf.ValidatePadesTrust(Options);
for I := 0 to High(Report.Signatures) do
if Report.Signatures[I].QualifiedTrustStatus = pcsValid then
Writeln(Format('signature %d qualified by %s / %s (%s)',
[I, Report.Signatures[I].QualifiedTrust.Territory,
Report.Signatures[I].QualifiedTrust.ProviderName,
Report.Signatures[I].QualifiedTrust.ServiceName]))
else if Report.Signatures[I].QualifiedTrustStatus = pcsIndeterminate then
// 맞는 서비스가 없거나 스냅샷이 이 시각에 대해 답할 수 없습니다
Writeln(Format('signature %d: qualified status undetermined', [I]));
end;
검증 시각이 지금이 아닌 이유
적격은 한 순간의 성질이기 때문입니다. 신뢰 서비스는 적격 상태를 부여받고, 나중에 철회되고, 더 나중에 복권될 수 있으며, 그 전이 각각은 목록 안에 시작 시각을 실어 가고 있습니다. 서비스가 적격인 동안 만들어진 서명은 이후에도 적격으로 남고, 부여 전에 만들어진 서명은 소급하여 적격이 되지 않습니다. 따라서 현재 시각 기준 평가는 양방향으로 틀린 답을 줍니다
목록은 이에 필요한 것을 실어 갑니다. 각 서비스 레코드는 상태 시작 시각과 과거 엔트리를 현재와 구분하는 플래그를 갖고, 평가자는 여러분이 공급한 시각에 맞춰 이들을 결합합니다. 실무에서 그 시각은 CMS가 주장하는 서명 시각이 아니라 서명 위의 신뢰된 타임스탬프에서 옵니다. 정책 조회처럼 보이는 질문에도 장기 검증 자료가 중요한 이유이며, 타임스탬프와 DSS 쪽은 장기 서명 문서에서 다룹니다
여전히 직접 만들어야 하는 것
세 가지이며, 어느 것도 PDF 라이브러리에 속하지 않습니다. 인증자, 즉 여러분이 신뢰하는 채널로 얻은 목록 서명 인증서에 대한 실제 XMLDSIG 검증입니다. 가져오기 정책, 즉 어떻게 얼마나 자주 갱신하는지와 갱신이 실패할 때 애플리케이션이 무엇을 하는지입니다. 그리고 영역 범위, 즉 어쨌든 어느 목록을 실지하는지로, 거래 상대가 어느 회원국에서 서명하는지에 관한 비즈니스 결정입니다
컴포넌트에서 얻는 것은 미묘하게 틀리기 쉬운 부분입니다. parse 전 인증 순서, 상한 있고 엔티티 없는 XML 파싱, 단조 갱신 불변식, 정확한 DER 서비스 매칭, 과거 상태 평가, 평범한 체인 신뢰와 분리된 채 남는 결과입니다. 당면 문제가 더 기본적이라면, 즉 여러분이 문제없다고 믿는 서명을 검증기가 거부한다면 흔한 원인들은 검증기가 PAdES 서명을 거부하는 이유에 정리되어 있고, 서명 검사 표면은 서명 및 PAdES 레벨 검사에서 설명합니다. 컴포넌트 능력은 PDFium Delphi component 제품 페이지에 정리되어 있습니다