From c68a7cbe0adaa7f992532b7669303ccc109aef59 Mon Sep 17 00:00:00 2001 From: Jasper Hugo Date: Sun, 6 Mar 2022 16:23:46 +0700 Subject: [PATCH] Make it possible to disable certificate verification --- Cargo.lock | 4 ++ gst-meet/Cargo.toml | 3 +- gst-meet/src/main.rs | 17 ++++-- lib-gst-meet-c/include/gstmeet.h | 3 +- lib-gst-meet-c/src/lib.rs | 2 + lib-gst-meet/Cargo.toml | 17 ++++-- lib-gst-meet/src/colibri.rs | 6 ++- lib-gst-meet/src/conference.rs | 43 +++++++-------- lib-gst-meet/src/lib.rs | 1 + lib-gst-meet/src/tls.rs | 83 +++++++++++++++++++++++++++++ lib-gst-meet/src/xmpp/connection.rs | 7 ++- 11 files changed, 147 insertions(+), 39 deletions(-) create mode 100644 lib-gst-meet/src/tls.rs diff --git a/Cargo.lock b/Cargo.lock index 43fe49f..32bd284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -755,12 +755,15 @@ dependencies = [ "jitsi-xmpp-parsers", "libc", "maplit", + "native-tls", "nice-gst-meet", "once_cell", "pem", "rand", "rcgen", "ring", + "rustls", + "rustls-native-certs", "serde", "serde_json", "tokio", @@ -769,6 +772,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", + "webpki-roots", "xmpp-parsers", ] diff --git a/gst-meet/Cargo.toml b/gst-meet/Cargo.toml index afa6f39..628c71b 100644 --- a/gst-meet/Cargo.toml +++ b/gst-meet/Cargo.toml @@ -22,7 +22,8 @@ tracing = { version = "0.1", default-features = false, features = ["attributes", cocoa = { version = "0.24", default-features = false } [features] -default = ["tls-native"] +default = ["tls-rustls-native-roots"] +tls-insecure = ["lib-gst-meet/tls-insecure"] tls-native = ["lib-gst-meet/tls-native"] tls-native-vendored = ["lib-gst-meet/tls-native-vendored"] tls-rustls-native-roots = ["lib-gst-meet/tls-rustls-native-roots"] diff --git a/gst-meet/src/main.rs b/gst-meet/src/main.rs index 06d216d..dc73366 100644 --- a/gst-meet/src/main.rs +++ b/gst-meet/src/main.rs @@ -45,13 +45,16 @@ struct Opt { #[structopt(long)] select_endpoints: Option, #[structopt(long)] - last_n: Option, + last_n: Option, #[structopt(long)] - recv_video_height: Option, + recv_video_height: Option, #[structopt(long)] video_type: Option, #[structopt(short, long, parse(from_occurrences))] verbose: u8, + #[cfg(feature = "tls-insecure")] + #[structopt(long, help = "Disable TLS certificate verification (use with extreme caution)")] + tls_insecure: bool, } #[cfg(not(target_os = "macos"))] @@ -117,9 +120,13 @@ async fn main_inner() -> Result<()> { &opt.web_socket_url, &opt.xmpp_domain, Authentication::Anonymous, + #[cfg(feature = "tls-insecure")] + opt.tls_insecure, + #[cfg(not(feature = "tls-insecure"))] + false, ) .await - .context("failed to connect")?; + .context("failed to build connection")?; tokio::spawn(background); @@ -165,13 +172,13 @@ async fn main_inner() -> Result<()> { if opt.select_endpoints.is_some() || opt.last_n.is_some() || opt.recv_video_height.is_some() { conference .send_colibri_message(ColibriMessage::ReceiverVideoConstraints { - last_n: opt.last_n, + last_n: Some(opt.last_n.map(i32::from).unwrap_or(-1)), selected_endpoints: opt .select_endpoints .map(|endpoints| endpoints.split(',').map(ToOwned::to_owned).collect()), on_stage_endpoints: None, default_constraints: opt.recv_video_height.map(|height| Constraints { - max_height: Some(height), + max_height: Some(height.into()), ideal_height: None, }), constraints: None, diff --git a/lib-gst-meet-c/include/gstmeet.h b/lib-gst-meet-c/include/gstmeet.h index 8a14473..c9ed3b8 100644 --- a/lib-gst-meet-c/include/gstmeet.h +++ b/lib-gst-meet-c/include/gstmeet.h @@ -39,7 +39,8 @@ void gstmeet_deinit(struct Context *context); JitsiConnection *gstmeet_connection_new(struct Context *context, const char *websocket_url, - const char *xmpp_domain); + const char *xmpp_domain, + bool tls_insecure); void gstmeet_connection_free(JitsiConnection *connection); diff --git a/lib-gst-meet-c/src/lib.rs b/lib-gst-meet-c/src/lib.rs index 74e8620..3f7e3c2 100644 --- a/lib-gst-meet-c/src/lib.rs +++ b/lib-gst-meet-c/src/lib.rs @@ -80,6 +80,7 @@ pub unsafe extern "C" fn gstmeet_connection_new( context: *mut Context, websocket_url: *const c_char, xmpp_domain: *const c_char, + tls_insecure: bool, ) -> *mut Connection { let websocket_url = CStr::from_ptr(websocket_url); let xmpp_domain = CStr::from_ptr(xmpp_domain); @@ -89,6 +90,7 @@ pub unsafe extern "C" fn gstmeet_connection_new( &websocket_url.to_string_lossy(), &xmpp_domain.to_string_lossy(), Authentication::Anonymous, + tls_insecure, )) .map(|(connection, background)| { (*context).runtime.spawn(background); diff --git a/lib-gst-meet/Cargo.toml b/lib-gst-meet/Cargo.toml index 69bcc49..c39f818 100644 --- a/lib-gst-meet/Cargo.toml +++ b/lib-gst-meet/Cargo.toml @@ -25,12 +25,15 @@ itertools = { version = "0.10", default-features = false, features = ["use_std"] jitsi-xmpp-parsers = { version = "0.1", path = "../jitsi-xmpp-parsers", default-features = false } libc = { version = "0.2", default-features = false } maplit = { version = "1", default-features = false } +native-tls = { version = "0.2", default-features = false, optional = true } nice-gst-meet = { version = "0.1", path = "../nice-gst-meet", default-features = false, features = ["v0_1_18"] } once_cell = { version = "1", default-features = false, features = ["std"] } pem = { version = "1", default-features = false } rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } rcgen = { version = "0.9", default-features = false } ring = { version = "0.16", default-features = false } +rustls = { version = "0.20", default-features = false, features = ["logging", "tls12"], optional = true } +rustls-native-certs = { version = "0.6", default-features = false, optional = true } serde = { version = "1", default-features = false, features = ["derive"] } serde_json = { version = "1", default-features = false, features = ["std"] } tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros", "sync", "time"] } @@ -45,11 +48,15 @@ tracing-subscriber = { version = "0.3", optional = true, default-features = fals "tracing-log", ] } uuid = { version = "0.8", default-features = false, features = ["v4"] } +webpki-roots = { version = "0.22", default-features = false, optional = true } xmpp-parsers = { git = "https://gitlab.com/xmpp-rs/xmpp-rs.git", default-features = false, features = ["disable-validation"] } [features] -default = ["tls-native"] -tls-native = ["tokio-tungstenite/native-tls"] -tls-native-vendored = ["tokio-tungstenite/native-tls-vendored"] -tls-rustls-native-roots = ["tokio-tungstenite/rustls-tls-native-roots"] -tls-rustls-webpki-roots = ["tokio-tungstenite/rustls-tls-webpki-roots"] +# Ideally we would enable rustls/dangerous_configuration only when tls-insecure is enabled, but until weak-dep-features is stabilised, that +# would cause rustls to always be pulled in. +default = ["tls-rustls-native-roots"] +tls-insecure = [] +tls-native = ["tokio-tungstenite/native-tls", "native-tls"] +tls-native-vendored = ["tokio-tungstenite/native-tls-vendored", "native-tls/vendored"] +tls-rustls-native-roots = ["tokio-tungstenite/rustls-tls-native-roots", "rustls", "rustls-native-certs", "rustls/dangerous_configuration"] +tls-rustls-webpki-roots = ["tokio-tungstenite/rustls-tls-webpki-roots", "rustls", "webpki-roots", "rustls/dangerous_configuration"] diff --git a/lib-gst-meet/src/colibri.rs b/lib-gst-meet/src/colibri.rs index bbff0cc..0839200 100644 --- a/lib-gst-meet/src/colibri.rs +++ b/lib-gst-meet/src/colibri.rs @@ -18,6 +18,8 @@ use tokio_tungstenite::tungstenite::{ }; use tracing::{debug, error, info, warn}; +use crate::tls::wss_connector; + const MAX_CONNECT_RETRIES: u8 = 3; const CONNECT_RETRY_SLEEP: Duration = Duration::from_secs(3); @@ -27,7 +29,7 @@ pub(crate) struct ColibriChannel { } impl ColibriChannel { - pub(crate) async fn new(uri: &str) -> Result { + pub(crate) async fn new(uri: &str, tls_insecure: bool) -> Result { let uri: Uri = uri.parse()?; let host = uri.host().context("invalid WebSocket URL: missing host")?; @@ -44,7 +46,7 @@ impl ColibriChannel { .header("connection", "Upgrade") .header("upgrade", "websocket") .body(())?; - match tokio_tungstenite::connect_async(request).await { + match tokio_tungstenite::connect_async_tls_with_config(request, None, Some(wss_connector(tls_insecure)?)).await { Ok((websocket, _)) => break websocket, Err(e) => { if retries < MAX_CONNECT_RETRIES { diff --git a/lib-gst-meet/src/conference.rs b/lib-gst-meet/src/conference.rs index 8a308d3..20e7979 100644 --- a/lib-gst-meet/src/conference.rs +++ b/lib-gst-meet/src/conference.rs @@ -40,8 +40,12 @@ use crate::{ const DISCO_NODE: &str = "https://github.com/avstack/gst-meet"; -static DISCO_INFO: Lazy = Lazy::new(|| { - let mut features = vec![ +static DISCO_INFO: Lazy = Lazy::new(|| DiscoInfoResult { + node: None, + identities: vec![ + Identity::new("client", "bot", "en", "gst-meet"), + ], + features: vec![ Feature::new(ns::DISCO_INFO), Feature::new(ns::JINGLE_RTP_AUDIO), Feature::new(ns::JINGLE_RTP_VIDEO), @@ -50,26 +54,9 @@ static DISCO_INFO: Lazy = Lazy::new(|| { Feature::new("urn:ietf:rfc:5888"), // BUNDLE Feature::new("urn:ietf:rfc:5761"), // RTCP-MUX Feature::new("urn:ietf:rfc:4588"), // RTX - ]; - let gst_version = gstreamer::version(); - if gst_version.0 >= 1 && gst_version.1 >= 19 { - // RTP header extensions are supported on GStreamer 1.19+ - features.push(Feature::new("http://jitsi.org/tcc")); - } - else { - warn!("Upgrade GStreamer to 1.19 or later to enable RTP header extensions"); - } - let identities = vec![ - Identity::new("client", "bot", "en", "gst-meet"), - ]; - // Not supported yet: - // Feature::new("http://jitsi.org/opus-red") - DiscoInfoResult { - node: None, - identities, - features, - extensions: vec![], - } + Feature::new("http://jitsi.org/tcc"), + ], + extensions: vec![], }); static COMPUTED_CAPS_HASH: Lazy = Lazy::new(|| { @@ -102,6 +89,7 @@ pub struct JitsiConference { pub(crate) external_services: Vec, pub(crate) jingle_session: Arc>>, pub(crate) inner: Arc>, + pub(crate) tls_insecure: bool, } impl fmt::Debug for JitsiConference { @@ -220,6 +208,7 @@ impl JitsiConference { on_colibri_message: None, connected_tx: Some(tx), })), + tls_insecure: xmpp_connection.tls_insecure, }; xmpp_connection.add_stanza_filter(conference.clone()).await; @@ -590,7 +579,7 @@ impl StanzaFilter for JitsiConference { if let Some(colibri_url) = colibri_url { info!("Connecting Colibri WebSocket to {}", colibri_url); - let colibri_channel = ColibriChannel::new(&colibri_url).await?; + let colibri_channel = ColibriChannel::new(&colibri_url, self.tls_insecure).await?; let (tx, rx) = mpsc::channel(8); colibri_channel.subscribe(tx).await; jingle_session.colibri_channel = Some(colibri_channel); @@ -599,6 +588,14 @@ impl StanzaFilter for JitsiConference { tokio::spawn(async move { let mut stream = ReceiverStream::new(rx); while let Some(msg) = stream.next().await { + // Some message types are handled internally rather than passed to the on_colibri_message handler. + + // End-to-end ping + if let ColibriMessage::EndpointMessage { to, .. } = &msg { + // if to == + + } + let locked_inner = self_.inner.lock().await; if let Some(f) = &locked_inner.on_colibri_message { if let Err(e) = f(self_.clone(), msg).await { diff --git a/lib-gst-meet/src/lib.rs b/lib-gst-meet/src/lib.rs index f7df054..20a2484 100644 --- a/lib-gst-meet/src/lib.rs +++ b/lib-gst-meet/src/lib.rs @@ -4,6 +4,7 @@ mod jingle; mod pinger; mod source; mod stanza_filter; +mod tls; mod util; mod xmpp; diff --git a/lib-gst-meet/src/tls.rs b/lib-gst-meet/src/tls.rs new file mode 100644 index 0000000..3e634f9 --- /dev/null +++ b/lib-gst-meet/src/tls.rs @@ -0,0 +1,83 @@ +#[cfg(any(feature = "tls-rustls-native-roots", feature = "tls-rustls-webpki-roots"))] +use std::sync::Arc; + +#[cfg(not(feature = "tls-insecure"))] +use anyhow::bail; +use anyhow::Result; +use tokio_tungstenite::Connector; + +#[cfg(feature = "tls-rustls-native-roots")] +pub(crate) fn wss_connector(insecure: bool) -> Result { + let mut roots = rustls::RootCertStore::empty(); + for cert in rustls_native_certs::load_native_certs()? { + roots.add(&rustls::Certificate(cert.0))?; + } + + let mut config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth(); + #[cfg(feature = "tls-insecure")] + if insecure { + config.dangerous().set_certificate_verifier(Arc::new(InsecureServerCertVerifier)); + } + #[cfg(not(feature = "tls-insecure"))] + if insecure { + bail!("Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time.") + } + Ok(Connector::Rustls(Arc::new(config))) +} + +#[cfg(feature = "tls-rustls-webpki-roots")] +pub(crate) fn wss_connector(insecure: bool) -> Result { + let mut roots = rustls::RootCertStore::empty(); + roots.add_server_trust_anchors( + webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { + rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + }) + ); + + let config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth(); + #[cfg(feature = "tls-insecure")] + if insecure { + config.dangerous().set_certificate_verifier(Arc::new(InsecureServerCertVerifier)); + } + #[cfg(not(feature = "tls-insecure"))] + if insecure { + bail!("Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time.") + } + Ok(Connector::Rustls(Arc::new(config))) +} + +#[cfg(any(feature = "tls-native", feature = "tls-native-vendored"))] +pub(crate) fn wss_connector(insecure: bool) -> Result { + let mut builder = native_tls::TlsConnector::builder(); + builder.min_protocol_version(Some(native_tls::Protocol::Tlsv12)); + #[cfg(feature = "tls-insecure")] + if insecure { + builder.danger_accept_invalid_certs(true); + builder.danger_accept_invalid_hostnames(true); + } + #[cfg(not(feature = "tls-insecure"))] + if insecure { + bail!("Insecure TLS mode can only be enabled if the tls-insecure feature was enabled at compile time.") + } + Ok(Connector::NativeTls(builder.build()?)) +} + +#[cfg(all(feature = "tls-insecure", any(feature = "tls-rustls-native-roots", feature = "tls-rustls-webpki-roots")))] +struct InsecureServerCertVerifier; + +#[cfg(all(feature = "tls-insecure", any(feature = "tls-rustls-native-roots", feature = "tls-rustls-webpki-roots")))] +impl rustls::client::ServerCertVerifier for InsecureServerCertVerifier { + fn verify_server_cert(&self, _end_entity: &rustls::Certificate, _intermediates: &[rustls::Certificate], _server_name: &rustls::ServerName, _scts: &mut dyn Iterator, _ocsp_response: &[u8], _now: std::time::SystemTime) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} \ No newline at end of file diff --git a/lib-gst-meet/src/xmpp/connection.rs b/lib-gst-meet/src/xmpp/connection.rs index ea4e3cd..a7cc434 100644 --- a/lib-gst-meet/src/xmpp/connection.rs +++ b/lib-gst-meet/src/xmpp/connection.rs @@ -22,7 +22,7 @@ use xmpp_parsers::{ BareJid, Element, FullJid, Jid, }; -use crate::{pinger::Pinger, stanza_filter::StanzaFilter, util::generate_id, xmpp}; +use crate::{pinger::Pinger, stanza_filter::StanzaFilter, tls::wss_connector, util::generate_id, xmpp}; #[derive(Debug, Clone, Copy)] enum ConnectionState { @@ -60,6 +60,7 @@ impl fmt::Debug for ConnectionInner { pub struct Connection { pub(crate) tx: mpsc::Sender, inner: Arc>, + pub(crate) tls_insecure: bool, } pub enum Authentication { @@ -72,6 +73,7 @@ impl Connection { websocket_url: &str, xmpp_domain: &str, authentication: Authentication, + tls_insecure: bool, ) -> Result<(Self, impl Future)> { let websocket_url: Uri = websocket_url.parse().context("invalid WebSocket URL")?; let xmpp_domain: BareJid = xmpp_domain.parse().context("invalid XMPP domain")?; @@ -88,7 +90,7 @@ impl Connection { .header("upgrade", "websocket") .body(()) .context("failed to build WebSocket request")?; - let (websocket, _response) = tokio_tungstenite::connect_async(request) + let (websocket, _response) = tokio_tungstenite::connect_async_tls_with_config(request, None, Some(wss_connector(tls_insecure)?)) .await .context("failed to connect XMPP WebSocket")?; let (sink, stream) = websocket.split(); @@ -107,6 +109,7 @@ impl Connection { let connection = Self { tx: tx.clone(), inner: inner.clone(), + tls_insecure, }; let writer = Connection::write_loop(rx, sink);