diff --git a/Cargo.lock b/Cargo.lock index 09bcfceb4491b2..d220093d3724d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -580,6 +580,7 @@ dependencies = [ "deno_net", "deno_runtime", "deno_timers", + "deno_tls", "deno_url", "deno_web", "deno_webgpu", @@ -744,8 +745,10 @@ dependencies = [ "bytes", "data-url", "deno_core", + "deno_tls", "deno_web", "http", + "lazy_static", "reqwest", "serde", "tokio", @@ -803,15 +806,13 @@ name = "deno_net" version = "0.4.0" dependencies = [ "deno_core", + "deno_tls", "lazy_static", "log", - "rustls", "serde", "tokio", "trust-dns-proto", "trust-dns-resolver", - "webpki", - "webpki-roots", ] [[package]] @@ -828,6 +829,7 @@ dependencies = [ "deno_http", "deno_net", "deno_timers", + "deno_tls", "deno_url", "deno_web", "deno_webgpu", @@ -871,6 +873,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "deno_tls" +version = "0.1.0" +dependencies = [ + "deno_core", + "lazy_static", + "reqwest", + "rustls", + "rustls-native-certs", + "serde", + "webpki", + "webpki-roots", +] + [[package]] name = "deno_url" version = "0.13.0" @@ -920,14 +936,13 @@ name = "deno_websocket" version = "0.18.0" dependencies = [ "deno_core", + "deno_tls", "http", "hyper", "serde", "tokio", "tokio-rustls", "tokio-tungstenite", - "webpki", - "webpki-roots", ] [[package]] @@ -2403,6 +2418,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl-probe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" + [[package]] name = "os_pipe" version = "0.9.2" @@ -3028,6 +3049,18 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls", + "schannel", + "security-framework", +] + [[package]] name = "rusty_v8" version = "0.25.3" @@ -3089,6 +3122,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + [[package]] name = "scoped-tls" version = "1.0.0" @@ -3111,6 +3154,29 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 22b734f13c4675..525a0d3526b1cd 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -47,6 +47,7 @@ deno_core = { version = "0.95.0", path = "../core" } deno_doc = "0.9.0" deno_lint = "0.11.0" deno_runtime = { version = "0.21.0", path = "../runtime" } +deno_tls = { version = "0.1.0", path = "../extensions/tls" } atty = "0.2.14" base64 = "0.13.0" diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index a7bd503ae27462..207f08c64976e7 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -3,7 +3,6 @@ use crate::auth_tokens::AuthTokens; use crate::colors; use crate::http_cache::HttpCache; -use crate::http_util::create_http_client; use crate::http_util::fetch_once; use crate::http_util::FetchOnceArgs; use crate::http_util::FetchOnceResult; @@ -22,6 +21,8 @@ use deno_core::ModuleSpecifier; use deno_runtime::deno_fetch::reqwest; use deno_runtime::deno_web::BlobStore; use deno_runtime::permissions::Permissions; +use deno_tls::create_http_client; +use deno_tls::rustls::RootCertStore; use log::debug; use log::info; use std::borrow::Borrow; @@ -220,7 +221,7 @@ impl FileFetcher { http_cache: HttpCache, cache_setting: CacheSetting, allow_remote: bool, - ca_data: Option>, + root_cert_store: Option, blob_store: BlobStore, ) -> Result { Ok(Self { @@ -229,7 +230,12 @@ impl FileFetcher { cache: Default::default(), cache_setting, http_cache, - http_client: create_http_client(get_user_agent(), ca_data)?, + http_client: create_http_client( + get_user_agent(), + root_cert_store, + None, + None, + )?, blob_store, }) } diff --git a/cli/flags.rs b/cli/flags.rs index 1dafa205f73ecc..1c7eaf9a090b9a 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -139,6 +139,7 @@ pub struct Flags { pub allow_read: Option>, pub allow_run: Option>, pub allow_write: Option>, + pub ca_stores: Option>, pub ca_file: Option, pub cache_blocklist: Vec, /// This is not exposed as an option in the CLI, it is used internally when @@ -276,6 +277,9 @@ static ENV_VARIABLES_HELP: &str = r#"ENVIRONMENT VARIABLES: hostnames to use when fetching remote modules from private repositories (e.g. "abcde12345@deno.land;54321edcba@github.com") + DENO_TLS_CA_STORE Comma-seperated list of order dependent certificate stores + (system, mozilla) + (defaults to mozilla) DENO_CERT Load certificate authority from PEM encoded file DENO_DIR Set the cache directory DENO_INSTALL_ROOT Set deno install's output directory diff --git a/cli/http_util.rs b/cli/http_util.rs index a199f20c8e67fb..6710939238fc4f 100644 --- a/cli/http_util.rs +++ b/cli/http_util.rs @@ -1,46 +1,18 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - use crate::auth_tokens::AuthToken; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::url::Url; -use deno_runtime::deno_fetch::reqwest; -use deno_runtime::deno_fetch::reqwest::header::HeaderMap; use deno_runtime::deno_fetch::reqwest::header::HeaderValue; use deno_runtime::deno_fetch::reqwest::header::AUTHORIZATION; use deno_runtime::deno_fetch::reqwest::header::IF_NONE_MATCH; use deno_runtime::deno_fetch::reqwest::header::LOCATION; -use deno_runtime::deno_fetch::reqwest::header::USER_AGENT; -use deno_runtime::deno_fetch::reqwest::redirect::Policy; use deno_runtime::deno_fetch::reqwest::Client; use deno_runtime::deno_fetch::reqwest::StatusCode; use log::debug; use std::collections::HashMap; -/// Create new instance of async reqwest::Client. This client supports -/// proxies and doesn't follow redirects. -pub fn create_http_client( - user_agent: String, - ca_data: Option>, -) -> Result { - let mut headers = HeaderMap::new(); - headers.insert(USER_AGENT, user_agent.parse().unwrap()); - let mut builder = Client::builder() - .redirect(Policy::none()) - .default_headers(headers) - .use_rustls_tls(); - - if let Some(ca_data) = ca_data { - let cert = reqwest::Certificate::from_pem(&ca_data)?; - builder = builder.add_root_certificate(cert); - } - - builder - .build() - .map_err(|e| generic_error(format!("Unable to build http client: {}", e))) -} - /// Construct the next uri based on base uri and location header fragment /// See fn resolve_url_from_location(base_url: &Url, location: &str) -> Url { @@ -168,10 +140,12 @@ pub async fn fetch_once( mod tests { use super::*; use crate::version; + use deno_tls::create_http_client; + use deno_tls::rustls::RootCertStore; use std::fs::read; fn create_test_client(ca_data: Option>) -> Client { - create_http_client("test_client".to_string(), ca_data).unwrap() + create_http_client("test_client".to_string(), None, ca_data, None).unwrap() } #[tokio::test] @@ -362,6 +336,7 @@ mod tests { let client = create_http_client( version::get_user_agent(), + None, Some( read( test_util::root_path() @@ -371,6 +346,7 @@ mod tests { ) .unwrap(), ), + None, ) .unwrap(); let result = fetch_once(FetchOnceArgs { @@ -390,6 +366,64 @@ mod tests { } } + #[tokio::test] + async fn test_fetch_with_default_certificate_store() { + let _http_server_guard = test_util::http_server(); + // Relies on external http server with a valid mozilla root CA cert. + let url = Url::parse("https://deno.land").unwrap(); + let client = create_http_client( + version::get_user_agent(), + None, // This will load mozilla certs by default + None, + None, + ) + .unwrap(); + + let result = fetch_once(FetchOnceArgs { + client, + url, + maybe_etag: None, + maybe_auth_token: None, + }) + .await; + + println!("{:?}", result); + if let Ok(FetchOnceResult::Code(body, _headers)) = result { + assert!(!body.is_empty()); + } else { + panic!(); + } + } + + // TODO(@justinmchase): Windows should verify certs too and fail to make this request without ca certs + #[cfg(not(windows))] + #[tokio::test] + async fn test_fetch_with_empty_certificate_store() { + let _http_server_guard = test_util::http_server(); + // Relies on external http server with a valid mozilla root CA cert. + let url = Url::parse("https://deno.land").unwrap(); + let client = create_http_client( + version::get_user_agent(), + Some(RootCertStore::empty()), // no certs loaded at all + None, + None, + ) + .unwrap(); + + let result = fetch_once(FetchOnceArgs { + client, + url, + maybe_etag: None, + maybe_auth_token: None, + }) + .await; + + if let Ok(FetchOnceResult::Code(_body, _headers)) = result { + // This test is expected to fail since to CA certs have been loaded + panic!(); + } + } + #[tokio::test] async fn test_fetch_with_cafile_gzip() { let _http_server_guard = test_util::http_server(); @@ -400,6 +434,7 @@ mod tests { .unwrap(); let client = create_http_client( version::get_user_agent(), + None, Some( read( test_util::root_path() @@ -409,6 +444,7 @@ mod tests { ) .unwrap(), ), + None, ) .unwrap(); let result = fetch_once(FetchOnceArgs { @@ -437,6 +473,7 @@ mod tests { let url = Url::parse("https://localhost:5545/etag_script.ts").unwrap(); let client = create_http_client( version::get_user_agent(), + None, Some( read( test_util::root_path() @@ -446,6 +483,7 @@ mod tests { ) .unwrap(), ), + None, ) .unwrap(); let result = fetch_once(FetchOnceArgs { @@ -488,6 +526,7 @@ mod tests { .unwrap(); let client = create_http_client( version::get_user_agent(), + None, Some( read( test_util::root_path() @@ -497,6 +536,7 @@ mod tests { ) .unwrap(), ), + None, ) .unwrap(); let result = fetch_once(FetchOnceArgs { diff --git a/cli/main.rs b/cli/main.rs index 7d375c0c4cc648..77cce1d05b97dc 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -109,7 +109,7 @@ fn create_web_worker_callback( .log_level .map_or(false, |l| l == log::Level::Debug), unstable: program_state.flags.unstable, - ca_data: program_state.ca_data.clone(), + root_cert_store: program_state.root_cert_store.clone(), user_agent: version::get_user_agent(), seed: program_state.flags.seed, module_loader, @@ -189,7 +189,7 @@ pub fn create_main_worker( .log_level .map_or(false, |l| l == log::Level::Debug), unstable: program_state.flags.unstable, - ca_data: program_state.ca_data.clone(), + root_cert_store: program_state.root_cert_store.clone(), user_agent: version::get_user_agent(), seed: program_state.flags.seed, js_error_create_fn: Some(js_error_create_fn), diff --git a/cli/program_state.rs b/cli/program_state.rs index b8fb5e33b26580..244351a03db848 100644 --- a/cli/program_state.rs +++ b/cli/program_state.rs @@ -30,12 +30,16 @@ use deno_core::resolve_url; use deno_core::url::Url; use deno_core::ModuleSource; use deno_core::ModuleSpecifier; +use deno_tls::rustls::RootCertStore; +use deno_tls::rustls_native_certs::load_native_certs; +use deno_tls::webpki_roots::TLS_SERVER_ROOTS; use log::debug; use log::warn; use std::collections::HashMap; use std::collections::HashSet; use std::env; -use std::fs::read; +use std::fs::File; +use std::io::BufReader; use std::sync::Arc; /// This structure represents state of single "deno" program. @@ -53,7 +57,7 @@ pub struct ProgramState { pub maybe_config_file: Option, pub maybe_import_map: Option, pub maybe_inspector_server: Option>, - pub ca_data: Option>, + pub root_cert_store: Option, pub blob_store: BlobStore, pub broadcast_channel: InMemoryBroadcastChannel, pub shared_array_buffer_store: SharedArrayBufferStore, @@ -68,11 +72,50 @@ impl ProgramState { let dir = deno_dir::DenoDir::new(maybe_custom_root)?; let deps_cache_location = dir.root.join("deps"); let http_cache = http_cache::HttpCache::new(&deps_cache_location); + + let mut root_cert_store = RootCertStore::empty(); + let ca_stores: Vec = flags + .ca_stores + .clone() + .or_else(|| { + let env_ca_store = env::var("DENO_TLS_CA_STORE").ok()?; + Some( + env_ca_store + .split(',') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(), + ) + }) + .unwrap_or_else(|| vec!["mozilla".to_string()]); + + for store in ca_stores.iter() { + match store.as_str() { + "mozilla" => { + root_cert_store.add_server_trust_anchors(&TLS_SERVER_ROOTS); + } + "system" => { + let roots = load_native_certs() + .expect("could not load platform certs") + .roots; + root_cert_store.roots.extend(roots); + } + _ => { + return Err(anyhow!("Unknown certificate store \"{}\" specified (allowed: \"system,mozilla\")", store)); + } + } + } + let ca_file = flags.ca_file.clone().or_else(|| env::var("DENO_CERT").ok()); - let ca_data = match &ca_file { - Some(ca_file) => Some(read(ca_file).context("Failed to open ca file")?), - None => None, - }; + if let Some(ca_file) = ca_file { + let certfile = File::open(&ca_file)?; + let mut reader = BufReader::new(certfile); + + // This function does not return specific errors, if it fails give a generic message. + if let Err(_err) = root_cert_store.add_pem_file(&mut reader) { + return Err(anyhow!("Unable to add pem file to certificate store")); + } + } let cache_usage = if flags.cached_only { CacheSetting::Only @@ -92,7 +135,7 @@ impl ProgramState { http_cache, cache_usage, !flags.no_remote, - ca_data.clone(), + Some(root_cert_store.clone()), blob_store.clone(), )?; @@ -152,7 +195,7 @@ impl ProgramState { maybe_config_file, maybe_import_map, maybe_inspector_server, - ca_data, + root_cert_store: Some(root_cert_store.clone()), blob_store, broadcast_channel, shared_array_buffer_store, diff --git a/cli/standalone.rs b/cli/standalone.rs index 3c8dabd3a6d43c..460ee23d055d23 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -8,6 +8,7 @@ use crate::ops; use crate::program_state::ProgramState; use crate::version; use data_url::DataUrl; +use deno_core::error::anyhow; use deno_core::error::type_error; use deno_core::error::uri_error; use deno_core::error::AnyError; @@ -29,11 +30,14 @@ use deno_runtime::permissions::Permissions; use deno_runtime::permissions::PermissionsOptions; use deno_runtime::worker::MainWorker; use deno_runtime::worker::WorkerOptions; +use deno_tls::create_default_root_cert_store; use log::Level; use std::cell::RefCell; use std::convert::TryInto; use std::env::current_exe; use std::fs::File; +use std::io::BufReader; +use std::io::Cursor; use std::io::Read; use std::io::Seek; use std::io::SeekFrom; @@ -51,6 +55,7 @@ pub struct Metadata { pub location: Option, pub v8_flags: Vec, pub log_level: Option, + pub ca_stores: Option>, pub ca_data: Option>, } @@ -201,6 +206,7 @@ fn metadata_to_flags(metadata: &Metadata) -> Flags { allow_write: permissions.allow_write, v8_flags: metadata.v8_flags.clone(), log_level: metadata.log_level, + ca_stores: metadata.ca_stores.clone(), ..Default::default() } } @@ -227,13 +233,26 @@ pub async fn run( .collect::>(), ); + let mut root_cert_store = program_state + .root_cert_store + .clone() + .unwrap_or_else(create_default_root_cert_store); + + if let Some(cert) = metadata.ca_data { + let reader = &mut BufReader::new(Cursor::new(cert)); + // This function does not return specific errors, if it fails give a generic message. + if let Err(_err) = root_cert_store.add_pem_file(reader) { + return Err(anyhow!("Unable to add pem file to certificate store")); + } + } + let options = WorkerOptions { apply_source_maps: false, args: metadata.argv, debug_flag: metadata.log_level.map_or(false, |l| l == log::Level::Debug), user_agent: version::get_user_agent(), unstable: metadata.unstable, - ca_data: metadata.ca_data, + root_cert_store: Some(root_cert_store), seed: metadata.seed, js_error_create_fn: None, create_web_worker_cb, diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs index c11d26dc9cf85e..cc016382fa88d3 100644 --- a/cli/tests/integration/mod.rs +++ b/cli/tests/integration/mod.rs @@ -2,9 +2,9 @@ use crate::itest; use deno_core::url; -use deno_runtime::deno_net::ops_tls::rustls; -use deno_runtime::deno_net::ops_tls::webpki; use deno_runtime::deno_net::ops_tls::TlsStream; +use deno_runtime::deno_tls::rustls; +use deno_runtime::deno_tls::webpki; use std::fs; use std::io::BufReader; use std::io::Cursor; diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs index 5f89b592d8f134..46ac27b83b4599 100644 --- a/cli/tools/standalone.rs +++ b/cli/tools/standalone.rs @@ -100,6 +100,7 @@ pub fn create_standalone_binary( permissions: flags.clone().into(), v8_flags: flags.v8_flags.clone(), log_level: flags.log_level, + ca_stores: flags.ca_stores, ca_data, }; let mut metadata = serde_json::to_string(&metadata)?.as_bytes().to_vec(); @@ -205,6 +206,7 @@ pub fn compile_to_runtime_flags( allow_read: flags.allow_read, allow_run: flags.allow_run, allow_write: flags.allow_write, + ca_stores: flags.ca_stores, ca_file: flags.ca_file, cache_blocklist: vec![], cache_path: None, diff --git a/extensions/fetch/Cargo.toml b/extensions/fetch/Cargo.toml index 9c9a642229234d..2110712369c9e0 100644 --- a/extensions/fetch/Cargo.toml +++ b/extensions/fetch/Cargo.toml @@ -17,8 +17,10 @@ path = "lib.rs" bytes = "1.0.1" data-url = "0.1.0" deno_core = { version = "0.95.0", path = "../../core" } +deno_tls = { version = "0.1.0", path = "../tls" } deno_web = { version = "0.44.0", path = "../web" } http = "0.2.4" +lazy_static = "1.4.0" reqwest = { version = "0.11.4", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli"] } serde = { version = "1.0.126", features = ["derive"] } tokio = { version = "1.8.1", features = ["full"] } diff --git a/extensions/fetch/lib.rs b/extensions/fetch/lib.rs index ad599b87ca3569..0ac853cbcaf4a6 100644 --- a/extensions/fetch/lib.rs +++ b/extensions/fetch/lib.rs @@ -1,7 +1,7 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use data_url::DataUrl; use deno_core::error::bad_resource_id; -use deno_core::error::generic_error; use deno_core::error::null_opbuf; use deno_core::error::type_error; use deno_core::error::AnyError; @@ -24,16 +24,14 @@ use deno_core::RcRef; use deno_core::Resource; use deno_core::ResourceId; use deno_core::ZeroCopyBuf; - -use data_url::DataUrl; +use deno_tls::create_http_client; +use deno_tls::rustls::RootCertStore; +use deno_tls::Proxy; use deno_web::BlobStore; use http::header::CONTENT_LENGTH; -use reqwest::header::HeaderMap; use reqwest::header::HeaderName; use reqwest::header::HeaderValue; use reqwest::header::HOST; -use reqwest::header::USER_AGENT; -use reqwest::redirect::Policy; use reqwest::Body; use reqwest::Client; use reqwest::Method; @@ -59,7 +57,7 @@ pub use reqwest; // Re-export reqwest pub fn init( user_agent: String, - ca_data: Option>, + root_cert_store: Option, proxy: Option, request_builder_hook: Option RequestBuilder>, ) -> Extension { @@ -84,12 +82,17 @@ pub fn init( ]) .state(move |state| { state.put::({ - create_http_client(user_agent.clone(), ca_data.clone(), proxy.clone()) - .unwrap() + create_http_client( + user_agent.clone(), + root_cert_store.clone(), + None, + proxy.clone(), + ) + .unwrap() }); state.put::(HttpClientDefaults { - ca_data: ca_data.clone(), user_agent: user_agent.clone(), + root_cert_store: root_cert_store.clone(), proxy: proxy.clone(), request_builder_hook, }); @@ -100,7 +103,7 @@ pub fn init( pub struct HttpClientDefaults { pub user_agent: String, - pub ca_data: Option>, + pub root_cert_store: Option, pub proxy: Option, pub request_builder_hook: Option RequestBuilder>, } @@ -501,26 +504,12 @@ impl HttpClientResource { #[serde(rename_all = "camelCase")] #[serde(default)] pub struct CreateHttpClientOptions { + ca_stores: Option>, ca_file: Option, ca_data: Option, proxy: Option, } -#[derive(Deserialize, Default, Debug, Clone)] -#[serde(rename_all = "camelCase")] -#[serde(default)] -pub struct Proxy { - pub url: String, - pub basic_auth: Option, -} - -#[derive(Deserialize, Default, Debug, Clone)] -#[serde(default)] -pub struct BasicAuth { - pub username: String, - pub password: String, -} - pub fn op_create_http_client( state: &mut OpState, args: CreateHttpClientOptions, @@ -541,12 +530,12 @@ where } let defaults = state.borrow::(); - let cert_data = get_cert_data(args.ca_file.as_deref(), args.ca_data.as_deref())?; let client = create_http_client( defaults.user_agent.clone(), - cert_data.or_else(|| defaults.ca_data.clone()), + defaults.root_cert_store.clone(), + cert_data, args.proxy, ) .unwrap(); @@ -569,36 +558,3 @@ fn get_cert_data( Ok(None) } } - -/// Create new instance of async reqwest::Client. This client supports -/// proxies and doesn't follow redirects. -pub fn create_http_client( - user_agent: String, - ca_data: Option>, - proxy: Option, -) -> Result { - let mut headers = HeaderMap::new(); - headers.insert(USER_AGENT, user_agent.parse().unwrap()); - let mut builder = Client::builder() - .redirect(Policy::none()) - .default_headers(headers) - .use_rustls_tls(); - - if let Some(ca_data) = ca_data { - let cert = reqwest::Certificate::from_pem(&ca_data)?; - builder = builder.add_root_certificate(cert); - } - - if let Some(proxy) = proxy { - let mut reqwest_proxy = reqwest::Proxy::all(&proxy.url)?; - if let Some(basic_auth) = &proxy.basic_auth { - reqwest_proxy = - reqwest_proxy.basic_auth(&basic_auth.username, &basic_auth.password); - } - builder = builder.proxy(reqwest_proxy); - } - - builder - .build() - .map_err(|e| generic_error(format!("Unable to build http client: {}", e))) -} diff --git a/extensions/net/Cargo.toml b/extensions/net/Cargo.toml index d774acf881c9bf..58363fd2ebb915 100644 --- a/extensions/net/Cargo.toml +++ b/extensions/net/Cargo.toml @@ -15,13 +15,11 @@ path = "lib.rs" [dependencies] deno_core = { version = "0.95.0", path = "../../core" } +deno_tls = { version = "0.1.0", path = "../tls" } lazy_static = "1.4.0" log = "0.4.14" -rustls = "0.19.0" serde = { version = "1.0.126", features = ["derive"] } tokio = { version = "1.8.1", features = ["full"] } trust-dns-proto = "0.20.3" trust-dns-resolver = { version = "0.20.3", features = ["tokio-runtime", "serde-config"] } -webpki = "0.21.4" -webpki-roots = "0.21.1" diff --git a/extensions/net/lib.rs b/extensions/net/lib.rs index 11d0b4493682ec..6b0b728b156f9d 100644 --- a/extensions/net/lib.rs +++ b/extensions/net/lib.rs @@ -11,6 +11,7 @@ use deno_core::error::AnyError; use deno_core::include_js_files; use deno_core::Extension; use deno_core::OpState; +use deno_tls::rustls::RootCertStore; use std::cell::RefCell; use std::path::Path; use std::path::PathBuf; @@ -90,20 +91,17 @@ pub fn get_unstable_declaration() -> PathBuf { #[derive(Clone)] pub struct DefaultTlsOptions { - pub ca_data: Option>, + pub root_cert_store: Option, } pub fn init( - ca_data: Option>, + root_cert_store: Option, unstable: bool, ) -> Extension { let mut ops_to_register = vec![]; ops_to_register.extend(io::init()); ops_to_register.extend(ops::init::

()); ops_to_register.extend(ops_tls::init::

()); - - let default_tls_options = DefaultTlsOptions { ca_data }; - Extension::builder() .js(include_js_files!( prefix "deno:extensions/net", @@ -113,7 +111,9 @@ pub fn init( )) .ops(ops_to_register) .state(move |state| { - state.put(default_tls_options.clone()); + state.put(DefaultTlsOptions { + root_cert_store: root_cert_store.clone(), + }); state.put(UnstableChecker { unstable }); Ok(()) }) diff --git a/extensions/net/ops_tls.rs b/extensions/net/ops_tls.rs index a082f7f620392e..124da2f037a013 100644 --- a/extensions/net/ops_tls.rs +++ b/extensions/net/ops_tls.rs @@ -1,8 +1,5 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -pub use rustls; -pub use webpki; - use crate::io::TcpStreamResource; use crate::io::TlsStreamResource; use crate::ops::IpAddr; @@ -38,30 +35,29 @@ use deno_core::OpState; use deno_core::RcRef; use deno_core::Resource; use deno_core::ResourceId; +use deno_tls::create_client_config; +use deno_tls::rustls::internal::pemfile::certs; +use deno_tls::rustls::internal::pemfile::pkcs8_private_keys; +use deno_tls::rustls::internal::pemfile::rsa_private_keys; +use deno_tls::rustls::Certificate; +use deno_tls::rustls::ClientConfig; +use deno_tls::rustls::ClientSession; +use deno_tls::rustls::NoClientAuth; +use deno_tls::rustls::PrivateKey; +use deno_tls::rustls::ServerConfig; +use deno_tls::rustls::ServerSession; +use deno_tls::rustls::Session; +use deno_tls::webpki::DNSNameRef; use io::Error; use io::Read; use io::Write; -use rustls::internal::pemfile::certs; -use rustls::internal::pemfile::pkcs8_private_keys; -use rustls::internal::pemfile::rsa_private_keys; -use rustls::Certificate; -use rustls::ClientConfig; -use rustls::ClientSession; -use rustls::NoClientAuth; -use rustls::PrivateKey; -use rustls::ServerConfig; -use rustls::ServerSession; -use rustls::Session; -use rustls::StoresClientSessions; use serde::Deserialize; use std::borrow::Cow; use std::cell::RefCell; -use std::collections::HashMap; use std::convert::From; use std::fs::File; use std::io; use std::io::BufReader; -use std::io::Cursor; use std::io::ErrorKind; use std::ops::Deref; use std::ops::DerefMut; @@ -76,32 +72,6 @@ use tokio::io::ReadBuf; use tokio::net::TcpListener; use tokio::net::TcpStream; use tokio::task::spawn_local; -use webpki::DNSNameRef; - -lazy_static::lazy_static! { - static ref CLIENT_SESSION_MEMORY_CACHE: Arc = - Arc::new(ClientSessionMemoryCache::default()); -} - -#[derive(Default)] -struct ClientSessionMemoryCache(Mutex, Vec>>); - -impl StoresClientSessions for ClientSessionMemoryCache { - fn get(&self, key: &[u8]) -> Option> { - self.0.lock().get(key).cloned() - } - - fn put(&self, key: Vec, value: Vec) -> bool { - let mut sessions = self.0.lock(); - // TODO(bnoordhuis) Evict sessions LRU-style instead of arbitrarily. - while sessions.len() >= 1024 { - let key = sessions.keys().next().unwrap().clone(); - sessions.remove(&key); - } - sessions.insert(key, value); - true - } -} #[derive(Debug)] enum TlsSession { @@ -703,8 +673,6 @@ where n => n, }; let cert_file = args.cert_file.as_deref(); - - let default_tls_options; { super::check_unstable2(&state, "Deno.startTls"); let mut s = state.borrow_mut(); @@ -713,12 +681,28 @@ where if let Some(path) = cert_file { permissions.check_read(Path::new(path))?; } - default_tls_options = s.borrow::().clone(); } + let ca_data = match cert_file { + Some(path) => { + let mut buf = Vec::new(); + File::open(path)?.read_to_end(&mut buf)?; + Some(buf) + } + _ => None, + }; + let hostname_dns = DNSNameRef::try_from_ascii_str(hostname) .map_err(|_| invalid_hostname(hostname))?; + // TODO(@justinmchase): Ideally the certificate store is created once + // and not cloned. The store should be wrapped in Arc to reduce + // copying memory unnecessarily. + let root_cert_store = state + .borrow() + .borrow::() + .root_cert_store + .clone(); let resource_rc = state .borrow_mut() .resource_table @@ -732,22 +716,7 @@ where let local_addr = tcp_stream.local_addr()?; let remote_addr = tcp_stream.peer_addr()?; - let mut tls_config = ClientConfig::new(); - tls_config.set_persistence(CLIENT_SESSION_MEMORY_CACHE.clone()); - tls_config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - if let Some(ca_data) = default_tls_options.ca_data { - let reader = &mut Cursor::new(ca_data); - tls_config.root_store.add_pem_file(reader).unwrap(); - }; - if let Some(path) = cert_file { - let key_file = File::open(path)?; - let reader = &mut BufReader::new(key_file); - tls_config.root_store.add_pem_file(reader).unwrap(); - } - let tls_config = Arc::new(tls_config); - + let tls_config = Arc::new(create_client_config(root_cert_store, ca_data)?); let tls_stream = TlsStream::new_client_side(tcp_stream, &tls_config, hostname_dns); @@ -786,8 +755,6 @@ where }; let port = args.port; let cert_file = args.cert_file.as_deref(); - - let default_tls_options; { let mut s = state.borrow_mut(); let permissions = s.borrow_mut::(); @@ -795,9 +762,22 @@ where if let Some(path) = cert_file { permissions.check_read(Path::new(path))?; } - default_tls_options = s.borrow::().clone(); } + let ca_data = match cert_file { + Some(path) => { + let mut buf = Vec::new(); + File::open(path)?.read_to_end(&mut buf)?; + Some(buf) + } + _ => None, + }; + + let root_cert_store = state + .borrow() + .borrow::() + .root_cert_store + .clone(); let hostname_dns = DNSNameRef::try_from_ascii_str(hostname) .map_err(|_| invalid_hostname(hostname))?; @@ -808,23 +788,7 @@ where let tcp_stream = TcpStream::connect(connect_addr).await?; let local_addr = tcp_stream.local_addr()?; let remote_addr = tcp_stream.peer_addr()?; - - let mut tls_config = ClientConfig::new(); - tls_config.set_persistence(CLIENT_SESSION_MEMORY_CACHE.clone()); - tls_config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - if let Some(ca_data) = default_tls_options.ca_data { - let reader = &mut Cursor::new(ca_data); - tls_config.root_store.add_pem_file(reader).unwrap(); - }; - if let Some(path) = cert_file { - let key_file = File::open(path)?; - let reader = &mut BufReader::new(key_file); - tls_config.root_store.add_pem_file(reader).unwrap(); - } - let tls_config = Arc::new(tls_config); - + let tls_config = Arc::new(create_client_config(root_cert_store, ca_data)?); let tls_stream = TlsStream::new_client_side(tcp_stream, &tls_config, hostname_dns); diff --git a/extensions/tls/Cargo.toml b/extensions/tls/Cargo.toml new file mode 100644 index 00000000000000..ee7be04dc12051 --- /dev/null +++ b/extensions/tls/Cargo.toml @@ -0,0 +1,24 @@ +# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_tls" +version = "0.1.0" +authors = ["the Deno authors"] +edition = "2018" +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" +description = "TLS for Deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno_core = { version = "0.95.0", path = "../../core" } +lazy_static = "1.4.0" +reqwest = { version = "0.11.4", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli"] } +rustls = "0.19.0" +rustls-native-certs = "0.5.0" +serde = { version = "1.0.126", features = ["derive"] } +webpki = "0.21.4" +webpki-roots = "0.21.1" diff --git a/extensions/tls/lib.rs b/extensions/tls/lib.rs new file mode 100644 index 00000000000000..f91249792c8d6c --- /dev/null +++ b/extensions/tls/lib.rs @@ -0,0 +1,129 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +pub use reqwest; +pub use rustls; +pub use rustls_native_certs; +pub use webpki; +pub use webpki_roots; + +use deno_core::error::anyhow; +use deno_core::error::generic_error; +use deno_core::error::AnyError; +use deno_core::parking_lot::Mutex; +use deno_core::Extension; + +use reqwest::header::HeaderMap; +use reqwest::header::USER_AGENT; +use reqwest::redirect::Policy; +use reqwest::Client; +use rustls::ClientConfig; +use rustls::RootCertStore; +use rustls::StoresClientSessions; +use serde::Deserialize; +use std::collections::HashMap; +use std::io::BufReader; +use std::io::Cursor; +use std::sync::Arc; + +/// This extension has no runtime apis, it only exports some shared native functions. +pub fn init() -> Extension { + Extension::builder().build() +} + +#[derive(Deserialize, Default, Debug, Clone)] +#[serde(rename_all = "camelCase")] +#[serde(default)] +pub struct Proxy { + pub url: String, + pub basic_auth: Option, +} + +#[derive(Deserialize, Default, Debug, Clone)] +#[serde(default)] +pub struct BasicAuth { + pub username: String, + pub password: String, +} + +lazy_static::lazy_static! { + static ref CLIENT_SESSION_MEMORY_CACHE: Arc = + Arc::new(ClientSessionMemoryCache::default()); +} + +#[derive(Default)] +struct ClientSessionMemoryCache(Mutex, Vec>>); + +impl StoresClientSessions for ClientSessionMemoryCache { + fn get(&self, key: &[u8]) -> Option> { + self.0.lock().get(key).cloned() + } + + fn put(&self, key: Vec, value: Vec) -> bool { + let mut sessions = self.0.lock(); + // TODO(bnoordhuis) Evict sessions LRU-style instead of arbitrarily. + while sessions.len() >= 1024 { + let key = sessions.keys().next().unwrap().clone(); + sessions.remove(&key); + } + sessions.insert(key, value); + true + } +} + +pub fn create_default_root_cert_store() -> RootCertStore { + let mut root_cert_store = RootCertStore::empty(); + // TODO(@justinmchase): Consider also loading the system keychain here + root_cert_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + root_cert_store +} + +pub fn create_client_config( + root_cert_store: Option, + ca_data: Option>, +) -> Result { + let mut tls_config = ClientConfig::new(); + tls_config.set_persistence(CLIENT_SESSION_MEMORY_CACHE.clone()); + tls_config.root_store = + root_cert_store.unwrap_or_else(create_default_root_cert_store); + + // If a custom cert is specified, add it to the store + if let Some(cert) = ca_data { + let reader = &mut BufReader::new(Cursor::new(cert)); + // This function does not return specific errors, if it fails give a generic message. + if let Err(_err) = tls_config.root_store.add_pem_file(reader) { + return Err(anyhow!("Unable to add pem file to certificate store")); + } + } + + Ok(tls_config) +} + +/// Create new instance of async reqwest::Client. This client supports +/// proxies and doesn't follow redirects. +pub fn create_http_client( + user_agent: String, + root_cert_store: Option, + ca_data: Option>, + proxy: Option, +) -> Result { + let tls_config = create_client_config(root_cert_store, ca_data)?; + let mut headers = HeaderMap::new(); + headers.insert(USER_AGENT, user_agent.parse().unwrap()); + let mut builder = Client::builder() + .redirect(Policy::none()) + .default_headers(headers) + .use_preconfigured_tls(tls_config); + + if let Some(proxy) = proxy { + let mut reqwest_proxy = reqwest::Proxy::all(&proxy.url)?; + if let Some(basic_auth) = &proxy.basic_auth { + reqwest_proxy = + reqwest_proxy.basic_auth(&basic_auth.username, &basic_auth.password); + } + builder = builder.proxy(reqwest_proxy); + } + + builder + .build() + .map_err(|e| generic_error(format!("Unable to build http client: {}", e))) +} diff --git a/extensions/websocket/Cargo.toml b/extensions/websocket/Cargo.toml index 3e17059502883d..1e01c269195b19 100644 --- a/extensions/websocket/Cargo.toml +++ b/extensions/websocket/Cargo.toml @@ -15,11 +15,10 @@ path = "lib.rs" [dependencies] deno_core = { version = "0.95.0", path = "../../core" } +deno_tls = { version = "0.1.0", path = "../tls" } http = "0.2.4" hyper = { version = "0.14.9" } serde = { version = "1.0.126", features = ["derive"] } tokio = { version = "1.8.1", features = ["full"] } tokio-rustls = "0.22.0" tokio-tungstenite = { version = "0.14.0", features = ["rustls-tls"] } -webpki = "0.21.4" -webpki-roots = "0.21.1" diff --git a/extensions/websocket/lib.rs b/extensions/websocket/lib.rs index f5bf15c79b97c3..01f0a523d095fe 100644 --- a/extensions/websocket/lib.rs +++ b/extensions/websocket/lib.rs @@ -22,31 +22,31 @@ use deno_core::RcRef; use deno_core::Resource; use deno_core::ResourceId; use deno_core::ZeroCopyBuf; +use deno_tls::create_client_config; +use deno_tls::webpki::DNSNameRef; use http::{Method, Request, Uri}; use serde::Deserialize; use serde::Serialize; use std::borrow::Cow; use std::cell::RefCell; -use std::io::BufReader; -use std::io::Cursor; use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; use tokio::net::TcpStream; -use tokio_rustls::{rustls::ClientConfig, TlsConnector}; +use tokio_rustls::rustls::RootCertStore; +use tokio_rustls::TlsConnector; use tokio_tungstenite::tungstenite::{ handshake::client::Response, protocol::frame::coding::CloseCode, protocol::CloseFrame, Message, }; use tokio_tungstenite::MaybeTlsStream; use tokio_tungstenite::{client_async, WebSocketStream}; -use webpki::DNSNameRef; pub use tokio_tungstenite; // Re-export tokio_tungstenite #[derive(Clone)] -pub struct WsCaData(pub Vec); +pub struct WsRootStore(pub Option); #[derive(Clone)] pub struct WsUserAgent(pub String); @@ -197,7 +197,7 @@ where ); } - let ws_ca_data = state.borrow().try_borrow::().cloned(); + let root_cert_store = state.borrow().borrow::().0.clone(); let user_agent = state.borrow().borrow::().0.clone(); let uri: Uri = args.url.parse()?; let mut request = Request::builder().method(Method::GET).uri(&uri); @@ -221,17 +221,8 @@ where let socket: MaybeTlsStream = match uri.scheme_str() { Some("ws") => MaybeTlsStream::Plain(tcp_socket), Some("wss") => { - let mut config = ClientConfig::new(); - config - .root_store - .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); - - if let Some(ws_ca_data) = ws_ca_data { - let reader = &mut BufReader::new(Cursor::new(ws_ca_data.0)); - config.root_store.add_pem_file(reader).unwrap(); - } - - let tls_connector = TlsConnector::from(Arc::new(config)); + let tls_config = create_client_config(root_cert_store, None)?; + let tls_connector = TlsConnector::from(Arc::new(tls_config)); let dnsname = DNSNameRef::try_from_ascii_str(domain) .map_err(|_| invalid_hostname(domain))?; let tls_socket = tls_connector.connect(dnsname, tcp_socket).await?; @@ -385,7 +376,7 @@ pub async fn op_ws_next_event( pub fn init( user_agent: String, - ca_data: Option>, + root_cert_store: Option, ) -> Extension { Extension::builder() .js(include_js_files!( @@ -404,9 +395,7 @@ pub fn init( ]) .state(move |state| { state.put::(WsUserAgent(user_agent.clone())); - if let Some(ca_data) = ca_data.clone() { - state.put::(WsCaData(ca_data)); - } + state.put::(WsRootStore(root_cert_store.clone())); Ok(()) }) .build() diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index b2bad984487744..78bc369ef02bd8 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -27,6 +27,7 @@ deno_ffi = { version = "0.1.0", path = "../extensions/ffi" } deno_http = { version = "0.4.0", path = "../extensions/http" } deno_net = { version = "0.4.0", path = "../extensions/net" } deno_timers = { version = "0.11.0", path = "../extensions/timers" } +deno_tls = { version = "0.1.0", path = "../extensions/tls" } deno_url = { version = "0.13.0", path = "../extensions/url" } deno_web = { version = "0.44.0", path = "../extensions/web" } deno_webgpu = { version = "0.14.0", path = "../extensions/webgpu" } @@ -48,6 +49,7 @@ deno_ffi = { version = "0.1.0", path = "../extensions/ffi" } deno_http = { version = "0.4.0", path = "../extensions/http" } deno_net = { version = "0.4.0", path = "../extensions/net" } deno_timers = { version = "0.11.0", path = "../extensions/timers" } +deno_tls = { version = "0.1.0", path = "../extensions/tls" } deno_url = { version = "0.13.0", path = "../extensions/url" } deno_web = { version = "0.44.0", path = "../extensions/web" } deno_webgpu = { version = "0.14.0", path = "../extensions/webgpu" } diff --git a/runtime/build.rs b/runtime/build.rs index bb7947f36ce804..e6f7de6411721f 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -41,6 +41,7 @@ fn create_runtime_snapshot(snapshot_path: &Path, files: Vec) { deno_webidl::init(), deno_console::init(), deno_url::init(), + deno_tls::init(), deno_web::init(deno_web::BlobStore::default(), Default::default()), deno_fetch::init::( "".to_owned(), diff --git a/runtime/examples/hello_runtime.rs b/runtime/examples/hello_runtime.rs index 4883ee7c720f14..eaedcac10db262 100644 --- a/runtime/examples/hello_runtime.rs +++ b/runtime/examples/hello_runtime.rs @@ -27,7 +27,7 @@ async fn main() -> Result<(), AnyError> { args: vec![], debug_flag: false, unstable: false, - ca_data: None, + root_cert_store: None, user_agent: "hello_runtime".to_string(), seed: None, js_error_create_fn: None, diff --git a/runtime/lib.rs b/runtime/lib.rs index d7aaa8eecd54f8..37d48def151dda 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -8,6 +8,7 @@ pub use deno_ffi; pub use deno_http; pub use deno_net; pub use deno_timers; +pub use deno_tls; pub use deno_url; pub use deno_web; pub use deno_webgpu; diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index 74e5fbafe20ee3..3f68fc4e64601d 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -29,6 +29,7 @@ use deno_core::ModuleLoader; use deno_core::ModuleSpecifier; use deno_core::RuntimeOptions; use deno_core::SharedArrayBufferStore; +use deno_tls::rustls::RootCertStore; use deno_web::create_entangled_message_port; use deno_web::BlobStore; use deno_web::MessagePort; @@ -252,7 +253,7 @@ pub struct WebWorkerOptions { pub args: Vec, pub debug_flag: bool, pub unstable: bool, - pub ca_data: Option>, + pub root_cert_store: Option, pub user_agent: String, pub seed: Option, pub module_loader: Rc, @@ -300,13 +301,13 @@ impl WebWorker { deno_web::init(options.blob_store.clone(), Some(main_module.clone())), deno_fetch::init::( options.user_agent.clone(), - options.ca_data.clone(), + options.root_cert_store.clone(), None, None, ), deno_websocket::init::( options.user_agent.clone(), - options.ca_data.clone(), + options.root_cert_store.clone(), ), deno_broadcast_channel::init( options.broadcast_channel.clone(), @@ -336,8 +337,9 @@ impl WebWorker { vec![ ops::fs_events::init(), ops::fs::init(), + deno_tls::init(), deno_net::init::( - options.ca_data.clone(), + options.root_cert_store.clone(), options.unstable, ), ops::os::init(), diff --git a/runtime/worker.rs b/runtime/worker.rs index c64ef2baf8e2e2..69602d0dd6bef9 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -22,6 +22,7 @@ use deno_core::ModuleLoader; use deno_core::ModuleSpecifier; use deno_core::RuntimeOptions; use deno_core::SharedArrayBufferStore; +use deno_tls::rustls::RootCertStore; use deno_web::BlobStore; use log::debug; use std::env; @@ -49,7 +50,7 @@ pub struct WorkerOptions { pub args: Vec, pub debug_flag: bool, pub unstable: bool, - pub ca_data: Option>, + pub root_cert_store: Option, pub user_agent: String, pub seed: Option, pub module_loader: Rc, @@ -99,13 +100,13 @@ impl MainWorker { deno_web::init(options.blob_store.clone(), options.location.clone()), deno_fetch::init::( options.user_agent.clone(), - options.ca_data.clone(), + options.root_cert_store.clone(), None, None, ), deno_websocket::init::( options.user_agent.clone(), - options.ca_data.clone(), + options.root_cert_store.clone(), ), deno_webstorage::init(options.origin_storage_dir.clone()), deno_crypto::init(options.seed), @@ -126,7 +127,11 @@ impl MainWorker { ops::fs::init(), ops::io::init(), ops::io::init_stdio(), - deno_net::init::(options.ca_data.clone(), options.unstable), + deno_tls::init(), + deno_net::init::( + options.root_cert_store.clone(), + options.unstable, + ), ops::os::init(), ops::permissions::init(), ops::process::init(), @@ -295,7 +300,7 @@ mod tests { args: vec![], debug_flag: false, unstable: false, - ca_data: None, + root_cert_store: None, seed: None, js_error_create_fn: None, create_web_worker_cb: Arc::new(|_| unreachable!()),