diff --git a/Cargo.lock b/Cargo.lock index 1691a84..a702427 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1066,6 +1066,7 @@ dependencies = [ "rfc6381-codec", "rustls", "rustls-native-certs", + "rustls-pemfile", "serde_json", "tokio", "tracing", diff --git a/moq-pub/Cargo.toml b/moq-pub/Cargo.toml index 2fc3d1d..d70a93d 100644 --- a/moq-pub/Cargo.toml +++ b/moq-pub/Cargo.toml @@ -23,8 +23,9 @@ webtransport-quinn = "0.6" url = "2" # Crypto -rustls = "0.21" +rustls = { version = "0.21", features = ["dangerous_configuration"] } rustls-native-certs = "0.6" +rustls-pemfile = "1" # Async stuff tokio = { version = "1", features = ["full"] } diff --git a/moq-pub/src/cli.rs b/moq-pub/src/cli.rs index 1e34dcb..a0a044a 100644 --- a/moq-pub/src/cli.rs +++ b/moq-pub/src/cli.rs @@ -1,5 +1,5 @@ use clap::Parser; -use std::net; +use std::{net, path}; use url::Url; #[derive(Parser, Clone, Debug)] @@ -21,6 +21,19 @@ pub struct Config { /// Connect to the given URL starting with https:// #[arg(value_parser = moq_url)] pub url: Url, + + /// Use the TLS root CA at this path, encoded as PEM. + /// + /// This value can be provided multiple times for multiple roots. + /// If this is empty, system roots will be used instead + #[arg(long)] + pub tls_root: Vec, + + /// Danger: Disable TLS certificate verification. + /// + /// Fine for local development, but should be used in caution in production. + #[arg(long)] + pub tls_disable_verify: bool, } fn moq_url(s: &str) -> Result { diff --git a/moq-pub/src/main.rs b/moq-pub/src/main.rs index c753dc5..d00a89e 100644 --- a/moq-pub/src/main.rs +++ b/moq-pub/src/main.rs @@ -1,3 +1,5 @@ +use std::{fs, io, sync::Arc, time}; + use anyhow::Context; use clap::Parser; @@ -26,10 +28,28 @@ async fn main() -> anyhow::Result<()> { let (publisher, subscriber) = broadcast::new(""); let mut media = Media::new(&config, publisher).await?; - // Ugh, just let me use my native root certs already + // Create a list of acceptable root certificates. let mut roots = rustls::RootCertStore::empty(); - for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") { - roots.add(&rustls::Certificate(cert.0)).unwrap(); + + if config.tls_root.is_empty() { + // Add the platform's native root certificates. + for cert in rustls_native_certs::load_native_certs().context("could not load platform certs")? { + roots + .add(&rustls::Certificate(cert.0)) + .context("failed to add root cert")?; + } + } else { + // Add the specified root certificates. + for root in &config.tls_root { + let root = fs::File::open(root).context("failed to open root cert file")?; + let mut root = io::BufReader::new(root); + + let root = rustls_pemfile::certs(&mut root).context("failed to read root cert")?; + anyhow::ensure!(root.len() == 1, "expected a single root cert"); + let root = rustls::Certificate(root[0].to_owned()); + + roots.add(&root).context("failed to add root cert")?; + } } let mut tls_config = rustls::ClientConfig::builder() @@ -37,6 +57,12 @@ async fn main() -> anyhow::Result<()> { .with_root_certificates(roots) .with_no_client_auth(); + // Allow disabling TLS verification altogether. + if config.tls_disable_verify { + let noop = NoCertificateVerification {}; + tls_config.dangerous().set_certificate_verifier(Arc::new(noop)); + } + tls_config.alpn_protocols = vec![webtransport_quinn::ALPN.to_vec()]; // this one is important let arc_tls_config = std::sync::Arc::new(tls_config); @@ -63,3 +89,19 @@ async fn main() -> anyhow::Result<()> { Ok(()) } + +pub struct NoCertificateVerification {} + +impl rustls::client::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _server_name: &rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: time::SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} diff --git a/moq-relay/Cargo.toml b/moq-relay/Cargo.toml index 3fbfe97..94607ac 100644 --- a/moq-relay/Cargo.toml +++ b/moq-relay/Cargo.toml @@ -23,7 +23,7 @@ url = "2" # Crypto ring = "0.16" -rustls = "0.21" +rustls = { version = "0.21", features = ["dangerous_configuration"] } rustls-pemfile = "1" rustls-native-certs = "0.6" webpki = "0.22" diff --git a/moq-relay/src/config.rs b/moq-relay/src/config.rs index a0a6646..20c0420 100644 --- a/moq-relay/src/config.rs +++ b/moq-relay/src/config.rs @@ -31,6 +31,12 @@ pub struct Config { #[arg(long)] pub tls_root: Vec, + /// Danger: Disable TLS certificate verification. + /// + /// Fine for local development and between relays, but should be used in caution in production. + #[arg(long)] + pub tls_disable_verify: bool, + /// Optional: Use the moq-api via HTTP to store origin information. #[arg(long)] pub api: Option, diff --git a/moq-relay/src/quic.rs b/moq-relay/src/quic.rs index c94a81b..563ad56 100644 --- a/moq-relay/src/quic.rs +++ b/moq-relay/src/quic.rs @@ -19,8 +19,8 @@ pub struct Quic { impl Quic { // Create a QUIC endpoint that can be used for both clients and servers. pub async fn new(config: Config, tls: Tls) -> anyhow::Result { - let mut client_config = tls.client(); - let mut server_config = tls.server(); + let mut client_config = tls.client.clone(); + let mut server_config = tls.server.clone(); client_config.alpn_protocols = vec![webtransport_quinn::ALPN.to_vec()]; server_config.alpn_protocols = vec![webtransport_quinn::ALPN.to_vec()]; diff --git a/moq-relay/src/tls.rs b/moq-relay/src/tls.rs index 14eeff2..98e07b9 100644 --- a/moq-relay/src/tls.rs +++ b/moq-relay/src/tls.rs @@ -3,23 +3,19 @@ use ring::digest::{digest, SHA256}; use rustls::server::{ClientHello, ResolvesServerCert}; use rustls::sign::CertifiedKey; use rustls::{Certificate, PrivateKey, RootCertStore}; -use rustls::{ClientConfig, ServerConfig}; -use std::fs; use std::io::{self, Cursor, Read}; use std::path; use std::sync::Arc; +use std::{fs, time}; use webpki::{DnsNameRef, EndEntityCert}; use crate::Config; #[derive(Clone)] pub struct Tls { - // Support serving multiple certificates, choosing one that looks valid for the given SNI. - // We store the parsed certificate, and the certified cert/key that rustls expects - serve: Arc, - - // Accept any cert that is trusted by the system's native trust store. - accept: Arc, + pub server: rustls::ServerConfig, + pub client: rustls::ClientConfig, + pub fingerprints: Vec, } impl Tls { @@ -56,32 +52,34 @@ impl Tls { } } + // Create the TLS configuration we'll use as a client (relay -> relay) + let mut client = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth(); + + // Allow disabling TLS verification altogether. + if config.tls_disable_verify { + let noop = NoCertificateVerification {}; + client.dangerous().set_certificate_verifier(Arc::new(noop)); + } + + let fingerprints = serve.fingerprints(); + + // Create the TLS configuration we'll use as a server (relay <- browser) + let server = rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_cert_resolver(Arc::new(serve)); + let certs = Self { - serve: Arc::new(serve), - accept: Arc::new(roots), + server, + client, + fingerprints, }; Ok(certs) } - - pub fn client(&self) -> ClientConfig { - rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(self.accept.clone()) - .with_no_client_auth() - } - - pub fn server(&self) -> ServerConfig { - rustls::ServerConfig::builder() - .with_safe_defaults() - .with_no_client_auth() - .with_cert_resolver(self.serve.clone()) - } - - // Return the SHA256 fingerprint of our certificates. - pub fn fingerprints(&self) -> Vec { - self.serve.fingerprints() - } } #[derive(Default)] @@ -166,3 +164,19 @@ impl ResolvesServerCert for ServeCerts { self.list.last().cloned() } } + +pub struct NoCertificateVerification {} + +impl rustls::client::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _server_name: &rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: time::SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} diff --git a/moq-relay/src/web.rs b/moq-relay/src/web.rs index ce6c820..75b185a 100644 --- a/moq-relay/src/web.rs +++ b/moq-relay/src/web.rs @@ -17,9 +17,9 @@ impl Web { pub fn new(config: Config, tls: Tls) -> Self { // Get the first certificate's fingerprint. // TODO serve all of them so we can support multiple signature algorithms. - let fingerprint = tls.fingerprints().first().expect("missing certificate").clone(); + let fingerprint = tls.fingerprints.first().expect("missing certificate").clone(); - let mut tls_config = tls.server(); + let mut tls_config = tls.server.clone(); tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; let tls_config = axum_server::tls_rustls::RustlsConfig::from_config(Arc::new(tls_config));