Um PDF que você acabou de assinar é uma assinatura B-B e nada mais. Ele prova quem assinou e que os bytes não se moveram, mas não carrega prova de que o certificado do signatário era válido no momento da assinatura, então um validador daqui a anos precisa sair procurando dados de revogação que talvez não existam mais. Fechar essa lacuna significa escrever respostas OCSP e CRLs no Document Security Store de nível de documento, e no HotPDF isso é uma chamada: o PopulatePAdESLTVEvidence percorre cada assinatura carregada, deriva as solicitações de revogação do conjunto de certificados, as executa por um transporte que você fornece e escreve o material obtido mais a cadeia CMS no DSS. Ele retorna o número de assinaturas cuja evidência pousou, ou menos um quando o documento não tem campo de assinatura algum
A decisão de design que vale entender antes de usar é que a biblioteca nunca abre um socket. Cada byte que chega da rede chega por um callback que você escreveu. Isso não é cautela por cautela; é o único jeito de este recurso funcionar dentro dos ambientes que de fato exigem validação de longo prazo
Por que a biblioteca se recusa a fazer seu próprio HTTP?
Porque os lugares que exigem assinaturas B-LT são os lugares em que não se pode confiar a rede a uma biblioteca. Serviços de assinatura rodam atrás de proxies com autenticação e raízes corporativas. Camadas de assinatura isoladas por air gap não têm rota a um respondedor e precisam ser alimentadas com evidência em cache. Regimes de auditoria exigem que toda requisição de saída seja registrada pelo aplicativo, não enterrada em uma dependência. E suítes de teste precisam de respostas determinísticas, o que é impossível se a biblioteca disca para fora por conta própria
O transporte é uma referência de função simples com forma fixa, então a política continua sua. O HotPDF entrega a você um record de requisição descrevendo exatamente o que buscar, incluindo o content type e um teto de tamanho de resposta, e você retorna os bytes mais um status
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 este é um POST OCSP ou um GET de CRL;
// Request.ContentType e Request.Body já estão preparados,
// e Request.MaxResponseBytes é o teto que você deve honrar
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 retry recuar; use
// setsPermanentFailure para um 404 ou uma URL ruim
Result := setsRetry;
end;
end;
end;
// Atualização de B-B para B-LT em uma chamada para toda assinatura do arquivo
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
// Salvamento append-only: os bytes que as assinaturas
// existentes cobrem são preservados verbatim
Pdf.SaveIncrementalUpdate('signed-lt.pdf');
finally
Pdf.Free;
end;
end;
Falhas são por assinatura, não por documento. Um respondedor que dá timeout para um signatário pula o material desse signatário e deixa o resto da passada intacto, que é o comportamento que você quer em um lote: evidência parcial supera uma execução abortada, e o valor de retorno diz quantas assinaturas de fato melhoraram
A cadeia que o CMS esqueceu de incluir
A verificação de revogação precisa do certificado do emissor, e um número surpreendente de stacks de assinatura omite intermediários do contêiner CMS. A rota de recuperação é a extensão Authority Information Access, método de acesso 1.3.6.1.5.5.7.48.2, que anuncia uma URL de onde o certificado do emissor pode ser baixado. O HPDFFetchAIAIntermediates percorre essas URLs pelo mesmo transporte, extrai o DER de cada resposta e retorna apenas os certificados que o CMS ainda não carregava, chaveados por hash DER para que duplicatas e loops não girem
Dois detalhes decidem se isso funciona contra autoridades certificadoras reais. O primeiro é a codificação: endpoints de CA servem o certificado como DER cru quase tão frequentemente quanto o servem com armadura PEM, e não há content type confiável para distingui-los. A sondagem robusta é textual, depois estrutural. Procure o marcador -----BEGIN CERTIFICATE-----, retire a armadura e decodifique base64 se ela estiver presente, e em ambos os caminhos confirme que o primeiro byte do resultado é $30, a tag DER de uma SEQUENCE. O segundo é a profundidade: um intermediário obtido pode ele próprio anunciar uma URL AIA para seu próprio emissor, então a caminhada anexa novos candidatos à fila e completa cadeias que estão dois ou três saltos curtas. Isso precisa ser limitado, que é para o que serve o parâmetro MaxFetch
O que é um seed value de assinatura e por que 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: qual SubFilter, qual algoritmo de digest, quais razões, qual versão mínima de PDF, se a informação de revogação deve ser embutida. Ele vive em um dicionário /SV no campo e é definido na ISO 32000-1 §12.7.5.5. O HotPDF o escreve com AttachPAdESSeedValue e o verifica com CheckLoadedSignatureSeedValue, que retorna True quando o campo não tem restrições ou toda restrição presente passa, e no False nomeia a primeira restrição que falhou por um parâmetro de saída que você pode colocar direto em uma mensagem de erro
O mecanismo que torna os seed values fáceis de errar é a entrada de flags /Ff descrita em §12.7.5.5.3. Um bit definido marca sua restrição como requerida: uma incompatibilidade é um erro e o signatário deve recusar. Um bit zerado marca a mesma restrição como preferência: o valor filtra o que a UI deve oferecer e nada mais. Duas armadilhas decorrem disso. Primeiro, o /Ff vive dentro do dicionário /SV, não na anotação widget, então código que lê o /Ff de nível de 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 writer emite 2 para SubFilter, 4 para MinVersion, 32 para AddRevInfo e 64 para DigestMethod. Um leitor que assume bits sequenciais decodifica toda restrição como opcional e passa em todo teste exceto o que importa
var
Violation: AnsiString;
begin
// Pergunte ao campo se o perfil com o qual 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: prossiga com a passada de assinatura
end;
O teste que expôs o bug original de decodificação não era um teste positivo. Era a asserção de que uma incompatibilidade forçada deve ser rejeitada, e é o único tipo de teste que pode capturar essa classe de defeito: um decoder lendo o dicionário errado ou as posições de bits erradas produz "nenhuma restrição violada" para toda entrada, o que parece exatamente comportamento correto até você violar deliberadamente uma
Onde isto fica na escada LTV
Quatro degraus, e cada um precisa do de baixo. B-B é a assinatura nua. B-T adiciona um timestamp confiável, que fixa o tempo de assinatura para que um validador saiba contra qual momento avaliar a revogação. B-LT adiciona a evidência de revogação ao DSS, que é o que o PopulatePAdESLTVEvidence automatiza. B-LTA adiciona timestamps 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 timestamp como uma revisão incremental e preserva cada assinatura, timestamp e entrada de DSS anterior intocados
Um modelo de atualização incremental é o único jeito correto de adicionar evidência a um documento assinado, porque reescrever o arquivo quebraria os byte ranges que as assinaturas existentes cobrem. Se você precisa raciocinar sobre o que mudou entre revisões, e se essas mudanças são do tipo que uma assinatura permite, essa análise é coberta separadamente em a análise de revisões DocMDP e FieldMDP. O pipeline de assinatura em si, incluindo fontes de certificado e armadilhas de ordem de bytes, está em o passo a passo de assinatura PAdES, e o lado da validação está em verificar assinaturas em documentos carregados
Um aviso prático sobre ordenação. Colete a evidência o mais rápido possível depois de assinar, idealmente no mesmo job. Os respondedores que podem responder por um certificado estão online enquanto o certificado está vigente e somem anos depois, então um documento que sai do seu pipeline como B-B pode nunca mais ser atualizável. O HotPDF roda como um componente VCL nativo para Delphi e C++Builder, e toda a passada de evidência é in-process exceto o seu próprio transporte; os perfis suportados estão listados na página de produto do HotPDF Delphi PDF component