Implement TLS support for WebSocket connections in yourchat2. Updated main.rs to handle secure WebSocket connections based on environment variables. Enhanced install-systemd.sh to include a template for environment configuration. Updated README to document new TLS-related environment variables and installation instructions.

This commit is contained in:
Torsten Schulz (local)
2026-03-04 17:42:47 +01:00
parent 0037ac5c28
commit aca290f1d0
6 changed files with 331 additions and 18 deletions

229
Cargo.lock generated
View File

@@ -19,6 +19,28 @@ dependencies = [
"syn",
]
[[package]]
name = "aws-lc-rs"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]]
name = "base64"
version = "0.22.1"
@@ -71,6 +93,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
@@ -90,6 +114,15 @@ dependencies = [
"inout",
]
[[package]]
name = "cmake"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
dependencies = [
"cc",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -126,6 +159,12 @@ dependencies = [
"subtle",
]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -175,6 +214,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futures-channel"
version = "0.3.32"
@@ -359,6 +404,16 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "jobserver"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.4",
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.91"
@@ -700,6 +755,65 @@ dependencies = [
"bitflags",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"aws-lc-rs",
"log",
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -964,6 +1078,16 @@ dependencies = [
"whoami",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.28.0"
@@ -1045,6 +1169,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "utf-8"
version = "0.7.6"
@@ -1224,13 +1354,22 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
"windows-targets 0.53.5",
]
[[package]]
@@ -1242,6 +1381,22 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
@@ -1249,58 +1404,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
@@ -1402,11 +1605,13 @@ dependencies = [
"futures-util",
"hex",
"openssl",
"rustls-pemfile",
"scrypt",
"serde",
"serde_json",
"tokio",
"tokio-postgres",
"tokio-rustls",
"tokio-tungstenite",
"uuid",
]
@@ -1431,6 +1636,12 @@ dependencies = [
"syn",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zmij"
version = "1.0.21"

View File

@@ -14,3 +14,5 @@ tokio-postgres = "0.7"
openssl = "0.10"
hex = "0.4"
scrypt = "0.11"
tokio-rustls = "0.26"
rustls-pemfile = "2"

View File

@@ -33,6 +33,9 @@ Der Server lauscht danach standardmaessig auf:
- `CHAT_WS_ADDR` (default: `0.0.0.0:1235`)
- `CHAT_TCP_ADDR` (default: `127.0.0.1:1236`)
- `CHAT_UNIX_SOCKET` (optional, z. B. `/run/yourchat2/yourchat2.sock`)
- `CHAT_WS_TLS` (optional, `true` fuer direktes WSS auf `CHAT_WS_ADDR`)
- `CHAT_TLS_CERT_PATH` (Pfad zum PEM-Zertifikat fuer WSS)
- `CHAT_TLS_KEY_PATH` (Pfad zum PEM-Private-Key fuer WSS)
- `CHAT_ALLOWED_USERS` (optional, CSV-Liste erlaubter Usernamen, z. B. `alice,bob,carol`)
- `CHAT_DB_URL` (optional, PostgreSQL-Connection-String fuer Community-/Chat-User-Checks)
- `SECRET_KEY` (benoetigt fuer Entschluesselung verschluesselter Birthdate-Werte aus der DB)
@@ -116,6 +119,7 @@ Zusaetzlich gibt es ein Installationsskript `install-systemd.sh`, das:
- Arbeitsverzeichnis `/var/lib/yourchat2` erstellt
- Environment-Datei unter `/etc/yourchat2/yourchat2.env` anlegt (falls nicht vorhanden)
- Service aktiviert und startet
- Standard-Config aus `config/yourchat2.env.example` nach `/etc/yourchat2/yourchat2.env` uebernimmt (falls noch nicht vorhanden)
Installation:
@@ -132,3 +136,7 @@ Wichtige Variablen fuer produktiven Betrieb:
- `CHAT_DB_URL`
- `SECRET_KEY`
Standard-Config-Template im Projekt:
- `config/yourchat2.env.example`

View File

@@ -0,0 +1,21 @@
# yourchat2 standard environment configuration
# Network bindings
CHAT_WS_ADDR=0.0.0.0:1235
CHAT_TCP_ADDR=127.0.0.1:1236
# CHAT_UNIX_SOCKET=/run/yourchat2/yourchat2.sock
# Enable direct WSS on CHAT_WS_ADDR (TLS in daemon itself)
# CHAT_WS_TLS=true
# CHAT_TLS_CERT_PATH=/etc/letsencrypt/live/www.your-part.de/fullchain.pem
# CHAT_TLS_KEY_PATH=/etc/letsencrypt/live/www.your-part.de/privkey.pem
# Optional user allowlist (comma-separated)
# CHAT_ALLOWED_USERS=alice,bob,carol
# Database authentication and room metadata
# CHAT_DB_URL=postgres://user:pass@127.0.0.1:5432/yourchat
# Required if encrypted birthdate values are used in DB
# Must match the key from the legacy system.
# SECRET_KEY=replace-with-real-secret

View File

@@ -7,6 +7,7 @@ SERVICE_SRC="${PROJECT_DIR}/yourchat2.service"
SERVICE_DST="/etc/systemd/system/${SERVICE_NAME}.service"
ENV_DIR="/etc/yourchat2"
ENV_FILE="${ENV_DIR}/yourchat2.env"
ENV_TEMPLATE="${PROJECT_DIR}/config/yourchat2.env.example"
BIN_PATH="${PROJECT_DIR}/target/release/yourchat2"
INSTALL_BIN="/usr/local/bin/yourchat2"
WORK_DIR="/var/lib/yourchat2"
@@ -45,15 +46,20 @@ chown "${BUILD_USER}:${BUILD_USER}" "${WORK_DIR}"
echo "[6/7] Ensure environment file ..."
if [[ ! -f "${ENV_FILE}" ]]; then
cat > "${ENV_FILE}" <<'EOF'
if [[ -f "${ENV_TEMPLATE}" ]]; then
install -m 0640 "${ENV_TEMPLATE}" "${ENV_FILE}"
else
cat > "${ENV_FILE}" <<'EOF'
# yourchat2 environment
# CHAT_WS_ADDR=0.0.0.0:1235
# CHAT_TCP_ADDR=127.0.0.1:1236
CHAT_WS_ADDR=0.0.0.0:1235
CHAT_TCP_ADDR=127.0.0.1:1236
# CHAT_UNIX_SOCKET=/run/yourchat2/yourchat2.sock
# CHAT_ALLOWED_USERS=alice,bob
# CHAT_DB_URL=postgres://user:pass@host:5432/dbname
# SECRET_KEY=replace-with-real-secret
EOF
chmod 0640 "${ENV_FILE}"
fi
chmod 0640 "${ENV_FILE}"
chown root:root "${ENV_FILE}"
fi

View File

@@ -1,12 +1,17 @@
use futures_util::{SinkExt, StreamExt};
use std::collections::HashSet;
use std::env;
use std::fs::File;
use std::io::BufReader as StdBufReader;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
use tokio::net::{TcpListener, UnixListener};
use tokio::sync::{mpsc, watch, RwLock};
use tokio_rustls::TlsAcceptor;
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
use tokio_rustls::rustls::ServerConfig as RustlsServerConfig;
use tokio_tungstenite::{accept_async, tungstenite::Message};
mod commands;
@@ -18,8 +23,14 @@ use types::{ChatState, ClientConn, ServerConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let ws_addr = env::var("CHAT_WS_ADDR").unwrap_or_else(|_| "0.0.0.0:1235".to_string());
let ws_tls = env_bool("CHAT_WS_TLS");
let tcp_addr = env::var("CHAT_TCP_ADDR").unwrap_or_else(|_| "127.0.0.1:1236".to_string());
let unix_socket = env::var("CHAT_UNIX_SOCKET").ok().filter(|s| !s.trim().is_empty());
let tls_acceptor = if ws_tls {
Some(Arc::new(load_tls_acceptor_from_env()?))
} else {
None
};
let state = Arc::new(RwLock::new(ChatState::default()));
let db_client = db::connect_db_from_env().await?;
@@ -45,11 +56,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let (shutdown_tx, shutdown_rx) = watch::channel(false);
let ws_listener = TcpListener::bind(&ws_addr).await?;
println!("[yourchat2] listening on ws://{}", ws_addr);
if ws_tls {
println!("[yourchat2] listening on wss://{}", ws_addr);
} else {
println!("[yourchat2] listening on ws://{}", ws_addr);
}
let ws_state = Arc::clone(&state);
let ws_config = Arc::clone(&config);
let ws_next = Arc::clone(&next_client_id);
let ws_tls_acceptor = tls_acceptor.clone();
let mut ws_shutdown_rx = shutdown_rx.clone();
let ws_task = tokio::spawn(async move {
loop {
@@ -62,13 +78,29 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
accepted = ws_listener.accept() => {
match accepted {
Ok((socket, addr)) => {
println!("[yourchat2] ws client connected: {}", addr);
if ws_tls_acceptor.is_some() {
println!("[yourchat2] wss client connected: {}", addr);
} else {
println!("[yourchat2] ws client connected: {}", addr);
}
let state = Arc::clone(&ws_state);
let config = Arc::clone(&ws_config);
let next = Arc::clone(&ws_next);
let tls_acceptor = ws_tls_acceptor.clone();
let shutdown = ws_shutdown_rx.clone();
tokio::spawn(async move {
if let Err(err) = handle_ws_client(socket, state, config, next, shutdown).await {
if let Some(acceptor) = tls_acceptor {
match acceptor.accept(socket).await {
Ok(tls_stream) => {
if let Err(err) = handle_ws_stream(tls_stream, state, config, next, shutdown).await {
eprintln!("[yourchat2] wss client error: {err}");
}
}
Err(err) => {
eprintln!("[yourchat2] tls handshake error: {err}");
}
}
} else if let Err(err) = handle_ws_stream(socket, state, config, next, shutdown).await {
eprintln!("[yourchat2] ws client error: {err}");
}
});
@@ -249,13 +281,16 @@ where
Ok(())
}
async fn handle_ws_client(
socket: tokio::net::TcpStream,
async fn handle_ws_stream<S>(
socket: S,
state: Arc<RwLock<ChatState>>,
config: Arc<ServerConfig>,
next_client_id: Arc<AtomicU64>,
mut shutdown_rx: watch::Receiver<bool>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
let ws_stream = accept_async(socket).await?;
let (mut ws_write, mut ws_read) = ws_stream.split();
let client_id = next_client_id.fetch_add(1, Ordering::Relaxed);
@@ -319,3 +354,33 @@ async fn handle_ws_client(
writer_task.abort();
Ok(())
}
fn env_bool(name: &str) -> bool {
matches!(
env::var(name).ok().as_deref(),
Some("1") | Some("true") | Some("TRUE") | Some("yes") | Some("YES") | Some("on") | Some("ON")
)
}
fn load_tls_acceptor_from_env() -> Result<TlsAcceptor, Box<dyn std::error::Error + Send + Sync>> {
let cert_path = env::var("CHAT_TLS_CERT_PATH")
.map_err(|_| "CHAT_WS_TLS=true requires CHAT_TLS_CERT_PATH")?;
let key_path = env::var("CHAT_TLS_KEY_PATH")
.map_err(|_| "CHAT_WS_TLS=true requires CHAT_TLS_KEY_PATH")?;
let mut cert_reader = StdBufReader::new(File::open(&cert_path)?);
let certs: Vec<CertificateDer<'static>> =
rustls_pemfile::certs(&mut cert_reader).collect::<Result<Vec<_>, _>>()?;
if certs.is_empty() {
return Err("No certificates found in CHAT_TLS_CERT_PATH".into());
}
let mut key_reader = StdBufReader::new(File::open(&key_path)?);
let key: PrivateKeyDer<'static> = rustls_pemfile::private_key(&mut key_reader)?
.ok_or("No private key found in CHAT_TLS_KEY_PATH")?;
let config = RustlsServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)?;
Ok(TlsAcceptor::from(Arc::new(config)))
}