Skip to content

Commit

Permalink
fix(lsp): respect DENO_CERT and other options related to TLS certs (d…
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk committed Jan 24, 2022
1 parent 3959d9f commit 3ec248c
Show file tree
Hide file tree
Showing 11 changed files with 393 additions and 101 deletions.
80 changes: 80 additions & 0 deletions cli/file_fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::version::get_user_agent;

use data_url::DataUrl;
use deno_ast::MediaType;
use deno_core::anyhow::anyhow;
use deno_core::error::custom_error;
use deno_core::error::generic_error;
use deno_core::error::uri_error;
Expand All @@ -22,7 +23,11 @@ use deno_core::parking_lot::Mutex;
use deno_core::ModuleSpecifier;
use deno_runtime::deno_fetch::create_http_client;
use deno_runtime::deno_fetch::reqwest;
use deno_runtime::deno_tls::rustls;
use deno_runtime::deno_tls::rustls::RootCertStore;
use deno_runtime::deno_tls::rustls_native_certs::load_native_certs;
use deno_runtime::deno_tls::rustls_pemfile;
use deno_runtime::deno_tls::webpki_roots;
use deno_runtime::deno_web::BlobStore;
use deno_runtime::permissions::Permissions;
use log::debug;
Expand All @@ -31,6 +36,7 @@ use std::collections::HashMap;
use std::env;
use std::fs;
use std::future::Future;
use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;
use std::pin::Pin;
Expand Down Expand Up @@ -161,6 +167,80 @@ fn fetch_local(specifier: &ModuleSpecifier) -> Result<File, AnyError> {
})
}

/// Create and populate a root cert store based on the passed options and
/// environment.
pub(crate) fn get_root_cert_store(
maybe_root_path: Option<PathBuf>,
maybe_ca_stores: Option<Vec<String>>,
maybe_ca_file: Option<String>,
) -> Result<RootCertStore, AnyError> {
let mut root_cert_store = RootCertStore::empty();
let ca_stores: Vec<String> = maybe_ca_stores
.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(
webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
)
}),
);
}
"system" => {
let roots = load_native_certs().expect("could not load platform certs");
for root in roots {
root_cert_store
.add(&rustls::Certificate(root.0))
.expect("Failed to add platform cert to root cert store");
}
}
_ => {
return Err(anyhow!("Unknown certificate store \"{}\" specified (allowed: \"system,mozilla\")", store));
}
}
}

let ca_file = maybe_ca_file.or_else(|| env::var("DENO_CERT").ok());
if let Some(ca_file) = ca_file {
let ca_file = if let Some(root) = &maybe_root_path {
root.join(&ca_file)
} else {
PathBuf::from(ca_file)
};
let certfile = fs::File::open(&ca_file)?;
let mut reader = BufReader::new(certfile);

match rustls_pemfile::certs(&mut reader) {
Ok(certs) => {
root_cert_store.add_parsable_certificates(&certs);
}
Err(e) => {
return Err(anyhow!(
"Unable to add pem file to certificate store: {}",
e
));
}
}
}

Ok(root_cert_store)
}

