Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wisp v2 #4

Merged
merged 14 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
757 changes: 744 additions & 13 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["server", "client", "wisp", "simple-wisp-client"]
members = ["server", "client", "wisp", "simple-wisp-client", "certs-grabber"]
default-members = ["server"]

[profile.release]
Expand Down
13 changes: 13 additions & 0 deletions certs-grabber/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "certs-grabber"
version = "0.1.0"
edition = "2021"

[dependencies]
hex = "0.4.3"
ring = "0.17.8"
rustls-pki-types = "1.4.1"
rustls-webpki = "0.102.2"
tokio = { version = "1.37.0", features = ["full"] }
webpki-ccadb = "0.1.0"
x509-parser = "0.16.0"
64 changes: 64 additions & 0 deletions certs-grabber/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::fmt::Write;

use ring::digest::{digest, SHA256};
use rustls_pki_types::{CertificateDer, TrustAnchor};
use webpki::anchor_from_trusted_cert;
use webpki_ccadb::fetch_ccadb_roots;

#[tokio::main]
async fn main() {
let tls_roots_map = fetch_ccadb_roots().await;
let mut code = String::with_capacity(256 * 1_024);
code.push_str("const ROOTS = [");
for (_, root) in tls_roots_map {
// Verify the DER FP matches the metadata FP.
let der = root.der();
let calculated_fp = digest(&SHA256, &der);
let metadata_fp = hex::decode(&root.sha256_fingerprint).expect("malformed fingerprint");
assert_eq!(calculated_fp.as_ref(), metadata_fp.as_slice());

let ta_der = CertificateDer::from(der.as_ref());
let TrustAnchor {
subject,
subject_public_key_info,
name_constraints,
} = anchor_from_trusted_cert(&ta_der).expect("malformed trust anchor der");

/*
let (_, parsed_cert) =
x509_parser::parse_x509_certificate(&der).expect("malformed x509 der");
let issuer = name_to_string(parsed_cert.issuer());
let subject_str = name_to_string(parsed_cert.subject());
let label = root.common_name_or_certificate_name.clone();
let serial = root.serial().to_string();
let sha256_fp = root.sha256_fp();
*/

code.write_fmt(format_args!(
"{{subject:new Uint8Array([{}]),subject_public_key_info:new Uint8Array([{}]),name_constraints:{}}},",
subject
.as_ref()
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>().join(","),
subject_public_key_info
.as_ref()
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>().join(","),
if let Some(constraints) = name_constraints {
format!("new Uint8Array([{}])",constraints
.as_ref()
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>().join(","))
} else {
"null".into()
}
))
.unwrap();
}
code.pop();
code.push_str("];");
println!("{}", code);
}
5 changes: 3 additions & 2 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ wasm-bindgen = { version = "0.2.91", features = ["enable-interning"] }
wasm-bindgen-futures = "0.4.39"
futures-util = "0.3.30"
js-sys = "0.3.66"
webpki-roots = "0.26.0"
tokio-rustls = "0.25.0"
web-sys = { version = "0.3.66", features = ["Request", "RequestInit", "Headers", "Response", "ResponseInit", "WebSocket", "BinaryType", "MessageEvent"] }
wasm-streams = "0.4.0"
tokio-util = { version = "0.7.10", features = ["io"] }
async-compression = { version = "0.4.5", features = ["tokio", "gzip", "brotli"] }
fastwebsockets = { version = "0.6.0", features = ["unstable-split"] }
base64 = "0.21.7"
wisp-mux = { path = "../wisp", features = ["tokio_io"] }
wisp-mux = { path = "../wisp", features = ["tokio_io", "wasm"] }
async_io_stream = { version = "0.3.3", features = ["tokio_io"] }
getrandom = { version = "0.2.12", features = ["js"] }
hyper-util-wasm = { version = "0.1.3", features = ["client", "client-legacy", "http1", "http2"] }
Expand All @@ -35,6 +34,7 @@ console_error_panic_hook = "0.1.7"
send_wrapper = "0.6.0"
event-listener = "5.2.0"
wasmtimer = "0.2.0"
async-trait = "0.1.80"

[dependencies.ring]
features = ["wasm32_unknown_unknown_js"]
Expand All @@ -46,3 +46,4 @@ features = ["web"]
default-env = "0.1.1"
wasm-bindgen-test = "0.3.42"
web-sys = { version = "0.3.69", features = ["FormData", "UrlSearchParams"] }
webpki-roots = "0.26.0"
9 changes: 9 additions & 0 deletions client/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,14 @@ echo "}\ndeclare function epoxy(maybe_memory?: WebAssembly.Memory): Promise<type
cp out/epoxy_client.d.ts pkg/epoxy.d.ts
cp out/epoxy_client_bg.wasm pkg/epoxy.wasm

