mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
fix: updater uses TOFU-pinned cert validation instead of disabling TLS (BUG-134)
Replace danger_accept_invalid_certs(true) with PinnedVerifier-based rustls config that validates server cert against TOFU fingerprint from the cert store. For CA-signed servers (no stored fingerprint), system TLS is used. Shared PinnedVerifier, cert_store_key, and load_stored_fingerprint are now pub(crate) for reuse.
This commit is contained in:
@@ -68,20 +68,20 @@ impl LiveKitProxyState {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Tauri store file for certificate fingerprints (shared with ws_proxy).
|
||||
const CERTS_STORE: &str = "certs.json";
|
||||
pub(crate) const CERTS_STORE: &str = "certs.json";
|
||||
|
||||
/// Verifies the server certificate against a known SHA-256 fingerprint.
|
||||
/// Reuses the fingerprint stored by ws_proxy's TOFU handshake for the same
|
||||
/// host, so LiveKit connections are pinned to the same certificate the user
|
||||
/// already trusted during WebSocket setup.
|
||||
#[derive(Debug)]
|
||||
struct PinnedVerifier {
|
||||
pub(crate) struct PinnedVerifier {
|
||||
/// Expected SHA-256 colon-hex fingerprint (e.g. "aa:bb:cc:...").
|
||||
expected_fingerprint: String,
|
||||
}
|
||||
|
||||
impl PinnedVerifier {
|
||||
fn new(expected_fingerprint: String) -> Self {
|
||||
pub(crate) fn new(expected_fingerprint: String) -> Self {
|
||||
Self { expected_fingerprint }
|
||||
}
|
||||
}
|
||||
@@ -155,12 +155,12 @@ impl rustls::client::danger::ServerCertVerifier for PinnedVerifier {
|
||||
/// Produce the cert store key matching ws_proxy's format.
|
||||
/// ws_proxy extracts the host from "wss://host/path" which omits port 443.
|
||||
/// We normalise by stripping the default ":443" suffix so the keys match.
|
||||
fn cert_store_key(remote_host: &str) -> String {
|
||||
pub(crate) fn cert_store_key(remote_host: &str) -> String {
|
||||
remote_host.strip_suffix(":443").unwrap_or(remote_host).to_string()
|
||||
}
|
||||
|
||||
/// Load the stored certificate fingerprint for a host from the Tauri cert store.
|
||||
fn load_stored_fingerprint<R: Runtime>(
|
||||
pub(crate) fn load_stored_fingerprint<R: Runtime>(
|
||||
app: &tauri::AppHandle<R>,
|
||||
host: &str,
|
||||
) -> Result<Option<String>, String> {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
use serde::Serialize;
|
||||
use tauri::AppHandle;
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
|
||||
use crate::livekit_proxy::{cert_store_key, load_stored_fingerprint, PinnedVerifier};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UpdateCheckResult {
|
||||
pub available: bool,
|
||||
@@ -9,6 +12,42 @@ pub struct UpdateCheckResult {
|
||||
pub body: Option<String>,
|
||||
}
|
||||
|
||||
/// Extract the host (with port if non-443) from an https:// URL for cert store lookup.
|
||||
fn extract_host_for_cert_store(server_url: &str) -> Result<String, String> {
|
||||
let parsed = url::Url::parse(server_url)
|
||||
.map_err(|e| format!("failed to parse server URL: {e}"))?;
|
||||
let host = parsed.host_str()
|
||||
.ok_or_else(|| "server URL has no host".to_string())?;
|
||||
let port = parsed.port().unwrap_or(443);
|
||||
let raw = if port == 443 {
|
||||
host.to_string()
|
||||
} else {
|
||||
format!("{host}:{port}")
|
||||
};
|
||||
Ok(cert_store_key(&raw))
|
||||
}
|
||||
|
||||
/// Build a rustls ClientConfig that validates the server cert against the
|
||||
/// TOFU-pinned fingerprint. Falls back to system certs if no fingerprint
|
||||
/// is stored (server uses a real CA cert).
|
||||
fn build_tls_config(app: &AppHandle, server_url: &str) -> Result<Option<rustls::ClientConfig>, String> {
|
||||
let store_key = extract_host_for_cert_store(server_url)?;
|
||||
let fingerprint = load_stored_fingerprint(app, &store_key)?;
|
||||
match fingerprint {
|
||||
Some(fp) => {
|
||||
let config = rustls::ClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(PinnedVerifier::new(fp)))
|
||||
.with_no_client_auth();
|
||||
Ok(Some(config))
|
||||
}
|
||||
None => {
|
||||
// No TOFU fingerprint stored — use system TLS (works for CA-signed certs).
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate that a server URL is safe for the updater to connect to.
|
||||
fn validate_server_url(server_url: &str) -> Result<(), String> {
|
||||
let trimmed = server_url.trim_end_matches('/');
|
||||
@@ -50,14 +89,20 @@ pub async fn check_client_update(
|
||||
.parse()
|
||||
.map_err(|e: url::ParseError| format!("bad endpoint URL: {e}"))?;
|
||||
|
||||
// OwnCord is self-hosted and commonly uses self-signed TLS certs.
|
||||
// The updater connects to the user's own server, so accept invalid certs
|
||||
// (the update artifact itself is verified via Ed25519 signature).
|
||||
let updater = app
|
||||
// Use TOFU-pinned certificate for self-signed servers, or system certs
|
||||
// for CA-signed servers. Never blindly accept invalid certs (BUG-134).
|
||||
let tls_config = build_tls_config(&app, &server_url)?;
|
||||
let mut builder = app
|
||||
.updater_builder()
|
||||
.endpoints(vec![url])
|
||||
.map_err(|e| format!("failed to set endpoints: {e}"))?
|
||||
.configure_client(|client| client.danger_accept_invalid_certs(true))
|
||||
.map_err(|e| format!("failed to set endpoints: {e}"))?;
|
||||
if let Some(config) = tls_config {
|
||||
let config = Arc::new(config);
|
||||
builder = builder.configure_client(move |client| {
|
||||
client.use_preconfigured_tls((*config).clone())
|
||||
});
|
||||
}
|
||||
let updater = builder
|
||||
.build()
|
||||
.map_err(|e| format!("failed to build updater: {e}"))?;
|
||||
|
||||
@@ -106,11 +151,19 @@ pub async fn download_and_install_update(
|
||||
.parse()
|
||||
.map_err(|e: url::ParseError| format!("bad endpoint URL: {e}"))?;
|
||||
|
||||
let updater = app
|
||||
// Use TOFU-pinned certificate for self-signed servers (BUG-134).
|
||||
let tls_config = build_tls_config(&app, &server_url)?;
|
||||
let mut builder = app
|
||||
.updater_builder()
|
||||
.endpoints(vec![url])
|
||||
.map_err(|e| format!("failed to set endpoints: {e}"))?
|
||||
.configure_client(|client| client.danger_accept_invalid_certs(true))
|
||||
.map_err(|e| format!("failed to set endpoints: {e}"))?;
|
||||
if let Some(config) = tls_config {
|
||||
let config = Arc::new(config);
|
||||
builder = builder.configure_client(move |client| {
|
||||
client.use_preconfigured_tls((*config).clone())
|
||||
});
|
||||
}
|
||||
let updater = builder
|
||||
.build()
|
||||
.map_err(|e| format!("failed to build updater: {e}"))?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user