diff --git a/Cargo.lock b/Cargo.lock index 862f056..af8acc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "addr2line" version = "0.14.0" @@ -266,6 +268,7 @@ dependencies = [ "lazy_static", "linked-hash-map", "num_cpus", + "openssl", "rand", "regex", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 7aed79e..c7b0a1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,10 @@ yaml-rust = "0.4.3" url = "2.1.1" linked-hash-map = "0.5.3" tokio = { version = "0.2.20", features = ["rt-core", "rt-threaded", "time", "net", "io-driver"] } -reqwest = { version = "0.10.4", features = ["cookies", "trust-dns"] } +reqwest = { version = "0.10.4", features = ["cookies", "trust-dns", "native-tls"] } async-trait = "0.1.30" futures = "0.3.5" lazy_static = "1.4.0" num_cpus = "1.13.0" rand = "0.7.3" +openssl = "0.10.30" diff --git a/src/actions/request.rs b/src/actions/request.rs index f5f7530..f4b3a9c 100644 --- a/src/actions/request.rs +++ b/src/actions/request.rs @@ -151,7 +151,27 @@ impl Request { // Resolve the body let request = { let mut pool2 = pool.lock().unwrap(); - let client = pool2.entry(domain).or_insert_with(|| ClientBuilder::default().danger_accept_invalid_certs(config.no_check_certificate).build().unwrap()); + let client = pool2.entry(domain).or_insert_with(|| { + let mut builder = ClientBuilder::default().danger_accept_invalid_certs(config.no_check_certificate); + if let Some(ref pem) = config.maybe_cert { + let identity = make_identity(pem); + builder = builder.identity(identity); + } + + if let Some(ref cacert) = config.maybe_cacert { + let cacert = match reqwest::Certificate::from_pem(&cacert) { + Ok(cert) => cert, + Err(e) => { + eprintln!("Reqwest certificate error: {}", e); + std::process::exit(-1); + } + }; + + builder = builder.add_root_certificate(cacert); + } + + builder.build().unwrap() + }); let request = if let Some(body) = self.body.as_ref() { interpolated_body = uninterpolator.get_or_insert(interpolator::Interpolator::new(context)).resolve(body, !config.relaxed_interpolations); @@ -300,3 +320,28 @@ impl Runnable for Request { } } } + +fn make_identity(pem: &Vec) -> reqwest::Identity { + fn make_der(pem: &Vec) -> Result, openssl::error::ErrorStack> { + let key = openssl::pkey::PKey::private_key_from_pem(&pem)?; + let crt = openssl::x509::X509::from_pem(&pem)?; + + let pkcs12_builder = openssl::pkcs12::Pkcs12::builder(); + let pkcs12 = pkcs12_builder.build("", "client crt", &key, &crt)?; + pkcs12.to_der() + } + + match make_der(&pem) { + Ok(der) => match reqwest::Identity::from_pkcs12_der(&der, "") { + Ok(identity) => identity, + Err(e) => { + eprintln!("Reqwest ssl error: {}", e); + std::process::exit(-1) + } + }, + Err(e) => { + eprintln!("Openssl error: {}", e); + std::process::exit(-1); + } + } +} diff --git a/src/benchmark.rs b/src/benchmark.rs index 69a257c..7db1f0b 100644 --- a/src/benchmark.rs +++ b/src/benchmark.rs @@ -53,8 +53,8 @@ fn join(l: Vec, sep: &str) -> String { ) } -pub fn execute(benchmark_path: &str, report_path_option: Option<&str>, relaxed_interpolations: bool, no_check_certificate: bool, quiet: bool, nanosec: bool) -> BenchmarkResult { - let config = Arc::new(Config::new(benchmark_path, relaxed_interpolations, no_check_certificate, quiet, nanosec)); +pub fn execute(benchmark_path: &str, report_path_option: Option<&str>, relaxed_interpolations: bool, no_check_certificate: bool, maybe_cert_path: Option<&str>, maybe_cacert_path: Option<&str>, quiet: bool, nanosec: bool) -> BenchmarkResult { + let config = Arc::new(Config::new(benchmark_path, relaxed_interpolations, no_check_certificate, maybe_cert_path, maybe_cacert_path, quiet, nanosec)); if report_path_option.is_some() { println!("{}: {}. Ignoring {} and {} properties...", "Report mode".yellow(), "on".purple(), "concurrency".yellow(), "iterations".yellow()); diff --git a/src/config.rs b/src/config.rs index 0a91cbf..29e568e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,6 +3,7 @@ use yaml_rust::{Yaml, YamlLoader}; use crate::benchmark::Context; use crate::interpolator; use crate::reader; +use std::io::Read; const NITERATIONS: i64 = 1; const NRAMPUP: i64 = 0; @@ -13,13 +14,15 @@ pub struct Config { pub iterations: i64, pub relaxed_interpolations: bool, pub no_check_certificate: bool, + pub maybe_cert: Option>, + pub maybe_cacert: Option>, pub rampup: i64, pub quiet: bool, pub nanosec: bool, } impl Config { - pub fn new(path: &str, relaxed_interpolations: bool, no_check_certificate: bool, quiet: bool, nanosec: bool) -> Config { + pub fn new(path: &str, relaxed_interpolations: bool, no_check_certificate: bool, maybe_cert_path: Option<&str>, maybe_cacert_path: Option<&str>, quiet: bool, nanosec: bool) -> Config { let config_file = reader::read_file(path); let config_docs = YamlLoader::load_from_str(config_file.as_str()).unwrap(); @@ -37,12 +40,32 @@ impl Config { panic!("The concurrency can not be higher than the number of iterations") } + let maybe_cert = maybe_cert_path.map(|path| { + let mut pem = Vec::new(); + if let Err(e) = std::fs::File::open(path).and_then(|mut path| path.read_to_end(&mut pem)) { + eprintln!("Error opening --cert file {}: {}", path, e); + std::process::exit(-1); + } + pem + }); + + let maybe_cacert = maybe_cacert_path.map(|path| { + let mut cert = Vec::new(); + if let Err(e) = std::fs::File::open(path).and_then(|mut path| path.read_to_end(&mut cert)) { + eprintln!("Error opening --cacert file {}: {}", path, e); + std::process::exit(-1); + } + cert + }); + Config { base, concurrency, iterations, relaxed_interpolations, no_check_certificate, + maybe_cert, + maybe_cacert, rampup, quiet, nanosec, @@ -97,3 +120,5 @@ fn read_i64_configuration(config_doc: &Yaml, interpolator: &interpolator::Interp } } } + + diff --git a/src/expandable/include.rs b/src/expandable/include.rs index 8ca11ea..8ab4d76 100644 --- a/src/expandable/include.rs +++ b/src/expandable/include.rs @@ -71,6 +71,7 @@ pub fn expand_from_filepath(parent_path: &str, mut benchmark: &mut Benchmark, ac } } +#[cfg(test)] mod tests { use super::*; diff --git a/src/main.rs b/src/main.rs index 9f7808f..c87875f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,11 +27,13 @@ fn main() { let relaxed_interpolations = matches.is_present("relaxed-interpolations"); let quiet = matches.is_present("quiet"); let nanosec = matches.is_present("nanosec"); + let cert = matches.value_of("cert"); + let cacert = matches.value_of("cacert"); #[cfg(windows)] let _ = control::set_virtual_terminal(true); - let benchmark_result = benchmark::execute(benchmark_file, report_path_option, relaxed_interpolations, no_check_certificate, quiet, nanosec); + let benchmark_result = benchmark::execute(benchmark_file, report_path_option, relaxed_interpolations, no_check_certificate, cert, cacert, quiet, nanosec); let list_reports = benchmark_result.reports; let duration = benchmark_result.duration; @@ -54,6 +56,8 @@ fn app_args<'a>() -> clap::ArgMatches<'a> { .arg(Arg::with_name("no-check-certificate").long("no-check-certificate").help("Disables SSL certification check. (Not recommended)").takes_value(false)) .arg(Arg::with_name("quiet").short("q").long("quiet").help("Disables output").takes_value(false)) .arg(Arg::with_name("nanosec").short("n").long("nanosec").help("Shows statistics in nanoseconds").takes_value(false)) + .arg(Arg::with_name("cert").help("Use the specified client certificate (analogous to curl --cert)").long("cert").required(false).takes_value(true)) + .arg(Arg::with_name("cacert").help("Use the specified certificate to verify the peer (analogous to curl --cacert)").long("cacert").required(false).takes_value(true)) .get_matches() }