Attestation de evidencias de cumplimiento v1.
https://attestkeep.com/attestation/compliance-evidence/v1 es el predicateType de cada attestation que Attestkeep firma sobre un informe de cumplimiento. Si un documento le trajo a esta URL, esta página es su definición: la afirmación, cada campo, el envelope, la codificación que se firma y un ejemplo real que puede verificar.
La afirmación
En una frase: este informe de cumplimiento lo produjo esta instalación y no se ha modificado desde entonces. Esa es toda la afirmación, y todo lo demás se deriva de ella.
La afirmación viaja en el formato que el mundo de la supply chain ya usa para estas cosas (un statement in-toto dentro de un envelope DSSE, firmado con Ed25519) y no en un esquema de firma de diseño propio. Es una elección deliberada: un formato que solo nosotros pudiéramos verificar nos convertiría en un cuello de botella, y el valor del documento está precisamente en que un auditor pueda comprobarlo sin que estemos presentes. La clave de firma se genera dentro de la instalación y nunca sale de ella; al resto del mundo solo se le pide que confíe en su mitad pública.
El statement y su subject
El payload del envelope es un statement in-toto, versión 1. Sus campos exteriores:
| Campo | Tipo | Significado |
|---|---|---|
_type | string | Siempre https://in-toto.io/Statement/v1. |
subject | array | Exactamente un resource descriptor que nombra el informe del que trata la afirmación: name es el id del informe y digest.sha256 es el SHA-256 de los bytes exactos que se generaron, como 64 caracteres hexadecimales en minúscula sin prefijo sha256:. |
predicateType | string | Siempre https://attestkeep.com/attestation/compliance-evidence/v1: la dirección de esta página. |
predicate | object | La afirmación en sí; cada campo se define en la sección siguiente. |
El statement trata de un documento, así que el documento es el subject. El informe en sí no viaja dentro de la attestation, y es a propósito: el informe es grande y un lector puede tenerlo como PDF o como CSV. Lo que debe poder probarse es que la copia que tiene en las manos es la que se firmó, y un digest lo responde para cualquier copia de los bytes; un duplicado incrustado solo lo respondería para ese duplicado.
El framework y el periodo de auditoría van dentro de la firma, junto al subject. El mismo periodo evaluado frente a dos frameworks da dos documentos distintos, y un lector no debe poder hacer pasar uno por el otro. Nada del statement (subject, digest, periodo, framework, marcas de tiempo) puede editarse después de la firma sin que falle la verificación.
Campos del predicate
Los campos opcionales se omiten del JSON cuando están vacíos; nunca se escriben como marcadores. Una marca de tiempo que la instalación no tiene se escribe como cadena vacía, nunca como una fecha inventada.
| Campo | Tipo | Significado |
|---|---|---|
report_id | string | La identidad del documento, el mismo id impreso en el propio informe, para que la attestation y el informe que tiene en las manos puedan emparejarse de un vistazo. Repite el name del subject. |
framework | string | El mapeo de cumplimiento con el que se montó el informe, por ejemplo soc2. Va dentro de la firma para que el informe de un framework no pueda reetiquetarse como el de otro. |
period | object | La ventana de auditoría que cubre el informe. |
period.start, period.end | string | Marcas de tiempo RFC 3339 normalizadas a UTC. Cadenas en la transmisión, porque lo que se firma son los bytes exactos. |
installation | object | Identifica al firmante sin describir el cluster. |
installation.fingerprint | string | El identificador opaco del cluster al que está vinculada la licencia: un hash sin topología ni nombres, nada que un cliente no quisiera poner en un documento que sale de su edificio. |
installation.license_id | string, opcional | Permite al emisor confirmar que la instalación tenía licencia en ese momento: el enlace entre «este archivo está intacto» y «viene de una instalación real». |
installation.binding_id | string, opcional | El vínculo de licencia con el que se activó esta instalación; sirve para la misma confirmación. |
generated_at | string | Cuándo produjo la instalación el informe, según su propio reloj: RFC 3339 UTC. Es una afirmación del firmante, no una prueba: nada vincula el documento a un reloj de confianza, y el lector que lo necesite debe obtenerlo por otra vía. |
generated_by | string, opcional | La cuenta que pidió el informe, como user:<account id>. Desde 1.2.0 nunca es un nombre ni una dirección de correo; el administrador de la instalación resuelve el id desde el registro de auditoría de su consola cuando se le pide. |
generator | object | El software que produjo el informe. |
generator.name, generator.version | string | Qué mapeos de cumplimiento de qué release estaban en vigor. Los mapeos cambian a medida que los frameworks se leen con más cuidado; una evidencia que no dice qué lectura usó no puede volver a examinarse más adelante. |
El envelope DSSE
El statement va dentro de un envelope DSSE con esta forma:
{
"payload": "<standard base64 of the statement JSON>",
"payloadType": "application/vnd.in-toto+json",
"signatures": [
{ "keyid": "ak-…", "sig": "<base64 of an Ed25519 signature>" }
]
}payloades el base64 estándar, con relleno, de los bytes exactos del statement serializado. El statement se serializa una sola vez: los bytes que se firman son los bytes que se transmiten.payloadTypeforma parte de lo que se firma (vea la codificación más abajo), así que una firma sobre un statement in-toto nunca puede presentarse como una firma sobre los mismos bytes leídos como otra cosa.siges una firma Ed25519 (RFC 8032) sobrePAE(payloadType, payload): sobre la pre-authentication encoding, no sobre la cadena base64.keyidse deriva de la clave pública:"ak-"seguido de los primeros 16 caracteres hexadecimales del SHA-256 de la clave de 32 bytes. Al ser derivado, no puede contradecir a la clave que nombra; y es solo una pista: un verificador debe comprobar contra una clave en la que ya confía, nunca contra una clave que le haya indicado el envelope.- Un envelope puede llevar varias firmas. Un verificador prueba cada entrada contra su clave de confianza y acepta el envelope si alguna se verifica.
La codificación PAE
Lo que se firma es la pre-authentication encoding del payload junto con su tipo:
PAE(type, body) = "DSSEv1" SP LEN(type) SP type SP LEN(body) SP bodydonde SP es un único espacio y LEN es la longitud en bytes en ASCII decimal. Las longitudes cuentan bytes, nunca caracteres. Por ejemplo, PAE("http://example.com/HelloWorld", "hello world") es la cadena de bytes DSSEv1 29 http://example.com/HelloWorld 11 hello world.
Toda la seguridad de DSSE está en esta idea. Firmar solo el payload permitiría presentar una firma hecha sobre un statement in-toto como una firma sobre otra cosa que casualmente tuviera los mismos bytes con otro payloadType. PAE une ambos, con prefijo de longitud, para que ninguno pueda parecer parte del otro.
La cadena de certificados
La firma del envelope prueba que el informe no ha cambiado y nombra una clave. Por sí sola no puede probar de quién es esa clave: un verificador que toma la clave pública de quien trae el documento solo prueba coherencia interna. El certificado de licencia cierra ese hueco. Es un documento que Attestkeep firma para cada instalación con licencia y que lleva, dentro de la firma, la clave de firma de evidencias de la instalación, así que un lector que confía en una clave de Attestkeep puede verificar un informe de cualquier instalación:
Clave raíz de Attestkeep
└─ firma → certificado de licencia (JWT compacto)
└─ cnf.jwk nombra → clave de evidencias de la instalación
└─ firma → envelope DSSE
└─ el digest del subject fija → informeLa clave raíz. Este valor, obtenido de esta página a través de TLS, es la única ancla de confianza de la cadena:
kid: ak1
ed25519: e287aabe4712197cfa5b6fdf9ea8f2d7f0333ce16ca69aac7e4a3f1e84c04179El certificado es un compact JWT (RFC 7515): base64url(header) "." base64url(payload) "." base64url(signature), con base64url sin relleno (RFC 4648 sección 5, sin =). El header es {"alg":"EdDSA","typ":"JWT","kid":"ak1"}; un verificador debe rechazar cualquier alg distinto de EdDSA, lo que deja fuera por construcción los ataques clásicos de degradación de JWT (alg=none, confusión de algoritmo). La firma es Ed25519 sobre los bytes ASCII de header_b64 "." payload_b64.
Los campos del payload que lee un verificador de cadena (los desconocidos deben ignorarse: el emisor puede añadir claims):
| Campo | Tipo | Significado |
|---|---|---|
sub | string | El id de la licencia para la que se emitió el certificado. |
fpr | string | La huella del cluster al que está vinculada la licencia: el valor al que debe ser igual installation.fingerprint del predicate. |
plan | string | El código del plan, informativo para un verificador de cadena. |
iat, exp | number | Emisión y caducidad, en segundos unix. Un exp de 0 significa que el certificado no caduca. |
jti | string | Número de serie del certificado. |
cnf.jwk | string | La confirmation claim de la RFC 7800: la clave pública Ed25519 de evidencias de la instalación, como exactamente 64 caracteres hexadecimales. No es un objeto JWK anidado, sino los 32 bytes en bruto codificados en hexadecimal. Un certificado sin ella no responde de ninguna clave y no puede anclar una cadena. |
Para verificar un informe mediante la cadena:
- Divida el certificado por
.en exactamente tres partes; decodifique el header y recházelo salvo quealgseaEdDSA. - Verifique la firma Ed25519 sobre los bytes ASCII de
header_b64 "." payload_b64con la clave raíz de arriba. - Decodifique el payload y lea
cnf.jwk: la clave de evidencias de la instalación. - Verifique el envelope DSSE con esa clave, exactamente como describen las secciones anteriores.
- Comprobación cruzada: el
predicate.installation.fingerprintdel statement debe ser igual alfprdel certificado. Sin este paso, podría presentarse un certificado auténtico junto a un envelope auténtico de otra instalación. Siinstallation.license_idestá presente y es distinto desub, avise al lector.
La caducidad es información, no un rechazo. Los certificados rotan cada treinta días, y de un informe del trimestre pasado responde el certificado vigente entonces; rechazar certificados caducados haría inverificable cualquier informe antiguo. Un verificador debe indicar la fecha de un certificado caducado y dejar el juicio al lector.
Un ejemplo completo
Este envelope lo produjo el firmante de referencia. Está firmado con una clave de prueba deliberadamente pública (la semilla Ed25519 son los bytes 0x01 a 0x20), así que muestra el formato, no la procedencia de ningún informe real. Las attestations reales se firman con una clave generada dentro de cada instalación, que nunca sale de ella.
{
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicnB0LTlmMmM3YTQxZDBiODNlNTYiLCJkaWdlc3QiOnsic2hhMjU2IjoiNzQ3YjgzZWNkMTY2OTEyNjk2NmVlNjAyZTM3MjI0NTE2Y2M5YmMyZGQ5NWFhZWJlNDIwZWQxY2IzNGVkZWMwYyJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2F0dGVzdGtlZXAuY29tL2F0dGVzdGF0aW9uL2NvbXBsaWFuY2UtZXZpZGVuY2UvdjEiLCJwcmVkaWNhdGUiOnsicmVwb3J0X2lkIjoicnB0LTlmMmM3YTQxZDBiODNlNTYiLCJmcmFtZXdvcmsiOiJzb2MyIiwicGVyaW9kIjp7InN0YXJ0IjoiMjAyNi0wNy0wMVQwMDowMDowMFoiLCJlbmQiOiIyMDI2LTA3LTMxVDIzOjU5OjU5WiJ9LCJpbnN0YWxsYXRpb24iOnsiZmluZ2VycHJpbnQiOiIzZjdiMWE5ZTVjMmQ4ZjA0YTZiM2UxYzdkOTA1MmU4YTFiNGY2YzNkN2U5YTBiMmM0ZDZlOGYwYTFiM2M1ZDdlIiwibGljZW5zZV9pZCI6ImxpYy03YzMxZjBkMiIsImJpbmRpbmdfaWQiOiJiaW5kLWE5NGUyYzE3In0sImdlbmVyYXRlZF9hdCI6IjIwMjYtMDgtMDFUMDY6MDA6MDBaIiwiZ2VuZXJhdGVkX2J5IjoidXNlcjp1c3ItN2QyYzFhOWU0YjNmNWM2ZDhlMGYxYTJiIiwiZ2VuZXJhdG9yIjp7Im5hbWUiOiJhdHRlc3RrZWVwIiwidmVyc2lvbiI6IjAuMS4wIn19fQ==",
"payloadType": "application/vnd.in-toto+json",
"signatures": [
{
"keyid": "ak-65b60673d6ed884b",
"sig": "DUTZfcrSxs8IuyoPBcBOVa10sgg5Yy2/QQkCF8t0RFa2U+sjp5NGfVwHprbZ30dNtCogfZeexpLR8EYKsEWKCQ=="
}
]
}El payload se decodifica en este statement:
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "rpt-9f2c7a41d0b83e56",
"digest": {
"sha256": "747b83ecd1669126966ee602e37224516cc9bc2dd95aaebe420ed1cb34edec0c"
}
}
],
"predicateType": "https://attestkeep.com/attestation/compliance-evidence/v1",
"predicate": {
"report_id": "rpt-9f2c7a41d0b83e56",
"framework": "soc2",
"period": {
"start": "2026-07-01T00:00:00Z",
"end": "2026-07-31T23:59:59Z"
},
"installation": {
"fingerprint": "3f7b1a9e5c2d8f04a6b3e1c7d9052e8a1b4f6c3d7e9a0b2c4d6e8f0a1b3c5d7e",
"license_id": "lic-7c31f0d2",
"binding_id": "bind-a94e2c17"
},
"generated_at": "2026-08-01T06:00:00Z",
"generated_by": "user:usr-7d2c1a9e4b3f5c6d8e0f1a2b",
"generator": {
"name": "attestkeep",
"version": "0.1.0"
}
}
}La clave pública que lo verifica, tal como la espera el verificador:
79b5562e8fe654f94078b112e8a98ba7901f853ae695bed7e0e3910bad049664Compruébelo con el script de la documentación, que solo usa la biblioteca estándar, o con cualquier implementación de DSSE: pasa, y basta con cambiar un solo carácter del payload para que falle. El verificador del navegador recibe el paquete entregado, no un envelope y una clave sueltos: está pensado para un auditor con un archivo real, no para leer una especificación.
Verificar una attestation
Tres caminos, de menor a mayor independencia:
- la página de verificación de este sitio, que funciona por completo en su navegador y no envía nada a ninguna parte;
- cualquier herramienta que hable DSSE e in-toto: el formato no lleva nada propietario;
- su propia docena de líneas en cualquier lenguaje: verificar el certificado de licencia con la clave raíz, tomar la clave de la instalación de su
cnf.jwk, decodificar el payload en base64, construir el PAE, verificar la firma Ed25519 con esa clave y solo entonces leer el statement.
Elija el que elija, el ancla de confianza es la misma: la clave raíz de Attestkeep, que responde de la clave de la instalación a través del certificado de licencia como se describe arriba; nunca una clave tomada del propio documento, cuyo keyid es una etiqueta y no una credencial, y nunca una clave aceptada por confianza en quien entregó el informe. Una clave entregada en mano sigue verificando un envelope, pero solo prueba coherencia interna, no el origen. Y los límites también son los mismos: una cadena válida prueba que el documento no ha cambiado y qué instalación lo firmó. No es una firma electrónica legal y no prueba cuándo se firmó: generated_at y el periodo son la propia declaración de la instalación. Ambos límites se explican en detalle en la página de verificación.
Actualizado: 29 de agosto de 2026