/// Returns the decoded body and content-type of a provided
/// data URL.
pub fn get_source_from_data_url(
Expand Down
6 changes: 6 additions & 0 deletions cli/lsp/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,19 @@ impl CacheServer {
maybe_cache_path: Option<PathBuf>,
maybe_import_map: Option<Arc<ImportMap>>,
maybe_config_file: Option<ConfigFile>,
maybe_ca_stores: Option<Vec<String>>,
maybe_ca_file: Option<String>,
unsafely_ignore_certificate_errors: Option<Vec<String>>,
) -> Self {
let (tx, mut rx) = mpsc::unbounded_channel::<Request>();
let _join_handle = thread::spawn(move || {
let runtime = create_basic_runtime();
runtime.block_on(async {
let ps = ProcState::build(Flags {
cache_path: maybe_cache_path,
ca_stores: maybe_ca_stores,
ca_file: maybe_ca_file,
unsafely_ignore_certificate_errors,
..Default::default()
})
.await
Expand Down
16 changes: 16 additions & 0 deletions cli/lsp/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ pub struct WorkspaceSettings {
/// cache/DENO_DIR for the language server.
pub cache: Option<String>,

/// Override the default stores used to validate certificates. This overrides
/// the environment variable `DENO_TLS_CA_STORE` if present.
pub certificate_stores: Option<Vec<String>>,

/// An option that points to a path string of the config file to apply to
/// code within the workspace.
pub config: Option<String>,
Expand All @@ -179,6 +183,15 @@ pub struct WorkspaceSettings {
#[serde(default)]
pub suggest: CompletionSettings,

/// An option which sets the cert file to use when attempting to fetch remote
/// resources. This overrides `DENO_CERT` if present.
pub tls_certificate: Option<String>,

/// An option, if set, will unsafely ignore certificate errors when fetching
/// remote resources.
#[serde(default)]
pub unsafely_ignore_certificate_errors: Option<Vec<String>>,

#[serde(default)]
pub unstable: bool,
}
Expand Down Expand Up @@ -485,6 +498,7 @@ mod tests {
WorkspaceSettings {
enable: false,
cache: None,
certificate_stores: None,
config: None,
import_map: None,
code_lens: CodeLensSettings {
Expand All @@ -505,6 +519,8 @@ mod tests {
hosts: HashMap::new(),
}
},
tls_certificate: None,
unsafely_ignore_certificate_errors: None,
unstable: false,
}
);
Expand Down
62 changes: 44 additions & 18 deletions cli/lsp/language_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ use super::lsp_custom;
use super::parent_process_checker;
use super::performance::Performance;
use super::refactor;
use super::registries;
use super::registries::ModuleRegistry;
use super::registries::ModuleRegistryOptions;
use super::text;
use super::tsc;
use super::tsc::AssetDocument;
Expand Down Expand Up @@ -96,7 +97,7 @@ pub(crate) struct Inner {
/// on disk or "open" within the client.
pub(crate) documents: Documents,
/// Handles module registries, which allow discovery of modules
module_registries: registries::ModuleRegistry,
module_registries: ModuleRegistry,
/// The path to the module registries cache
module_registries_location: PathBuf,
/// An optional path to the DENO_DIR which has been specified in the client
Expand Down Expand Up @@ -139,8 +140,11 @@ impl Inner {
let dir = deno_dir::DenoDir::new(maybe_custom_root)
.expect("could not access DENO_DIR");
let module_registries_location = dir.root.join(REGISTRIES_PATH);
let module_registries =
registries::ModuleRegistry::new(&module_registries_location);
let module_registries = ModuleRegistry::new(
&module_registries_location,
ModuleRegistryOptions::default(),
)
.expect("could not create module registries");
let location = dir.root.join(CACHE_PATH);
let documents = Documents::new(&location);
let performance = Arc::new(Performance::default());
Expand Down Expand Up @@ -425,11 +429,23 @@ impl Inner {
let maybe_custom_root = maybe_cache_path
.clone()
.or_else(|| env::var("DENO_DIR").map(String::into).ok());
let dir = deno_dir::DenoDir::new(maybe_custom_root)
.expect("could not access DENO_DIR");
let dir = deno_dir::DenoDir::new(maybe_custom_root)?;
let module_registries_location = dir.root.join(REGISTRIES_PATH);
self.module_registries =
registries::ModuleRegistry::new(&module_registries_location);
let workspace_settings = self.config.get_workspace_settings();
let maybe_root_path = self
.root_uri
.as_ref()
.and_then(|uri| fs_util::specifier_to_file_path(uri).ok());
self.module_registries = ModuleRegistry::new(
&module_registries_location,
ModuleRegistryOptions {
maybe_root_path,
maybe_ca_stores: workspace_settings.certificate_stores.clone(),
maybe_ca_file: workspace_settings.tls_certificate.clone(),
unsafely_ignore_certificate_errors: workspace_settings
.unsafely_ignore_certificate_errors,
},
)?;
self.module_registries_location = module_registries_location;
self.documents.set_location(dir.root.join(CACHE_PATH));
self.maybe_cache_path = maybe_cache_path;
Expand Down Expand Up @@ -496,14 +512,23 @@ impl Inner {

async fn update_registries(&mut self) -> Result<(), AnyError> {
let mark = self.performance.mark("update_registries", None::<()>);
for (registry, enabled) in self
.config
.get_workspace_settings()
.suggest
.imports
.hosts
.iter()
{
let workspace_settings = self.config.get_workspace_settings();
let maybe_root_path = self
.root_uri
.as_ref()
.and_then(|uri| fs_util::specifier_to_file_path(uri).ok());
self.module_registries = ModuleRegistry::new(
&self.module_registries_location,
ModuleRegistryOptions {
maybe_root_path,
maybe_ca_stores: workspace_settings.certificate_stores.clone(),
maybe_ca_file: workspace_settings.tls_certificate.clone(),
unsafely_ignore_certificate_errors: workspace_settings
.unsafely_ignore_certificate_errors
.clone(),
},
)?;
for (registry, enabled) in workspace_settings.suggest.imports.hosts.iter() {
if *enabled {
lsp_log!("Enabling import suggestions for: {}", registry);
self.module_registries.enable(registry).await?;
Expand Down Expand Up @@ -2583,6 +2608,9 @@ impl Inner {
self.maybe_cache_path.clone(),
self.maybe_import_map.clone(),
self.maybe_config_file.clone(),
None,
None,
None,
)
.await,
);
Expand Down Expand Up @@ -2616,8 +2644,6 @@ impl Inner {
error!("Unable to remove registries cache: {}", err);
LspError::internal_error()
})?;
self.module_registries =
registries::ModuleRegistry::new(&self.module_registries_location);
self.update_registries().await.map_err(|err| {
error!("Unable to update registries: {}", err);
LspError::internal_error()
Expand Down
Loading

0 comments on commit 3ec248c

Please sign in to comment.