docs(pusher): document v2 onion delegation and legacy ping defaults
- Correct WELIST_ALLOW_LEGACY_PING: legacy pings are accepted by default, the flag only disables them. - Describe onion delegation: v2-only inclusion, service-side host normalization, strict expiry check, deployment order and rollback. - Warn that disabled delegation rejects onion pings when SSL_KEY_PATH is the dedicated ping key. - Document certificate lifetime and revocation limits, offline master key requirements, helper --days bounds, and a delegation verification checklist. - Fix the bal-pusher.sh comment about the service-side defaults.
This commit is contained in:
@@ -204,12 +204,15 @@ This is the async daemon that monitors the blockchain and pushes pending transac
|
||||
fetches `info`, `base_fee` and `version` from the local
|
||||
`/{chain}/info` endpoint and signs them together with the block metadata.
|
||||
The WeTime service discovers the public key from a DNSSEC-validated
|
||||
`_welist-key.<host>` TXT record or from a v3 onion address, so no HTTP
|
||||
callback is needed. The `url` is trimmed of trailing slashes to match the
|
||||
service-side normalization; absent metadata is signed as `null`. When
|
||||
onion delegation is configured, the pusher verifies at startup that the
|
||||
certificate is bound to the configured onion host, has not expired, and
|
||||
delegates to the key at `SSL_KEY_PATH`.
|
||||
`_welist-key.<host>` TXT record, from a v3 onion address, or from a ping key
|
||||
delegated by that onion identity key, so no HTTP callback is needed. The
|
||||
`url` is trimmed of trailing slashes to match the service-side
|
||||
normalization; absent metadata is signed as `null`. When onion delegation
|
||||
is configured, the pusher sends the certificate only with v2 pings and
|
||||
verifies at startup that it is bound to the configured onion host, has not
|
||||
expired, and delegates to the key at `SSL_KEY_PATH`. If that check fails the
|
||||
pusher disables delegation and signs directly, which the service rejects
|
||||
unless `SSL_KEY_PATH` is the onion identity key.
|
||||
- `bal-pusher.sh` pins `WELIST_PING_SIG_VERSION=1` for the development setup:
|
||||
a `localhost` URL cannot use DNSSEC, so the dev pusher uses the legacy
|
||||
scheme verified against `/.pub_key.pem`. v2 requires a public DNS hostname
|
||||
|
||||
@@ -169,14 +169,19 @@ scheme is selected with `WELIST_PING_SIG_VERSION`:
|
||||
or from a v3 `.onion` address. No HTTP callback to `/.pub_key.pem` is made.
|
||||
|
||||
For `.onion` URLs the pusher includes a `delegation` object when
|
||||
`WELIST_PING_DELEGATION_FILE` is set. The onion identity key signs that
|
||||
certificate offline; `SSL_KEY_PATH` is then the dedicated ping key, and the
|
||||
service verifies the certificate before checking the ping signature. The
|
||||
pusher validates the certificate at startup: `onion` must match the configured
|
||||
host, `valid_until` must be in the future, and `key` must match `SSL_KEY_PATH`.
|
||||
If the file is missing or malformed, or any check fails, the pusher logs a
|
||||
warning and sends no `delegation` object. It then signs with `SSL_KEY_PATH`
|
||||
directly, which works only when that key is the onion identity key.
|
||||
`WELIST_PING_DELEGATION_FILE` is set and `WELIST_PING_SIG_VERSION=2`. The onion
|
||||
identity key signs that certificate offline; `SSL_KEY_PATH` is then the
|
||||
dedicated ping key, and the service verifies the certificate before checking
|
||||
the ping signature. The service normalizes `onion` (lowercase, trailing dot
|
||||
removed) before comparing it with the ping host; the pusher's startup check uses
|
||||
the exact configured host, so issue lowercase certificates.
|
||||
|
||||
The pusher validates the certificate at startup: `onion` must match the
|
||||
configured host, `valid_until` must be in the future, and `key` must match
|
||||
`SSL_KEY_PATH`. If the file is missing or malformed, or any check fails, the
|
||||
pusher logs a warning and sends no `delegation` object. It then signs with
|
||||
`SSL_KEY_PATH` directly; in the recommended setup that key is the dedicated
|
||||
ping key, so the service rejects the pings until delegation is restored.
|
||||
|
||||
The `url` is trimmed of surrounding whitespace and trailing slashes, and the
|
||||
`chain` is lowercase, matching the service-side canonicalization.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# DNSSEC-authenticated WeTime pings (v2)
|
||||
# WeTime v2 ping keys: DNSSEC TXT and onion delegation
|
||||
|
||||
This document explains how to publish the Ed25519 public key used by the
|
||||
`bal-pusher` v2 WeTime ping and how to verify that the record is usable.
|
||||
`bal-pusher` v2 WeTime ping, how to delegate onion ping signing to a dedicated,
|
||||
rotatable key, and how to verify the configuration.
|
||||
|
||||
In v2 the WeTime service discovers the executor public key from:
|
||||
|
||||
@@ -198,8 +199,9 @@ use the legacy scheme and let the service verify the published public key:
|
||||
|
||||
- Keep `WELIST_PING_SIG_VERSION` unset (or `1`), so the pusher signs with
|
||||
`SSL_KEY_PATH` and the service verifies against `GET /.pub_key.pem`.
|
||||
- Start the service with `WELIST_ALLOW_LEGACY_PING=true` and, for loopback or
|
||||
private addresses, `WELIST_ALLOW_LOCALHOST_PING=true`.
|
||||
- For loopback or private addresses, start the service with
|
||||
`WELIST_ALLOW_LOCALHOST_PING=true`. Legacy pings are accepted by default;
|
||||
only set `WELIST_ALLOW_LEGACY_PING=false` if the service must require v2.
|
||||
|
||||
No TXT record and no DNSSEC setup are required in this case.
|
||||
|
||||
@@ -278,8 +280,8 @@ export WELIST_PING_SIG_VERSION=2
|
||||
- `BAL_SERVER_URL` must be the public DNS hostname. The pusher normalizes it by
|
||||
trimming whitespace and trailing slashes before signing.
|
||||
- `WELIST_PING_SIG_VERSION=2` activates the canonical JSON signature. Leaving it
|
||||
unset (or `1`) keeps the legacy v1 scheme, which the service rejects unless
|
||||
`WELIST_ALLOW_LEGACY_PING=true`.
|
||||
unset (or `1`) keeps the legacy v1 scheme, which the service accepts by
|
||||
default (set `WELIST_ALLOW_LEGACY_PING=false` to require v2).
|
||||
|
||||
For onion hosts, also configure the delegation certificate (see
|
||||
[Onion URLs](#onion-urls)):
|
||||
@@ -365,6 +367,23 @@ values are persisted.
|
||||
A response of `"ko"` means the signature was rejected. Check the service logs
|
||||
for the specific `PING REJECTED` reason.
|
||||
|
||||
### Onion delegation
|
||||
|
||||
For an onion pusher with delegation configured:
|
||||
|
||||
1. At startup, the pusher must not log `disabling onion delegation`.
|
||||
2. After the next block, look for `Report to welist(...) status=200 body="ok"`
|
||||
and `PING ADDED` in the service log.
|
||||
3. Negative check: send a crafted v2 ping with a tampered certificate (change
|
||||
`key` or `valid_until` after signing) directly to the WeTime service. It must
|
||||
answer `"ko"` and log `PING REJECTED invalid onion delegation`. Do not run
|
||||
the pusher with a tampered certificate: it disables delegation locally
|
||||
instead of sending it.
|
||||
|
||||
`bal-welist/scripts/test_v2_ping.py` is a local end-to-end reference: it
|
||||
generates a throwaway onion identity key, issues a delegation certificate, and
|
||||
checks the accepted and rejected paths on a throwaway database.
|
||||
|
||||
---
|
||||
|
||||
## 9. Key rotation
|
||||
@@ -398,10 +417,10 @@ keys for that host.
|
||||
| `v2 DNS lookup requires a hostname` | `BAL_SERVER_URL` uses an IP address | Use a DNS hostname |
|
||||
| `invalid v2 signature` | Key mismatch or non-canonical URL | Ensure the TXT key matches `SSL_KEY_PATH` and the URL has no trailing slash |
|
||||
| `invalid onion delegation` | Certificate signature does not verify against the onion identity key | Re-issue the certificate with the correct onion key; do not edit `key`/`valid_until` after signing |
|
||||
| `delegation does not match the ping onion host` | `onion` field differs from `BAL_SERVER_URL` | Re-issue the certificate for the exact host |
|
||||
| `delegation expired` | `valid_until` is in the past | Re-issue the certificate and restart the pusher |
|
||||
| `delegation does not match the ping onion host` | `onion` field differs from `BAL_SERVER_URL` after normalization | Re-issue the certificate for the exact host |
|
||||
| `delegation expired` | `valid_until` is at or before the current time | Re-issue the certificate and restart the pusher |
|
||||
| `delegation is only supported for onion hosts` | `delegation` sent with a DNS URL | Unset `WELIST_PING_DELEGATION_FILE` for DNS hosts |
|
||||
| `legacy ping disabled` | v1 ping sent while the service requires v2 | Set `WELIST_PING_SIG_VERSION=2` (or enable legacy on the service) |
|
||||
| `legacy ping disabled` | v1 ping sent while the service requires v2 | Set `WELIST_PING_SIG_VERSION=2` (or set `WELIST_ALLOW_LEGACY_PING=true` if it was disabled) |
|
||||
|
||||
Useful diagnostics:
|
||||
|
||||
@@ -440,7 +459,7 @@ The request carries:
|
||||
{
|
||||
"onion": "<address>.onion",
|
||||
"key": "<base64 SPKI DER of the delegated Ed25519 key>",
|
||||
"valid_until": 1767225600,
|
||||
"valid_until": 1800000000,
|
||||
"signature": "<base64 Ed25519 signature by the onion identity key>"
|
||||
}
|
||||
```
|
||||
@@ -448,13 +467,16 @@ The request carries:
|
||||
The identity signature covers exactly:
|
||||
|
||||
```text
|
||||
WELIST-PING-DELEGATION-V1\n{"key":"...","onion":"...","valid_until":1767225600}
|
||||
WELIST-PING-DELEGATION-V1\n{"key":"...","onion":"...","valid_until":1800000000}
|
||||
```
|
||||
|
||||
Rules enforced by the service:
|
||||
|
||||
- `onion` must exactly equal the ping URL host (lowercase, no trailing dot).
|
||||
- `valid_until` is a Unix timestamp in seconds and must be in the future.
|
||||
- `onion` is normalized (lowercase, trailing dot removed) before it is compared
|
||||
with the ping URL host. The pusher's startup check is stricter (exact string),
|
||||
so issue certificates with the lowercase form; the helper does this.
|
||||
- `valid_until` is a Unix timestamp in seconds and must be strictly in the
|
||||
future. A certificate expiring exactly now is already rejected.
|
||||
- `key` must be a valid Ed25519 SubjectPublicKeyInfo DER in standard base64.
|
||||
- The identity signature must verify against the key embedded in the onion
|
||||
address.
|
||||
@@ -482,7 +504,7 @@ contrib/make_onion_delegation.py \
|
||||
--onion "$ONION" \
|
||||
--identity-key "$ONION_KEY" \
|
||||
--ping-key /etc/bal/ping_private_key.pem \
|
||||
--days 365 \
|
||||
--days 90 \
|
||||
--out /etc/bal/onion-delegation.json
|
||||
```
|
||||
|
||||
@@ -493,10 +515,20 @@ pip install cryptography pynacl
|
||||
```
|
||||
|
||||
If the onion identity key is a PKCS#8 PEM file, point `--identity-key` at it and
|
||||
`pynacl` is not needed. Run this step on a machine that holds the identity key
|
||||
(ideally offline), then copy only `onion-delegation.json` and
|
||||
`ping_private_key.pem` to the executor. The certificate contains public data;
|
||||
the ping private key must remain secret.
|
||||
`pynacl` is not needed. `--days` accepts 1–3650 (default 365); prefer short
|
||||
lifetimes as explained in [Revocation and lifetime](#revocation-and-lifetime).
|
||||
|
||||
Run this step on a machine that holds the identity key (ideally offline), then
|
||||
copy only `onion-delegation.json` and `ping_private_key.pem` to the executor.
|
||||
The certificate contains public data (`0644` is fine); only
|
||||
`ping_private_key.pem` must stay `0600` and never leave the executor.
|
||||
|
||||
> **Tor offline master keys:** when the onion service runs with
|
||||
> `OfflineMasterKey 1`, the key file on the Tor host is a blinded signing key,
|
||||
> not the v3 identity key, and the helper fails with `onion secret key does not
|
||||
> match the onion address`. Issue the certificate on a machine that holds the
|
||||
> original `hs_ed25519_secret_key` (keep an offline copy from when the service
|
||||
> was created). The blinded signing key cannot issue a delegation certificate.
|
||||
|
||||
### Configure the pusher
|
||||
|
||||
@@ -507,11 +539,32 @@ export BAL_SERVER_URL="http://<address>.onion"
|
||||
export WELIST_PING_SIG_VERSION=2
|
||||
```
|
||||
|
||||
Delegation is only sent with v2 pings. With `WELIST_PING_SIG_VERSION=1` (or
|
||||
unset) the certificate is loaded and validated but never included in the
|
||||
request.
|
||||
|
||||
The pusher sends `delegation` only when `BAL_SERVER_URL` is an `.onion` host. It
|
||||
validates the certificate at startup against the onion host, its expiry, and
|
||||
`SSL_KEY_PATH`. If the file is missing or malformed, or validation fails,
|
||||
delegation is disabled and the pusher signs with `SSL_KEY_PATH` directly; that
|
||||
works only when `SSL_KEY_PATH` is the onion identity key.
|
||||
delegation is disabled and the pusher signs with `SSL_KEY_PATH` directly.
|
||||
|
||||
> **Warning — disabled delegation means onion pings fail.** In this setup
|
||||
> `SSL_KEY_PATH` is the dedicated ping key, so when delegation is disabled the
|
||||
> service verifies the ping against the onion identity key and rejects every
|
||||
> request (`invalid v2 signature`). The executor then stops earning WeTime
|
||||
> points. Watch for the pusher warning `disabling onion delegation` and
|
||||
> re-issue before `valid_until`.
|
||||
|
||||
### Deployment order
|
||||
|
||||
1. Upgrade the WeTime service (`bal-welist`) to a version that supports v2
|
||||
onion delegation.
|
||||
2. Then set `WELIST_PING_DELEGATION_FILE` on the pusher and restart it.
|
||||
|
||||
A pusher that sends delegation to an older service receives `"ko"`: the old
|
||||
service ignores the `delegation` field and verifies the ping with the onion
|
||||
identity key. When rolling back the service, unset
|
||||
`WELIST_PING_DELEGATION_FILE` first.
|
||||
|
||||
For local development, the `WELIST_ALLOW_LOCALHOST_PING` and legacy v1 notes
|
||||
above still apply; delegation itself needs no network access.
|
||||
@@ -526,13 +579,32 @@ above still apply; delegation itself needs no network access.
|
||||
|
||||
The onion address does not change, and the identity key stays offline. There is
|
||||
no DNS cache to wait for; the service checks the certificate on every ping.
|
||||
Rotation does **not** revoke the previous certificate; see
|
||||
[Revocation and lifetime](#revocation-and-lifetime).
|
||||
|
||||
### Revocation and lifetime
|
||||
|
||||
The service has no revocation list. It accepts any certificate that is bound to
|
||||
the onion host and not expired, so rotating to a new ping key does not stop the
|
||||
old key from working until the old certificate's `valid_until` passes.
|
||||
|
||||
Treat the certificate lifetime as the exposure window:
|
||||
|
||||
- Prefer short lifetimes (for example 30–90 days) and re-issue before expiry.
|
||||
- Keep `--days` well below the helper maximum (3650).
|
||||
- If a ping key leaks, the only immediate revocation is changing the onion
|
||||
identity, which also changes the `.onion` address. Otherwise the leaked key
|
||||
remains usable until the certificate expires.
|
||||
|
||||
### Security notes
|
||||
|
||||
- Delegation limits the blast radius: a compromised online ping key forges
|
||||
pings only until rotation, not the onion service identity.
|
||||
- A long `valid_until` increases the impact of a leaked delegated key. Prefer
|
||||
one year or less and rotate before expiry.
|
||||
- Delegation limits the blast radius: a compromised online ping key can publish
|
||||
pings and metadata for that onion host until the certificate expires, but it
|
||||
cannot take over the onion service identity or sign for another host.
|
||||
- The delegated key authorizes the full ping capability for that host, including
|
||||
the `info`, `base_fee` and `version` metadata.
|
||||
- The certificate is public data and can be copied freely; only the ping private
|
||||
key is secret.
|
||||
- The onion identity key should never be copied to the executor.
|
||||
|
||||
### Metadata for onion pings
|
||||
@@ -563,9 +635,14 @@ authenticated and stored.
|
||||
|
||||
For onion hosts, replace the TXT/DNSSEC items with:
|
||||
|
||||
- [ ] The WeTime service (`bal-welist`) runs a version with onion delegation support.
|
||||
- [ ] A dedicated `ping_private_key.pem` exists and is `chmod 600`.
|
||||
- [ ] `onion-delegation.json` was issued offline by the onion identity key.
|
||||
- [ ] `onion-delegation.json` was issued offline by the v3 **master** identity
|
||||
key (not a blinded signing key).
|
||||
- [ ] `SSL_KEY_PATH` points at the delegated ping key, not the identity key.
|
||||
- [ ] `WELIST_PING_DELEGATION_FILE` points at the certificate.
|
||||
- [ ] `valid_until` is in the future and rotation is scheduled.
|
||||
- [ ] `WELIST_PING_SIG_VERSION=2` is set.
|
||||
- [ ] `valid_until` is strictly in the future, monitored, and rotation is
|
||||
scheduled before it.
|
||||
- [ ] The pusher starts without a `disabling onion delegation` warning.
|
||||
- [ ] Pusher restarted and reports `body="ok"`.
|
||||
|
||||
Reference in New Issue
Block a user