Make it possible to disable certificate verification

This commit is contained in:
Jasper Hugo 2022-03-06 16:23:46 +07:00
parent 3f27c90489
commit c68a7cbe0a
11 changed files with 147 additions and 39 deletions

4
Cargo.lock generated
View File

@ -755,12 +755,15 @@ dependencies = [
"jitsi-xmpp-parsers", "jitsi-xmpp-parsers",
"libc", "libc",
"maplit", "maplit",
"native-tls",
"nice-gst-meet", "nice-gst-meet",
"once_cell", "once_cell",
"pem", "pem",
"rand", "rand",
"rcgen", "rcgen",
"ring", "ring",
"rustls",
"rustls-native-certs",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
@ -769,6 +772,7 @@ dependencies = [
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"uuid", "uuid",
"webpki-roots",
"xmpp-parsers", "xmpp-parsers",
] ]

View File

@ -22,7 +22,8 @@ tracing = { version = "0.1", default-features = false, features = ["attributes",
cocoa = { version = "0.24", default-features = false } cocoa = { version = "0.24", default-features = false }
[features] [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 = ["lib-gst-meet/tls-native"]
tls-native-vendored = ["lib-gst-meet/tls-native-vendored"] tls-native-vendored = ["lib-gst-meet/tls-native-vendored"]
tls-rustls-native-roots = ["lib-gst-meet/tls-rustls-native-roots"] tls-rustls-native-roots = ["lib-gst-meet/tls-rustls-native-roots"]

View File

@ -45,13 +45,16 @@ struct Opt {
#[structopt(long)] #[structopt(long)]
select_endpoints: Option<String>, select_endpoints: Option<String>,
#[structopt(long)] #[structopt(long)]
last_n: Option<i32>, last_n: Option<u16>,
#[structopt(long)] #[structopt(long)]
recv_video_height: Option<i32>, recv_video_height: Option<u16>,
#[structopt(long)] #[structopt(long)]
video_type: Option<String>, video_type: Option<String>,
#[structopt(short, long, parse(from_occurrences))] #[structopt(short, long, parse(from_occurrences))]
verbose: u8, 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"))] #[cfg(not(target_os = "macos"))]
@ -117,9 +120,13 @@ async fn main_inner() -> Result<()> {
&opt.web_socket_url, &opt.web_socket_url,
&opt.xmpp_domain, &opt.xmpp_domain,
Authentication::Anonymous, Authentication::Anonymous,
#[cfg(feature = "tls-insecure")]
opt.tls_insecure,
#[cfg(not(feature = "tls-insecure"))]
false,
) )
.await .await
.context("failed to connect")?; .context("failed to build connection")?;
tokio::spawn(background); 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() { if opt.select_endpoints.is_some() || opt.last_n.is_some() || opt.recv_video_height.is_some() {
conference conference
.send_colibri_message(ColibriMessage::ReceiverVideoConstraints { .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 selected_endpoints: opt
.select_endpoints .select_endpoints
.map(|endpoints| endpoints.split(',').map(ToOwned::to_owned).collect()), .map(|endpoints| endpoints.split(',').map(ToOwned::to_owned).collect()),
on_stage_endpoints: None, on_stage_endpoints: None,
default_constraints: opt.recv_video_height.map(|height| Constraints { default_constraints: opt.recv_video_height.map(|height| Constraints {
max_height: Some(height), max_height: Some(height.into()),
ideal_height: None, ideal_height: None,
}), }),
constraints: None, constraints: None,

View File

@ -39,7 +39,8 @@ void gstmeet_deinit(struct Context *context);
JitsiConnection *gstmeet_connection_new(struct Context *context, JitsiConnection *gstmeet_connection_new(struct Context *context,
const char *websocket_url, const char *websocket_url,
const char *xmpp_domain); const char *xmpp_domain,
bool tls_insecure);
void gstmeet_connection_free(JitsiConnection *connection); void gstmeet_connection_free(JitsiConnection *connection);

View File

@ -80,6 +80,7 @@ pub unsafe extern "C" fn gstmeet_connection_new(
context: *mut Context, context: *mut Context,
websocket_url: *const c_char, websocket_url: *const c_char,
xmpp_domain: *const c_char, xmpp_domain: *const c_char,
tls_insecure: bool,
) -> *mut Connection { ) -> *mut Connection {
let websocket_url = CStr::from_ptr(websocket_url); let websocket_url = CStr::from_ptr(websocket_url);
let xmpp_domain = CStr::from_ptr(xmpp_domain); 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(), &websocket_url.to_string_lossy(),
&xmpp_domain.to_string_lossy(), &xmpp_domain.to_string_lossy(),
Authentication::Anonymous, Authentication::Anonymous,
tls_insecure,
)) ))
.map(|(connection, background)| { .map(|(connection, background)| {
(*context).runtime.spawn(background); (*context).runtime.spawn(background);

View File

@ -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 } jitsi-xmpp-parsers = { version = "0.1", path = "../jitsi-xmpp-parsers", default-features = false }
libc = { version = "0.2", default-features = false } libc = { version = "0.2", default-features = false }
maplit = { version = "1", 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"] } 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"] } once_cell = { version = "1", default-features = false, features = ["std"] }
pem = { version = "1", default-features = false } pem = { version = "1", default-features = false }
rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] }
rcgen = { version = "0.9", default-features = false } rcgen = { version = "0.9", default-features = false }
ring = { version = "0.16", 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 = { version = "1", default-features = false, features = ["derive"] }
serde_json = { version = "1", default-features = false, features = ["std"] } serde_json = { version = "1", default-features = false, features = ["std"] }
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros", "sync", "time"] } 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", "tracing-log",
] } ] }
uuid = { version = "0.8", default-features = false, features = ["v4"] } 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"] } xmpp-parsers = { git = "https://gitlab.com/xmpp-rs/xmpp-rs.git", default-features = false, features = ["disable-validation"] }
[features] [features]
default = ["tls-native"] # Ideally we would enable rustls/dangerous_configuration only when tls-insecure is enabled, but until weak-dep-features is stabilised, that
tls-native = ["tokio-tungstenite/native-tls"] # would cause rustls to always be pulled in.
tls-native-vendored = ["tokio-tungstenite/native-tls-vendored"] default = ["tls-rustls-native-roots"]
tls-rustls-native-roots = ["tokio-tungstenite/rustls-tls-native-roots"] tls-insecure = []
tls-rustls-webpki-roots = ["tokio-tungstenite/rustls-tls-webpki-roots"] 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"]

View File

@ -18,6 +18,8 @@ use tokio_tungstenite::tungstenite::{
}; };
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use crate::tls::wss_connector;
const MAX_CONNECT_RETRIES: u8 = 3; const MAX_CONNECT_RETRIES: u8 = 3;
const CONNECT_RETRY_SLEEP: Duration = Duration::from_secs(3); const CONNECT_RETRY_SLEEP: Duration = Duration::from_secs(3);
@ -27,7 +29,7 @@ pub(crate) struct ColibriChannel {
} }
impl ColibriChannel { impl ColibriChannel {
pub(crate) async fn new(uri: &str) -> Result<Self> { pub(crate) async fn new(uri: &str, tls_insecure: bool) -> Result<Self> {
let uri: Uri = uri.parse()?; let uri: Uri = uri.parse()?;
let host = uri.host().context("invalid WebSocket URL: missing host")?; let host = uri.host().context("invalid WebSocket URL: missing host")?;
@ -44,7 +46,7 @@ impl ColibriChannel {
.header("connection", "Upgrade") .header("connection", "Upgrade")
.header("upgrade", "websocket") .header("upgrade", "websocket")
.body(())?; .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, Ok((websocket, _)) => break websocket,
Err(e) => { Err(e) => {
if retries < MAX_CONNECT_RETRIES { if retries < MAX_CONNECT_RETRIES {

View File

@ -40,8 +40,12 @@ use crate::{
const DISCO_NODE: &str = "https://github.com/avstack/gst-meet"; const DISCO_NODE: &str = "https://github.com/avstack/gst-meet";
static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| { static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| DiscoInfoResult {
let mut features = vec![ node: None,
identities: vec![
Identity::new("client", "bot", "en", "gst-meet"),
],
features: vec![
Feature::new(ns::DISCO_INFO), Feature::new(ns::DISCO_INFO),
Feature::new(ns::JINGLE_RTP_AUDIO), Feature::new(ns::JINGLE_RTP_AUDIO),
Feature::new(ns::JINGLE_RTP_VIDEO), Feature::new(ns::JINGLE_RTP_VIDEO),
@ -50,26 +54,9 @@ static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| {
Feature::new("urn:ietf:rfc:5888"), // BUNDLE Feature::new("urn:ietf:rfc:5888"), // BUNDLE
Feature::new("urn:ietf:rfc:5761"), // RTCP-MUX Feature::new("urn:ietf:rfc:5761"), // RTCP-MUX
Feature::new("urn:ietf:rfc:4588"), // RTX Feature::new("urn:ietf:rfc:4588"), // RTX
]; Feature::new("http://jitsi.org/tcc"),
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![], extensions: vec![],
}
}); });
static COMPUTED_CAPS_HASH: Lazy<Hash> = Lazy::new(|| { static COMPUTED_CAPS_HASH: Lazy<Hash> = Lazy::new(|| {
@ -102,6 +89,7 @@ pub struct JitsiConference {
pub(crate) external_services: Vec<xmpp::extdisco::Service>, pub(crate) external_services: Vec<xmpp::extdisco::Service>,
pub(crate) jingle_session: Arc<Mutex<Option<JingleSession>>>, pub(crate) jingle_session: Arc<Mutex<Option<JingleSession>>>,
pub(crate) inner: Arc<Mutex<JitsiConferenceInner>>, pub(crate) inner: Arc<Mutex<JitsiConferenceInner>>,
pub(crate) tls_insecure: bool,
} }
impl fmt::Debug for JitsiConference { impl fmt::Debug for JitsiConference {
@ -220,6 +208,7 @@ impl JitsiConference {
on_colibri_message: None, on_colibri_message: None,
connected_tx: Some(tx), connected_tx: Some(tx),
})), })),
tls_insecure: xmpp_connection.tls_insecure,
}; };
xmpp_connection.add_stanza_filter(conference.clone()).await; xmpp_connection.add_stanza_filter(conference.clone()).await;
@ -590,7 +579,7 @@ impl StanzaFilter for JitsiConference {
if let Some(colibri_url) = colibri_url { if let Some(colibri_url) = colibri_url {
info!("Connecting Colibri WebSocket to {}", 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); let (tx, rx) = mpsc::channel(8);
colibri_channel.subscribe(tx).await; colibri_channel.subscribe(tx).await;
jingle_session.colibri_channel = Some(colibri_channel); jingle_session.colibri_channel = Some(colibri_channel);
@ -599,6 +588,14 @@ impl StanzaFilter for JitsiConference {
tokio::spawn(async move { tokio::spawn(async move {
let mut stream = ReceiverStream::new(rx); let mut stream = ReceiverStream::new(rx);
while let Some(msg) = stream.next().await { 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; let locked_inner = self_.inner.lock().await;
if let Some(f) = &locked_inner.on_colibri_message { if let Some(f) = &locked_inner.on_colibri_message {
if let Err(e) = f(self_.clone(), msg).await { if let Err(e) = f(self_.clone(), msg).await {

View File

@ -4,6 +4,7 @@ mod jingle;
mod pinger; mod pinger;
mod source; mod source;
mod stanza_filter; mod stanza_filter;
mod tls;
mod util; mod util;
mod xmpp; mod xmpp;

83
lib-gst-meet/src/tls.rs Normal file
View File

@ -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<tokio_tungstenite::Connector> {
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<tokio_tungstenite::Connector> {
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<tokio_tungstenite::Connector> {
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<Item = &[u8]>, _ocsp_response: &[u8], _now: std::time::SystemTime) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
Ok(rustls::client::ServerCertVerified::assertion())
}
}

View File

@ -22,7 +22,7 @@ use xmpp_parsers::{
BareJid, Element, FullJid, Jid, 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)] #[derive(Debug, Clone, Copy)]
enum ConnectionState { enum ConnectionState {
@ -60,6 +60,7 @@ impl fmt::Debug for ConnectionInner {
pub struct Connection { pub struct Connection {
pub(crate) tx: mpsc::Sender<Element>, pub(crate) tx: mpsc::Sender<Element>,
inner: Arc<Mutex<ConnectionInner>>, inner: Arc<Mutex<ConnectionInner>>,
pub(crate) tls_insecure: bool,
} }
pub enum Authentication { pub enum Authentication {
@ -72,6 +73,7 @@ impl Connection {
websocket_url: &str, websocket_url: &str,
xmpp_domain: &str, xmpp_domain: &str,
authentication: Authentication, authentication: Authentication,
tls_insecure: bool,
) -> Result<(Self, impl Future<Output = ()>)> { ) -> Result<(Self, impl Future<Output = ()>)> {
let websocket_url: Uri = websocket_url.parse().context("invalid WebSocket URL")?; let websocket_url: Uri = websocket_url.parse().context("invalid WebSocket URL")?;
let xmpp_domain: BareJid = xmpp_domain.parse().context("invalid XMPP domain")?; let xmpp_domain: BareJid = xmpp_domain.parse().context("invalid XMPP domain")?;
@ -88,7 +90,7 @@ impl Connection {
.header("upgrade", "websocket") .header("upgrade", "websocket")
.body(()) .body(())
.context("failed to build WebSocket request")?; .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 .await
.context("failed to connect XMPP WebSocket")?; .context("failed to connect XMPP WebSocket")?;
let (sink, stream) = websocket.split(); let (sink, stream) = websocket.split();
@ -107,6 +109,7 @@ impl Connection {
let connection = Self { let connection = Self {
tx: tx.clone(), tx: tx.clone(),
inner: inner.clone(), inner: inner.clone(),
tls_insecure,
}; };
let writer = Connection::write_loop(rx, sink); let writer = Connection::write_loop(rx, sink);