HotPDF verifica firmas digitales en documentos PDF cargados a través de tres métodos de THotPDF: GetLoadedSignatureInfo, VerifyLoadedSignature y VerifyLoadedSignatureEx, introducidos en la versión v2.259.0. El componente vuelve a calcular el hash de los segmentos de /ByteRange del archivo original, comprueba el atributo messageDigest de 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 común 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 a eso en código es la parte de la verificación de la firma; el lado de la firma, es decir, 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 sobre la otra dirección: llega un PDF ya firmado y desea un veredicto mediante programación en lugar de una captura de pantalla de la marca de verificación verde de Acrobat
¿Cómo demuestra un PDF firmado que no ha sido alterado?
¿Cómo demuestra un PDF firmado que no ha sido alterado?
Una firma de PDF protege rangos de bytes específicos del archivo, no una noción abstracta de "el documento". La norma ISO 32000-1 §12.8 define el mecanismo: el campo de formulario de firma contiene un diccionario cuya entrada /Contents contiene un contenedor CMS SignedData (RFC 5652), y cuya matriz /ByteRange indica las regiones exactas del archivo que cubre la firma, según §12.8.1. La matriz es una lista de pares de desplazamiento y longitud, en la práctica dos segmentos: todo lo anterior a la cadena hexadecimal /Contents y todo lo posterior a ella. El valor de la firma no puede cubrirse a sí mismo, por lo que se calcula el hash del archivo excluyendo ese hueco
Ese diseño tiene una consecuencia que da forma a toda la API: la verificación debe aplicar el hash a los bytes serializados originales, tal como se encuentran en el disco. Un modelo de objetos analizado es inútil para esto, porque volver a serializar incluso un documento sin cambios produce bytes diferentes. Por lo tanto, HotPDF realiza la verificación contra el archivo de origen desde el cual se cargó el documento, o contra un TStream de bytes sin formato que usted proporcione, nunca contra su representación en memoria
Leer los metadatos de la firma antes de verificar cualquier cosa
GetLoadedSignatureInfo analiza el diccionario de firma y su contenedor CMS sin tocar un solo byte del documento, lo que lo convierte en la llamada inicial adecuada cuando solo necesita mostrar quién firmó y cuándo. Los campos de firma se indexan desde 0 en el orden de los campos de formulario, y GetLoadedSignatureFieldCount le indica cuántos existen. El registro THPDFSignatureInfo devuelto contiene el nombre del campo, /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 (del atributo firmado cuando está presente, de lo contrario la entrada /M del diccionario), el nombre del algoritmo de resumen y las cadenas de /Reason, /Location y /ContactInfo. Su miembro Status permanece como svNotVerified, una etiqueta honesta para "analizado, no 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;
Ejecutar la comprobación criptográfica
VerifyLoadedSignatureEx realiza la verificación completa para un documento cargado desde archivo y devuelve el registro de información completo en una sola llamada: vuelve a abrir el archivo de origen, calcula el hash de los segmentos de /ByteRange con el algoritmo de resumen de SignerInfo, compara el resultado con el atributo firmado messageDigest (RFC 5652 §5.4) y luego realiza la verificación RSA de la firma sobre la nueva codificación DER SET de los atributos firmados. Cuando una firma no contiene 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 producidos por las herramientas de firma 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 fallas que parecen misteriosas desde el exterior. Primero, la comprobación de atributos firmados es estricta con la codificación: dentro del archivo, los atributos están etiquetados como [0] IMPLICIT, pero la firma se calculó sobre su formato DER SET OF, por lo que el verificador vuelve a etiquetar antes de calcular el hash, exactamente como requiere la norma RFC 5652 §5.4. Un verificador desarrollado a mano que aplique el hash a los bytes tal como aparecen en el archivo rechazará cualquier documento firmado correctamente. Segundo, /Contents está relleno con ceros por convención según un presupuesto de bytes reservado, por lo que el verificador trunca el blob DER a la longitud real de su SEQUENCE externa antes de analizarlo; los ceros finales que parecen basura son normales, no una corrupción. La misma familia de riesgos de análisis ASN.1, en el lado de la importación de certificados, es el tema del artículo sobre el endurecimiento de seguridad de PKCS#12 y ASN.1 en HotPDF
¿Qué garantiza realmente una firma válida?
svValid significa exactamente esto: el hash de los bytes indicados por /ByteRange coincide con el valor 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 claves, y nada más. La validación de la cadena de certificados y de confianza está 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 la matemática es internamente coherente. Si el firmante es quien dice ser y si alguien debería confiar en él es una decisión de políticas que pertenece a una capa independiente, ya sea la lista blanca de certificados de su organización, el almacén de certificados de Windows o una autoridad de validación
La bandera CoversWholeDocument protege contra una brecha más sutil. Una firma solo cubre su /ByteRange, y el mecanismo de actualización incremental de PDF permite añadir contenido después de una firma sin invalidarla, lo cual es por diseño y es la forma en que funcionan los flujos de trabajo de firma múltiple. La bandera se calcula durante la verificación y es verdadera solo cuando los dos segmentos más el espacio de /Contents abarcan todo el archivo. Cuando se obtiene svValid con CoversWholeDocument en falso, la revisión firmada está intacta pero el archivo contiene adiciones posteriores, y su flujo de trabajo debería decidir si tolera lo que cambiaron esas adiciones
Los documentos cargados por flujo y cifrados necesitan sus propios bytes de origen
Los métodos sin parámetros VerifyLoadedSignature y VerifyLoadedSignatureEx dependen de que el componente recuerde el archivo del que provino el documento. Si carga el documento desde un flujo, no hay un nombre de archivo para volver a abrir; lo mismo se aplica después de la ruta de recarga con contraseña utilizada para documentos cifrados, el flujo de trabajo descrito en el artículo sobre el cifrado de PDF con AES-256 en HotPDF. En ambos casos, las sobrecargas basadas en archivos devuelven svSourceUnavailable en lugar de adivinar. La solución es la sobrecarga de TStream, que le permite entregar los bytes sin formato originales desde donde los conserve: un archivo que aún tenga, un búfer de memoria o un blob de base de datos
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;
Informar lo que no se puede verificar
Un verificador que solo conoce "válido" e "inválido" informará incorrectamente sobre documentos que simplemente no comprende, por lo que la enumeración de estados separa los casos que su interfaz de usuario debería distinguir. svDigestMismatch significa que los bytes del documento cambiaron después de la firma, la señal clásica de alteración. svSignatureInvalid significa que el hash de los bytes es correcto pero falló la comprobación RSA, lo que apunta a un valor de firma dañado o falsificado. svUnsupportedAlgorithm es la respuesta honesta para claves ECDSA y resúmenes no reconocidos: la firma puede ser perfectamente válida, pero HotPDF simplemente no puede comprobarla, y reportarla como "inválida" difamaría un documento en buen estado. svMalformed marca un contenedor CMS que no se pudo analizar en absoluto. Para comprobaciones de tipo compuerta, VerifyAllLoadedSignatures devuelve verdadero solo cuando existe al menos un campo de firma y cada uno de ellos se verifica como svValid, un valor booleano único y conveniente para un flujo de ingesta de archivos que no acepte nada menos
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 características y las versiones de IDE compatibles se encuentran en la página del producto HotPDF Component