allow custom certs/disabling cert verification

This commit is contained in:
Toshit Chawda 2024-08-31 19:17:50 -07:00
parent 40a3ed616a
commit 55e1ef92bf
No known key found for this signature in database
GPG key ID: 91480ED99E2B3D9D
5 changed files with 154 additions and 27 deletions

13
Cargo.lock generated
View file

@ -530,11 +530,12 @@ dependencies = [
"hyper", "hyper",
"hyper-util-wasm", "hyper-util-wasm",
"js-sys", "js-sys",
"lazy_static",
"parking_lot_core", "parking_lot_core",
"pin-project-lite", "pin-project-lite",
"ring", "ring",
"rustls-pemfile",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki",
"send_wrapper 0.6.0", "send_wrapper 0.6.0",
"thiserror", "thiserror",
"tokio", "tokio",
@ -1459,6 +1460,16 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-pemfile"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
dependencies = [
"base64 0.22.1",
"rustls-pki-types",
]
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.8.0" version = "1.8.0"

View file

@ -21,8 +21,9 @@ http-body-util = "0.1.2"
hyper = "1.4.1" hyper = "1.4.1"
hyper-util-wasm = { git = "https://github.com/r58Playz/hyper-util-wasm", branch = "opinionated", version = "0.1.7", features = ["client-legacy", "http1"] } hyper-util-wasm = { git = "https://github.com/r58Playz/hyper-util-wasm", branch = "opinionated", version = "0.1.7", features = ["client-legacy", "http1"] }
js-sys = "0.3.70" js-sys = "0.3.70"
lazy_static = "1.5.0"
pin-project-lite = "0.2.14" pin-project-lite = "0.2.14"
rustls-pemfile = { version = "2.1.3", optional = true }
rustls-webpki = { version = "0.102.7", optional = true }
send_wrapper = { version = "0.6.0", features = ["futures"] } send_wrapper = { version = "0.6.0", features = ["futures"] }
thiserror = "1.0.63" thiserror = "1.0.63"
tokio = "1.39.3" tokio = "1.39.3"
@ -51,5 +52,5 @@ features = ["nightly"]
[features] [features]
default = ["full"] default = ["full"]
full = ["fastwebsockets", "async-compression", "hyper-util-wasm/http2"] full = ["dep:fastwebsockets", "dep:async-compression", "dep:rustls-webpki", "dep:rustls-pemfile", "hyper-util-wasm/http2"]

View file

@ -73,6 +73,12 @@ pub enum EpoxyError {
#[cfg(feature = "full")] #[cfg(feature = "full")]
#[error("Fastwebsockets: {0:?} ({0})")] #[error("Fastwebsockets: {0:?} ({0})")]
FastWebSockets(#[from] fastwebsockets::WebSocketError), FastWebSockets(#[from] fastwebsockets::WebSocketError),
#[cfg(feature = "full")]
#[error("Pemfile: {0:?} ({0})")]
Pemfile(std::io::Error),
#[cfg(feature = "full")]
#[error("Webpki: {0:?} ({0})")]
Webpki(#[from] webpki::Error),
#[error("Custom wisp transport: {0}")] #[error("Custom wisp transport: {0}")]
WispTransport(String), WispTransport(String),
@ -188,6 +194,10 @@ pub struct EpoxyClientOptions {
pub redirect_limit: usize, pub redirect_limit: usize,
#[wasm_bindgen(getter_with_clone)] #[wasm_bindgen(getter_with_clone)]
pub user_agent: String, pub user_agent: String,
pub disable_certificate_validation: bool,
#[cfg(feature = "full")]
#[wasm_bindgen(getter_with_clone)]
pub pem_files: Vec<String>,
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -206,6 +216,9 @@ impl Default for EpoxyClientOptions {
websocket_protocols: Vec::new(), websocket_protocols: Vec::new(),
redirect_limit: 10, redirect_limit: 10,
user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36".to_string(), user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36".to_string(),
disable_certificate_validation: false,
#[cfg(feature = "full")]
pem_files: Vec::new(),
} }
} }
} }
@ -242,6 +255,8 @@ pub struct EpoxyClient {
stream_provider: Arc<StreamProvider>, stream_provider: Arc<StreamProvider>,
client: Client<StreamProviderService, HttpBody>, client: Client<StreamProviderService, HttpBody>,
certs_tampered: bool,
pub redirect_limit: usize, pub redirect_limit: usize,
#[wasm_bindgen(getter_with_clone)] #[wasm_bindgen(getter_with_clone)]
pub user_agent: String, pub user_agent: String,
@ -335,6 +350,7 @@ impl EpoxyClient {
client, client,
redirect_limit: options.redirect_limit, redirect_limit: options.redirect_limit,
user_agent: options.user_agent, user_agent: options.user_agent,
certs_tampered: options.disable_certificate_validation || !options.pem_files.is_empty(),
}) })
} }
@ -566,10 +582,17 @@ impl EpoxyClient {
} }
} }
let (response, response_uri, redirected) = self let (mut response, response_uri, redirected) = self
.send_req(request_builder.body(HttpBody::new(body))?, request_redirect) .send_req(request_builder.body(HttpBody::new(body))?, request_redirect)
.await?; .await?;
if self.certs_tampered {
response.headers_mut().insert(
HeaderName::from_static("X-Epoxy-CertsTampered"),
HeaderValue::from_static("true"),
);
}
let response_headers: Array = response let response_headers: Array = response
.headers() .headers()
.iter() .iter()

