diff options
Diffstat (limited to 'extensions/tls/lib.rs')
-rw-r--r-- | extensions/tls/lib.rs | 129 |
1 files changed, 129 insertions, 0 deletions
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))) +} |