Saltar al contenido
Serie 7 · Criptografía de la firma · 7.7

Qué se firma exactamente.

CMS, atributos firmados y el rango de bytes de un PDF: anatomía de una firma dentro de un documento.

Octubre de 20269 min de lectura

Una firma digital no firma un documento. Firma una secuencia exacta de bytes. Entre esos bytes y lo que una persona ve en pantalla hay formatos, estructuras y programas que interpretan, y en ese espacio ocurrieron la mayoría de los ataques prácticos contra firmas de documentos de las últimas dos décadas. La criptografía estaba intacta; lo que falló fue la respuesta a una pregunta en apariencia simple: qué se firmó, exactamente.

CMS: el sobre de la firma

Casi todas las firmas de documentos, en PDF y en muchos otros formatos, se empaquetan en una estructura llamada CMS (Cryptographic Message Syntax), heredera del estándar PKCS #7 de RSA Laboratories y especificada hoy en el RFC 5652. Su variante para firmar se llama SignedData, y su estructura merece verse por dentro.

SignedDataalgoritmos de resumen usados, contenido (o una referencia a él, si la firma es separada), certificados y listas de revocación
SignerInfouno por firmante: quién firma (emisor y número de serie de su certificado), algoritmo de resumen y de firma
Atributos firmadoscontentType · messageDigest = H(contenido) · signingCertificateV2 = H(certificado del firmante) · signingTime (declarado, no probado)
Firmacalculada sobre la codificación DER de los atributos firmados, no sobre el contenido
Atributos no firmadospor ejemplo, el sello de tiempo de la firma: un token RFC 3161 sobre el valor de la firma, agregado después

El punto clave está en los atributos firmados. Cuando existen, y en la práctica siempre existen, la firma no se calcula sobre el resumen del documento sino sobre un pequeño conjunto de atributos codificados en DER, uno de los cuales, messageDigest, contiene ese resumen. Es un resumen de dos niveles: el documento queda atado al atributo, y el atributo a la firma. Eso permite firmar, junto con el contenido, datos sobre la propia firma.

md = H(contenido)
A = DER( SET { contentType, messageDigest = md, signingCertificateV2, … } )
firma = Firmar(sk, A)    el verificador recalcula md, lo compara con el atributo y verifica la firma sobre A

La firma dentro de un PDF

Un PDF firmado contiene la estructura CMS dentro del propio archivo. Eso plantea un problema circular: la firma debe cubrir el archivo, pero el archivo contiene la firma. La norma ISO 32000 lo resuelve dejando un hueco. El diccionario de firma reserva un espacio de tamaño fijo, la entrada /Contents, que se llena con la estructura CMS en hexadecimal, y declara en /ByteRange qué bytes del archivo se resumieron: todo, salvo el hueco.

/ByteRange [ 0 a b c ]
Se resumen los primeros a bytes y luego c bytes a partir de la posición b. Entre a y b queda el hueco con la firma. Un verificador estricto comprueba que el hueco contenga sólo el valor de /Contents y que el rango cubra el archivo entero hasta esa revisión.

El filtro de la firma indica el formato del contenido. En las firmas avanzadas europeas, el perfil PAdES usa ETSI.CAdES.detached: una firma CMS separada, con los atributos que exigen las normas de firma avanzada, sobre los bytes del rango.

Revisiones incrementales: el documento que sigue cambiando

PDF permite modificar un archivo sin reescribirlo: los cambios se agregan al final, como una nueva revisión que redefine los objetos alterados. Es lo que posibilita que varias personas firmen el mismo documento, una tras otra, sin invalidar las firmas anteriores: cada firma cubre su revisión y todas las previas, y lo que viene después queda fuera de su rango.

