Artigo Técnico

Evidência LTV e seed values PAdES no HotPDF

Um PDF que acabou de assinar é uma assinatura B-B e nada mais. Prova quem assinou e que os bytes não se moveram, mas não traz prova de que o certificado do signatário era válido no momento da assinatura, pelo que um validador daqui a anos tem de ir procurar dados de revogação que talvez já não existam. Fechar esse buraco significa escrever respostas OCSP e CRLs no Document Security Store ao nível do documento, e no HotPDF isso é uma chamada: PopulatePAdESLTVEvidence percorre cada assinatura carregada, deriva os pedidos de revogação do conjunto de certificados, executa-os através de um transporte que fornece, e escreve o material obtido mais a cadeia CMS no DSS. Devolve o número de assinaturas cuja evidência aterrou, ou menos um quando o documento não tem campo de assinatura nenhum

A decisão de design que vale a pena compreender antes de a usar é que a biblioteca nunca abre um socket. Cada byte que chega da rede chega através de um callback que escreveu. Isso não é cautela por si só; é a única forma de esta funcionalidade funcionar dentro dos ambientes que realmente exigem validação de longo prazo

Porque é que a biblioteca se recusa a fazer o seu próprio HTTP?

Porque os lugares que exigem assinaturas B-LT são os lugares onde não se pode confiar a rede a uma biblioteca. Os serviços de assinatura correm atrás de proxies com autenticação com raízes corporativas. Os níveis de assinatura isolados do ar não têm rota para nenhum respondedor e têm de ser alimentados com evidência em cache. Os regimes de auditoria exigem que cada pedido de saída seja registado pela aplicação, não enterrado numa dependência. E as suítes de teste precisam de respostas determinísticas, o que é impossível se a biblioteca ligar por conta própria

O transporte é uma referência de função simples com uma forma fixa, pelo que a política continua a ser sua. O HotPDF entrega-lhe um registo de pedido que descreve exatamente o que ir buscar, incluindo o tipo de conteúdo e um limite de tamanho de resposta, e devolve os bytes mais um estado

Fluxo do PopulatePAdESLTVEvidence do HotPDF: o transporte FetchEvidence fornecido pelo chamador, campos do registo de pedido e resultados de estado por assinatura
Cada byte de rede passa pelo seu callback FetchEvidence, e cada assinatura recebe o seu próprio estado para que um timeout nunca aborte a passagem
function FetchEvidence(const Request: THPDFSignatureEvidenceRequest;
  Attempt: Integer; CancellationToken: THPDFCancellationToken;
  out Response: TBytes; out RetryAfterMS: Cardinal;
  out ErrorMessage: UnicodeString): THPDFSignatureEvidenceTransportStatus;
begin
  RetryAfterMS := 0;
  try
    // Request.Kind diz se isto é um POST OCSP ou um GET de CRL;
    // Request.ContentType e Request.Body já estão preparados,
    // e Request.MaxResponseBytes é o limite que deve respeitar
    Response := HttpExchange(Request.URI, Request.ContentType,
      Request.Body, Request.MaxResponseBytes);
    Result := setsSucceeded;
  except
    on E: Exception do
    begin
      ErrorMessage := E.Message;
      // setsRetry deixa a política de repetição recuar; use
      // setsPermanentFailure para um 404 ou um URL mau
      Result := setsRetry;
    end;
  end;
end;

// Atualização B-B para B-LT numa só chamada para cada assinatura do ficheiro carregado
var
  Pdf: THotPDF;
  Upgraded: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginIncrementalUpdate('signed.pdf');
    Upgraded := Pdf.PopulatePAdESLTVEvidence(FetchEvidence,
      THPDFSignatureEvidenceRetryPolicy.Default);
    if Upgraded > 0 then
      // Gravação só de acréscimo: os bytes que as assinaturas
      // existentes cobrem são preservados verbatim
      Pdf.SaveIncrementalUpdate('signed-lt.pdf');
  finally
    Pdf.Free;
  end;
end;

As falhas são por assinatura, não por documento. Um respondedor que exceda o tempo para um signatário salta o material desse signatário e deixa o resto da passagem intacto, que é o comportamento que se quer num lote: evidência parcial supera uma execução abortada, e o valor de retorno diz-lhe quantas assinaturas realmente melhoraram

A cadeia que o CMS se esqueceu de incluir

A verificação de revogação precisa do certificado do emissor, e um número surpreendente de pilhas de assinatura omite intermediários do contentor CMS. A via de recuperação é a extensão Authority Information Access, método de acesso 1.3.6.1.5.5.7.48.2, que anuncia um URL de onde o certificado do emissor pode ser descarregado. O HPDFFetchAIAIntermediates percorre esses URLs através do mesmo transporte, extrai o DER de cada resposta, e devolve apenas os certificados que o CMS ainda não transportava, chaveados por hash DER para que duplicados e ciclos não rodem

Dois detalhes decidem se isto funciona contra autoridades de certificação reais. O primeiro é a codificação: os endpoints das CAs servem o certificado como DER puro quase tão frequentemente como o servem com armadura PEM, e não há tipo de conteúdo fiável para os distinguir. A sonda robusta é textual, depois estrutural. Procure o marcador -----BEGIN CERTIFICATE-----, retire a armadura e descodifique o base64 se estiver presente, e em ambos os caminhos confirme que o primeiro byte do resultado é $30, a etiqueta DER para uma SEQUENCE. O segundo é a profundidade: um intermediário obtido pode ele próprio anunciar um URL AIA para o seu próprio emissor, pelo que o percurso acrescenta novos candidatos à fila e completa cadeias que estão dois ou três saltos curtas. Isso tem de ser limitado, que é para o que serve o parâmetro MaxFetch

