Documentacion Tecnica
Guia completa para integrar el servicio TSA RFC 3161
Inicio Rapido
POST
https://tsa.transparency.solutions/sign
Content-Type (Request)
application/timestamp-query
Content-Type (Response)
application/timestamp-reply
El endpoint acepta un TimeStampRequest en formato DER (binario) y retorna un TimeStampResponse segun RFC 3161.
Ejemplos de Codigo
# 1. Crear timestamp request desde un archivo
openssl ts -query -data documento.pdf -sha256 -cert -out request.tsq
# 2. Enviar al servidor TSA
curl -X POST \
--data-binary @request.tsq \
-H "Content-Type: application/timestamp-query" \
-o response.tsr \
https://tsa.transparency.solutions/sign
# 3. Verificar la respuesta (mostrar contenido)
openssl ts -reply -in response.tsr -text
# 4. Verificar con el certificado CA
# Primero descarga los certificados:
curl -o ca.crt https://tsa.transparency.solutions/pki/ca.crt
curl -o tsa.crt https://tsa.transparency.solutions/pki/tsa.crt
# Luego verifica:
openssl ts -verify -in response.tsr \
-queryfile request.tsq \
-CAfile ca.crt \
-untrusted tsa.crt
# Crear timestamp desde un hash existente
echo -n "tu_hash_sha256_aqui" | xxd -r -p > hash.bin
openssl ts -query -digest hash.bin -sha256 -cert -out request.tsq# Usando cURL directamente con un archivo .tsq existente
curl -X POST \
--data-binary @request.tsq \
-H "Content-Type: application/timestamp-query" \
-H "Accept: application/timestamp-reply" \
-o response.tsr \
-w "HTTP Status: %{http_code}\n" \
https://tsa.transparency.solutions/sign
# Verificar que el archivo se creo correctamente
file response.tsr
# Output esperado: response.tsr: data
# Ver el tamano del token
ls -la response.tsr#!/usr/bin/env python3
"""
Ejemplo de cliente TSA RFC 3161 en Python
Requiere: pip install rfc3161ng requests
"""
import hashlib
import requests
from rfc3161ng import RemoteTimestamper, get_timestamp
TSA_URL = "https://tsa.transparency.solutions/sign"
def timestamp_file(filepath: str) -> bytes:
"""Genera un timestamp para un archivo."""
# Calcular hash SHA-256 del archivo
with open(filepath, 'rb') as f:
file_hash = hashlib.sha256(f.read()).digest()
# Crear timestamper
timestamper = RemoteTimestamper(
TSA_URL,
hashname='sha256'
)
# Obtener timestamp
tsr = timestamper(data=None, digest=file_hash)
return tsr
def timestamp_hash(hash_hex: str) -> bytes:
"""Genera un timestamp para un hash existente."""
hash_bytes = bytes.fromhex(hash_hex)
timestamper = RemoteTimestamper(
TSA_URL,
hashname='sha256'
)
tsr = timestamper(data=None, digest=hash_bytes)
return tsr
# Ejemplo de uso
if __name__ == "__main__":
# Timestamp de archivo
tsr = timestamp_file("documento.pdf")
with open("documento.pdf.tsr", 'wb') as f:
f.write(tsr)
print("Timestamp guardado en documento.pdf.tsr")
# Timestamp de hash
my_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
tsr = timestamp_hash(my_hash)
print(f"Timestamp generado: {len(tsr)} bytes")/**
* Cliente TSA RFC 3161 en Node.js
* Requiere: npm install node-forge axios
*/
const crypto = require('crypto');
const axios = require('axios');
const forge = require('node-forge');
const asn1 = forge.asn1;
const TSA_URL = 'https://tsa.transparency.solutions/sign';
/**
* Crea un TimeStampRequest ASN.1
*/
function createTimestampRequest(hash) {
// OID para SHA-256: 2.16.840.1.101.3.4.2.1
const sha256Oid = '2.16.840.1.101.3.4.2.1';
// MessageImprint
const messageImprint = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// AlgorithmIdentifier
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
asn1.oidToDer(sha256Oid).getBytes()),
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
]),
// HashedMessage
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, hash)
]);
// Nonce aleatorio
const nonce = crypto.randomBytes(8);
// TimeStampReq
const tsRequest = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
// version INTEGER (1)
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
String.fromCharCode(1)),
// messageImprint
messageImprint,
// nonce INTEGER OPTIONAL
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
nonce.toString('binary')),
// certReq BOOLEAN OPTIONAL (true)
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
String.fromCharCode(0xFF))
]);
return Buffer.from(asn1.toDer(tsRequest).getBytes(), 'binary');
}
/**
* Solicita un timestamp al servidor TSA
*/
async function requestTimestamp(filePath) {
// Leer archivo y calcular hash
const fs = require('fs');
const fileBuffer = fs.readFileSync(filePath);
const hash = crypto.createHash('sha256').update(fileBuffer).digest();
// Crear request
const tsqBuffer = createTimestampRequest(hash.toString('binary'));
// Enviar al TSA
const response = await axios.post(TSA_URL, tsqBuffer, {
headers: {
'Content-Type': 'application/timestamp-query'
},
responseType: 'arraybuffer'
});
return Buffer.from(response.data);
}
// Ejemplo de uso
(async () => {
try {
const tsr = await requestTimestamp('documento.pdf');
require('fs').writeFileSync('documento.pdf.tsr', tsr);
console.log(`Timestamp guardado: ${tsr.length} bytes`);
} catch (error) {
console.error('Error:', error.message);
}
})();import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.tsp.*;
import org.bouncycastle.operator.DigestCalculator;
import java.io.*;
import java.net.*;
import java.security.MessageDigest;
import java.math.BigInteger;
import java.security.SecureRandom;
/**
* Cliente TSA RFC 3161 en Java
* Requiere: Bouncy Castle (bcprov, bcpkix)
*/
public class TSAClient {
private static final String TSA_URL = "https://tsa.transparency.solutions/sign";
public static byte[] timestamp(byte[] data) throws Exception {
// Calcular hash SHA-256
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data);
// Crear TimeStampRequest
TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator();
reqGen.setCertReq(true);
BigInteger nonce = new BigInteger(64, new SecureRandom());
TimeStampRequest request = reqGen.generate(
TSPAlgorithms.SHA256,
hash,
nonce
);
byte[] tsqBytes = request.getEncoded();
// Enviar al TSA
URL url = new URL(TSA_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/timestamp-query");
conn.setRequestProperty("Content-Length", String.valueOf(tsqBytes.length));
try (OutputStream out = conn.getOutputStream()) {
out.write(tsqBytes);
}
// Leer respuesta
byte[] tsrBytes;
try (InputStream in = conn.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[4096];
int len;
while ((len = in.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
tsrBytes = baos.toByteArray();
}
// Verificar respuesta
TimeStampResponse response = new TimeStampResponse(tsrBytes);
response.validate(request);
if (response.getStatus() != PKIStatus.GRANTED &&
response.getStatus() != PKIStatus.GRANTED_WITH_MODS) {
throw new Exception("Timestamp failed: " + response.getStatusString());
}
return response.getTimeStampToken().getEncoded();
}
public static void main(String[] args) throws Exception {
// Leer archivo
byte[] fileData = java.nio.file.Files.readAllBytes(
java.nio.file.Paths.get("documento.pdf")
);
// Obtener timestamp
byte[] tsr = timestamp(fileData);
// Guardar
try (FileOutputStream fos = new FileOutputStream("documento.pdf.tsr")) {
fos.write(tsr);
}
System.out.println("Timestamp guardado: " + tsr.length + " bytes");
}
}using System;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Org.BouncyCastle.Tsp;
using Org.BouncyCastle.Math;
/**
* Cliente TSA RFC 3161 en C#/.NET
* Requiere: NuGet BouncyCastle.Cryptography
*/
public class TSAClient
{
private const string TSA_URL = "https://tsa.transparency.solutions/sign";
public static async Task TimestampAsync(byte[] data)
{
// Calcular hash SHA-256
byte[] hash;
using (var sha256 = SHA256.Create())
{
hash = sha256.ComputeHash(data);
}
// Crear TimeStampRequest
var reqGen = new TimeStampRequestGenerator();
reqGen.SetCertReq(true);
var random = new Random();
var nonce = new BigInteger(64, random);
var request = reqGen.Generate(
TspAlgorithms.Sha256,
hash,
nonce
);
byte[] tsqBytes = request.GetEncoded();
// Enviar al TSA
using var client = new HttpClient();
using var content = new ByteArrayContent(tsqBytes);
content.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("application/timestamp-query");
var response = await client.PostAsync(TSA_URL, content);
response.EnsureSuccessStatusCode();
byte[] tsrBytes = await response.Content.ReadAsByteArrayAsync();
// Verificar respuesta
var tsResponse = new TimeStampResponse(tsrBytes);
tsResponse.Validate(request);
if (tsResponse.Status != (int)PkiStatus.Granted &&
tsResponse.Status != (int)PkiStatus.GrantedWithMods)
{
throw new Exception($"Timestamp failed: {tsResponse.GetStatusString()}");
}
return tsResponse.TimeStampToken.GetEncoded();
}
public static async Task Main(string[] args)
{
// Leer archivo
byte[] fileData = await File.ReadAllBytesAsync("documento.pdf");
// Obtener timestamp
byte[] tsr = await TimestampAsync(fileData);
// Guardar
await File.WriteAllBytesAsync("documento.pdf.tsr", tsr);
Console.WriteLine($"Timestamp guardado: {tsr.Length} bytes");
}
} Referencia de API
POST /sign
Endpoint principal del TSA. Genera timestamps RFC 3161.
Request
| Header | Valor | Requerido |
|---|---|---|
| Content-Type | application/timestamp-query | Si |
Body: TimeStampRequest en formato DER (binario)
Response
| Header | Valor |
|---|---|
| Content-Type | application/timestamp-reply |
Body: TimeStampResponse en formato DER (binario)
POST /api/timestamp
API REST alternativa. Acepta JSON y retorna el token en base64.
Request
{
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"hash_algorithm": "sha256"
}Response
{
"success": true,
"timestamp_token": "MIIHxQYJKoZI...", // Base64
"serial_number": "1234567890",
"gen_time": "2026-01-08T12:00:00Z",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"hash_algorithm": "sha256"
}POST /api/verify
Verifica un timestamp contra un hash.
Request
{
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"tsr_base64": "MIIHxQYJKoZI..."
}Response
{
"valid": true,
"message": "Timestamp valido",
"timestamp_info": {
"gen_time": "2026-01-08T12:00:00Z",
"serial_number": "1234567890",
"hash_algorithm": "sha256"
}
}GET /api/certificates
Obtiene informacion de los certificados PKI.
Response
{
"ca": {
"name": "Transparency Solutions CA",
"subject": "CN=Transparency Solutions Root CA,...",
"fingerprint_sha256": "C0:D7:0B:E0:...",
"not_after": "2036-01-03T05:36:58Z",
"days_remaining": 3647,
"download_url": "/pki/ca.crt"
},
"tsa": {
"name": "Transparency Solutions TSA",
"subject": "CN=Transparency Solutions TSA,...",
"fingerprint_sha256": "3E:FB:53:EE:...",
"not_after": "2031-01-04T05:37:00Z",
"days_remaining": 1822,
"download_url": "/pki/tsa.crt"
},
"chain_url": "/pki/chain.pem"
}Informacion PKI
Certificados
Revocacion
- CRL: tsa.crl
- OCSP: http://ocsp.transparency.solutions
Algoritmos
- RSA 4096-bit
- SHA256withRSA
- Hash: SHA-256