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

HeaderValorRequerido
Content-Typeapplication/timestamp-querySi

Body: TimeStampRequest en formato DER (binario)

Response

HeaderValor
Content-Typeapplication/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

Revocacion

  • CRL: tsa.crl
  • OCSP: http://ocsp.transparency.solutions

Algoritmos

  • RSA 4096-bit
  • SHA256withRSA
  • Hash: SHA-256