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.
228 lines
7.7 KiB
Markdown
228 lines
7.7 KiB
Markdown
# 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`:
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
```nginx
|
||
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
|
||
|
||
```bash
|
||
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](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
|