Diagrama de completação de cadeia AIA para o HotPDF: obtenção de URL caIssuers, sonda PEM versus DER, desduplicação por hash DER e o limite de profundidade MaxFetch
O HPDFFetchAIAIntermediates percorre URLs caIssuers através do mesmo transporte, sondando a armadura PEM e limitando a fila com MaxFetch

O que é um seed value de assinatura, e porque falha em silêncio?

Um seed value é uma restrição que o autor do documento anexa a um campo de assinatura para dizer ao signatário que tipo de assinatura é aceitável: que SubFilter, que algoritmo de resumo, que razões, que versão mínima de PDF, se a informação de revogação tem de estar embutida. Vive num dicionário /SV no campo e é definido na ISO 32000-1 §12.7.5.5. O HotPDF escreve-o com AttachPAdESSeedValue e verifica-o com CheckLoadedSignatureSeedValue, que devolve True quando o campo não tem restrições ou todas as restrições presentes passam, e em False nomeia a primeira restrição violada através de um parâmetro de saída que pode pôr diretamente numa mensagem de erro

O mecanismo que torna os seed values fáceis de errar é a entrada de flags /Ff descrita na §12.7.5.5.3. Um bit ativo marca a sua restrição como obrigatória: uma discrepância é um erro e o signatário deve recusar. Um bit limpo marca a mesma restrição como preferência: o valor filtra o que a UI deve oferecer e nada mais. Duas armadilhas decorrem daí. Primeiro, o /Ff vive dentro do dicionário /SV, não na anotação de widget, pelo que código que leia o /Ff ao nível do campo recebe uma resposta vazia para sempre e conclui que nada é forçado. Segundo, as atribuições de bits não são uma simples sequência de um, dois, quatro, oito; no HotPDF o escritor emite 2 para SubFilter, 4 para MinVersion, 32 para AddRevInfo e 64 para DigestMethod. Um leitor que assuma bits sequenciais descodifica todas as restrições como opcionais e passa todos os testes exceto o que importa

Tabela de bits de flags de seed value para assinatura PAdES no HotPDF mostrando os bits Ff 2, 4, 32 e 64 e o tratamento de restrições obrigatórias versus preferidas
A entrada /Ff vive dentro de /SV, e cada posição de bit decide se uma discrepância é uma recusa dura ou uma preferência de UI
var
  Violation: AnsiString;
begin
  // Perguntar ao campo se o perfil com que vamos assinar é permitido
  if not Pdf.CheckLoadedSignatureSeedValue(0, 'ETSI.CAdES.detached',
       'SHA256', 'Approved for payment', 1, Violation) then
    raise Exception.Create('Signature field rejects this profile: ' +
      String(Violation));
  // Restrição satisfeita: prosseguir com a passagem de assinatura
end;

O teste que expôs o bug original de descodificação não era um teste positivo. Era a asserção de que uma discrepância forçada tem de ser rejeitada, e é o único tipo de teste que consegue apanhar esta classe de defeito: um descodificador que leia o dicionário errado ou as posições de bits erradas produz "nenhuma restrição violada" para toda a entrada, o que se parece exatamente com comportamento correto até violar deliberadamente uma

Onde isto se situa na escada LTV

Quatro degraus, e cada um precisa do abaixo dele. B-B é a assinatura nua. B-T acrescenta um carimbo temporal fidedigno, que fixa o tempo de assinatura para que um validador saiba contra que momento avaliar a revogação. B-LT acrescenta a evidência de revogação ao DSS, que é o que o PopulatePAdESLTVEvidence automatiza. B-LTA acrescenta carimbos temporais de documento que são renovados antes de o anterior enfraquecer, estendendo a validade indefinidamente; o HotPDF expõe isso como RenewPAdESLTATimestamp, que anexa um novo carimbo como revisão incremental e preserva cada assinatura, carimbo e entrada DSS anteriores intactos

Um modelo de atualização incremental é a única forma correta de acrescentar evidência a um documento assinado, porque reescrever o ficheiro partiria os intervalos de bytes que as assinaturas existentes cobrem. Se precisa de raciocinar sobre o que mudou entre revisões, e se essas mudanças são do tipo que uma assinatura permite, essa análise está coberta separadamente na análise de revisões DocMDP e FieldMDP. A pipeline de assinatura em si, incluindo fontes de certificados e armadilhas de ordem de bytes, está na explicação de assinatura PAdES, e o lado da validação está em verificar assinaturas em documentos carregados

Um aviso prático sobre a ordenação. Recolha a evidência o mais cedo possível depois de assinar, idealmente no mesmo trabalho. Os respondedores que podem responder por um certificado estão online enquanto o certificado está atual e desaparecem anos depois, pelo que um documento que sai da sua pipeline como B-B pode nunca mais ser atualizável. O HotPDF corre como componente VCL nativo para Delphi e C++Builder, e toda a passagem de evidência é em processo aparte do seu próprio transporte; os perfis suportados estão listados na página de produto do HotPDF Delphi PDF component