Una firma prueba quién y qué. Nunca cuándo.
El campo «firmado el» de un documento firmado sale de su propio reloj. La firma lo cubre, así que no se puede modificar después — y sí elegir antes. Antedatar no cuesta nada, y una cadena de firmas tampoco lo arregla. La respuesta son cuatro gestos, se comprueba con el openssl de serie, y se reparte entre varias jurisdicciones por treinta y dos bytes.
Un informe firmado responde a dos preguntas: qué clave firmó y qué hallazgos firmó. Se suele tomar como respuesta a una tercera, que no está ahí.
El campo signed_at sí existe, y la firma lo cubre — no se puede modificar después sin romper el sello. Pero se lee del reloj de la máquina que firma. El suyo. Antedatar un documento firmado no cuesta absolutamente nada: basta con firmar con el reloj que se quiera.
Y el encadenamiento no lo remedia. Un informe que nombra la huella del que reemplaza es un buen rastro de auditoría — mientras las claves las tenga otra persona. Si las tiene usted, puede reconstruir la cadena entera en orden, y se verificará perfectamente.
Entonces: ¿qué aspecto tiene una fecha que no se puede discutir?
La respuesta, en cuatro gestos
Cabe en una frase: se saca la fecha de casa. En detalle, cuatro gestos, y ninguno exige confiar en nosotros.
- Hacer atestiguar una huella por un tercero, no el documento. Salen treinta y dos bytes, nada más. Ese tercero no tiene interés alguno en sus conclusiones, y eso es lo que da valor a su firma.
- Elegir un tercero cuya raíz ya sea pública, para que su lector verifique con el
opensslde serie y nada que venga de usted. - Comparar la huella antes de hablar de confianza. Es una comparación de hashes; no necesita certificado, ni raíz, ni permiso.
- Conservar la atestación más antigua, y volver a atestiguar mientras aguante el esquema que la firma.
Lo que se obtiene es preciso, y conviene enunciarlo con precisión: esta huella existía a más tardar en esta fecha, según alguien que no es usted. Es un límite superior — y es exactamente la forma de la afirmación «habíamos inventariado antes del 31 de diciembre de 2026».
El resto de este artículo explica por qué cada uno de los cuatro gestos es necesario, lo que cuesta, y dónde se detiene.
Lo que hace exactamente una autoridad de sellado de tiempo
RFC 3161. Usted le envía una huella, nunca el documento. Treinta y dos bytes. Le devuelve un testigo firmado que dice que esa huella le fue presentada en ese instante.
Ese es todo el mecanismo, y toda su elegancia: una autoridad puede atestiguar una fecha sin que se le diga qué está fechando. Nada de su código, de sus nombres de archivo o de sus hallazgos sale de la máquina — una huella no se puede invertir.
La autoridad, además, no tiene ningún interés en sus conclusiones, y eso es precisamente lo que da valor a su firma.
Qué hacemos atestiguar, y por qué esa elección
Tres candidatos: el archivo, la firma, o la huella de los hallazgos. Atestiguamos la huella, por una razón que cabe en un comando.
Está impresa en el informe. Así que cualquiera comprueba la fecha sin tocar nunca nuestra herramienta:
openssl ts -verify -digest 8ecb829ccc278ef7ab3138f56e07ee13319cbb72ce1dabbfc58ee7b4d6b670b5 \
-in report.html.tsr -CAfile <almacén de certificados>
Forma simplificada, para mostrar la mecánica. Ese -CAfile es la trampa más costosa de todo este artículo, y la sección «¿y en qué máquina?» más abajo da la versión que funciona en todas partes.
Ningún archivo que canonicalizar, ningún orden de campos que acordar, ningún formato nuestro. openssl ts -verify -digest acepta una huella hexadecimal directamente.
Y sobrevive a una clave efímera. Cuando un informe lo firma un par creado solo para él y luego destruido, la clave ya no existe — la fecha atestiguada sí.
También por eso el testigo es un archivo aparte, en DER crudo junto a la firma: es exactamente lo que lee el comando openssl ts de serie. Un artefacto que solo se abre con nuestra herramienta no es una prueba, es una promesa.
La trampa: openssl ts -verify no responde a la pregunta que se cree
Esta es la parte que exigió medir en lugar de leer.
openssl ts -verify se detiene ante una cadena de certificados que no sabe construir — antes de haber comparado la huella. Consecuencia: dos situaciones radicalmente distintas vuelven con el mismo mensaje de error.
- «No consigo subir hasta una raíz de confianza» — la autoridad es autofirmada, o falta un intermedio. No es una falsificación.
- «Este testigo atestigua otro documento» — eso sí es grave.
Con una autoridad autofirmada, las dos muestran Verification: FAILED. Lo comprobamos pasando a propósito una huella equivocada: la misma respuesta que para la correcta.
La corrección consiste en comparar la huella uno mismo, primero, leyéndola en el testigo. Es una comparación de hashes: no necesita certificado, ni raíz, ni confianza. Solo después llega la cuestión de la cadena.
Lo que da cuatro estados, no dos:
| estado | qué significa |
|---|---|
| verificada | la firma de la autoridad aguanta, hasta una raíz de confianza de esta máquina |
| cadena no verificada | la huella sí corresponde a este documento; no se pudo construir ninguna cadena. Autoridad autofirmada o intermedio ausente — no es una falsificación |
| inválida | el testigo atestigua otra huella, o fue modificado |
| no comprobable aquí | no hay openssl, o no hay almacén de certificados |
Confundirlos convierte un certificado raíz ausente en una acusación de falsedad. O, mucho peor, lo contrario.
¿Qué autoridad? Es una decisión, no un valor por defecto
No pusimos ninguna autoridad por defecto en la herramienta. Quién atestigua sus fechas es una decisión del mismo orden que el régimen normativo que adopta — un valor por defecto la tomaría por usted, en una jurisdicción que no eligió.
Esto es lo que da la medición, sobre autoridades gratuitas:
| autoridad | resultado |
|---|---|
| Certum / Asseco (Polonia, UE) | verificada con el almacén del sistema, RSA-4096 |
| GlobalSign (Bélgica, UE) | verificada con el almacén del sistema, RSA-3072 |
| DigiCert (Estados Unidos) | verificada con el almacén del sistema, RSA-4096 |
| Sectigo (Reino Unido) | un testigo cuyo firmante openssl no logra resolver |
| FreeTSA (Alemania) | autofirmada: hay que entregar su raíz para que alguien compruebe |
| TSA federal belga | autofirmada también |
timestamp.apple.com |
responde, pero con cadena autofirmada y política declarada 1.2.3 — no es un servicio público |
El criterio que decide no es la notoriedad, es la raíz pública: un lector comprueba con el openssl de serie y nada que venga de usted. En cuanto hay que adjuntar un certificado raíz a su documento, su prueba viaja con una pieza que usted mismo suministra — lo que le quita buena parte del sentido.
«Solo ha movido la confianza de sitio»
Esta es la objeción seria, y hay que tomarla en su versión fuerte: ha sustituido «crea en mi reloj» por «crea en el reloj de Certum». El problema no está resuelto, ha cambiado de dirección.
En parte es justa. Estas son las tres razones por las que el desplazamiento no es neutro, y lo que queda cuando se admiten.
1. Cambia la forma de la afirmación. Su signed_at es una fecha que usted elige. El testigo es un límite que pone otra persona. Para antedatar ya no basta con ajustar un reloj: hace falta que una autoridad emita un testigo falso, o que usted fabrique uno — es decir, que rompa RSA-4096. Se ha pasado de un gesto gratuito a un ataque.
2. Los intereses no son los mismos. La autoridad no sabe qué está fechando: solo ve una huella. No tiene por tanto ninguna manera de favorecer una conclusión sobre otra, y toda su actividad se basa en que su firma signifique algo. Antedatar para usted es arriesgar todo su oficio en su archivo. Para una autoridad cualificada eIDAS esas obligaciones están auditadas, y es precisamente por eso que su raíz está en los almacenes de certificados.
3. Y sobre todo: esa confianza se divide. Una autoridad sigue siendo un único punto de confianza. Pero nada, en RFC 3161, impide presentar la misma huella a varias autoridades — solo ven esos treinta y dos bytes, no tienen nada que coordinar, y ninguna necesita saber que no está sola. Lo hicimos con la huella del inventario de este sitio, presentando la misma huella a tres de ellas:
| autoridad | jurisdicción | fecha atestiguada | testigo publicado |
|---|---|---|---|
| Certum / Asseco | Polonia (UE) | 6 oct. 2026 21:58 UTC | report.html.tsr |
| GlobalSign | Bélgica (UE) | 8 oct. 2026 02:19 UTC | report.html.2.tsr |
| DigiCert | Estados Unidos | 8 oct. 2026 02:19 UTC | report.html.3.tsr |
Esos tres archivos están en línea junto al informe, y los tres se verifican con la receta de más abajo. El coste marginal de una autoridad es una petición HTTP: una autoridad solo ve treinta y dos bytes, no tiene nada que coordinar, y no necesita saber que no está sola.
Las fechas difieren, y es el mecanismo mostrándose: Certum tiene la atestación más antigua de estos hallazgos y la conserva — las otras dos se añadieron el día en que escribimos este artículo. Lo que se gana es preciso, y merece serlo: cada autoridad pone un límite independiente. Así que la fecha defendible sin conceder confianza a un solo operador es la más tardía de las tres, el 8 de octubre; la defendible confiando en Certum es el 6. Falsificar la más antigua ya no desliga este documento de una fecha: las otras dos lo limitan por sí solas.
Lo que queda, y hay que decirlo. Un testigo da un límite superior, nunca uno inferior: atestigua que esos hallazgos existían a más tardar en esa fecha, y nunca dirá que no existían antes. Si su necesidad va en el otro sentido — probar una anterioridad que se le discute — no es la herramienta.
Y nadie atestigua retroactivamente. Una autoridad no firmará hoy una fecha de la semana pasada, y en eso está todo el interés. El corolario es incómodo: la fecha hay que pedirla en el momento en que se produce el documento. Un sellado de tiempo que hay que acordarse de ir a buscar es un sellado que nadie hace — que es la única razón por la que este mecanismo pertenece a la herramienta que produce el informe, y no a un procedimiento.
Hacerlo en su casa, hoy, sin nosotros
Nada de esto necesita nuestra herramienta. Cuatro comandos, openssl y curl, sobre cualquier artefacto que quiera poder fechar — un SBOM, un informe de auditoría, un paquete de versión, un acta.
# 1. la huella de lo que quiere fechar
openssl dgst -sha256 -r artefacto.json | cut -d' ' -f1
7643cc3ff2957a137b15c509382463efe5fe29c26a14224009462266adf77672
# 2. la petición — «-cert» pide a la autoridad que incluya su certificado,
# sin lo cual nadie podrá verificar sin pedírselo a usted
openssl ts -query -digest 7643cc3f… -sha256 -cert -out sellado.tsq
# 3. enviarla. Salen 69 bytes, vuelve un testigo de 6 kB
curl -s -H 'Content-Type: application/timestamp-query' \
--data-binary @sellado.tsq http://time.certum.pl > sellado.tsr
# 4. la verificación — es el comando que lanzará su lector. Las primeras
# líneas encuentran su almacén de certificados y la cadena que el testigo
# ya lleva; la sección «¿y en qué máquina?» explica por qué
O=$(openssl version -d | sed 's/.*"\(.*\)"/\1/')
for f in "$O/cert.pem" "$O/certs/ca-certificates.crt" /etc/ssl/ca-bundle.pem; do
[ -f "$f" ] && CA="$f" && break
done
openssl ts -reply -in sellado.tsr -token_out -out token.der
openssl pkcs7 -inform DER -in token.der -print_certs -out chain.pem
openssl ts -verify -digest 7643cc3f… -in sellado.tsr \
-CAfile "$CA" -untrusted chain.pem
Verification: OK
Guarde el .tsr junto al artefacto e imprima la huella en el documento. Eso es todo: su lector no necesita ni a usted, ni un formato propio, ni una biblioteca.
Para leer lo que dice un testigo — la fecha, y la huella que atestigua:
openssl ts -reply -in sellado.tsr -text | grep '^Time stamp'
Time stamp: Oct 7 20:50:11 2026 GMT
openssl ts -reply -in sellado.tsr -text \
| awk '/^Message data:/{f=1;next} /^[A-Za-z]/{f=0} f' \
| sed -E 's/^ *[0-9a-f]{4} - //; s/ .*$//' | tr -dc '0-9a-f'
7643cc3ff2957a137b15c509382463efe5fe29c26a14224009462266adf77672
Esa última línea es la respuesta práctica a la trampa descrita arriba: compara la huella sin pedir nada a nadie, y funciona incluso cuando la cadena de certificados no se construye.
Una nota sobre el tr -dc '0-9a-f', porque ilustra el resto del artículo mejor que cualquier párrafo: primero habíamos escrito tr -d ' -\n', que funciona perfectamente en un Mac. El tr de GNU lee esos tres caracteres como un rango, del espacio al salto de línea, lo encuentra invertido, y se niega. Lo descubrimos reproduciendo este artículo en un contenedor Debian vacío antes de publicarlo. Todos los comandos de esta página pasaron por ahí.
Con una autoridad de raíz pública, openssl ts -verify hace la distinción por sí solo — modificamos el documento y relanzamos el comando:
message imprint mismatch
Verification: FAILED
Un mensaje claro y específico, que no se obtiene con una autoridad autofirmada. Es el argumento más concreto para elegir una cuya raíz ya esté en los almacenes de certificados.
En Sablier, todo esto cabe en una opción — --timestamp=<url> — porque una buena práctica que hay que acordarse de hacer a mano es una buena práctica que nadie hace. La opción se repite, o acepta una lista separada por comas, y cada autoridad recibe la misma huella:
sablier scan . --sign=sablier.key \
--timestamp=http://time.certum.pl,http://timestamp.digicert.com
Cada testigo va a su propia ranura — report.html.tsr, report.html.2.tsr, en el orden en que se nombraron las autoridades — y la ranura pertenece a la autoridad de una ejecución a la siguiente: la que hoy estaba inalcanzable deja su ranura vacía en lugar de desplazar a las demás y hacerles perder su anterioridad. sablier verify recorre las ranuras en vez de leer la primera, porque un lector al que se le han entregado tres atestaciones y un comando que verifica una habría verificado un tercio de lo que tiene, y concluido sobre la totalidad.
Pero los testigos producidos son archivos ordinarios, verificables con los mismos comandos, por alguien que nunca ha instalado la herramienta.
Y puede probarlo ahora mismo, sobre un documento real: el inventario criptográfico de este sitio se publica con su firma y su testigo.
curl -sO https://mesa.black/audit/report.html
curl -sO https://mesa.black/audit/report.html.tsr
O=$(openssl version -d | sed 's/.*"\(.*\)"/\1/')
for f in "$O/cert.pem" "$O/certs/ca-certificates.crt" /etc/ssl/ca-bundle.pem; do
[ -f "$f" ] && CA="$f" && break
done
openssl ts -reply -in report.html.tsr -token_out -out token.der
openssl pkcs7 -inform DER -in token.der -print_certs -out chain.pem
openssl ts -verify \
-digest 8ecb829ccc278ef7ab3138f56e07ee13319cbb72ce1dabbfc58ee7b4d6b670b5 \
-in report.html.tsr -CAfile "$CA" -untrusted chain.pem
Verification: OK
Los otros dos testigos están al lado — report.html.2.tsr y report.html.3.tsr, GlobalSign y DigiCert — y se verifican con el mismo comando cambiando el nombre del archivo. La sección siguiente explica por qué este bloque tiene seis líneas en lugar de una.
La huella está impresa en el informe mismo, y la fecha que obtendrá es el 6 de octubre de 2026 a las 21:58 UTC — la primera vez que estos hallazgos fueron atestiguados, y no la última vez que republicamos la página.
¿Y en qué máquina? El comando que todo el mundo publica es falso
Esta es la parte que no vimos venir, y que exigió una serie de mediciones en lugar de una lectura de documentación.
El comando que se encuentra en todas partes para verificar un testigo RFC 3161 es este:
openssl ts -verify -digest <huella> -in sellado.tsr \
-CAfile /etc/ssl/certs/ca-certificates.crt
Contiene dos suposiciones falsas.
El almacén de certificados no está en ese sitio. ca-certificates.crt es una convención de Debian. En Fedora, en Rocky, en openSUSE, ese archivo no existe — y cuando -CAfile apunta a un archivo ausente, openssl ts -verify no dice «ese archivo no existe»: responde Verification: FAILED. Su lector concluye que su testigo es malo.
Y el testigo lleva su cadena, pero no todo el mundo la lee. Un testigo pedido con -cert contiene los certificados que suben hasta la raíz — lo comprobamos, el nuestro lleva tres. OpenSSL 3 los usa. LibreSSL, es decir el openssl que viene con macOS, no los usa y responde:
Verify error:unable to get local issuer certificate
Verification: FAILED
El mismo mensaje de error. La misma conclusión equivocada para el lector.
Así que buscamos una receta que funcione en todas partes, y la medimos sobre ocho objetivos:
# 1. el almacén de certificados de su openssl, donde esté
O=$(openssl version -d | sed 's/.*"\(.*\)"/\1/')
for f in "$O/cert.pem" "$O/certs/ca-certificates.crt" /etc/ssl/ca-bundle.pem; do
[ -f "$f" ] && CA="$f" && break
done
# 2. la cadena que el testigo ya lleva, extraída para entregarla explícitamente
openssl ts -reply -in report.html.tsr -token_out -out token.der
openssl pkcs7 -inform DER -in token.der -print_certs -out chain.pem
# 3. la verificación
openssl ts -verify -digest <huella> -in report.html.tsr \
-CAfile "$CA" -untrusted chain.pem
| objetivo | openssl | almacén encontrado | resultado |
|---|---|---|---|
| Debian 13 | OpenSSL 3.5.7 | /usr/lib/ssl/cert.pem |
OK |
| Ubuntu 24.04 | OpenSSL 3.0.13 | /usr/lib/ssl/cert.pem |
OK |
| Alpine 3.22 | OpenSSL 3.5.9 | /etc/ssl/cert.pem |
OK |
| Fedora 42 | OpenSSL 3.2.6 | /etc/pki/tls/cert.pem |
OK |
| Rocky 9 | OpenSSL 3.5.8 | /etc/pki/tls/cert.pem |
OK |
| openSUSE Leap 15.6 | OpenSSL 3.1.4 | /etc/ssl/ca-bundle.pem |
OK |
| macOS + Homebrew | OpenSSL 3.6.0 | /opt/homebrew/etc/openssl@3/cert.pem |
OK |
| macOS, openssl de Apple | LibreSSL 3.3.6 | /private/etc/ssl/cert.pem |
OK |
openssl version -d da el directorio contra el que se compiló ese binario concreto, lo que cubre seis casos de ocho; openSUSE guarda su paquete en otro sitio, de ahí la tercera entrada del bucle. Y -untrusted chain.pem no cambia nada donde no era necesario — que es lo que permite tener una receta en lugar de una por sistema.
Es ahora la que imprime el informe, con la nota que explica los tres pasos. Porque una prueba que solo se puede verificar en la distribución de quien la produjo no es una prueba portable, y un lector cuyo comando falla no aprende que su distribución es distinta: aprende que nuestro sello está roto.
El límite, impreso en el documento
Una autoridad de sellado de tiempo firma con RSA o con ECDSA. Es decir, exactamente lo que un inventario post-cuántico clasifica como vulnerable.
Una herramienta que dedica cuarenta páginas a explicar que las firmas caducan no se concede una excepción para aquella de la que depende. Así que el informe lo escribe junto a la fecha:
Lo que el testigo no prueba: nada más allá de RSA-4096, el esquema que lo firma. Una fecha que se quiera oponer más tarde debe volver a atestiguarse mientras ese esquema aguante.
Un sellado de tiempo es una prueba para un litigio en los próximos años. No para 2040.
La trampa de diseño: la atestación más antigua es la única que vale
Esta merece enunciarse porque se coge al revés con mucha naturalidad.
Un sitio que republica su inventario pide de buena gana un testigo nuevo en cada publicación. Es el gesto que parece más limpio — y destruye la prueba que se quería construir.
Porque la única afirmación que cuenta es: estos hallazgos existían a más tardar en esta fecha. Aplastar la primera atestación con una más reciente borra exactamente eso. Uno acaba con un documento que prueba que existía esta mañana, cuando lo que quería probar es que existía en octubre.
La regla que se deriva es corta: el sello no se mueve mientras los hallazgos no se muevan. Un testigo que ya atestigua esta huella se conserva; una huella distinta pide uno nuevo. Lo mismo con la firma: volver a firmarla idéntica produce un eslabón de cadena que une una huella consigo misma, es decir ruido disfrazado de historia.
Por qué ahora
La hoja de ruta coordinada del grupo de cooperación NIS, del 23 de junio de 2025, convierte el inventario criptográfico en un First Step con plazo el 31 de diciembre de 2026.
«Habíamos inventariado antes del plazo» es una afirmación sobre una fecha. Es el único argumento que nos convenció de añadir todo lo anterior: sin un tercero, esa frase no vale más que la palabra de quien la pronuncia — y el campo que la lleva sale de su reloj.
Buenas prácticas
- No confunda «firmado el» con «fechado». El primero sale de su reloj, el segundo de un tercero sin interés alguno en sus conclusiones.
- Haga atestiguar una huella, no un archivo. Se imprime en el documento, se comprueba con un comando estándar, y sobrevive a una clave destruida.
- Compare la huella del testigo antes de plantear la cuestión de la confianza. Es una comparación de hashes, no necesita ningún certificado — y sin ella, «cadena no verificable» y «este testigo habla de otra cosa» se parecen.
- Elija una autoridad con raíz pública, o su prueba viajará con una pieza que usted mismo suministra.
- Pruebe el comando de verificación en otra máquina que la suya. La ruta del almacén de certificados y el
opensslinstalado varían, y los dos fallan con el mismoVerification: FAILEDque un testigo falso. - Conserve la atestación más antigua. Pedir un testigo en cada publicación borra lo único que establecía.
- Divida la confianza si lo que está en juego lo justifica. La misma huella puede ir a dos o tres autoridades de jurisdicciones distintas, por una petición HTTP más cada una. La fecha defendible sin confiar en nadie en particular es entonces la más tardía de la lista.
- Pida la fecha en el momento de producir el documento. Nadie la atestiguará retroactivamente — es el interés del mecanismo, y su restricción.
Puntos de vigilancia
- Una cadena de firmas sostenida por una sola clave no es un rastro de auditoría: se reconstruye entera, en orden, y se verifica.
- Un sellado de tiempo se apoya en una firma clásica. Hay que volver a atestiguarlo mientras el esquema aguante, y decirlo en el documento en lugar de dejar creer en una prueba eterna.
- La huella sale de la máquina. No revela nada, pero si su documento promete que nada sale, esa promesa pasa a ser «nada sale, salvo si pide un sellado de tiempo» — y eso debe ser una opción explícita, nunca un valor por defecto.
- Un testigo que hay que acompañar del certificado raíz de la autoridad es una prueba a medias portable.
- Un sellado de tiempo limita por arriba, nunca por abajo. Nunca probará que un documento no existía antes de la fecha atestiguada, y presentar un límite superior como una fecha exacta es el tipo de aproximación que se vuelve en contra delante de alguien atento.