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
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
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
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