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. |
||
|---|---|---|
| .env.example | ||
| .gitignore | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| service.js | ||
| setup-ca.sh | ||
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 (€200–1,000+ per year) or unreliable single-operator free services with no source code. This project closes that gap with production-grade open infrastructure.
- Live service: https://tsr.open-tsa.eu
- Project home: https://open-tsa.eu
- License: MIT
- Server location: Nuremberg, Germany (Hetzner)
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 versionshould printOpenSSL 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 = nois required inopenssl-tsa.cnf— there is a known bug with the-chainflag in OpenSSL 3.x.default_policymust be a numeric OID (text policies crashopenssl ts).other_policiesmust not be set to an empty value.- The
serialfile must contain an even number of hex digits (e.g.1000, not10000). OpenSSL 3.x rejects odd-length serials witha2i_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:
- Clone the repository (anonymous, no account needed):
git clone https://git.open-tsa.eu/open-tsa/server.git - Sign up on
git.open-tsa.eu/user/sign_up(e-mail confirmation required) - Fork the repository, push your branch, open a pull request against
main - 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
- Web: https://open-tsa.eu
- E-mail: info@open-tsa.eu
- Source: https://git.open-tsa.eu/open-tsa/server