Implement the `tcp-tls` protocol crate and generic tunnel codec abstractions alongside `wg-quic` to circumvent deep packet inspection (DPI/TSPU), UDP blocking, and QUIC throttling. Traffic uses standard TCP on port 443 with TLS 1.3 framing, looking identical to regular HTTPS traffic to network observers. Architectural highlights: - TunnelCodec & GenericTunnelPlugin: Provide an extensible plugin abstraction in `crates/tsunagi/src/dataplane/tunnel.rs` that encapsulates background task lifecycles, per-peer channels, and sink dispatch, so new transport plugins only define cryptographic transformations and handshake payloads. - MultiPacketTransport & Protocol Priority Selection: Support multiple simultaneous transports in `AgentConfig` and dynamically dispatch `open()` calls by protocol identifier. In `ensure_links`, choose the highest-priority mutually supported protocol per peer to avoid duplicate links. - TLS 1.3 & Certificate Pinning: Generate self-signed Ed25519 X.509 certificates derived from the persistent `DeviceIdentity` secret. Clients pin peer certificates using `PinnedEndpointVerifier` against `iroh::EndpointId`. - End-to-end Encryption: `PeerCipher` uses X25519 static-static ECDH, HKDF-SHA256, and ChaCha20-Poly1305 with incrementing 64-bit nonces. Relayed traffic across intermediate nodes remains confidential and tamper-proof. - Privilege handling: Default to TCP port 443 with fallback to 8443 on `PermissionDenied`, providing an actionable message recommending: `sudo setcap cap_net_admin,cap_net_bind_service+p <path>`. Validation on Linux: - `cargo fmt --all -- --check` passed. - `cargo clippy --locked --workspace --all-targets -- -D warnings` passed. - `cargo test --locked --workspace --all-targets` passed (all workspace unit, integration, CLI, and new `tsunagi-tcp-tls` tests passed).
53 lines
1.7 KiB
Rust
53 lines
1.7 KiB
Rust
#![allow(missing_docs)]
|
|
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
|
|
use rustls::client::danger::ServerCertVerifier;
|
|
use rustls::pki_types::{ServerName, UnixTime};
|
|
use tsunagi::identity::DeviceIdentity;
|
|
use tsunagi_tcp_tls::cert::{PinnedEndpointVerifier, generate_self_signed_cert};
|
|
|
|
#[test]
|
|
fn test_generate_and_pin_certificate() {
|
|
let identity = DeviceIdentity::generate();
|
|
let peer_id = identity.endpoint_id();
|
|
|
|
let (cert, _key) = generate_self_signed_cert(&identity.secret_bytes())
|
|
.expect("cert generation should succeed");
|
|
|
|
let verifier = PinnedEndpointVerifier::new(peer_id);
|
|
let server_name = ServerName::try_from("tsunagi.local").unwrap();
|
|
|
|
let verified = verifier.verify_server_cert(&cert, &[], &server_name, &[], UnixTime::now());
|
|
|
|
assert!(
|
|
verified.is_ok(),
|
|
"certificate matching peer endpoint id must verify"
|
|
);
|
|
|
|
let other_identity = DeviceIdentity::generate();
|
|
let wrong_verifier = PinnedEndpointVerifier::new(other_identity.endpoint_id());
|
|
|
|
let wrong_verified =
|
|
wrong_verifier.verify_server_cert(&cert, &[], &server_name, &[], UnixTime::now());
|
|
|
|
assert!(
|
|
wrong_verified.is_err(),
|
|
"certificate with wrong endpoint id must fail verification"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_make_client_and_server_config() {
|
|
let identity = DeviceIdentity::generate();
|
|
let peer_id = identity.endpoint_id();
|
|
|
|
let (cert, key) = generate_self_signed_cert(&identity.secret_bytes())
|
|
.expect("cert generation should succeed");
|
|
|
|
let server_config = tsunagi_tcp_tls::cert::make_server_config(cert, key);
|
|
assert!(server_config.is_ok());
|
|
|
|
let client_config = tsunagi_tcp_tls::cert::make_client_config(peer_id);
|
|
assert!(client_config.is_ok());
|
|
}
|