diff options
Diffstat (limited to 'extensions/tls')
-rw-r--r-- | extensions/tls/Cargo.toml | 24 | ||||
-rw-r--r-- | extensions/tls/lib.rs | 129 |
2 files changed, 153 insertions, 0 deletions
diff --git a/extensions/tls/Cargo.toml b/extensions/tls/Cargo.toml new file mode 100644 index 000000000..ee7be04dc --- /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 000000000..f91249792 --- /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<BasicAuth>, +} + +#[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<ClientSessionMemoryCache> = + Arc::new(ClientSessionMemoryCache::default()); +} + +#[derive(Default)] +struct ClientSessionMemoryCache(Mutex<HashMap<Vec<u8>, Vec<u8>>>); + +impl StoresClientSessions for ClientSessionMemoryCache { + fn get(&self, key: &[u8]) -> Option<Vec<u8>> { + self.0.lock().get(key).cloned() + } + + fn put(&self, key: Vec<u8>, value: Vec<u8>) -> 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<RootCertStore>, + ca_data: Option<Vec<u8>>, +) -> Result<ClientConfig, AnyError> { + 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<RootCertStore>, + ca_data: Option<Vec<u8>>, + proxy: Option<Proxy>, +) -> Result<Client, AnyError> { + 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))) +} |