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
3.4 KiB
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:
- TLS handshake — both sides negotiate a TLS 1.2+ connection via rustls with ring.
- Server certificate verification — the connector verifies the listener's certificate against the shared CA.
- Client certificate verification — the listener verifies the connector's certificate against the same CA (skipped in insecure mode).
- Application auth token — the connector sends an
X-Rustunnel-Tokenheader; 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, and127.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
Redactedwrapper. - 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
Redactedinsrc/redact.rswraps 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 withrtun3..
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 ofWebPkiClientVerifier. - The connector uses a custom
InsecureServerCertVerifierthat accepts any certificate. - A
warnlevel 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 |