diff --git a/core/Cargo.lock b/core/Cargo.lock index f00fb54..2eeba0a 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -63,15 +63,6 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "cc" version = "1.1.15" @@ -143,35 +134,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "cpufeatures" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "dirs" version = "5.0.1" @@ -224,16 +186,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.2.15" @@ -383,17 +335,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "shlex" version = "1.3.0" @@ -450,12 +391,6 @@ dependencies = [ "syn", ] -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - [[package]] name = "unicode-ident" version = "1.0.12" @@ -470,7 +405,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vanish" -version = "0.1.2" +version = "0.2.0" dependencies = [ "base64", "clap", @@ -478,7 +413,6 @@ dependencies = [ "dirs", "lazy_static", "openssl", - "sha2", "tempfile", ] @@ -488,12 +422,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/core/Cargo.toml b/core/Cargo.toml index f924eba..25af1c8 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vanish" -version = "0.1.2" +version = "0.2.0" description = "A simple config tool to make locally trusted X.509 development certificates for your domains" edition = "2021" license-file = "LICENSE" @@ -14,7 +14,6 @@ readme = "README.md" openssl = "0.10" clap = { version = "4.4.8", features = ["derive"] } dirs = "5.0" -sha2 = "0.10" lazy_static = "1.4" colored = "2.0" base64 = "0.21" diff --git a/core/src/commands/generate.rs b/core/src/commands/generate.rs index 6e7cf22..fcc6eed 100644 --- a/core/src/commands/generate.rs +++ b/core/src/commands/generate.rs @@ -168,6 +168,10 @@ pub fn generate( let default_cert_key_files: Option<(X509, PKey)> = get_certificates_from_data_dir(); if let Some((d_cert, d_pkey)) = default_cert_key_files { + if install { + println!(); + generate_install(&d_cert)?; + } if let Some(csr) = &csr { let distinguished_name: DistinguishedName = create_distinguished_name(&commonname, &country, &state); @@ -230,10 +234,6 @@ pub fn generate( output.unwrap() ); } - - if install { - generate_install(d_cert)?; - } } else { if noca { eprintln!( @@ -247,6 +247,12 @@ pub fn generate( let (created_cert, created_key) = CACert::new(distinguished_name)?.generate_certificate()?; save_generated_cert_key_files(&created_cert, &created_key)?; + + if install { + println!(); + generate_install(&created_cert)?; + } + if let Some(csr) = &csr { let distinguished_name: DistinguishedName = create_distinguished_name(&commonname, &country, &state); @@ -322,9 +328,6 @@ pub fn generate( output.unwrap() ); } - if install { - generate_install(created_cert)?; - } } println!(); Ok(()) diff --git a/core/src/commands/utils.rs b/core/src/commands/utils.rs index d5a535a..6bb486e 100644 --- a/core/src/commands/utils.rs +++ b/core/src/commands/utils.rs @@ -2,7 +2,9 @@ use crate::{ trust_stores::{ firefox::FirefoxTrustStore, nss::NSSValue, nss_profile::NSSProfile, utils::check_if_firefox_exists, CAValue, - }, utils::get_unique_hash, x509::{ca_req::CAReq, distinguished_name::DistinguishedName, leaf_cert::LeafCert} + }, + utils::get_unique_hash, + x509::{ca_req::CAReq, distinguished_name::DistinguishedName, leaf_cert::LeafCert}, }; use colored::*; use openssl::{ @@ -15,8 +17,10 @@ use std::{ path::{Path, PathBuf}, }; -pub fn generate_install(cert: X509) -> Result<(), Box> { - let ca_value_object: CAValue = CAValue { certificate: cert }; +pub fn generate_install(cert: &X509) -> Result<(), Box> { + let ca_value_object: CAValue = CAValue { + certificate: cert.clone(), + }; ca_value_object.install_certificate()?; let nss_profile_object: NSSProfile = NSSProfile::new(); let caroot: String = "/home/jerry/.local/share/vanish/ca_cert.pem".to_string(); diff --git a/core/src/main.rs b/core/src/main.rs index 065dec6..c17eb23 100644 --- a/core/src/main.rs +++ b/core/src/main.rs @@ -12,7 +12,7 @@ mod utils_tests; #[derive(Parser)] #[clap( name = "A simple config tool to make locally trusted X.509 development certificates for your domains", - version = "0.1.2", + version = "0.2.0", author = "Shubham Singh" )] struct CLI { diff --git a/core/src/trust_stores/firefox.rs b/core/src/trust_stores/firefox.rs index 2c87895..6445970 100644 --- a/core/src/trust_stores/firefox.rs +++ b/core/src/trust_stores/firefox.rs @@ -1,4 +1,7 @@ +use colored::Colorize; + use super::errors::FirefoxTrustStoreError; +use std::borrow::Cow; use std::process::{exit, Stdio}; use std::{ env, fs, io, @@ -80,6 +83,39 @@ impl FirefoxTrustStore { }) } + fn is_certificate_installed(&self, cert_dir: &Path) -> Result { + match &self.certutil_path { + Some(certutil) => { + let output = Command::new(certutil) + .arg("-L") + .arg("-d") + .arg(cert_dir.to_str().unwrap()) + .output() + .map_err(|err: io::Error| FirefoxTrustStoreError::IOError(err))?; + + if output.status.success() { + let stdout: Cow<'_, str> = String::from_utf8_lossy(&output.stdout); + + if stdout.contains(&self.ca_unique_name) { + return Ok(true); + } + } else { + eprintln!( + "{}: Failed to list certificates in {:?}", + "Error".red(), + cert_dir + ); + } + } + None => { + eprint!("{}: No certutil found. Please install!", "Error".red()); + exit(1); + } + } + + Ok(false) + } + pub fn find_cert_directories(&self) -> Result, FirefoxTrustStoreError> { let mut cert_dirs: Vec = Vec::new(); for profile_dir in &self.firefox_profile { @@ -111,39 +147,55 @@ impl FirefoxTrustStore { } pub fn install_firefox_certificates(&self, cert_paths: Vec) { - match &self.certutil_path { - Some(path) => { - for cert_dir in cert_paths { - let cmd_result: Result = Command::new(path) - .arg("-A") - .arg("-d") - .arg(cert_dir.to_str().unwrap()) - .arg("-t") - .arg("C,,") - .arg("-n") - .arg(&self.ca_unique_name) - .arg("-i") - .arg(&self.vanish_ca_path) - .stdout(Stdio::null()) - .status(); - - match cmd_result { - Ok(status) if status.success() => { - println!("Successfully installed certificate in {:?}", cert_dir); - } - Ok(_) => { - eprintln!("Failed to install certificate in {:?}", cert_dir); + let all_installed: bool = cert_paths.iter().all(|cert_dir: &PathBuf| { + match self.is_certificate_installed(cert_dir) { + Ok(true) => true, + Ok(false) => false, + Err(_) => false, + } + }); + + if all_installed { + println!( + "{}: Certificate already installed in all Firefox profiles ✅.", + "Note".green() + ); + return; + } else { + match &self.certutil_path { + Some(path) => { + for cert_dir in cert_paths { + if let Ok(true) = self.is_certificate_installed(&cert_dir) { + continue; } - Err(err) => { - eprintln!("Error executing certutil: {:?}", err); + + let cmd_result: Result = Command::new(path) + .arg("-A") + .arg("-d") + .arg(cert_dir.to_str().unwrap()) + .arg("-t") + .arg("C,,") + .arg("-n") + .arg(&self.ca_unique_name) + .arg("-i") + .arg(&self.vanish_ca_path) + .stdout(Stdio::null()) + .status(); + + match cmd_result { + Ok(_) => {} + Err(err) => { + eprintln!("{}: executing certutil: {:?}", "Error".red(), err); + } } } + println!("Certificate successfully installed in all Firefox profiles ✅."); } - } - None => { - eprint!("No certutil found. Please install!"); - exit(1); - } - }; + None => { + eprint!("No certutil found. Please install!"); + exit(1); + } + }; + } } } diff --git a/core/src/trust_stores/nss.rs b/core/src/trust_stores/nss.rs index 95c7bbc..fcc62d3 100644 --- a/core/src/trust_stores/nss.rs +++ b/core/src/trust_stores/nss.rs @@ -1,9 +1,10 @@ use crate::trust_stores::nss_profile::NSSProfile; use colored::*; use std::{ + borrow::Cow, fs, io, path::Path, - process::{Command, ExitStatus, Stdio}, + process::{exit, Command, ExitStatus, Stdio}, }; pub struct NSSValue { @@ -21,6 +22,39 @@ impl NSSValue { } } + fn is_certificate_installed(&self, cert_dir: &Path) -> Result { + match &self.profile.certutil_path { + Some(certutil) => { + let output = Command::new(certutil) + .arg("-L") + .arg("-d") + .arg(cert_dir.to_str().unwrap()) + .output() + .map_err(|err: io::Error| err)?; + + if output.status.success() { + let stdout: Cow<'_, str> = String::from_utf8_lossy(&output.stdout); + + if stdout.contains(&self.ca_unique_name) { + return Ok(true); + } + } else { + eprintln!( + "{}: Failed to list certificates in {:?}", + "Error".red(), + cert_dir + ); + } + } + None => { + eprint!("{}: No certutil found. Please install!", "Error".red()); + exit(1); + } + } + + Ok(false) + } + pub fn check_nss(&self) -> bool { if !self.profile.has_certutil { return false; @@ -53,38 +87,62 @@ impl NSSValue { } pub fn install_nss(&self) -> bool { - if self.for_each_nss_profile(|profile: &str| { - let cmd: Result = - Command::new(self.profile.certutil_path.as_ref().unwrap()) - .arg("-A") - .arg("-d") - .arg(profile) - .arg("-t") - .arg("C,,") - .arg("-n") - .arg(&self.ca_unique_name) - .arg("-i") - .arg(&self.caroot) - .stdout(Stdio::null()) - .status(); + let mut all_installed: bool = true; + let mut any_installed: bool = false; - if let Err(err) = cmd { - eprintln!("Error: {:?}", err); + self.for_each_nss_profile(|profile: &str| { + let cert_dir = Path::new(profile); + if let Ok(installed) = self.is_certificate_installed(cert_dir) { + if installed { + any_installed = true; + } else { + all_installed = false; + } + } else { + all_installed = false; } - }) == 0 - { - eprintln!("{}: No NSS security databases found", "Error".red()); - return false; - } + }); - if !self.check_nss() { - eprintln!( - "Installing in NSS failed. Please report the issue with details about your environment." + if all_installed { + println!( + "{}: Certificate already installed in all NSS (Browser) profiles ✅.", + "Note".green() ); - return false; + return true; } - true + let installed: bool = self.for_each_nss_profile(|profile: &str| { + let cert_dir = Path::new(profile); + if let Ok(installed) = self.is_certificate_installed(cert_dir) { + if !installed { + let cmd: Result = + Command::new(self.profile.certutil_path.as_ref().unwrap()) + .arg("-A") + .arg("-d") + .arg(profile) + .arg("-t") + .arg("C,,") + .arg("-n") + .arg(&self.ca_unique_name) + .arg("-i") + .arg(&self.caroot) + .stdout(Stdio::null()) + .status(); + + if let Err(err) = cmd { + eprintln!("Error installing in profile {}: {:?}", profile, err); + } + } + } + }) > 0; + + if installed && self.check_nss() { + println!("Certificate successfully installed in all NSS (Browser) profiles ✅."); + return true; + } else { + eprintln!("{}: Installing in NSS failed. Please report the issue with details about your environment.", "Error".red()); + return false; + } } pub fn _uninstall_nss(&self) { diff --git a/core/src/utils.rs b/core/src/utils.rs index dbf5309..c7292d6 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -171,7 +171,6 @@ pub fn binary_exists(binary: &str) -> bool { .unwrap_or(false) } -#[allow(dead_code)] pub fn get_unique_hash(csr_path: &str) -> Result { let mut file: File = File::open(csr_path)?; let mut csr_contents: Vec = Vec::new();