Decidir que uma assinatura PDF é qualificada ao abrigo do eIDAS significa responder a uma pergunta que nada tem a ver com criptografia: o certificado foi emitido por um serviço de confiança que um Estado membro listou como qualificado, no momento em que a assinatura foi feita. A resposta vive numa lista fidedigna, um documento XML publicado por território, e todo o valor desse documento depende da sua autenticidade. Por isso o componente PDFium recusa-se a olhar para dentro de uma até alguém a ter garantido. O TPdfEuropeanTrustedList.ParseAuthenticated entrega os bytes brutos completos a um IPdfTrustedListAuthenticator fornecido pelo chamador antes de analisar um único serviço, e só cria um snapshot se esse autenticador aprovar explicitamente
Essa ordenação é o design. Tudo o resto nesta funcionalidade decorre dela, incluindo as partes que parecem inconvenientes
Analisado não é fidedigno
Uma lista fidedigna que analisa limpa diz-lhe que o XML está bem formado. Não lhe diz nada sobre quem a escreveu. Como a lista é aquilo em que toda a sua decisão de estado qualificado assenta, aceitar uma porque analisou tornaria a decisão sem sentido: um atacante que consiga substituir a lista pode declarar a sua própria autoridade de certificação qualificada
O mesmo raciocínio aplica-se à cache, e esta é a armadilha que vale nomear. A cache de snapshots guarda o XML original junto com um resumo SHA-256, e seria fácil tratar um resumo correspondente ao carregar como prova de que a lista é genuína. Não é. Um resumo calculado pelo mesmo processo que guardou o ficheiro, sem chave envolvida, verifica apenas que os bytes não mudaram desde que os escreveu; se a lista era fraudulenta quando foi posta em cache, o resumo confirma que é a mesma lista fraudulenta. Por isso carregar um snapshot em cache passa pelo mesmo autenticador que analisar uma fresca. Integridade e autenticidade são propriedades diferentes e só uma delas precisa de uma chave
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
// A sua política vive aqui: verifique a assinatura enveloped
// XMLDSIG contra o certificado de assinatura da lista que fixou
// fora de banda, e descreva o que verificou para o registo de auditoria
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);
// O snapshot só existe porque o autenticador disse sim
Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
try
List.SaveCache(Cache);
finally
Cache.Free;
end;
end;
O validador não é dono da política de rede
Um validador PAdES não tem nada a ver com decidir como alcançar a lista de listas fidedignas, se passar por um proxy, com que frequência repetir, ou o que fazer quando um território está inalcançável. Essas são decisões de aplicação e implantação, e em ambientes regulados são frequentemente auditadas. Por isso as atualizações chegam através de IPdfTrustedListSource, que recebe um URI e um limite de bytes e devolve bytes
O que o componente impõe são os invariantes que fazem uma atualização ser uma atualização e não uma substituição. O Update exige que o território permaneça inalterado, que o número de sequência aumente estritamente, e que o tempo de emissão não recue. Essas três verificações derrotam os ataques de downgrade mais óbvios: reproduzir uma lista mais antiga que ainda lista um serviço entretanto retirado, ou meter a lista de outro território cujos serviços nunca pretendeu confiar
Limites do parser, e nenhum DTD de todo
O TPdfTrustedListOptions limita o tamanho do XML, a contagem de tokens, a profundidade de aninhamento, o número de serviços, o número de certificados e o tamanho de um certificado individual, com uma função de classe Default a fornecer valores utilizáveis. As listas fidedignas são documentos publicados de tamanho previsível, pelo que os limites são baratos de definir e não há lista legítima que precise de os exceder
Separamente e incondicionalmente, o parser rejeita declarações de DTD e de entidades. Isso fecha tanto a negação de serviço por expansão de entidades como a via de divulgação por entidades externas numa única recusa, e não custa nada porque as listas fidedignas não usam entidades. Qualquer parser XML alcançável a partir de entrada não confiável devia ser configurado assim; a diferença aqui é que a recusa não é configurável, pelo que não pode ser desligada por uma alteração de opção bem-intencionada
O estado qualificado é registado ao lado da confiança de cadeia, não fundido nela
O lado da avaliação é deliberadamente separado. O TPadesTrustValidationOptions.QualifiedTrustEvaluator recebe um IPdfQualifiedTrustEvaluator, que o snapshot da lista fidedigna implementa. Durante a validação o avaliador recebe o certificado folha, a cadeia e um tempo de validação, faz corresponder certificados de serviço por comparação DER exata contra o signatário e a cadeia, combina o estado do serviço, o identificador de tipo de serviço e os URIs qualificadores naquele momento, e devolve um registo de avaliação
O resultado aterra em dois lugares em cada assinatura: QualifiedTrustStatus como estado grosseiro, e QualifiedTrust como a avaliação completa com território, nome do fornecedor, nome do serviço, identificador de tipo, estado e tempo de início do estado. O que não faz é mudar CertificateTrustStatus. A confiança de cadeia do sistema e o estado qualificado respondem a perguntas diferentes, e um relatório que as colapse não consegue distinguir "fidedigno mas não qualificado" de "qualificado mas a cadeia não valida", ambos reais e ambos a precisar de tratamento diferente
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
I: Integer;
begin
Options := TPadesTrustValidationOptions.Default;
Options.CheckRevocation := True;
Options.QualifiedTrustEvaluator := List; // o snapshot autenticado
Options.QualifiedValidationTime := SigningTime; // não Now
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
// Nenhum serviço correspondente, ou o snapshot não pode responder por este tempo
Writeln(Format('signature %d: qualified status undetermined', [I]));
end;
Porque é que o tempo de validação não é agora
Porque a qualificação é uma propriedade de um momento. Um serviço de confiança pode receber o estado qualificado, depois vê-lo retirado, e depois ainda ser reposto, e cada uma dessas transições transporta um tempo de início na lista. Uma assinatura feita enquanto o serviço estava qualificado mantém-se qualificada depois; uma assinatura feita antes da concessão não se torna qualificada retroativamente. Avaliar contra o tempo atual portanto dá a resposta errada nos dois sentidos
A lista transporta o que é preciso para isto: cada registo de serviço tem um tempo de início de estado e uma flag que distingue entradas históricas das atuais, e o avaliador combina-os contra o tempo que fornece. Na prática esse tempo vem de um carimbo temporal fidedigno na assinatura em vez do tempo de assinatura alegado no CMS, que é porque é que o material de validação de longo prazo importa mesmo para uma pergunta que parece uma consulta de política; o lado do carimbo temporal e do DSS está coberto no artigo sobre assinaturas de longo prazo
O que ainda tem de construir
Três coisas, e nenhuma delas pertence a uma biblioteca PDF. O autenticador, ou seja, verificação XMLDSIG real contra um certificado de assinatura da lista que obteve através de um canal em que confia. A política de obtenção, ou seja, como e com que frequência atualiza, e o que a sua aplicação faz quando uma atualização falha. E o âmbito territorial, ou seja, que listas transporta de todo, que é uma decisão de negócio sobre em que Estados membros as suas contrapartes assinam
O que obtém do componente é a parte que é fácil de errar subtilmente: a ordenação de autenticar-antes-de-analisar, análise XML limitada e sem entidades, invariantes de atualização monótonos, correspondência de serviços por DER exato, avaliação de estado histórico, e um resultado que se mantém separado da confiança de cadeia ordinária. Se o seu problema imediato é mais básico, que um validador rejeita uma assinatura que acredita estar bem, as causas habituais estão catalogadas em porque é que os validadores rejeitam assinaturas PAdES, e a superfície de inspeção de assinaturas está descrita em inspeção de assinaturas e níveis PAdES. As capacidades do componente estão listadas na página de produto do PDFium Delphi component