Esa misma flexibilidad es la superficie de ataque. Si una revisión posterior puede cambiar lo que se muestra, un documento puede exhibir contenido nuevo mientras la firma anterior sigue verificando, porque los bytes que cubre no cambiaron. Las normas prevén controles. Una firma de certificación puede declarar, con permisos DocMDP, qué cambios se admiten después: ninguno, sólo completar formularios, o también anotaciones. Y el validador debe comparar las revisiones y advertir cualquier cambio no permitido.

AtaqueIdea
Falsificación universalManipular el diccionario de firma (valores nulos, rangos absurdos) para que el visor muestre la firma como válida sin verificar nada.
Guardado incrementalAgregar una revisión que reemplaza el contenido visible, y disimularla para que el visor no avise.
Envoltura de firmaMover los bytes firmados de lugar dentro del archivo y redefinir el rango, de modo que lo verificado y lo mostrado sean cosas distintas.
Ataques de sombraPreparar el documento antes de que se firme con contenido oculto, que luego una actualización admitida por los permisos vuelve visible.

En 2019, un equipo de la Universidad del Ruhr en Bochum probó las tres primeras técnicas contra 22 visores de escritorio y 7 servicios de validación en línea: 21 visores y 5 servicios fueron vulnerables a al menos una. En 2021 el mismo grupo presentó los ataques de sombra, que no violan ninguna regla del formato sino que abusan de los cambios permitidos. Los fabricantes corrigieron la mayoría de los casos, pero la lección perdura: la seguridad de una firma en un documento depende tanto del validador como del algoritmo.

Lo que se firma son bytes. Lo que se lee es una interpretación de esos bytes. La distancia entre ambos es el terreno del atacante.

XML y otros formatos

Las firmas XML, base del perfil XAdES, enfrentan el mismo problema con otra forma. Como un mismo documento XML puede escribirse de muchas maneras equivalentes, la firma se calcula después de una canonicalización, y cubre referencias a partes del documento seleccionadas mediante transformaciones. Si el verificador comprueba la firma sobre un elemento y la aplicación procesa otro con el mismo nombre en otro lugar, el resultado es un ataque de envoltura de firma, una familia que afectó a sistemas de inicio de sesión único durante años. El principio de defensa es el mismo: la aplicación debe usar exactamente lo que el verificador verificó.

Qué exigir a un validador
  • Que indique si la firma cubre el documento entero o sólo una revisión anterior.
  • Que muestre qué cambió después de cada firma y si esos cambios estaban permitidos.
  • Que verifique el atributo del certificado del firmante y no acepte certificados sustituidos.
  • Que distinga la hora declarada por el firmante de la hora probada por un sello de tiempo.
  • Que permita ver la versión exacta que se firmó, no sólo la última.
Para recordar

En CMS, la firma cubre atributos firmados que contienen el resumen del contenido y el del certificado del firmante; la hora declarada no es prueba de tiempo. En un PDF, la firma cubre un rango de bytes con un hueco para sí misma, y las revisiones posteriores quedan fuera. La mayoría de los ataques reales explotaron la distancia entre lo verificado y lo mostrado, y se combaten con validadores estrictos que informan exactamente qué se firmó.

Fuentes

  1. IETF, RFC 5652, Cryptographic Message Syntax (CMS), 2009
  2. IETF, RFC 5035, atributo de certificado del firmante, versión 2, 2007
  3. ISO 32000 2, formato PDF 2.0
  4. ETSI EN 319 142, firmas electrónicas avanzadas en PDF (PAdES)
  5. Mladenov, Mainka, Meyer zu Selhausen, Grothe y Schwenk, «1 Trillion Dollar Refund: How To Spoof PDF Signatures», ACM CCS 2019
  6. Mainka, Mladenov y Rohlmann, «Shadow Attacks: Hiding and Replacing Content in Signed PDFs», NDSS 2021

← Todas las notas

Notas de confianza

Artículos para quienes deciden cómo firma una organización: principios, prácticas y criterios que siguen valiendo cuando cambian las normas y las tecnologías.

Una publicación de Signing Flows, de Interfase.

Series