View file

@ -1,7 +1,13 @@
use std::{io::ErrorKind, pin::Pin, sync::Arc, task::Poll}; use std::{
io::{BufReader, ErrorKind},
pin::Pin,
sync::Arc,
task::Poll,
};
use cfg_if::cfg_if;
use futures_rustls::{ use futures_rustls::{
rustls::{ClientConfig, RootCertStore}, rustls::{crypto::ring::default_provider, ClientConfig, RootCertStore},
TlsConnector, TlsConnector,
}; };
use futures_util::{ use futures_util::{
@ -10,7 +16,6 @@ use futures_util::{
AsyncRead, AsyncWrite, Future, AsyncRead, AsyncWrite, Future,
}; };
use hyper_util_wasm::client::legacy::connect::{ConnectSvc, Connected, Connection}; use hyper_util_wasm::client::legacy::connect::{ConnectSvc, Connected, Connection};
use lazy_static::lazy_static;
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use wasm_bindgen_futures::spawn_local; use wasm_bindgen_futures::spawn_local;
use webpki_roots::TLS_SERVER_ROOTS; use webpki_roots::TLS_SERVER_ROOTS;
@ -20,18 +25,11 @@ use wisp_mux::{
ClientMux, MuxStreamAsyncRW, MuxStreamIo, StreamType, ClientMux, MuxStreamAsyncRW, MuxStreamIo, StreamType,
}; };
use crate::{console_log, utils::IgnoreCloseNotify, EpoxyClientOptions, EpoxyError}; use crate::{
console_log,
lazy_static! { utils::{IgnoreCloseNotify, NoCertificateVerification},
static ref CLIENT_CONFIG: Arc<ClientConfig> = { EpoxyClientOptions, EpoxyError,
let certstore = RootCertStore::from_iter(TLS_SERVER_ROOTS.iter().cloned()); };
Arc::new(
ClientConfig::builder()
.with_root_certificates(certstore)
.with_no_client_auth(),
)
};
}
pub type ProviderUnencryptedStream = MuxStreamIo; pub type ProviderUnencryptedStream = MuxStreamIo;
pub type ProviderUnencryptedAsyncRW = MuxStreamAsyncRW; pub type ProviderUnencryptedAsyncRW = MuxStreamAsyncRW;
@ -62,6 +60,8 @@ pub struct StreamProvider {
udp_extension: bool, udp_extension: bool,
current_client: Arc<Mutex<Option<ClientMux>>>, current_client: Arc<Mutex<Option<ClientMux>>>,
client_config: Arc<ClientConfig>,
} }
impl StreamProvider { impl StreamProvider {
@ -69,11 +69,44 @@ impl StreamProvider {
wisp_generator: ProviderWispTransportGenerator, wisp_generator: ProviderWispTransportGenerator,
options: &EpoxyClientOptions, options: &EpoxyClientOptions,
) -> Result<Self, EpoxyError> { ) -> Result<Self, EpoxyError> {
cfg_if! {
if #[cfg(feature = "full")] {
let pems: Result<Result<Vec<_>, webpki::Error>, std::io::Error> = options
.pem_files
.iter()
.flat_map(|x| {
rustls_pemfile::certs(&mut BufReader::new(x.as_bytes()))
.map(|x| x.map(|x| webpki::anchor_from_trusted_cert(&x).map(|x| x.to_owned())))
.collect::<Vec<_>>()
})
.collect();
let pems = pems.map_err(EpoxyError::Pemfile)??;
let certstore = RootCertStore::from_iter(pems.into_iter().chain(TLS_SERVER_ROOTS.iter().cloned()));
} else {
let certstore = RootCertStore::from_iter(TLS_SERVER_ROOTS.iter().cloned());
}
}
let client_config = if options.disable_certificate_validation {
ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoCertificateVerification(
default_provider(),
)))
.with_no_client_auth()
} else {
ClientConfig::builder()
.with_root_certificates(certstore)
.with_no_client_auth()
};
let client_config = Arc::new(client_config);
Ok(Self { Ok(Self {
wisp_generator, wisp_generator,
current_client: Arc::new(Mutex::new(None)), current_client: Arc::new(Mutex::new(None)),
wisp_v2: options.wisp_v2, wisp_v2: options.wisp_v2,
udp_extension: options.udp_extension_required, udp_extension: options.udp_extension_required,
client_config,
}) })
} }
@ -149,7 +182,7 @@ impl StreamProvider {
let stream = self let stream = self
.get_asyncread(StreamType::Tcp, host.clone(), port) .get_asyncread(StreamType::Tcp, host.clone(), port)
.await?; .await?;
let connector = TlsConnector::from(CLIENT_CONFIG.clone()); let connector = TlsConnector::from(self.client_config.clone());
let ret = connector let ret = connector
.connect(host.try_into()?, stream) .connect(host.try_into()?, stream)
.into_fallible() .into_fallible()

View file

@ -6,12 +6,21 @@ use std::{
use async_trait::async_trait; use async_trait::async_trait;
use bytes::{buf::UninitSlice, BufMut, Bytes, BytesMut}; use bytes::{buf::UninitSlice, BufMut, Bytes, BytesMut};
use futures_rustls::TlsStream; use futures_rustls::{
rustls::{
self,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
DigitallySignedStruct,
},
TlsStream,
};
use futures_util::{ready, AsyncRead, AsyncWrite, Future, Stream, StreamExt, TryStreamExt}; use futures_util::{ready, AsyncRead, AsyncWrite, Future, Stream, StreamExt, TryStreamExt};
use http::{HeaderValue, Uri}; use http::{HeaderValue, Uri};
use hyper::{body::Body, rt::Executor}; use hyper::{body::Body, rt::Executor};
use js_sys::{Array, ArrayBuffer, JsString, Object, Uint8Array}; use js_sys::{Array, ArrayBuffer, JsString, Object, Uint8Array};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use rustls_pki_types::{CertificateDer, ServerName, UnixTime};
use send_wrapper::SendWrapper; use send_wrapper::SendWrapper;
use wasm_bindgen::{prelude::*, JsCast, JsValue}; use wasm_bindgen::{prelude::*, JsCast, JsValue};
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
@ -285,9 +294,7 @@ impl AsyncWrite for IgnoreCloseNotify {
cx: &mut Context<'_>, cx: &mut Context<'_>,
buf: &[u8], buf: &[u8],
) -> Poll<std::io::Result<usize>> { ) -> Poll<std::io::Result<usize>> {
self.project() self.project().inner.poll_write(cx, buf)
.inner
.poll_write(cx, buf)
} }
fn poll_write_vectored( fn poll_write_vectored(
@ -295,9 +302,7 @@ impl AsyncWrite for IgnoreCloseNotify {
cx: &mut Context<'_>, cx: &mut Context<'_>,
bufs: &[std::io::IoSlice<'_>], bufs: &[std::io::IoSlice<'_>],
) -> Poll<std::io::Result<usize>> { ) -> Poll<std::io::Result<usize>> {
self.project() self.project().inner.poll_write_vectored(cx, bufs)
.inner
.poll_write_vectored(cx, bufs)
} }
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> { fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
@ -309,6 +314,60 @@ impl AsyncWrite for IgnoreCloseNotify {
} }
} }
#[derive(Debug)]
pub(crate) struct NoCertificateVerification(pub CryptoProvider);
impl NoCertificateVerification {
pub fn new(provider: CryptoProvider) -> Self {
Self(provider)
}
}
impl ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
verify_tls12_signature(
message,
cert,
dss,
&self.0.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
verify_tls13_signature(
message,
cert,
dss,
&self.0.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.0.signature_verification_algorithms.supported_schemes()
}
}
pub fn is_redirect(code: u16) -> bool { pub fn is_redirect(code: u16) -> bool {
[301, 302, 303, 307, 308].contains(&code) [301, 302, 303, 307, 308].contains(&code)
} }