# 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