Files
rustunnel/droid-wiki/security.md
T
bzuccaro 2bf8da739b
CI / cargo fmt (push) Canceled after 0s
CI / cargo clippy (macos-latest) (push) Canceled after 0s
CI / cargo clippy (ubuntu-latest) (push) Canceled after 0s
CI / cargo clippy (windows-latest) (push) Canceled after 0s
CI / cargo test (macos-latest) (push) Canceled after 0s
CI / cargo test (ubuntu-latest) (push) Canceled after 0s
CI / cargo test (windows-latest) (push) Canceled after 0s
CI / cargo build (macos-latest) (push) Canceled after 0s
CI / cargo build (ubuntu-latest) (push) Canceled after 0s
CI / cargo build (windows-latest) (push) Canceled after 0s
CI / cargo build --release (macos-latest) (push) Canceled after 0s
CI / cargo build --release (ubuntu-latest) (push) Canceled after 0s
CI / cargo build --release (windows-latest) (push) Canceled after 0s
CI / CLI smoke (macos-latest) (push) Canceled after 0s
CI / CLI smoke (ubuntu-latest) (push) Canceled after 0s
CI / CLI smoke (windows-latest) (push) Canceled after 0s
CI / Minimal E2E (macos-latest) (push) Canceled after 0s
CI / Minimal E2E (ubuntu-latest) (push) Canceled after 0s
CI / Minimal E2E (windows-latest) (push) Canceled after 0s
feat: replace connection keys with short seed-derived rtun3 keys
Connection keys are now a ~49-char seed (rtun3.) instead of a bundled
~1740-char certificate blob. Both endpoints deterministically derive an
identical Ed25519 CA from the seed and mint ephemeral server/client leaves
at startup (keyderive.rs); the app-layer auth token is derived from the seed.
Target is passed separately on connect (resocks-style).

- connkey.rs: rtun3 seed parse/format
- keyderive.rs: CA/server/client/token derivation
- keygen takes no args; connect requires --target
- remove miniz_oxide; TLS layer unchanged
- add determinism + key-based e2e + wrong-seed-rejected tests
- update wiki, README, design spec, CI smoke
2026-08-02 12:31:53 -06:00

69 lines
3.4 KiB
Markdown

# Security
rustunnel's security posture is built on multiple layers: TLS transport, mutual authentication, application-level auth tokens, secret redaction, and fail-closed error handling.
## Security gates
Before any traffic flows, four gates must pass:
1. **TLS handshake** — both sides negotiate a TLS 1.2+ connection via rustls with ring.
2. **Server certificate verification** — the connector verifies the listener's certificate against the shared CA.
3. **Client certificate verification** — the listener verifies the connector's certificate against the same CA (skipped in insecure mode).
4. **Application auth token** — the connector sends an `X-Rustunnel-Token` header; the listener validates it with constant-time comparison.
The E2E test `auth_required_in_addition_to_mtls` verifies that valid mTLS alone is not sufficient; the correct auth token is also required.
## mTLS
- The listener requires client certificates signed by the shared CA. It uses `WebPkiClientVerifier`.
- The connector presents its client certificate and validates the server certificate against the CA.
- Certificate identities are validated against SANs (Subject Alternative Names) and CN. The server certificate includes SANs for `localhost`, `rustunnel`, and `127.0.0.1`, plus the bind IP if applicable.
## Auth token
- Derived from the connection key seed as `sha256(seed || "rustunnel-auth-token")`
(first 16 bytes, hex), or 32 random bytes as 64 hex characters for the file-based path.
- Compared with a constant-time XOR loop to prevent timing attacks.
- Never logged in raw form. All log output uses the `Redacted` wrapper.
- Auth failures are terminal: the connector's reconnect loop exits on auth failure rather than retrying.
## Connection key as seed
- The connection key is a 32-byte seed (prefix `rtun3.`). It confers full access —
possession of the key lets you derive the same CA and ephemeral leaves, so treat it
as a secret.
- Both endpoints derive the same CA; a connector using a different seed cannot
authenticate to a listener (verified by `e2e_wrong_seed_key_is_rejected`).
## Secret redaction
- `Redacted` in `src/redact.rs` wraps strings and displays `[REDACTED(len=N)]`.
- `redact_pem()` shows only the PEM block type and line count.
- `redact_config_json()` scrubs sensitive keys from JSON.
- `is_sensitive()` detects PEM private key blocks.
- `is_auth_token()` detects long alphanumeric strings.
- `is_connection_key()` detects strings starting with `rtun3.`.
The E2E test `ops_auth_failure_is_actionable_and_redacted` verifies that error messages do not contain raw token values.
## Insecure mode
`--insecure-skip-tls-verify` disables certificate verification on either side. This is intended for DPI/intercepted environments only. When enabled:
- The listener uses `with_no_client_auth()` instead of `WebPkiClientVerifier`.
- The connector uses a custom `InsecureServerCertVerifier` that accepts any certificate.
- A `warn` level log is emitted.
## Fail-closed design
All error paths reject the connection rather than falling back to an insecure mode. There are no bypasses around TLS, certificate verification, or auth.
## Key source files
| File | Purpose |
| ---- | ------- |
| `src/tls.rs` | TLS config, certificate loading, identity validation |
| `src/tunnel.rs` | Auth handshake, constant-time comparison, security gate tests |
| `src/redact.rs` | Secret redaction wrappers and helpers |
| `src/errors.rs` | Error enums for TLS, auth, tunnel |