HotPDF verifica firmas digitales en documentos PDF cargados mediante tres métodos de THotPDF: GetLoadedSignatureInfo, VerifyLoadedSignature y VerifyLoadedSignatureEx, introducidos en la v2.259.0. El componente vuelve a calcular el hash de los segmentos /ByteRange del archivo original, comprueba el atributo messageDigest del CMS y ejecuta una verificación RSA PKCS#1 v1.5 contra el certificado del firmante incrustado, devolviendo svValid cuando los bytes del documento están intactos
El escenario es rutinario y lo que está en juego no lo es. Una contraparte devuelve un contrato firmado, su flujo de trabajo necesita archivarlo y alguien hace la única pregunta que importa: ¿es este el documento que enviamos, byte por byte, firmado por el certificado que afirma? Responder eso en código es el lado de verificación de la historia de las firmas; el lado de la firma, construir e incrustar firmas PAdES en primer lugar, se cubre en el artículo complementario sobre la creación de firmas digitales PAdES con HotPDF. Este artículo trata de la dirección opuesta: un PDF llega ya firmado y usted quiere un veredicto programático en lugar de una captura de pantalla de la marca verde de Acrobat
¿Cómo demuestra un PDF firmado que no ha sido manipulado?
Una firma PDF protege rangos de bytes específicos del archivo, no una noción abstracta de "el documento". ISO 32000-1 §12.8 define el mecanismo: el campo de formulario de firma lleva un diccionario cuya entrada /Contents contiene un contenedor CMS SignedData (RFC 5652) y cuyo arreglo /ByteRange nombra las regiones exactas del archivo que la firma cubre, según §12.8.1. El arreglo es una lista de pares de desplazamiento y longitud, en la práctica dos segmentos: todo lo que hay antes de la cadena hexadecimal de /Contents y todo lo que hay después. El valor de la firma no puede cubrirse a sí mismo, así que el hash del archivo se calcula alrededor de ese hueco
Ese diseño tiene una consecuencia que da forma a toda la API: la verificación debe calcular el hash de los bytes serializados originales, exactamente como están en disco. Un modelo de objetos analizado es inútil para esto, porque volver a serializar incluso un documento sin cambios produce bytes distintos. Por lo tanto, HotPDF verifica contra el archivo de origen desde el que se cargó el documento, o contra un TStream de bytes sin procesar que usted suministra, nunca contra su representación en memoria
Leer los metadatos de la firma antes de verificar nada
GetLoadedSignatureInfo analiza el diccionario de firma y su contenedor CMS sin tocar un solo byte del documento, lo que lo convierte en la primera llamada correcta cuando solo necesita mostrar quién firmó y cuándo. Los campos de firma se indexan desde 0 en el orden de los campos del formulario, y GetLoadedSignatureFieldCount le indica cuántos existen. El registro THPDFSignatureInfo devuelto contiene el nombre del campo, el /SubFilter, el nombre común del certificado del firmante, los nombres distinguidos del sujeto y del emisor, el número de serie, las fechas de validez, la hora de firma (tomada del atributo firmado cuando está presente, o si no de la entrada /M del diccionario), el nombre del algoritmo de resumen y las cadenas /Reason, /Location y /ContactInfo. Su miembro Status permanece en svNotVerified, una etiqueta honesta para "analizado, no comprobado"
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;
Ejecutar la comprobación criptográfica
VerifyLoadedSignatureEx realiza la verificación completa de un documento cargado desde archivo y devuelve el registro de información ya poblado en una sola llamada: reabre el archivo de origen, calcula el hash de los segmentos /ByteRange con el algoritmo de resumen del SignerInfo, compara el resultado con el atributo firmado messageDigest (RFC 5652 §5.4) y luego verifica con RSA la firma sobre la recodificación DER SET de los atributos firmados. Cuando una firma no lleva atributos firmados, la comprobación RSA se ejecuta directamente sobre el hash del documento. Las firmas admitidas son RSA PKCS#1 v1.5 con resúmenes SHA-1, SHA-256, SHA-384 o SHA-512, lo que cubre los subfiltros adbe.pkcs7.detached y ETSI.CAdES.detached que producen las herramientas de firma más habituales
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 la pena conocer dos detalles de implementación porque explican fallos que desde fuera parecen misteriosos. Primero, la comprobación de los atributos firmados es exigente con la codificación: dentro del archivo los atributos están etiquetados como [0] IMPLICIT, pero la firma se calculó sobre su forma DER SET OF, así que el verificador vuelve a etiquetarlos antes de calcular el hash, exactamente como exige RFC 5652 §5.4. Un verificador escrito a mano que calcule el hash de los bytes tal como aparecen en el archivo rechazará todos los documentos firmados correctamente. Segundo, /Contents se rellena por convención con ceros hasta un presupuesto de bytes reservado, así que el verificador trunca el blob DER a la longitud real de su SEQUENCE exterior antes de analizarlo; los ceros finales de aspecto sospechoso son normales, no corrupción. La misma familia de riesgos de análisis ASN.1, del lado de la importación de certificados, es el tema de el artículo sobre el endurecimiento de seguridad de PKCS#12 y ASN.1 en HotPDF
¿Qué garantiza realmente una firma válida?
svValid significa precisamente esto: los bytes nombrados por /ByteRange producen el hash que el firmante firmó, y la firma se verifica bajo la clave pública del certificado incrustado en el contenedor CMS. Eso es integridad de bytes más vinculación de clave, y nada más. La validación de la cadena de certificados y de la confianza queda explícitamente fuera del alcance del verificador de HotPDF: no recorre la cadena hasta una raíz, no comprueba la revocación ni consulta ningún almacén de confianza. Un certificado autofirmado de un atacante que volvió a firmar un documento modificado se verificará como svValid, porque las matemáticas son internamente consistentes. Si el firmante es quien dice ser, y si alguien debería confiar en él, es una decisión de política que pertenece a una capa separada, ya sea la lista blanca de certificados de su organización, el almacén de certificados de Windows o una autoridad de validación
El indicador CoversWholeDocument protege contra una brecha más sutil. Una firma solo cubre su /ByteRange, y el mecanismo de actualización incremental de PDF permite anexar contenido después de una firma sin invalidarla, lo cual es intencional y es la forma en que funcionan los flujos de trabajo con varias firmas. El indicador se calcula durante la verificación y es verdadero solo cuando los dos segmentos más el hueco de /Contents abarcan el archivo completo. Cuando svValid llega con CoversWholeDocument en falso, la revisión firmada está intacta pero el archivo contiene adiciones posteriores, y lo que esas adiciones cambiaron es algo que su flujo de trabajo debería decidir si tolera
Los documentos cargados desde stream y los cifrados necesitan sus propios bytes de origen
Las versiones sin parámetros de VerifyLoadedSignature y VerifyLoadedSignatureEx dependen de que el componente recuerde de qué archivo provino el documento. Cargue el documento desde un stream y no habrá ningún nombre de archivo que reabrir; lo mismo se aplica después de la ruta de recarga con contraseña que se usa para documentos cifrados, el flujo de trabajo descrito en el artículo sobre el cifrado AES-256 de PDF con HotPDF. En ambos casos, las sobrecargas respaldadas por archivo devuelven svSourceUnavailable en lugar de adivinar. La solución es la sobrecarga con TStream, que le permite entregar los bytes originales sin procesar desde donde los haya guardado: un archivo que aún conserva, un búfer en memoria, un blob de base de datos
var
Src: TFileStream;
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
// Documento cargado desde un stream: el componente no conserva
// el nombre del archivo de origen, así que suministre usted los bytes originales.
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 lo que no se puede verificar
Un verificador que solo conoce "válido" e "inválido" reportará mal los documentos que simplemente no entiende, así que la enumeración de estados separa los casos que su interfaz debería distinguir. svDigestMismatch significa que los bytes del documento cambiaron después de la firma, la señal clásica de manipulación. svSignatureInvalid significa que el hash de los bytes es correcto pero la comprobación RSA falló, lo que apunta a un valor de firma corrupto o falsificado. svUnsupportedAlgorithm es la respuesta honesta para claves ECDSA y resúmenes no reconocidos: la firma puede ser perfectamente buena, HotPDF simplemente no puede comprobarla, y reportarla como "inválida" difamaría un documento sano. svMalformed marca un contenedor CMS que no pudo analizarse en absoluto. Para comprobaciones de tipo compuerta, VerifyAllLoadedSignatures devuelve verdadero solo cuando existe al menos un campo de firma y todos ellos se verifican como svValid, un booleano único y conveniente para un pipeline de ingesta de archivo que rechaza cualquier cosa por debajo de eso
La verificación de firmas, la firma PAdES, el cifrado AES-256 y la API de edición de documentos cargados se distribuyen en la misma biblioteca VCL nativa para Delphi y C++Builder, sin dependencias de DLL externas; la lista completa de funciones y las versiones de IDE compatibles están en la página de producto de HotPDF Delphi Component