Uma assinatura sobre um PDF não proíbe alterações posteriores. Fixa um intervalo de bytes, e uma atualização incremental acrescenta novos bytes depois dela, pelo que a assinatura se mantém matematicamente válida enquanto o documento adquire novo conteúdo. Se esse conteúdo é aceitável é uma questão de política, e o DocMDP é onde o autor enuncia a política: nenhuma alteração de todo, só preenchimento de formulários e assinatura, ou isso mais anotações. Impô-la significa classificar o que realmente mudou, que é o que AnalyzeModifications faz. Aponte-a para uma revisão anterior, e depois leia GetModificationLevel para o veredicto global e os acessores por descoberta para o nível, número de objeto e descrição de cada diferença
Com isso no lugar, a imposição do DocMDP colapsa numa comparação: o nível calculado está no nível que a política permite ou abaixo dele
Porque é que se espera que um PDF assinado mude
Três casos legítimos, e cobrem a maior parte do que vai ver. Um segundo signatário acrescenta a sua assinatura. Um destinatário preenche campos de formulário que o autor deixou abertos. E material de validação de longo prazo é anexado: respostas OCSP e CRLs escritas no documento security store para que a assinatura permaneça verificável depois de os respondedores desaparecerem. Esse último caso não é apenas permitido, é o que um arquivo bem gerido faz deliberadamente a documentos assinados
Assim, "o ficheiro cresceu depois de assinar" não transporta informação nenhuma. A questão é sempre o que foi acrescentado, e a resposta tem de vir de comparar estados do documento em vez de observar bytes. A mecânica de acréscimo em si está coberta no artigo sobre atualização incremental
Classificar pela forma do objeto, não pelo caminho que o produziu
O classificador olha para o que um objeto é depois da alteração, não para que chamada de biblioteca o criou. Isso é deliberado, porque a análise corre contra ficheiros produzidos por outro software, onde não há caminho de chamada nenhum para inspecionar
Quatro formas são reconhecidas. Dicionários de informação do documento security store e relacionados com validação, objetos de cross-reference stream, a entrada de metadados do catálogo, e dicionários de assinatura que transportam um intervalo de bytes são material de arquivo de longo prazo. Um objeto que transporta tanto um tipo de campo como um valor de campo é preenchimento de formulário. Um objeto cujo tipo é annotation, ou cujo subtipo é um dos listados na Tabela 168 da ISO 32000-2, é uma alteração de anotação. Tudo o resto não é classificado
As remoções são tratadas mais estritamente do que as adições. Um objeto removido só é colocado na whitelist quando o objeto do lado antigo era ele próprio material de arquivo, o que cobre o caso normal de um security store substituído por um mais recente. Todas as outras remoções não são classificadas, porque apagar conteúdo de um documento assinado não é algo que um nível de permissão autorize. As diferenças ao nível do documento são ainda mais estritas: uma alteração na contagem de páginas vai direta a não classificada sem examinar objetos individuais, já que nenhum nível DocMDP permite acrescentar ou remover páginas
A whitelist erra no sentido de recusar
Esta é a regra de design que rege toda a decisão limítrofe. Uma alteração classificada erradamente como permitida é uma assinatura que valida sobre conteúdo que o autor nunca autorizou. Uma alteração classificada erradamente como não classificada é um documento que fica sinalizado e é revisto por uma pessoa. Esses dois erros não são simétricos, pelo que a whitelist se mantém estreita e as formas não reconhecidas caem em não classificado em vez de serem adivinhadas
Isso tem uma consequência prática que vale a pena antecipar: ficheiros de produtores invulgares por vezes reportarão alterações não classificadas que, à inspeção, são benignas. A resposta certa é olhar para o detalhe da descoberta e o número de objeto em vez de alargar a whitelist, porque uma whitelist que cresce para silenciar relatórios individuais deixa de ser um controlo de segurança
uses
PDFlibrary, PDFlibCompare;
var
Pdf: TPDFlib;
I, Level: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.LoadFromFile('contract-countersigned.pdf', '');
if Pdf.AnalyzeModifications('contract-as-signed.pdf', '') < 0 then
raise Exception.Create('the earlier revision could not be loaded');
// TPLModificationLevel ordenado mlNone, mlLTAUpdates, mlFormFilling,
// mlAnnotations, mlUnclassified; o getter devolve o seu ordinal
Level := Pdf.GetModificationLevel;
// A imposição do DocMDP é agora uma comparação contra a política
if Level > Ord(mlFormFilling) then
for I := 0 to Pdf.GetModificationFindingCount - 1 do
Report.Add(Format('object %d, level %d: %s',
[Pdf.GetModificationFindingObjNum(I),
Pdf.GetModificationFindingLevel(I),
Pdf.GetModificationFindingDetail(I)]));
finally
Pdf.Free;
end;
end;
O nível global é o máximo sobre todas as descobertas, que é a única agregação defensável: um documento contendo noventa e nove adições de arquivo e uma alteração não classificada é uma alteração não classificada
Por baixo: impressões digitais, não hashes criptográficos
O motor de comparação que o CompareWith expõe, e sobre o qual a análise de modificações é construída, identifica objetos por uma impressão digital do seu corpo normalizado usando um hash de 64 bits não criptográfico em vez de SHA-256. Essa é uma escolha ponderada. O que a comparação estrutural precisa é de determinismo: o mesmo corpo de objeto tem de produzir sempre a mesma impressão digital dentro de uma execução. Não precisa de resistência a colisões, porque um atacante que controla ambos os lados da comparação já ganhou por outros meios, e pagar um hash criptográfico completo sobre cada objeto num documento de um milhão de objetos é um custo real sem benefício
Duas regras de normalização importam mais do que a escolha do hash. As referências indiretas colapsam num token de marcador em vez de serem expandidas para o conteúdo referenciado: expandir copiaria o corpo de um objeto partilhado para cada referenciador, pelo que uma pequena edição num descritor de fonte partilhado invalidaria a impressão digital de todos os objetos que o alcançam, e o relatório ficaria ilegível. E os números de objetos em si estão excluídos da impressão digital, porque uma reescrita pode renumerar objetos sem mudar nada semântico
O emparelhamento corre depois em duas passagens, alinhando primeiro por impressão digital e emparelhando o resto por número de objeto para identificar alterações em vez de uma adição mais uma remoção. As verificações baratas vêm primeiro ao longo de tudo: uma diferença de contagem de páginas é reportada antes de qualquer travessia de objetos começar
Uma armadilha: a autocomparação não é garantidamente idêntica
O primeiro teste natural para um motor de diff é comparar um ficheiro consigo próprio e afirmar que o resultado é idêntico. Essa asserção não se sustenta aqui, e a razão é instrutiva. O caminho de carregamento público e o caminho de carregamento de documento de nível mais baixo não configuram a descodificação de forma idêntica, pelo que o mesmo ficheiro carregado pelas duas rotas pode produzir impressões digitais que diferem para alguns objetos. O motor não está errado; os dois carregamentos produziram genuinamente estados diferentes em memória
Em vez de forçar os dois caminhos a juntos, a semântica de comparação é enunciada de forma estreita: a análise compara o estado atual do documento com uma revisão anterior, e reporta idêntico só quando os dois conjuntos de impressões digitais coincidem exatamente. Essa é a pergunta que os utilizadores realmente fazem, e não exige que os dois carregadores sejam intercambiáveis. Ao desenhar uma funcionalidade de comparação, definir o que "o mesmo" significa é mais do trabalho do que calculá-lo
Onde utilizar
Dois lugares. Num relatório de validação, ao lado da verificação de assinaturas, para que um revisor veja não só se a assinatura está criptograficamente intacta mas o que aconteceu ao documento depois; o lado das assinaturas está coberto em assinatura e validação PAdES. E num portão de entrada, onde um documento que chega de fora é confrontado com a cópia que enviou, para que um contrato devolvido com uma anotação acrescentada seja tratado de forma diferente de um com uma página editada
Uma ressalva sobre o âmbito. Esta análise diz-lhe o que mudou entre duas revisões da mesma linhagem de documentos. Não lhe diz se o conteúdo visível é enganador, se um appearance stream de campo de formulário corresponde ao seu valor, ou se texto escondido sob uma sobreposição ainda está presente no content stream. Isso precisa de tratamento separado, e o lado da remoção de conteúdo está coberto no artigo sobre redação verdadeira. Os pontos de entrada de análise e comparação estão documentados na página de produto da losLab PDF Developer Library