O HotPDF verifica assinaturas digitais em documentos PDF carregados através de três métodos THotPDF: GetLoadedSignatureInfo, VerifyLoadedSignature e VerifyLoadedSignatureEx, introduzidos na v2.259.0. O componente recalcula o hash dos segmentos /ByteRange do ficheiro original, valida o atributo CMS messageDigest e executa uma verificação RSA PKCS#1 v1.5 contra o certificado do signatário incorporado, devolvendo svValid quando os bytes do documento estão intactos
O cenário é comum e a importância do mesmo também. Um contraparte devolve um contrato assinado, o seu fluxo de trabalho necessita de o arquivar, e alguém faz a única pergunta relevante: este é o documento que enviámos, byte a byte, assinado pelo certificado que reivindica? Responder a isso em código é o lado da verificação no processo de assinaturas; o lado da assinatura, construindo e incorporando assinaturas PAdES em primeiro lugar, é abordado no artigo complementar sobre a criação de assinaturas digitais PAdES com o HotPDF. Este artigo aborda a outra direção: um PDF chega já assinado, e pretende um veredicto programático em vez de uma captura de ecrã do sinal de visto verde do Acrobat
Como é que um PDF assinado prova que não foi adulterado?
Uma assinatura PDF protege intervalos de bytes específicos do ficheiro, e não um conceito abstrato de "o documento". A norma ISO 32000-1 §12.8 define o mecanismo: o campo de formulário da assinatura contém um dicionário cuja entrada /Contents armazena um contentor CMS SignedData (RFC 5652), e cujo array /ByteRange identifica as regiões exatas do ficheiro que a assinatura cobre, de acordo com a secção §12.8.1. O array consiste numa lista de pares de desvio e comprimento, na prática dois segmentos: tudo antes da string hexadecimal /Contents e tudo depois dela. O valor da assinatura não pode cobrir-se a si próprio, pelo que o hash do ficheiro é calculado contornando essa lacuna
Esse design tem uma consequência que molda toda a API: a verificação deve calcular o hash dos bytes serializados originais, exatamente como se encontram no disco. Um modelo de objetos analisado é inútil para este fim, porque a reserialização de um documento, mesmo inalterado, produz bytes diferentes. O HotPDF realiza, portanto, a verificação em relação ao ficheiro de origem a partir do qual o documento foi carregado, ou em relação a uma TStream de bytes brutos que forneça, nunca contra a sua representação em memória
Ler metadados da assinatura antes de realizar qualquer verificação
O GetLoadedSignatureInfo analisa o dicionário de assinatura e o seu contentor CMS sem tocar num único byte do documento, o que o torna a chamada inicial correta quando necessita apenas de apresentar quem assinou e quando. Os campos de assinatura são indexados a partir de 0 na ordem dos campos do formulário, e o GetLoadedSignatureFieldCount indica quantos existem. O registo THPDFSignatureInfo devolvido contém o nome do campo, /SubFilter, o nome comum do certificado do signatário, os nomes distintos (DN) do requerente e do emissor, o número de série, as datas de validade, a hora da assinatura (do atributo assinado quando presente, caso contrário a entrada /M do dicionário), o nome do algoritmo de resumo e as strings /Reason, /Location e /ContactInfo. O seu membro Status permanece como svNotVerified, uma classificação honesta para "analisado, não verificado"
var
Pdf: THotPDF;
Info: THPDFSignatureInfo;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('signed-contract.pdf');
for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
begin
Info := Pdf.GetLoadedSignatureInfo(I);
Writeln('Field: ', Info.FieldName);
Writeln('Signer: ', Info.SignerName);
Writeln('Issuer: ', Info.IssuerDN);
Writeln('Algorithm: ', Info.HashAlgorithm);
Writeln('SubFilter: ', Info.SubFilter);
end;
finally
Pdf.Free;
end;
end;
Executar a verificação criptográfica
O VerifyLoadedSignatureEx realiza a verificação completa para um documento carregado a partir de um ficheiro e devolve o registo de informação preenchido numa única chamada: reabre o ficheiro de origem, calcula o hash dos segmentos /ByteRange com o algoritmo de resumo SignerInfo, compara o resultado com o atributo assinado messageDigest (RFC 5652 §5.4) e, em seguida, efetua a verificação RSA da assinatura sobre a recodificação DER SET dos atributos assinados. Quando uma assinatura não contém atributos assinados, a verificação RSA é executada diretamente sobre o hash do documento. As assinaturas suportadas são RSA PKCS#1 v1.5 com resumos SHA-1, SHA-256, SHA-384 ou SHA-512, abrangendo os subfiltros adbe.pkcs7.detached e ETSI.CAdES.detached produzidos por ferramentas de assinatura comuns
var
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
Status := Pdf.VerifyLoadedSignatureEx(0, Info);
case Status of
svValid:
if Info.CoversWholeDocument then
Writeln('Valid; signature covers the whole file')
else
Writeln('Valid; file was extended after signing');
svDigestMismatch:
Writeln('Document bytes changed after signing');
svSignatureInvalid:
Writeln('RSA check failed over signed attributes');
svUnsupportedAlgorithm:
Writeln('Non-RSA key or unknown digest algorithm');
svMalformed:
Writeln('CMS container could not be parsed');
svSourceUnavailable:
Writeln('No source bytes; use the TStream overload');
end;
end;
Vale a pena conhecer dois detalhes de implementação porque explicam falhas que parecem misteriosas a partir do exterior. Primeiro, a verificação de atributos assinados é rigorosa quanto à codificação: dentro do ficheiro, os atributos são marcados como [0] IMPLICIT, mas a assinatura foi calculada sobre a sua forma DER SET OF, pelo que o verificador repõe a marcação antes de calcular o hash, exatamente como a RFC 5652 §5.4 exige. Um verificador personalizado que calcule o hash dos bytes tal como surgem no ficheiro rejeitará todos os documentos devidamente assinados. Segundo, o /Contents é convencionalmente preenchido com zeros até um limite de bytes reservado, pelo que o verificador trunca o blob DER para o comprimento real da sua SEQUENCE externa antes de o analisar; zeros finais com aspeto de lixo são normais, não constituindo corrupção. A mesma família de riscos de análise ASN.1, no lado da importação de certificados, é o tema do artigo sobre PKCS#12 e reforço de segurança ASN.1 no HotPDF
O que é que uma assinatura válida realmente garante?
O svValid significa precisamente isto: os bytes identificados por /ByteRange resultam no valor que o signatário assinou, e a assinatura é verificada sob a chave pública do certificado incorporado no contentor CMS. Isto representa integridade de bytes mais vinculação de chave, e nada mais. A cadeia de certificados e a validação de confiança estão explicitamente fora do âmbito do verificador do HotPDF: não percorre a cadeia até a uma raiz, não verifica a revogação nem consulta qualquer repositório de confiança. Um certificado autoassinado de um atacante que tenha voltado a assinar um documento modificado será verificado como svValid, porque a matemática é internamente consistente. Se o signatário é quem afirma ser, e se deve confiar nele, é uma decisão de política que pertence a uma camada separada, quer seja a lista branca de certificados da sua organização, o repositório de certificados do Windows ou uma autoridade de validação
A flag CoversWholeDocument protege uma lacuna mais subtil. Uma assinatura apenas cobre o seu /ByteRange, e o mecanismo de atualização incremental do PDF permite anexar conteúdo após uma assinatura sem a invalidar, o que é assim por conceção e define o funcionamento dos fluxos de trabalho com múltiplas assinaturas. A flag é calculada durante a verificação e é verdadeira apenas quando os dois segmentos mais a lacuna /Contents abrangem o ficheiro completo. Quando o svValid surge com a CoversWholeDocument falsa, a revisão assinada está intacta, mas o ficheiro contém adições posteriores, e o que essas adições alteraram é algo que o seu fluxo de trabalho deve decidir se tolera ou não
Documentos carregados por fluxo e encriptados necessitam dos seus próprios bytes de origem
Os métodos sem parâmetros VerifyLoadedSignature e VerifyLoadedSignatureEx dependem de o componente memorizar de que ficheiro proveio o documento. Carregue o documento a partir de um fluxo (stream) e não existirá um nome de ficheiro para reabrir; o mesmo se aplica após o caminho de recarregamento por palavra-passe utilizado para documentos encriptados, o fluxo de trabalho descrito no artigo sobre encriptação PDF AES-256 com o HotPDF. Em ambos os casos, as sobrecargas baseadas em ficheiros devolvem svSourceUnavailable em vez de tentarem adivinhar. A solução é a sobrecarga TStream, que lhe permite entregar os bytes brutos originais a partir de onde os guardou — um ficheiro que ainda possua, um buffer de memória ou um blob de base de dados
var
Src: TFileStream;
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
// Stream-loaded document: the component holds no source
// file name, so supply the original bytes yourself.
Src := TFileStream.Create('signed-contract.pdf',
fmOpenRead or fmShareDenyWrite);
try
Status := Pdf.VerifyLoadedSignature(0, Src, Info);
if Status <> svValid then
Writeln('Verification failed: ', Ord(Status));
finally
Src.Free;
end;
end;
Reportar o que não se pode verificar
Um verificador que apenas conheça "válido" e "inválido" reportará incorretamente documentos que simplesmente não compreende, pelo que a enumeração de estados separa os cenários que a sua interface de utilizador deve distinguir. O svDigestMismatch significa que os bytes do documento foram alterados após a assinatura, o sinal clássico de adulteração. O svSignatureInvalid indica que o hash dos bytes está correto, mas a verificação RSA falhou, o que aponta para um valor de assinatura corrompido ou forjado. O svUnsupportedAlgorithm é a resposta honesta para chaves ECDSA e resumos não reconhecidos: a assinatura pode ser perfeitamente válida, mas o HotPDF simplesmente não a consegue verificar, e reportar isso como "inválido" difamaria um documento saudável. O svMalformed sinaliza um contentor CMS que não pôde ser analisado de todo. Para verificações de validação direta, VerifyAllLoadedSignatures devolve verdadeiro apenas quando existe pelo menos um campo de assinatura e todos eles são verificados como svValid, um booleano único conveniente para um fluxo de receção de arquivos que recuse qualquer outra condição inferior
A verificação de assinaturas, a assinatura PAdES, a encriptação AES-256 e a API de edição de documentos carregados são todas fornecidas na mesma biblioteca VCL nativa para Delphi e C++Builder, sem dependências de DLLs externas; a lista completa de funcionalidades e as versões de IDE suportadas encontram-se na página do produto HotPDF Component