Pedir un sello de tiempo desde tu sistema: la petición RFC 3161 y qué comprobar en la respuesta
Un sistema que firma contratos en PAdES ya tiene resuelta la parte visible: el certificado, la clave privada, el documento con la firma incrustada. Lo que falta es una línea de código que casi nadie revisa dos veces: la llamada a la TSA que añade el sello de tiempo. Si esa llamada se implementa copiando un ejemplo de foro y guardando lo que vuelva, el sistema produce sellos que parecen correctos durante meses, hasta que alguien intenta validar un documento antiguo y descubre lo que nadie comprobó al recibir el sello: que el token no traía el certificado de la TSA, que ese certificado no tenía el uso extendido correcto o que la TSA había respondido con un rechazo que el sistema guardó como si fuera un sello.
Ya explicamos en el artículo sobre fecha cierta qué es el sellado de tiempo y qué exige el reglamento peruano. Aquí vamos al nivel de quien programa la llamada.
Lo que manda tu sistema: TimeStampReq
El RFC 3161 define la petición en su sección 2.4.1:
TimeStampReq ::= SEQUENCE {
version INTEGER { v1(1) },
messageImprint MessageImprint,
reqPolicy TSAPolicyId OPTIONAL,
nonce INTEGER OPTIONAL,
certReq BOOLEAN DEFAULT FALSE,
extensions [0] IMPLICIT Extensions OPTIONAL }
MessageImprint ::= SEQUENCE {
hashAlgorithm AlgorithmIdentifier,
hashedMessage OCTET STRING }
messageImprint es lo único que viaja de verdad: el algoritmo y el resumen de lo que quieres sellar. El documento nunca sale de tu sistema. reqPolicy pide explícitamente una política por OID; si lo omites, la TSA aplica la suya por defecto y casi ninguna integración se fija en cuál respondió. nonce es un número aleatorio que la TSA debe devolver sin tocar: sirve para comprobar que la respuesta es la respuesta a tu petición, no una reproducida o inyectada por un tercero.
certReq decide si el token trae el certificado de la TSA. Por defecto es falso, y ahí está el error habitual: pedirlo en falso ahorra bytes, pero obliga a tu validador a buscar ese certificado por otro canal cuando llegue el momento de verificar. Pide siempre certReq=true. El sello queda autocontenido, y quien lo valide dentro de cinco años no depende de que la web de la TSA siga publicando el mismo certificado en la misma URL.
Lo que responde la TSA: TimeStampResp
La respuesta trae primero un PKIStatusInfo:
PKIStatusInfo ::= SEQUENCE {
status PKIStatus,
statusString PKIFreeText OPTIONAL,
failInfo PKIFailureInfo OPTIONAL }
PKIStatus ::= INTEGER {
granted (0), grantedWithMods (1), rejection (2),
waiting (3), revocationWarning (4), revocationNotification (5) }
Solo con granted o grantedWithMods viene un token. Cualquier otro valor es un fallo duro que tu código tiene que tratar como tal: un rejection que se ignore silenciosamente deja un documento sin sello marcado como sellado en tu base de datos.
Cuando se concede, viene un TSTInfo:
TSTInfo ::= SEQUENCE {
version INTEGER { v1(1) },
policy TSAPolicyId,
messageImprint MessageImprint,
serialNumber INTEGER,
genTime GeneralizedTime,
accuracy Accuracy OPTIONAL,
ordering BOOLEAN DEFAULT FALSE,
nonce INTEGER OPTIONAL,
tsa [0] GeneralName OPTIONAL,
extensions [1] IMPLICIT Extensions OPTIONAL }
policy es el OID bajo el que se emitió, serialNumber identifica ese sello para esa TSA, genTime es la fecha y hora, y accuracy es el margen alrededor de genTime, en segundos, milisegundos y microsegundos. Ojo con cómo se lee: si falta uno de esos tres subcampos, el RFC manda tomarlo como cero, pero si falta el campo entero no hay margen declarado en el token y el RFC remite a otros medios, como la política de la TSA.
Qué comprobar antes de guardar el sello
En este orden:
- El hash. El
messageImprintde la respuesta tiene que ser byte a byte el que enviaste. - El nonce. Si lo enviaste, tiene que volver idéntico. Si no vuelve o vuelve distinto, descarta el sello.
- La firma de la TSA. El token es un CMS
SignedData; verifícala contra la cadena de certificación, no te limites a comprobar que el campo existe. - El certificado de la TSA. Tiene que tener
extendedKeyUsagecon el valorid-kp-timeStamping, marcado como crítico. El RFC 3161 lo exige en su sección 2.3: el certificado "MUST contain only one instance" de ese uso extendido y la extensión "MUST be critical". Sin esa marca, la clave privada pudo emitirse para cualquier otra cosa. - La política. Que el OID de
policysea uno que tu sistema reconoce, no el que la TSA aplique por defecto.
La cuarta es la más fácil de saltarse: dar por hecho que el certificado "es de una TSA" porque lo dice el campo tsa del token, en vez de comprobar la extensión que lo autoriza.
RFC 5816: dejar de depender de SHA-1
El RFC 5816, de marzo de 2010, actualiza el RFC 3161 en el identificador del certificado firmante que viaja en el propio token. El original usaba ESSCertID (RFC 2634), que solo admite SHA-1 para identificar ese certificado. El RFC 5816 permite ESSCertIDv2 (RFC 5035) dentro de SigningCertificateV2, con cualquier algoritmo. Su sección 2.2.1 lo dice en una nota: ese atributo "MUST be used if any algorithm other than SHA-1 is used and SHOULD NOT be used for SHA-1". Si tu TSA sigue identificando su certificado firmante solo con SHA-1, es una señal de que no actualizó ese punto, aunque el sello del documento use SHA-256.
Un sello real, con una TSA de prueba
Esto es reproducible contra FreeTSA, un servicio gratuito que responde al protocolo. Antes de seguir: FreeTSA no está acreditada en la IOFE peruana ni aparece en la TSL de INDECOPI que se revisa más abajo. Sirve para entender el protocolo, no para sellar nada con valor probatorio en Perú.
$ sha256sum documento.txt
050a78f336595e7916fa2de633a6a1425c268e4eac5c51b2fe08a18676d3e479 documento.txt
$ openssl ts -query -data documento.txt -sha256 -cert -out request.tsq
$ openssl ts -query -in request.tsq -text
Hash Algorithm: sha256
Message data: 05 0a 78 f3 36 59 5e 79 16 fa 2d e6 33 a6 a1 42 ...
Policy OID: unspecified
Nonce: 0x3B33C2092EC206F6
Certificate required: yes
Certificate required: yes es el certReq=true pedido con -cert. Se envía por HTTP y se lee la respuesta:
$ curl -H "Content-Type: application/timestamp-query" \
--data-binary @request.tsq https://freetsa.org/tsr -o response.tsr
$ openssl ts -reply -in response.tsr -text
Status info: Status: Granted.
Policy OID: tsa_policy1
Serial number: 0x089540DA
Time stamp: Sep 28 12:09:57 2026 GMT
Nonce: 0x3B33C2092EC206F6
El messageImprint de la respuesta coincide con el hash enviado, y el Nonce es el mismo 0x3B33C2092EC206F6 de la petición: las comprobaciones 1 y 2 de la lista anterior, hechas a mano. tsa_policy1 es un alias legible del openssl.cnf local; el OID real, leído con openssl asn1parse sobre el token, es 1.2.3.4.1 — el mismo que el archivo de ejemplo de OpenSSL usa para sus pruebas, dejado tal cual en producción por FreeTSA. Un sistema que solo pregunte "¿tiene política?" pasa esto por alto.
Para las comprobaciones 3 y 4, con el certificado raíz y el de la TSA descargados de freetsa.org/files/:
$ openssl ts -verify -in response.tsr -data documento.txt \
-CAfile cacert.pem -untrusted tsa.crt
Verification: OK
Y el certificado de FreeTSA, con openssl x509 -text, trae exactamente lo que el RFC exige:
X509v3 Extended Key Usage: critical
Time Stamping
Para confirmar que el hash se verifica y no solo se acepta, se repite contra un archivo con una palabra cambiada:
$ openssl ts -verify -in response.tsr -data documento-alterado.txt \
-CAfile cacert.pem -untrusted tsa.crt
...ts_check_imprints:message imprint mismatch...
Verification: FAILED
Ese es el error que un validador serio tiene que producir, y el que un sistema que solo comprueba "¿hay un sello adjunto?" nunca va a ver.
Dónde vive el sello dentro de un PDF firmado
La ETSI EN 319 142-1 V1.2.1 (2024-01), que define PAdES, contempla dos formas de meter un sello en el mismo documento, y no son intercambiables.
El sello de firma (signature-time-stamp) es un atributo no firmado dentro del CMS de la firma: sella "esta firma existía a esta hora" y es una de las dos formas de llegar al nivel PAdES-B-T. La norma lo dice así: el tiempo de confianza "shall be provided either by a signature-time-stamp attribute or a document-time-stamp".
El sello de documento (document-time-stamp) es distinto: no cuelga de una firma concreta, es su propio diccionario en el PDF, con /Type /DocTimeStamp. La cláusula 5.4.3 lo especifica campo por campo: SubFilter debe ser ETSI.RFC3161, y el contenido es el TimeStampToken del RFC 3161 actualizado por el RFC 5816, calculado sobre el ByteRange completo hasta ese punto. Sirve también para el B-T, y es el que usa el nivel PAdES-B-LTA, que puede renovarse varias veces con un document-time-stamp nuevo cada vez, cubriendo el anterior. Ese mecanismo se trata con más detalle en PAdES, XAdES y las decisiones que no se pueden deshacer.
Qué dice la TSL peruana sobre sellado de tiempo
INDECOPI publica la lista de servicios de confianza de la IOFE en iofe.indecopi.gob.pe/TSL/tsl-pe.xml. La copia revisada tiene ListIssueDateTime del 10 de agosto de 2026 y NextUpdate del 10 de febrero de 2027.
El archivo tiene 74 entradas de TSPService. Ninguna usa el identificador dedicado de ETSI para autoridades de sellado (.../Svctype/TSA/...); los únicos ServiceTypeIdentifier que aparecen en todo el documento son CA/QC, RA y unspecified. Buscar por tipo de servicio "TSA" no encuentra nada, porque el sellado peruano se declara de otras dos formas.
Cuatro prestadores tienen algo relacionado con sellado de tiempo, con este nombre literal, tipo y estado:
- LLAMA.PE S.A., "TIME STAMPING AUTHORITY LLAMA.PE S.A.", tipo
unspecified,undersupervisiondesde el 2 de mayo de 2024. - BPO ADVISORS SPA SUCURSAL DEL PERÚ, "TSA BPO ADVISORS SPA SUCURSAL DEL PERU", tipo
unspecified,undersupervisiondesde el 7 de marzo de 2025. - ACEPTA PERÚ S.A.C., "ACEPTA – INTERMEDIA SELLADO DE TIEMPO 2018", tipo
CA/QC,undersupervisiondesde el 22 de agosto de 2019. - CAMERFIRMA PERÚ S.A.C., "TSU CAMERFIRMA PERU SAC", tipo
CA/QC,undersupervisiondesde el 1 de julio de 2019.
Los dos primeros declaran la unidad de sellado directamente, con unspecified porque el catálogo de esta TSL no tiene un tipo propio para eso. Los otros dos la declaran como entidad certificadora intermedia (CA/QC) que emite los certificados que firman sellos, no como el servicio de sellado en sí. Filtrar solo por CA/QC descartaría a LLAMA.PE y BPO Advisors; filtrar solo por nombre incluiría intermedias que no son el punto de entrada del servicio.
undersupervision en las cuatro dice que INDECOPI las supervisa a la fecha de esta TSL. No dice, por sí solo, que cumplan el artículo 38 del D.S. 052-2008-PCM: para el nivel Medio Alto, el Sistema de Sellado de Tiempo necesita "certificación internacional de calidad para la provisión de sus servicios, de acuerdo a lo establecido por la Autoridad Administrativa Competente". Confirmamos ese texto contra el PDF original de Congreso; el artículo 38 no figura entre los que el D.S. 029-2021-PCM reescribió, así que es el texto vigente. Pero la TSL registra supervisión y certificado, no esa certificación de calidad. Nuestra recomendación: antes de contratar, pide al prestador la certificación concreta que sustenta su nivel. Aparecer en la TSL dice que INDECOPI lo supervisa, no que ya la tenga.
Antes de integrarlo
El sello de tiempo es una de las decisiones que se toman al diseñar la integración, junto con el formato de firma y dónde vive la clave privada, tratadas en la guía completa. Si tu sistema ya firma y necesita decidir con qué certificado y qué prestador, en Mi Firma Digital emitimos certificados dentro de la IOFE para personas naturales, jurídicas y agentes automatizados: revisa las opciones.
Lo que no afirmamos
No afirmamos ninguna URL de sellado operativa de LLAMA.PE, BPO Advisors, Acepta o Camerfirma más allá de lo que declara la propia TSL, que no publica ServiceSupplyPoint para ninguno de los cuatro. No afirmamos el significado exacto de la sigla "TSU" en el nombre del servicio de Camerfirma más allá de lo que dice la TSL. No afirmamos que el texto del artículo 38 citado corresponda a una versión consolidada oficial: lo verificamos contra el PDF del Congreso y contra la lista de artículos que reescribió el D.S. 029-2021-PCM, no contra un texto único actualizado por la Autoridad Administrativa Competente. Y no afirmamos ningún valor probatorio para el sello obtenido de FreeTSA en este artículo: es un ejercicio de protocolo, no una prueba de fecha cierta.
Fuentes
- RFC 3161, IETF, agosto de 2001. Secciones 2.3 y 2.4.
- RFC 5816, IETF, marzo de 2010. Sección 2.2.1.
- ETSI EN 319 142-1 V1.2.1 (2024-01). Cláusulas 5.2, 5.4.3 y niveles B-T/B-LTA.
- D.S. 052-2008-PCM, artículo 38.
- TSL de la IOFE, INDECOPI,
ListIssueDateTime2026-08-10,NextUpdate2027-02-10. Consultada el 28 de septiembre de 2026. - FreeTSA, certificados
cacert.pemytsa.crtdescargados defreetsa.org/files/, ejercicio realizado el 28 de septiembre de 2026 con OpenSSL 3.5.8.
Última revisión: 28 de septiembre de 2026.