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
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
69 lines
3.4 KiB
Markdown
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 |
|