From 00fd8f322130df5c5b220a871331a29ae4c35de0 Mon Sep 17 00:00:00 2001 From: Shubham Singh Date: Tue, 3 Sep 2024 00:06:08 +0530 Subject: [PATCH] feat(trust_store): incomplete implementation --- core/src/commands/generate.rs | 21 +++++++ core/src/main.rs | 11 +++- core/src/trust_stores/errors.rs | 60 +++++++++++++++++++ core/src/trust_stores/linux.rs | 36 +++--------- core/src/trust_stores/mod.rs | 20 ++++--- core/src/trust_stores/nss.rs | 86 ++++++++++++++++++++++++++++ core/src/trust_stores/nss_profile.rs | 51 +++++++++++++++++ 7 files changed, 247 insertions(+), 38 deletions(-) create mode 100644 core/src/trust_stores/errors.rs create mode 100644 core/src/trust_stores/nss.rs create mode 100644 core/src/trust_stores/nss_profile.rs diff --git a/core/src/commands/generate.rs b/core/src/commands/generate.rs index f388f24..4e8cf9d 100644 --- a/core/src/commands/generate.rs +++ b/core/src/commands/generate.rs @@ -1,4 +1,5 @@ use crate::{ + trust_stores::{CAValue, NSSValue}, utils::{get_certificates_from_data_dir, save_generated_cert_key_files}, x509::{ ca_cert::CACert, ca_req::CAReq, distinguished_name::DistinguishedName, leaf_cert::LeafCert, @@ -25,6 +26,7 @@ pub fn generate( state: Option, output: Option, request: bool, + install: bool, ) -> Result<(), Box> { if request { for domain in &domains { @@ -189,6 +191,14 @@ pub fn generate( } } } + if install { + let trust_store_object: CAValue = CAValue { + certificate: cert.clone(), + }; + CAValue::install_certificate(&trust_store_object)?; + let nss_store_object: NSSValue = NSSValue { certificate: cert }; + NSSValue::install_certificate(&nss_store_object)?; + } return Ok(()); } else { eprintln!("Corresponding KeyFile Not Found"); @@ -303,6 +313,17 @@ pub fn generate( } } } + + if install { + let trust_store_object: CAValue = CAValue { + certificate: d_cert.clone(), + }; + CAValue::install_certificate(&trust_store_object)?; + let nss_store_object: NSSValue = NSSValue { + certificate: d_cert, + }; + NSSValue::install_certificate(&nss_store_object)?; + } } else { if noca { eprintln!("Error: No CA Certificates found and generation of a new one is disabled by `--no-ca`"); diff --git a/core/src/main.rs b/core/src/main.rs index 418a54f..a26c6b8 100644 --- a/core/src/main.rs +++ b/core/src/main.rs @@ -51,6 +51,9 @@ enum Commands { #[arg(long = "req-only")] request: bool, + + #[arg(short = 'i', long = "install")] + install: bool, }, } @@ -77,6 +80,7 @@ fn main() { state, output, request, + install, } => { if certfile.is_some() != keyfile.is_some() { if certfile.is_some() { @@ -96,9 +100,14 @@ fn main() { std::process::exit(1); } + if request && install { + eprint!("Error: `--req-only` and `csr` are incompatible. You can't generate requests from a request certificate."); + std::process::exit(1); + } + let _ = generate( domains, noca, csr, certfile, keyfile, country, commonname, state, output, - request, + request, install, ); } } diff --git a/core/src/trust_stores/errors.rs b/core/src/trust_stores/errors.rs new file mode 100644 index 0000000..104a3e4 --- /dev/null +++ b/core/src/trust_stores/errors.rs @@ -0,0 +1,60 @@ +use openssl::error::ErrorStack; +use std::{error::Error, fmt, io}; + +#[derive(Debug)] +pub enum TrustStoreError { + PEMFileCreationError(io::Error), + PEMEncodingError(ErrorStack), + WriteToFileError(io::Error), +} + +impl fmt::Display for TrustStoreError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::PEMEncodingError(err) => { + write!(f, "Trust Store PEM Encoding Failed: {}", err) + } + Self::PEMFileCreationError(err) => { + write!(f, "Trust Store PEM File creation Failed: {}", err) + } + Self::WriteToFileError(err) => { + write!(f, "Trust Store Writing to PEM file: {}", err) + } + } + } +} + +impl Error for TrustStoreError {} + +#[derive(Debug)] +pub enum NSSStore { + PEMFileCreationError(io::Error), + PEMEncodingError(ErrorStack), + WriteToFileError(io::Error), + CertUtilFailed(String), + NSSProfileNotFound, +} + +impl fmt::Display for NSSStore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::CertUtilFailed(desc) => { + write!(f, "Certutil command failed: {}", desc) + } + Self::NSSProfileNotFound => { + write!(f, "NSS Profile Not Found") + } + Self::PEMEncodingError(err) => { + write!(f, "NSS Store PEM Encoding Failed: {}", err) + } + Self::PEMFileCreationError(err) => { + write!(f, "NSS Store PEM File creation Failed: {}", err) + } + Self::WriteToFileError(err) => { + write!(f, "NSS Store Writing to PEM file: {}", err) + } + } + } +} + +impl Error for NSSStore {} diff --git a/core/src/trust_stores/linux.rs b/core/src/trust_stores/linux.rs index 1c234dc..0204e41 100644 --- a/core/src/trust_stores/linux.rs +++ b/core/src/trust_stores/linux.rs @@ -1,13 +1,15 @@ use openssl::error::ErrorStack; use openssl::x509::X509; +use std::fs; use std::fs::File; use std::io; use std::io::Write; use std::path::{Path, PathBuf}; -use std::{fmt, fs}; + +use super::errors::TrustStoreError; pub struct CAValue { - certificate: X509, + pub certificate: X509, } impl CAValue { @@ -33,7 +35,8 @@ impl CAValue { match store { Some(store) => { let path: String = store.get_path(); - let pem_path: PathBuf = Path::new(&path).join("vanish-root.pem"); + println!("Adding file to trust store: {}", path); + let pem_path: PathBuf = Path::new(&path).join("vanish-root.crt"); if let Err(err) = fs::create_dir_all(&path) { eprintln!("Failed to create directory: {}. Error: {}", path, err); @@ -53,7 +56,7 @@ impl CAValue { Ok(()) } - pub fn save_cert(cert: &X509, path: &str) -> Result<(), TrustStoreError> { + fn save_cert(cert: &X509, path: &str) -> Result<(), TrustStoreError> { let mut file: File = File::create(path) .map_err(|err: io::Error| TrustStoreError::PEMFileCreationError(err))?; file.write_all( @@ -82,27 +85,4 @@ impl PossibleStores { PossibleStores::Other => "/usr/share/pki/trust/anchors/".to_string(), } } -} - -#[derive(Debug)] -pub enum TrustStoreError { - PEMFileCreationError(io::Error), - PEMEncodingError(ErrorStack), - WriteToFileError(io::Error), -} - -impl fmt::Display for TrustStoreError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::PEMEncodingError(err) => { - write!(f, "") - } - Self::PEMFileCreationError(err) => { - write!(f, "") - } - Self::WriteToFileError(err) => { - write!(f, "") - } - } - } -} +} \ No newline at end of file diff --git a/core/src/trust_stores/mod.rs b/core/src/trust_stores/mod.rs index f96a9a0..23c57fe 100644 --- a/core/src/trust_stores/mod.rs +++ b/core/src/trust_stores/mod.rs @@ -1,17 +1,19 @@ -#[cfg(target_os = "windows")] -mod windows; - -#[cfg(target_os = "macos")] -mod macos; +mod errors; +mod nss; +mod nss_profile; #[cfg(target_os = "linux")] mod linux; - +#[cfg(target_os = "macos")] +mod macos; #[cfg(target_os = "windows")] -pub use self::windows::*; +mod windows; +#[cfg(target_os = "linux")] +pub use self::linux::*; #[cfg(target_os = "macos")] pub use self::macos::*; +#[cfg(target_os = "windows")] +pub use self::windows::*; -#[cfg(target_os = "linux")] -pub use self::linux::*; +pub use self::nss::*; diff --git a/core/src/trust_stores/nss.rs b/core/src/trust_stores/nss.rs new file mode 100644 index 0000000..5b18ca4 --- /dev/null +++ b/core/src/trust_stores/nss.rs @@ -0,0 +1,86 @@ +use super::nss_profile::NSSProfile; +use crate::trust_stores::errors::NSSStore; +use openssl::x509::X509; +use std::io::{self, Write}; +use std::path::PathBuf; +use std::{ + fs::{self, File}, + path::Path, + process::Command, +}; + +pub struct NSSValue { + pub certificate: X509, +} + +impl NSSValue { + fn get_available_profile() -> Option { + let profiles: [NSSProfile; 3] = [ + NSSProfile::PkiNssdb, + NSSProfile::ChromiumNssdb, + NSSProfile::Firefox, + ]; + + for profile in profiles { + if let Some(_path) = profile.get_valid_path() { + return Some(profile); + } + } + + None + } + + pub fn install_certificate(&self) -> Result<(), NSSStore> { + if let Some(profile) = NSSValue::get_available_profile() { + if let Some(path) = profile.get_valid_path() { + println!("Adding certificate to NSS profile: {}", path.display()); + let pem_path: PathBuf = Path::new(&path).join("vanish-root.crt"); + + if let Err(err) = fs::create_dir_all(&path) { + eprintln!( + "Failed to create directory: {}. Error: {}", + path.display(), + err + ); + return Err(NSSStore::PEMFileCreationError(err)); + } + + NSSValue::save_cert(&self.certificate, pem_path.to_str().unwrap())?; + NSSValue::run_certutil(&pem_path, &path)?; + } + } else { + eprintln!("No valid NSS profile found."); + return Err(NSSStore::NSSProfileNotFound); + } + + Ok(()) + } + + fn save_cert(cert: &X509, path: &str) -> Result<(), NSSStore> { + let mut file: File = File::create(path).map_err(NSSStore::PEMFileCreationError)?; + file.write_all(&cert.to_pem().map_err(NSSStore::PEMEncodingError)?) + .map_err(NSSStore::WriteToFileError)?; + Ok(()) + } + + fn run_certutil(cert_path: &Path, profile_path: &Path) -> Result<(), NSSStore> { + let status = Command::new("certutil") + .arg("-A") + .arg("-d") + .arg(format!("sql:{}", profile_path.display())) + .arg("-t") + .arg("C,,") + .arg("-n") + .arg("vanish-root") + .arg("-i") + .arg(cert_path) + .status() + .map_err(|err: io::Error| NSSStore::CertUtilFailed(err.to_string()))?; + + if !status.success() { + return Err(NSSStore::CertUtilFailed("certutil command failed".into())); + } + + Ok(()) + } +} diff --git a/core/src/trust_stores/nss_profile.rs b/core/src/trust_stores/nss_profile.rs new file mode 100644 index 0000000..1384f42 --- /dev/null +++ b/core/src/trust_stores/nss_profile.rs @@ -0,0 +1,51 @@ +use std::path::{Path, PathBuf}; + +pub enum NSSProfile { + PkiNssdb, + ChromiumNssdb, + Firefox, +} + +impl NSSProfile { + pub fn get_valid_path(&self) -> Option { + match self { + NSSProfile::PkiNssdb => { + let path: PathBuf = dirs::home_dir()?.join(".pki/nssdb"); + if path.exists() { + Some(path) + } else { + None + } + } + NSSProfile::ChromiumNssdb => { + let path: PathBuf = dirs::home_dir()?.join("snap/chromium/current/.pki/nssdb"); + if path.exists() { + Some(path) + } else { + None + } + } + NSSProfile::Firefox => { + let firefox_paths: [&str; 9] = [ + "/usr/bin/firefox", + "/usr/bin/firefox-nightly", + "/usr/bin/firefox-developer-edition", + "/snap/firefox", + "/Applications/Firefox.app", + "/Applications/FirefoxDeveloperEdition.app", + "/Applications/Firefox Developer Edition.app", + "/Applications/Firefox Nightly.app", + "C:\\Program Files\\Mozilla Firefox", + ]; + + for path in firefox_paths.iter() { + let profile_path: &Path = Path::new(path); + if profile_path.exists() { + return Some(profile_path.to_path_buf()); + } + } + None + } + } + } +}