echo "[epx] fetching certs"
(
cd ../certs-grabber
cargo run
) > pkg/certs.js
cat pkg/certs.js > pkg/certs-module.js
echo "export default ROOTS;" >> pkg/certs-module.js
echo "[epx] fetching certs finished"

rm -r out/
echo "[epx] done!"
13 changes: 7 additions & 6 deletions client/demo.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import epoxy from "./pkg/epoxy-module-bundled.js";
import CERTS from "./pkg/certs-module.js";

onmessage = async (msg) => {
console.debug("recieved demo:", msg);
Expand Down Expand Up @@ -29,13 +30,13 @@ onmessage = async (msg) => {
postMessage(JSON.stringify(str, null, 4));
}

const { EpoxyClient, certs } = await epoxy();
const { EpoxyClient } = await epoxy();

console.log("certs:", certs());
console.log("certs:", CERTS);

const tconn0 = performance.now();
// args: websocket url, user agent, redirect limit
let epoxy_client = await new EpoxyClient("ws://localhost:4000", navigator.userAgent, 10);
// args: websocket url, user agent, redirect limit, certs
let epoxy_client = await new EpoxyClient("ws://localhost:4000", navigator.userAgent, 10, CERTS);
const tconn1 = performance.now();
log(`conn establish took ${tconn1 - tconn0} ms or ${(tconn1 - tconn0) / 1000} s`);

Expand Down Expand Up @@ -237,9 +238,9 @@ onmessage = async (msg) => {
log(`total avg mux (${num_outer_tests} tests of ${num_inner_tests} reqs): ${total_mux_multi} ms or ${total_mux_multi / 1000} s`);

} else {
let resp = await epoxy_client.fetch("https://httpbin.org/get");
let resp = await epoxy_client.fetch("https://www.example.com/");
console.log(resp, Object.fromEntries(resp.headers));
plog(await resp.json());
log(await resp.text());
}
log("done");
};
46 changes: 11 additions & 35 deletions client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod wrappers;
use tls_stream::EpxTlsStream;
use tokioio::TokioIo;
use udp_stream::EpxUdpStream;
use utils::object_to_trustanchor;
pub use utils::{Boolinator, ReplaceErr, UriExt};
use websocket::EpxWebSocket;
use wrappers::{IncomingBody, ServiceWrapper, TlsWispService, WebSocketWrapper};
Expand Down Expand Up @@ -70,42 +71,10 @@ fn init() {
intern("Content-Type");
}

fn cert_to_jval(cert: &TrustAnchor) -> Result<JsValue, JsValue> {
let val = Object::new();
Reflect::set(
&val,
&jval!("subject"),
&Uint8Array::from(cert.subject.as_ref()),
)?;
Reflect::set(
&val,
&jval!("subject_public_key_info"),
&Uint8Array::from(cert.subject_public_key_info.as_ref()),
)?;
Reflect::set(
&val,
&jval!("name_constraints"),
&jval!(cert
.name_constraints
.as_ref()
.map(|x| Uint8Array::from(x.as_ref()))),
)?;
Ok(val.into())
}

#[wasm_bindgen]
pub fn certs() -> Result<JsValue, JsValue> {
Ok(webpki_roots::TLS_SERVER_ROOTS
.iter()
.map(cert_to_jval)
.collect::<Result<Array, JsValue>>()?
.into())
}

