Free, open-source RFC 3161 timestamp infrastructure for Europe
Find a file
Open TSA Project 2536c55e33
Initial public release
Open TSA — Free European RFC 3161 Timestamp Authority.

- 4-tier CA hierarchy via setup-ca.sh (Root → TSA Root → Intermediate → Signing)
- Node.js service (service.js): RFC 3161 endpoint, health, info, stats,
  /now, Prometheus metrics, rate limiting, audit log, admin endpoints
- All identities and paths configurable via environment variables
- Documented in README, including reverse-proxy setup and operating notes

MIT licensed. See https://open-tsa.eu for the live service.
2026-05-15 18:37:34 +02:00
.env.example Initial public release 2026-05-15 18:37:34 +02:00
.gitignore Initial public release 2026-05-15 18:37:34 +02:00
LICENSE Initial public release 2026-05-15 18:37:34 +02:00
package.json Initial public release 2026-05-15 18:37:34 +02:00
README.md Initial public release 2026-05-15 18:37:34 +02:00
service.js Initial public release 2026-05-15 18:37:34 +02:00
setup-ca.sh Initial public release 2026-05-15 18:37:34 +02:00

Open TSA — Server

Free, open-source RFC 3161 timestamp infrastructure for Europe.

Open TSA provides a free public RFC 3161 timestamp service that any developer, researcher, NGO, or startup can build on without cost or lock-in. The dominant alternatives are either expensive commercial TSAs (€2001,000+ per year) or unreliable single-operator free services with no source code. This project closes that gap with production-grade open infrastructure.


Quick verification

Verify that the live service works correctly, from any machine with openssl and curl:

# 1. Download CA certificates (one-time)
curl -s https://open-tsa.eu/certs/ca.crt -o ca.crt
curl -s https://open-tsa.eu/certs/fullchain.pem -o fullchain.pem

# 2. Create a test document and request a timestamp
echo "Hello, Open TSA" > test.txt
openssl ts -query -data test.txt -cert -sha256 -no_nonce -out test.tsq
curl -s -H "Content-Type: application/timestamp-query" \
     --data-binary @test.tsq \
     https://tsr.open-tsa.eu -o test.tsr

# 3. Verify
openssl ts -verify -in test.tsr -queryfile test.tsq \
           -CAfile ca.crt -untrusted fullchain.pem
# Expected output: Verification: OK

If you see Verification: OK, the service is functioning correctly.


Run your own instance

You can run an independent Open TSA instance. The setup is reproducible on any standard Linux server with Node.js and OpenSSL 3.x.

