diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2021-08-11 12:27:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-11 12:27:05 +0200 |
commit | a0285e2eb88f6254f6494b0ecd1878db3a3b2a58 (patch) | |
tree | 90671b004537e20f9493fd3277ffd21d30b39a0e /ext/tls/lib.rs | |
parent | 3a6994115176781b3a93d70794b1b81bc95e42b4 (diff) |
Rename extensions/ directory to ext/ (#11643)
Diffstat (limited to 'ext/tls/lib.rs')
-rw-r--r-- | ext/tls/lib.rs | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/ext/tls/lib.rs b/ext/tls/lib.rs new file mode 100644 index 000000000..8f56f0ffd --- /dev/null +++ b/ext/tls/lib.rs @@ -0,0 +1,192 @@ +// 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::internal::msgs::handshake::DigitallySignedStruct; +use rustls::Certificate; +use rustls::ClientConfig; +use rustls::HandshakeSignatureValid; +use rustls::RootCertStore; +use rustls::ServerCertVerified; +use rustls::ServerCertVerifier; +use rustls::StoresClientSessions; +use rustls::TLSError; +use rustls::WebPKIVerifier; +use serde::Deserialize; +use std::collections::HashMap; +use std::io::BufReader; +use std::io::Cursor; +use std::sync::Arc; +use webpki::DNSNameRef; + +/// This extension has no runtime apis, it only exports some shared native functions. +pub fn init() -> Extension { + Extension::builder().build() +} + +pub struct NoCertificateVerification(pub Vec<String>); + +impl ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + roots: &RootCertStore, + presented_certs: &[Certificate], + dns_name_ref: DNSNameRef<'_>, + ocsp: &[u8], + ) -> Result<ServerCertVerified, TLSError> { + let dns_name: &str = dns_name_ref.into(); + let dns_name: String = dns_name.to_owned(); + if self.0.is_empty() || self.0.contains(&dns_name) { + Ok(ServerCertVerified::assertion()) + } else { + WebPKIVerifier::new().verify_server_cert( + roots, + presented_certs, + dns_name_ref, + ocsp, + ) + } + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, TLSError> { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result<HandshakeSignatureValid, TLSError> { + Ok(HandshakeSignatureValid::assertion()) + } +} + +#[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>>, + unsafely_ignore_certificate_errors: Option<Vec<String>>, +) -> 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")); + } + } + + if let Some(ic_allowlist) = unsafely_ignore_certificate_errors { + tls_config.dangerous().set_certificate_verifier(Arc::new( + NoCertificateVerification(ic_allowlist), + )); + } + + 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>, + unsafely_ignore_certificate_errors: Option<Vec<String>>, +) -> Result<Client, AnyError> { + let tls_config = create_client_config( + root_cert_store, + ca_data, + unsafely_ignore_certificate_errors, + )?; + 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))) +} |