#[wasm_bindgen(inspectable)]
pub struct EpoxyClient {
rustls_config: Arc<rustls::ClientConfig>,
mux: Arc<RwLock<ClientMux<WebSocketWrapper>>>,
mux: Arc<RwLock<ClientMux>>,
hyper_client: Client<TlsWispService, HttpBody>,
#[wasm_bindgen(getter_with_clone)]
pub useragent: String,
Expand All @@ -120,6 +89,7 @@ impl EpoxyClient {
ws_url: String,
useragent: String,
redirect_limit: usize,
certs: Array,
) -> Result<EpoxyClient, JsError> {
let ws_uri = ws_url
.parse::<uri::Uri>()
Expand All @@ -137,7 +107,13 @@ impl EpoxyClient {
utils::spawn_mux_fut(mux.clone(), fut, ws_url.clone());

let mut certstore = RootCertStore::empty();
certstore.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let certs: Result<Vec<TrustAnchor>, JsValue> =
certs.iter().map(object_to_trustanchor).collect();
certstore.extend(
certs
.replace_err("Failed to get certificates from cert store")?
.into_iter(),
);

let rustls_config = Arc::new(
rustls::ClientConfig::builder()
Expand All @@ -164,7 +140,7 @@ impl EpoxyClient {
async fn get_tls_io(&self, url_host: &str, url_port: u16) -> Result<EpxIoTlsStream, JsError> {
let channel = self
.mux
.read()
.write()
.await
.client_new_stream(StreamType::Tcp, url_host.to_string(), url_port)
.await
Expand Down
2 changes: 1 addition & 1 deletion client/src/udp_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl EpxUdpStream {

let io = tcp
.mux
.read()
.write()
.await
.client_new_stream(StreamType::Udp, url_host.to_string(), url_port)
.await
Expand Down
42 changes: 26 additions & 16 deletions client/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::*;

use rustls_pki_types::Der;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;

use hyper::rt::Executor;
use js_sys::ArrayBuffer;
use std::future::Future;
use wisp_mux::WispError;
use wisp_mux::{extensions::udp::UdpProtocolExtensionBuilder, WispError};

#[wasm_bindgen]
extern "C" {
Expand Down Expand Up @@ -194,26 +195,24 @@ pub async fn make_mux(
url: &str,
) -> Result<
(
ClientMux<WebSocketWrapper>,
impl Future<Output = Result<(), WispError>>,
ClientMux,
impl Future<Output = Result<(), WispError>> + Send,
),
WispError,
> {
let (wtx, wrx) = WebSocketWrapper::connect(url, vec![])
.await
.map_err(|_| WispError::WsImplSocketClosed)?;
let (wtx, wrx) =
WebSocketWrapper::connect(url, vec![]).map_err(|_| WispError::WsImplSocketClosed)?;
wtx.wait_for_open().await;
let mux = ClientMux::new(wrx, wtx).await?;

Ok(mux)
ClientMux::new(wrx, wtx, Some(&[Box::new(UdpProtocolExtensionBuilder())])).await
}

pub fn spawn_mux_fut(
mux: Arc<RwLock<ClientMux<WebSocketWrapper>>>,
fut: impl Future<Output = Result<(), WispError>> + 'static,
mux: Arc<RwLock<ClientMux>>,
fut: impl Future<Output = Result<(), WispError>> + Send + 'static,
url: String,
) {
wasm_bindgen_futures::spawn_local(async move {
debug!("epoxy: mux future started");
if let Err(e) = fut.await {
log!("epoxy: error in mux future, restarting: {:?}", e);
while let Err(e) = replace_mux(mux.clone(), &url).await {
Expand All @@ -225,13 +224,10 @@ pub fn spawn_mux_fut(
});
}

pub async fn replace_mux(
mux: Arc<RwLock<ClientMux<WebSocketWrapper>>>,
url: &str,
) -> Result<(), WispError> {
pub async fn replace_mux(mux: Arc<RwLock<ClientMux>>, url: &str) -> Result<(), WispError> {
let (mux_replace, fut) = make_mux(url).await?;
let mut mux_write = mux.write().await;
mux_write.close().await?;
let _ = mux_write.close().await;
*mux_write = mux_replace;
drop(mux_write);
spawn_mux_fut(mux, fut, url.into());
Expand Down Expand Up @@ -264,3 +260,17 @@ pub async fn jval_to_u8_array_req(val: JsValue) -> Result<(Uint8Array, web_sys::
req,
))
}

pub fn object_to_trustanchor(obj: JsValue) -> Result<TrustAnchor<'static>, JsValue> {
let subject: Uint8Array = Reflect::get(&obj, &jval!("subject"))?.dyn_into()?;
let pub_key_info: Uint8Array =
Reflect::get(&obj, &jval!("subject_public_key_info"))?.dyn_into()?;
let name_constraints: Option<Uint8Array> = Reflect::get(&obj, &jval!("name_constraints"))
.and_then(|x| x.dyn_into())
.ok();
Ok(TrustAnchor {
subject: Der::from(subject.to_vec()),
subject_public_key_info: Der::from(pub_key_info.to_vec()),
name_constraints: name_constraints.map(|x| Der::from(x.to_vec())),
})
}
10 changes: 8 additions & 2 deletions client/src/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl EpxWebSocket {
break;
}
// ping/pong/continue
_ => {},
_ => {}
}
}
});
Expand All @@ -115,7 +115,13 @@ impl EpxWebSocket {
.call0(&Object::default())
.replace_err("Failed to call onopen")?;

Ok(Self { tx, onerror, origin, protocols, url: url.to_string() })
Ok(Self {
tx,
onerror,
origin,
protocols,
url: url.to_string(),
})
}
.await;
if let Err(ret) = ret {
Expand Down
Loading
Loading