Requirements

  • Linux server (tested on AlmaLinux 9, Debian 12, Ubuntu 22.04 LTS)
  • Node.js 18 or newer
  • OpenSSL 3.x (openssl version should print OpenSSL 3.x)
  • A domain name pointing to the server (for TLS via Let's Encrypt)
  • Port 443 reachable from the internet
  • Approximately 5 GB of disk space

Build and deploy

# 1. Clone
git clone https://git.open-tsa.eu/open-tsa/server.git
cd server

# 2. Install dependencies
npm install

# 3. Generate the 4-tier CA hierarchy
#    Root CA (25y, offline) → TSA Root CA (15y, offline)
#    → Intermediate CA (10y) → TSA Signing Cert (2y)
sudo ./setup-ca.sh

# 4. Copy and adjust environment configuration
cp .env.example .env
nano .env
#    → set TSA_HOST, TSA_PORT, CA_DIR, TSA_POLICY_OID, etc.

# 5. Start the service
node service.js

Reverse proxy

The service listens on 127.0.0.1:3700 by default. Expose it via nginx with Let's Encrypt:

server {
    listen 443 ssl http2;
    server_name tsr.example.org;

    ssl_certificate     /etc/letsencrypt/live/tsr.example.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tsr.example.org/privkey.pem;

    # Block internal endpoints from public access
    location /admin/  { return 404; }
    location /metrics { return 404; }

    location / {
        proxy_pass http://127.0.0.1:3700;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $remote_addr;
        proxy_set_header X-Real-IP         $remote_addr;
        client_max_body_size 64K;
    }
}

X-Forwarded-For must be set to the real client IP — the service uses it for rate-limit accounting and statistics. Without it, the rate limiter sees only 127.0.0.1 and treats all traffic as one client.

Health check

curl http://127.0.0.1:3700/health
# {"status":"ok","service":"open-tsa","version":"1.0.0","openssl":"OpenSSL 3.x.x ...","time":"...","total_timestamps":N}

Architecture

Internet
   │
   ▼  HTTPS (port 443)
nginx — TLS termination, Let's Encrypt
   │
   ▼  HTTP (port 3700, localhost only)
service.js — Node.js / Express
   │
   ▼
openssl ts -reply — RFC 3161 token generation

The service is intentionally minimal. The cryptographic heavy lifting is done by openssl ts -reply. The Node.js layer handles HTTP, request validation, rate limiting, audit logging, and metrics.

Certificate hierarchy

Root CA                    (25 years, offline)
└── TSA Root CA            (15 years, offline)
    └── TSA Intermediate   (10 years)
        └── TSA Signing    (2 years)
            EKU: id-kp-timeStamping (critical)

Root and intermediate private keys are kept offline. Only the TSA signing key is online.


Configuration

See .env.example for all available options. The most important ones:

Variable Description Default
TSA_HOST Interface to bind to 127.0.0.1
TSA_PORT HTTP port to bind 3700
CA_DIR Path to CA hierarchy /opt/open-tsa/ca
TSA_POLICY_OID Your own policy OID (allocate at https://pen.iana.org/) placeholder
RATE_LIMIT_PER_MINUTE Per-IP rate limit (0 = disabled) 60
AUDIT_LOG Append-only JSONL log path empty (disabled)
METRICS_ENABLED Prometheus /metrics (loopback-only) true
LOG_LEVEL error, warn, info, debug info

Operating notes

OpenSSL 3.x quirks

Open TSA targets OpenSSL 3.x explicitly. A few things to know:

  • ess_cert_id_chain = no is required in openssl-tsa.cnf — there is a known bug with the -chain flag in OpenSSL 3.x.
  • default_policy must be a numeric OID (text policies crash openssl ts).
  • other_policies must not be set to an empty value.
  • The serial file must contain an even number of hex digits (e.g. 1000, not 10000). OpenSSL 3.x rejects odd-length serials with a2i_ASN1_INTEGER:odd number of chars.

Serial number permissions

The CA setup runs as root, but the service runs as a less-privileged user. The setup script sets the necessary permissions on serial, index.txt, and newcerts/ at the TSA tier so that the service can issue tokens. The higher tiers (Root, TSA Root, Intermediate) remain root-only.

Renewing the TSA signing certificate

The TSA Signing Cert has a 2-year lifetime. Only the signing cert needs replacement at the 2-year mark; root and intermediate certificates have much longer lifetimes (25 / 15 / 10 years respectively).


Roadmap

  • Phase 1 (current): Single-node free public TSA in Nuremberg, RFC 3161 compliant, MIT-licensed.
  • Phase 2: Multi-region deployment (Falkenstein DE + Helsinki FI), GeoDNS failover, browser-based verification tool.
  • Phase 3: Explore browser trust paths (HARICA collaboration, Mozilla root program submission) — non-binding research track.

Contributing

Contributions are welcome. The canonical home of this code is git.open-tsa.eu.

To contribute:

  1. Clone the repository (anonymous, no account needed): git clone https://git.open-tsa.eu/open-tsa/server.git
  2. Sign up on git.open-tsa.eu/user/sign_up (e-mail confirmation required)
  3. Fork the repository, push your branch, open a pull request against main
  4. We review within a few days

For larger changes or feature proposals, please open an issue first to discuss the approach.

Reporting bugs: open an issue at https://git.open-tsa.eu/open-tsa/server/issues or send an e-mail to info@open-tsa.eu.

Security issues: please do not file public issues. Send details to security@open-tsa.eu.


License

This project is released under the MIT License. See LICENSE for the full text.


Acknowledgements

This project addresses a gap identified by the European open-source community: free RFC 3161 timestamp infrastructure that is independently operated, source-available, and not dependent on a single private individual or commercial entity.

Funded development supported by the NLnet Foundation under the NGI Zero Commons Fund (pending review at time of initial publication).


Contact