From 61c174747573eff80032e682bf02dbd008cba3b7 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Mon, 25 Sep 2023 11:22:47 +1000 Subject: [PATCH 1/2] integrate tls trusted root_store into mtxchat Set up tls with rustls::ClientConnection, BUT - on Error::InvalidCertificate - then probe the host for the untrusted certificate chain and prompt the user to perhaps trust one of the certificates in the chain - then try again. --- apps/mtxchat/src/lib.rs | 5 +- apps/mtxchat/src/listen.rs | 5 +- apps/mtxchat/src/web.rs | 12 ++-- libs/tls/Cargo.toml | 1 + libs/tls/src/lib.rs | 5 +- libs/tls/src/xtls.rs | 117 +++++++++++++++++++++++++++++++++++++ 6 files changed, 132 insertions(+), 13 deletions(-) create mode 100644 libs/tls/src/xtls.rs diff --git a/apps/mtxchat/src/lib.rs b/apps/mtxchat/src/lib.rs index 9aa068a2e..bee9672b2 100644 --- a/apps/mtxchat/src/lib.rs +++ b/apps/mtxchat/src/lib.rs @@ -13,7 +13,7 @@ use std::fmt::Write as _; use std::io::{Error, ErrorKind, Read, Write as StdWrite}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; -use tls::Tls; +use tls::xtls::TlsConnector; use trng::*; use ureq::Agent; use url::Url; @@ -91,7 +91,6 @@ impl<'a> MtxChat<'a> { let trng = Trng::new(&xns).unwrap(); let pddb = pddb::Pddb::new(); pddb.try_mount(); - let tls = Tls::new(); MtxChat { chat: chat, trng: trng, @@ -101,7 +100,7 @@ impl<'a> MtxChat<'a> { user_name: None, user_domain: Some(DOMAIN_MATRIX.to_string()), agent: ureq::builder() - .tls_config(Arc::new(tls.client_config())) + .tls_connector(Arc::new(TlsConnector{})) .build(), token: None, logged_in: false, diff --git a/apps/mtxchat/src/listen.rs b/apps/mtxchat/src/listen.rs index 9263ef882..51bb325cf 100644 --- a/apps/mtxchat/src/listen.rs +++ b/apps/mtxchat/src/listen.rs @@ -2,7 +2,7 @@ use crate::{get_username, web, MTX_LONG_TIMEOUT}; use chat::ChatOp; use modals::Modals; use std::sync::Arc; -use tls::Tls; +use tls::xtls::TlsConnector; use url::Url; use xous::CID; use xous_ipc::Buffer; @@ -20,9 +20,8 @@ pub fn listen( let modals = Modals::new(&xns).expect("can't connect to Modals server"); log::info!("client_sync for {} ms...", MTX_LONG_TIMEOUT); - let tls = Tls::new(); let mut agent = ureq::builder() - .tls_config(Arc::new(tls.client_config())) + .tls_connector(Arc::new(TlsConnector{})) .build(); if let Some((_since, events)) = web::client_sync( url, diff --git a/apps/mtxchat/src/web.rs b/apps/mtxchat/src/web.rs index c351dae34..eaa2b934f 100644 --- a/apps/mtxchat/src/web.rs +++ b/apps/mtxchat/src/web.rs @@ -1,7 +1,7 @@ use crate::Msg; use serde::{Deserialize, Serialize}; use ureq::serde_json::{Map, Value}; -use ureq::Agent; +use ureq::{Agent, ErrorKind}; use url::Url; const ACCEPT: &str = "Accept"; @@ -45,14 +45,16 @@ pub fn handle_response(maybe_response: Result) -> O } } Err(ureq::Error::Status(code, response)) => { - /* the server returned an unexpected status - code (such as 400, 500 etc) */ + // the server returned an unexpected status code (such as 400, 500 etc) let err_body = response.into_string().unwrap(); log::info!("ERROR code {} err_body = {}", code, err_body); None } - Err(e) => { - log::info!("ERROR in handle_response: {:?}", e); + Err(ureq::Error::Transport(kind)) => { + match kind.kind() { + ErrorKind::ConnectionFailed => log::warn!("TLS failure"), + _ => log::info!("Transport error: {:?}", kind), + }; None } } diff --git a/libs/tls/Cargo.toml b/libs/tls/Cargo.toml index eb5e3c731..243432b57 100644 --- a/libs/tls/Cargo.toml +++ b/libs/tls/Cargo.toml @@ -26,6 +26,7 @@ sha2 = "0.9.8" # note requirement for patch to xous-ring in workspace Cargo.toml rustls = { version = "0.21.7", features = ["dangerous_configuration"] } +ureq = "2.7.1" webpki = { package = "rustls-webpki", version = "0.101.4" } webpki-roots = {version = "0.24.0", optional = true} x509-parser = "0.15.0" diff --git a/libs/tls/src/lib.rs b/libs/tls/src/lib.rs index 8a9cea8d9..c7c862221 100644 --- a/libs/tls/src/lib.rs +++ b/libs/tls/src/lib.rs @@ -1,6 +1,7 @@ pub mod cmd; mod danger; pub mod rota; +pub mod xtls; use crate::rota::RustlsOwnedTrustAnchor; use locales::t; @@ -13,9 +14,9 @@ use rkyv::{ use rustls::{Certificate, ClientConfig, RootCertStore}; use sha2::Digest; use std::convert::{Into, TryFrom, TryInto}; -use std::io::{Error, Read, Write, ErrorKind}; -use std::sync::Arc; +use std::io::{Error, ErrorKind, Read, Write}; use std::net::TcpStream; +use std::sync::Arc; use x509_parser::prelude::{FromDer, X509Certificate}; use xous_names::XousNames; diff --git a/libs/tls/src/xtls.rs b/libs/tls/src/xtls.rs new file mode 100644 index 000000000..53e476f90 --- /dev/null +++ b/libs/tls/src/xtls.rs @@ -0,0 +1,117 @@ +use crate::Tls; +use rustls::{ClientConnection, StreamOwned}; +use std::{convert::TryInto, fmt::Debug, io, net::TcpStream, result::Result, sync::Arc}; +use ureq::{ReadWrite, Response}; + +pub struct TlsConnector {} + +/// Set up tls with rustls::ClientConnection, +/// BUT - on Error::InvalidCertificate - then +/// probe the host for the untrusted certificate chain and prompt the user +/// to perhaps trust one of the certificates in the chain - then try again. +impl ureq::TlsConnector for TlsConnector { + fn connect( + &self, + dns_name: &str, + mut io: Box, + ) -> Result, ureq::Error> { + log::info!("Commencing tls connection setup"); + loop { + // refresh rustls client config with current root_store + let tls = Tls::new(); + let config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(tls.root_store()) + .with_no_client_auth(); + match rustls::ClientConnection::new(Arc::new(config), dns_name.try_into().unwrap()) { + Ok(mut connection) => { + log::info!("tls handshake started"); + match connection.complete_io(&mut io) { + Ok(_) => { + if connection.peer_certificates().is_some() { + return Ok(Box::new(TlsStream(StreamOwned::new(connection, io)))); + } + } + // errors generated late in the tls handshake + Err(e) => { + if let Some(inner) = e.get_ref() { + if let Some(rustls_error) = inner.downcast_ref::() { + if let rustls::Error::InvalidCertificate(_) = rustls_error { + if let Ok(n) = tls.probe(dns_name) { + if n > 0 { + log::info!("try again with new trusted certs"); + continue; + } + } + } + } + } + // non certificate chain errors + log::warn!("{e}"); + break; + } + } + } + // errors generated early in the tls handshake + Err(rustls::Error::InvalidCertificate(_)) => { + if let Ok(n) = tls.probe(dns_name) { + if n > 0 { + log::info!("try again with new trusted certs"); + continue; + } + } + } + // non certificate chain errors + Err(e) => { + log::warn!("{e}"); + break; + } + } + } + log::warn!("failed to establish tls connection"); + // this would be better as a ureq:Error::Transport but they are hard to build + Err(ureq::Error::Status( + 526, + Response::new(526, "tls", "untrusted certificate chain").unwrap(), + )) + } +} + +// TlsStream wraps StreamOwned and implements ReadWrite for use in TlsConnect::connect() +#[derive(Debug)] +pub struct TlsStream(StreamOwned>); + +impl TlsStream { + /// Returns a shared reference to the inner stream. + pub fn get_ref(&self) -> &Box { + self.0.get_ref() + } + + /// Returns a mutable reference to the inner stream. + pub fn get_mut(&mut self) -> &mut Box { + self.0.get_mut() + } +} + +impl io::Read for TlsStream { + fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(self.0.read(buf)?) + } +} + +impl io::Write for TlsStream { + fn write(&mut self, buf: &[u8]) -> Result { + Ok(self.0.write(buf)?) + } + + fn flush(&mut self) -> Result<(), std::io::Error> { + Ok(self.0.flush()?) + } +} + +// required for the return type in TlsConnect::connect() +impl ReadWrite for TlsStream { + fn socket(&self) -> Option<&TcpStream> { + self.get_ref().socket() + } +} From a89a344480a96e07151e267bfcbf930114223292 Mon Sep 17 00:00:00 2001 From: nworbnhoj Date: Mon, 25 Sep 2023 21:10:35 +1000 Subject: [PATCH 2/2] update mtxchat README.md --- apps/mtxchat/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/mtxchat/README.md b/apps/mtxchat/README.md index 9dd70290a..6ec9cac17 100644 --- a/apps/mtxchat/README.md +++ b/apps/mtxchat/README.md @@ -26,9 +26,6 @@ Use another device such as a mobile or PC to: * https://spec.matrix.org/latest/#room-structure * Join the room -On Precursor, make sure to trust a tls certificate offered by the matrix server. -* shellchat `net tls probe matrix.org` and then `sleep coldboot` - ## Functionality