diff --git a/.travis.yml b/.travis.yml index 50d8ef50a3f..bfd839b6428 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,12 +24,11 @@ script: - cargo build - RUST_BACKTRACE=1 cargo test - RUST_BACKTRACE=1 cargo test --all-features - - cargo test --release --no-run - - ./target/release/examples/bench + - ( cd rustls-mio && cargo test --release --no-run && ./target/release/examples/bench ) + - ( cd rustls-mio && cargo test --all-features ) # - ( cd trytls && ./runme ) - ( cd bogo && ./runme ) - cargo build --no-default-features - cargo test --no-default-features --no-run - - ( cd examples && cargo build --examples --all-features ) - if [[ "$COVERAGE" == "1" ]]; then ./admin/coverage ; fi - if [[ "$COVERAGE" == "1" ]]; then coveralls-lcov final.info ; fi diff --git a/Cargo.toml b/Cargo.toml index 5100de27f05..c9ea15edeaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,18 @@ quic = [] [dev-dependencies] env_logger = "0.6.1" -regex = "1.0" tempfile = "3.0" webpki-roots = "0.16" + +[[example]] +name = "bogo_shim" +path = "examples/internal/bogo_shim.rs" +required-features = ["dangerous_configuration", "quic"] + +[[example]] +name = "trytls_shim" +path = "examples/internal/trytls_shim.rs" + +[[example]] +name = "bench" +path = "examples/internal/bench.rs" diff --git a/admin/capture-certdata b/admin/capture-certdata index 966fe0944f3..2e48de9220b 100755 --- a/admin/capture-certdata +++ b/admin/capture-certdata @@ -33,7 +33,7 @@ def extract_certs(line): def collect(hostname): subp = subprocess.Popen([ - './target/debug/examples/tlsclient', + './rustls-mio/target/debug/examples/tlsclient', '--verbose', '--http', hostname], diff --git a/admin/coverage b/admin/coverage index 44d513fe2ca..8c4a89308fc 100755 --- a/admin/coverage +++ b/admin/coverage @@ -68,11 +68,11 @@ run('rustls') all_infos.append(lcov('rustls.info')) cleanup() -for example in 'tlsclient tlsserver bench bogo_shim trytls_shim'.split(): +for example in 'bench bogo_shim trytls_shim'.split(): rustc('--profile', 'dev', '--example', example) # tests -for test in 'api badssl bugs client_suites curves errors features server_suites topsites'.split(): +for test in 'api'.split(): rustc('--profile', 'dev', '--test', test) run(test) diff --git a/admin/pull-usage b/admin/pull-usage index 8bc21edbb24..3f86fc63548 100755 --- a/admin/pull-usage +++ b/admin/pull-usage @@ -4,13 +4,13 @@ set -e awk 'BEGIN { take=1 }/```tlsclient/{take=0;print}take' < README.md > README.md.new -./target/debug/examples/tlsclient --help >> README.md.new +./rustls-mio/target/debug/examples/tlsclient --help >> README.md.new awk '/^```tlsclient$/ {start=1;} start' < README.md > README.md.tmp awk '/^```$/ {start=1;} start' < README.md.tmp >> README.md.new mv README.md.new README.md awk 'BEGIN { take=1 }/```tlsserver/{take=0;print}take' < README.md > README.md.new -./target/debug/examples/tlsserver --help >> README.md.new +./rustls-mio/target/debug/examples/tlsserver --help >> README.md.new awk '/^```tlsserver$/ {start=1;} start' < README.md > README.md.tmp awk '/^```$/ {start=1;} start' < README.md.tmp >> README.md.new mv README.md.new README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000000..0b2599ff0ea --- /dev/null +++ b/examples/README.md @@ -0,0 +1 @@ +Find examples using mio in rustls-mio/examples. diff --git a/examples/examples/internal/bench.rs b/examples/internal/bench.rs similarity index 99% rename from examples/examples/internal/bench.rs rename to examples/internal/bench.rs index 93fccf43824..4f1cd73d088 100644 --- a/examples/examples/internal/bench.rs +++ b/examples/internal/bench.rs @@ -134,8 +134,8 @@ impl KeyType { fn path_for(&self, part: &str) -> String { match self { - KeyType::RSA => format!("test-ca/rsa/{}", part), - KeyType::ECDSA => format!("test-ca/ecdsa/{}", part), + KeyType::RSA => format!("../test-ca/rsa/{}", part), + KeyType::ECDSA => format!("../test-ca/ecdsa/{}", part), } } diff --git a/examples/examples/internal/bogo_shim.rs b/examples/internal/bogo_shim.rs similarity index 100% rename from examples/examples/internal/bogo_shim.rs rename to examples/internal/bogo_shim.rs diff --git a/examples/examples/internal/trytls_shim.rs b/examples/internal/trytls_shim.rs similarity index 100% rename from examples/examples/internal/trytls_shim.rs rename to examples/internal/trytls_shim.rs diff --git a/examples/Cargo.toml b/rustls-mio/Cargo.toml similarity index 74% rename from examples/Cargo.toml rename to rustls-mio/Cargo.toml index 7fc467228bc..04e38607bd4 100644 --- a/examples/Cargo.toml +++ b/rustls-mio/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "rustls-examples" +name = "rustls-mio" version = "0.15.1" edition = "2018" authors = ["Joseph Birr-Pixton "] license = "Apache-2.0/ISC/MIT" readme = "README.md" -description = "Rustls is a modern TLS library written in Rust." +description = "Rustls example code and tests that depend on mio." homepage = "https://github.com/ctz/rustls" repository = "https://github.com/ctz/rustls" categories = ["network-programming", "cryptography"] @@ -28,24 +28,13 @@ ct-logs = "0.5" docopt = "1.0" env_logger = "0.6" mio = "0.6" +regex = "1.0" serde = "1.0" serde_derive = "1.0" +tempfile = "3.0" vecio = "0.1" webpki-roots = "0.16" -[[example]] -name = "bogo_shim" -path = "examples/internal/bogo_shim.rs" -required-features = ["dangerous_configuration", "quic"] - -[[example]] -name = "trytls_shim" -path = "examples/internal/trytls_shim.rs" - -[[example]] -name = "bench" -path = "examples/internal/bench.rs" - [[example]] name = "tlsclient" path = "examples/tlsclient.rs" diff --git a/examples/examples/simple_0rtt_client.rs b/rustls-mio/examples/simple_0rtt_client.rs similarity index 100% rename from examples/examples/simple_0rtt_client.rs rename to rustls-mio/examples/simple_0rtt_client.rs diff --git a/examples/examples/simpleclient.rs b/rustls-mio/examples/simpleclient.rs similarity index 100% rename from examples/examples/simpleclient.rs rename to rustls-mio/examples/simpleclient.rs diff --git a/examples/examples/tlsclient.rs b/rustls-mio/examples/tlsclient.rs similarity index 100% rename from examples/examples/tlsclient.rs rename to rustls-mio/examples/tlsclient.rs diff --git a/examples/examples/tlsserver.rs b/rustls-mio/examples/tlsserver.rs similarity index 100% rename from examples/examples/tlsserver.rs rename to rustls-mio/examples/tlsserver.rs diff --git a/examples/examples/util/mod.rs b/rustls-mio/examples/util/mod.rs similarity index 100% rename from examples/examples/util/mod.rs rename to rustls-mio/examples/util/mod.rs diff --git a/tests/badssl.rs b/rustls-mio/tests/badssl.rs similarity index 100% rename from tests/badssl.rs rename to rustls-mio/tests/badssl.rs diff --git a/tests/bugs.rs b/rustls-mio/tests/bugs.rs similarity index 100% rename from tests/bugs.rs rename to rustls-mio/tests/bugs.rs diff --git a/tests/client_suites.rs b/rustls-mio/tests/client_suites.rs similarity index 100% rename from tests/client_suites.rs rename to rustls-mio/tests/client_suites.rs diff --git a/rustls-mio/tests/common/mod.rs b/rustls-mio/tests/common/mod.rs new file mode 100644 index 00000000000..2257ac65e91 --- /dev/null +++ b/rustls-mio/tests/common/mod.rs @@ -0,0 +1,790 @@ +use std::env; +use std::net; + +use std::fs::{self, File}; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process; +use std::str; +use std::thread; +use std::time; + +use regex; +use self::regex::Regex; +use tempfile; + +macro_rules! embed_files { + ( + $( + ($name:ident, $keytype:expr, $path:expr); + )+ + ) => { + $( + const $name: &'static [u8] = include_bytes!( + concat!("../../../test-ca/", $keytype, "/", $path)); + )+ + + pub fn bytes_for(keytype: &str, path: &str) -> &'static [u8] { + match (keytype, path) { + $( + ($keytype, $path) => $name, + )+ + _ => panic!("unknown keytype {} with path {}", keytype, path), + } + } + + pub fn new_test_ca() -> tempfile::TempDir { + let dir = tempfile::TempDir::new().unwrap(); + + fs::create_dir(dir.path().join("ecdsa")).unwrap(); + fs::create_dir(dir.path().join("rsa")).unwrap(); + + $( + let mut f = File::create(dir.path().join($keytype).join($path)).unwrap(); + f.write($name).unwrap(); + )+ + + dir + } + } +} + +embed_files! { + (ECDSA_CA_CERT, "ecdsa", "ca.cert"); + (ECDSA_CA_DER, "ecdsa", "ca.der"); + (ECDSA_CA_KEY, "ecdsa", "ca.key"); + (ECDSA_CLIENT_CERT, "ecdsa", "client.cert"); + (ECDSA_CLIENT_CHAIN, "ecdsa", "client.chain"); + (ECDSA_CLIENT_FULLCHAIN, "ecdsa", "client.fullchain"); + (ECDSA_CLIENT_KEY, "ecdsa", "client.key"); + (ECDSA_CLIENT_REQ, "ecdsa", "client.req"); + (ECDSA_END_CERT, "ecdsa", "end.cert"); + (ECDSA_END_CHAIN, "ecdsa", "end.chain"); + (ECDSA_END_FULLCHAIN, "ecdsa", "end.fullchain"); + (ECDSA_END_KEY, "ecdsa", "end.key"); + (ECDSA_END_REQ, "ecdsa", "end.req"); + (ECDSA_INTER_CERT, "ecdsa", "inter.cert"); + (ECDSA_INTER_KEY, "ecdsa", "inter.key"); + (ECDSA_INTER_REQ, "ecdsa", "inter.req"); + (ECDSA_NISTP256_PEM, "ecdsa", "nistp256.pem"); + (ECDSA_NISTP384_PEM, "ecdsa", "nistp384.pem"); + + (RSA_CA_CERT, "rsa", "ca.cert"); + (RSA_CA_DER, "rsa", "ca.der"); + (RSA_CA_KEY, "rsa", "ca.key"); + (RSA_CLIENT_CERT, "rsa", "client.cert"); + (RSA_CLIENT_CHAIN, "rsa", "client.chain"); + (RSA_CLIENT_FULLCHAIN, "rsa", "client.fullchain"); + (RSA_CLIENT_KEY, "rsa", "client.key"); + (RSA_CLIENT_REQ, "rsa", "client.req"); + (RSA_CLIENT_RSA, "rsa", "client.rsa"); + (RSA_END_CERT, "rsa", "end.cert"); + (RSA_END_CHAIN, "rsa", "end.chain"); + (RSA_END_FULLCHAIN, "rsa", "end.fullchain"); + (RSA_END_KEY, "rsa", "end.key"); + (RSA_END_REQ, "rsa", "end.req"); + (RSA_END_RSA, "rsa", "end.rsa"); + (RSA_INTER_CERT, "rsa", "inter.cert"); + (RSA_INTER_KEY, "rsa", "inter.key"); + (RSA_INTER_REQ, "rsa", "inter.req"); +} + +// For tests which connect to internet servers, don't go crazy. +pub fn polite() { + thread::sleep(time::Duration::from_secs(1)); +} + +// Wait until we can connect to localhost:port. +fn wait_for_port(port: u16) -> Option<()> { + let mut count = 0; + loop { + thread::sleep(time::Duration::from_millis(500)); + if net::TcpStream::connect(("127.0.0.1", port)).is_ok() { + return Some(()); + } + count += 1; + if count == 10 { + return None; + } + } +} + +// Find an unused port +fn unused_port(mut port: u16) -> u16 { + loop { + if net::TcpStream::connect(("127.0.0.1", port)).is_err() { + return port; + } + + port += 1; + } +} + +// Note we skipped this test. +pub fn skipped(why: &str) { + use std::io::{self, Write}; + let mut stdout = io::stdout(); + write!(&mut stdout, + "[ SKIPPED ] because: {}\n -- UNTESTED: ", + why) + .unwrap(); +} + +pub fn tlsserver_find() -> &'static str { + "target/debug/examples/tlsserver" +} + +pub fn tlsclient_find() -> &'static str { + "target/debug/examples/tlsclient" +} + +pub fn openssl_find() -> String { + if let Ok(dir) = env::var("OPENSSL_DIR") { + return format!("{}/bin/openssl", dir); + } + + // We need a homebrew openssl, because OSX comes with + // 0.9.8y or something equally ancient! + if cfg!(target_os = "macos") { + return "/usr/local/opt/openssl/bin/openssl".to_string(); + } + + "openssl".to_string() +} + +fn openssl_supports_option(cmd: &str, opt: &str) -> bool { + let output = process::Command::new(openssl_find()) + .arg(cmd) + .arg("-help") + .output() + .unwrap(); + + String::from_utf8(output.stderr) + .unwrap() + .contains(opt) +} + +// Does openssl s_client support -alpn? +pub fn openssl_client_supports_alpn() -> bool { + openssl_supports_option("s_client", " -alpn ") +} + +// Does openssl s_server support -alpn? +pub fn openssl_server_supports_alpn() -> bool { + openssl_supports_option("s_server", " -alpn ") +} + +// Does openssl s_server support -no_ecdhe? +pub fn openssl_server_supports_no_echde() -> bool { + openssl_supports_option("s_server", " -no_ecdhe ") +} + +pub struct TlsClient { + pub hostname: String, + pub port: u16, + pub http: bool, + pub cafile: Option, + pub client_auth_key: Option, + pub client_auth_certs: Option, + pub cache: Option, + pub suites: Vec, + pub protos: Vec>, + pub no_tickets: bool, + pub no_sni: bool, + pub insecure: bool, + pub verbose: bool, + pub mtu: Option, + pub expect_fails: bool, + pub expect_output: Vec, + pub expect_log: Vec, +} + +impl TlsClient { + pub fn new(hostname: &str) -> TlsClient { + TlsClient { + hostname: hostname.to_string(), + port: 443, + http: true, + cafile: None, + client_auth_key: None, + client_auth_certs: None, + cache: None, + no_tickets: false, + no_sni: false, + insecure: false, + verbose: false, + mtu: None, + suites: Vec::new(), + protos: Vec::new(), + expect_fails: false, + expect_output: Vec::new(), + expect_log: Vec::new(), + } + } + + pub fn client_auth(&mut self, certs: &Path, key: &Path) -> &mut Self { + self.client_auth_key = Some(key.to_path_buf()); + self.client_auth_certs = Some(certs.to_path_buf()); + self + } + + pub fn cafile(&mut self, cafile: &Path) -> &mut TlsClient { + self.cafile = Some(cafile.to_path_buf()); + self + } + + pub fn cache(&mut self, cache: &str) -> &mut TlsClient { + self.cache = Some(cache.to_string()); + self + } + + pub fn no_tickets(&mut self) -> &mut TlsClient { + self.no_tickets = true; + self + } + + pub fn no_sni(&mut self) -> &mut TlsClient { + self.no_sni = true; + self + } + + pub fn insecure(&mut self) -> &mut TlsClient { + self.insecure = true; + self + } + + pub fn verbose(&mut self) -> &mut TlsClient { + self.verbose = true; + self + } + + pub fn mtu(&mut self, mtu: usize) -> &mut TlsClient { + self.mtu = Some(mtu); + self + } + + pub fn port(&mut self, port: u16) -> &mut TlsClient { + self.port = port; + self + } + + pub fn expect(&mut self, expect: &str) -> &mut TlsClient { + self.expect_output.push(expect.to_string()); + self + } + + pub fn expect_log(&mut self, expect: &str) -> &mut TlsClient { + self.verbose = true; + self.expect_log.push(expect.to_string()); + self + } + + pub fn suite(&mut self, suite: &str) -> &mut TlsClient { + self.suites.push(suite.to_string()); + self + } + + pub fn proto(&mut self, proto: &[u8]) -> &mut TlsClient { + self.protos.push(proto.to_vec()); + self + } + + pub fn fails(&mut self) -> &mut TlsClient { + self.expect_fails = true; + self + } + + pub fn go(&mut self) -> Option<()> { + let mtustring; + let portstring = self.port.to_string(); + let mut args = Vec::<&str>::new(); + args.push(&self.hostname); + + args.push("--port"); + args.push(&portstring); + + if self.http { + args.push("--http"); + } + + if self.cache.is_some() { + args.push("--cache"); + args.push(self.cache.as_ref().unwrap()); + } + + if self.no_tickets { + args.push("--no-tickets"); + } + + if self.no_sni { + args.push("--no-sni"); + } + + if self.insecure { + args.push("--insecure"); + } + + if self.cafile.is_some() { + args.push("--cafile"); + args.push(self.cafile.as_ref().unwrap().to_str().unwrap()); + } + + if self.client_auth_key.is_some() { + args.push("--auth-key"); + args.push(self.client_auth_key.as_ref().unwrap().to_str().unwrap()); + } + + if self.client_auth_certs.is_some() { + args.push("--auth-certs"); + args.push(self.client_auth_certs.as_ref().unwrap().to_str().unwrap()); + } + + for suite in &self.suites { + args.push("--suite"); + args.push(suite.as_ref()); + } + + for proto in &self.protos { + args.push("--proto"); + args.push(str::from_utf8(proto.as_ref()).unwrap()); + } + + if self.verbose { + args.push("--verbose"); + } + + if self.mtu.is_some() { + args.push("--mtu"); + mtustring = self.mtu.unwrap().to_string(); + args.push(&mtustring); + } + + let output = process::Command::new(tlsclient_find()) + .args(&args) + .env("SSLKEYLOGFILE", "./sslkeylogfile.txt") + .output() + .unwrap_or_else(|e| panic!("failed to execute: {}", e)); + + let stdout_str = String::from_utf8(output.stdout.clone()) + .unwrap(); + let stderr_str = String::from_utf8(output.stderr.clone()) + .unwrap(); + + for expect in &self.expect_output { + let re = Regex::new(expect).unwrap(); + if re.find(&stdout_str).is_none() { + println!("We expected to find '{}' in the following output:", expect); + println!("{:?}", output); + panic!("Test failed"); + } + } + + for expect in &self.expect_log { + let re = Regex::new(expect).unwrap(); + if re.find(&stderr_str).is_none() { + println!("We expected to find '{}' in the following output:", expect); + println!("{:?}", output); + panic!("Test failed"); + } + } + + if self.expect_fails { + assert!(output.status.code().unwrap() != 0); + } else { + assert!(output.status.success()); + } + + Some(()) + } +} + +pub struct OpenSSLServer { + pub port: u16, + pub http: bool, + pub quiet: bool, + pub key: PathBuf, + pub cert: PathBuf, + pub chain: PathBuf, + pub intermediate: PathBuf, + pub cacert: PathBuf, + pub extra_args: Vec<&'static str>, + pub child: Option, +} + +impl OpenSSLServer { + pub fn new(test_ca: &Path, keytype: &str, start_port: u16) -> OpenSSLServer { + OpenSSLServer { + port: unused_port(start_port), + http: true, + quiet: true, + key: test_ca.join(keytype).join("end.key"), + cert: test_ca.join(keytype).join("end.cert"), + chain: test_ca.join(keytype).join("end.chain"), + cacert: test_ca.join(keytype).join("ca.cert"), + intermediate: test_ca.join(keytype).join("inter.cert"), + extra_args: Vec::new(), + child: None, + } + } + + pub fn new_rsa(test_ca: &Path, start_port: u16) -> OpenSSLServer { + OpenSSLServer::new(test_ca, "rsa", start_port) + } + + pub fn new_ecdsa(test_ca: &Path, start_port: u16) -> OpenSSLServer { + OpenSSLServer::new(test_ca, "ecdsa", start_port) + } + + pub fn partial_chain(&mut self) -> &mut Self { + self.chain = self.intermediate.clone(); + self + } + + pub fn arg(&mut self, arg: &'static str) -> &mut Self { + self.extra_args.push(arg); + self + } + + pub fn run(&mut self) -> &mut Self { + let mut extra_args = Vec::<&'static str>::new(); + extra_args.extend(&self.extra_args); + if self.http { + extra_args.push("-www"); + } + + let mut subp = process::Command::new(openssl_find()); + subp.arg("s_server") + .arg("-accept") + .arg(self.port.to_string()) + .arg("-key") + .arg(&self.key) + .arg("-cert") + .arg(&self.cert) + .arg("-key2") + .arg(&self.key) + .arg("-cert2") + .arg(&self.cert) + .arg("-CAfile") + .arg(&self.chain) + .args(&extra_args); + + if self.quiet { + subp.stdout(process::Stdio::null()) + .stderr(process::Stdio::null()); + } + + let child = subp.spawn() + .expect("cannot run openssl server"); + + let port_up = wait_for_port(self.port); + port_up.expect("server did not come up"); + self.child = Some(child); + + self + } + + pub fn running(&self) -> bool { + self.child.is_some() + } + + pub fn kill(&mut self) { + self.child.as_mut().unwrap().kill().unwrap(); + self.child = None; + } + + pub fn client(&self) -> TlsClient { + let mut c = TlsClient::new("localhost"); + c.port(self.port); + c.cafile(&self.cacert); + c + } +} + +impl Drop for OpenSSLServer { + fn drop(&mut self) { + if self.running() { + self.kill(); + } + } +} + +pub struct TlsServer { + pub port: u16, + pub http: bool, + pub echo: bool, + pub certs: PathBuf, + pub key: PathBuf, + pub cafile: PathBuf, + pub suites: Vec, + pub protos: Vec>, + used_suites: Vec, + used_protos: Vec>, + pub resumes: bool, + pub tickets: bool, + pub client_auth_roots: Option, + pub client_auth_required: bool, + pub verbose: bool, + pub child: Option, +} + +impl TlsServer { + pub fn new(test_ca: &Path, port: u16) -> Self { + Self::new_keytype(test_ca, port, "rsa") + } + + pub fn new_keytype(test_ca: &Path, port: u16, keytype: &str) -> Self { + TlsServer { + port: unused_port(port), + http: false, + echo: false, + key: test_ca.join(keytype).join("end.key"), + certs: test_ca.join(keytype).join("end.fullchain"), + cafile: test_ca.join(keytype).join("ca.cert"), + verbose: false, + suites: Vec::new(), + protos: Vec::new(), + used_suites: Vec::new(), + used_protos: Vec::new(), + resumes: false, + tickets: false, + client_auth_roots: None, + client_auth_required: false, + child: None, + } + } + + pub fn echo_mode(&mut self) -> &mut Self { + self.echo = true; + self.http = false; + self + } + + pub fn http_mode(&mut self) -> &mut Self { + self.echo = false; + self.http = true; + self + } + + pub fn verbose(&mut self) -> &mut Self { + self.verbose = true; + self + } + + pub fn port(&mut self, port: u16) -> &mut Self { + self.port = port; + self + } + + pub fn suite(&mut self, suite: &str) -> &mut Self { + self.suites.push(suite.to_string()); + self + } + + pub fn proto(&mut self, proto: &[u8]) -> &mut Self { + self.protos.push(proto.to_vec()); + self + } + + pub fn resumes(&mut self) -> &mut Self { + self.resumes = true; + self + } + + pub fn tickets(&mut self) -> &mut Self { + self.tickets = true; + self + } + + pub fn client_auth_roots(&mut self, cafile: &Path) -> &mut Self { + self.client_auth_roots = Some(cafile.to_path_buf()); + self + } + + pub fn client_auth_required(&mut self) -> &mut Self { + self.client_auth_required = true; + self + } + + pub fn run(&mut self) { + let portstring = self.port.to_string(); + let mut args = Vec::<&str>::new(); + args.push("--port"); + args.push(&portstring); + args.push("--key"); + args.push(self.key.to_str().unwrap()); + args.push("--certs"); + args.push(self.certs.to_str().unwrap()); + + self.used_suites = self.suites.clone(); + for suite in &self.used_suites { + args.push("--suite"); + args.push(suite.as_ref()); + } + + self.used_protos = self.protos.clone(); + for proto in &self.used_protos { + args.push("--proto"); + args.push(str::from_utf8(proto.as_ref()).unwrap()); + } + + if self.resumes { + args.push("--resumption"); + } + + if self.tickets { + args.push("--tickets"); + } + + if let Some(ref client_auth_roots) = self.client_auth_roots { + args.push("--auth"); + args.push(client_auth_roots.to_str().unwrap()); + + if self.client_auth_required { + args.push("--require-auth"); + } + } + + if self.verbose { + args.push("--verbose"); + } + + if self.http { + args.push("http"); + } else if self.echo { + args.push("echo"); + } else { + assert!(false, "specify http/echo mode"); + } + + println!("args {:?}", args); + + let child = process::Command::new(tlsserver_find()) + .args(&args) + .spawn() + .expect("cannot run tlsserver"); + + wait_for_port(self.port).expect("tlsserver didn't come up"); + self.child = Some(child); + } + + pub fn kill(&mut self) { + self.child.as_mut().unwrap().kill().unwrap(); + self.child = None; + } + + pub fn running(&self) -> bool { + self.child.is_some() + } + + pub fn client(&self) -> OpenSSLClient { + let mut c = OpenSSLClient::new(self.port); + c.cafile(&self.cafile); + c + } +} + +impl Drop for TlsServer { + fn drop(&mut self) { + if self.running() { + self.kill(); + } + } +} + +pub struct OpenSSLClient { + pub port: u16, + pub cafile: PathBuf, + pub extra_args: Vec, + pub expect_fails: bool, + pub expect_output: Vec, + pub expect_log: Vec, +} + +impl OpenSSLClient { + pub fn new(port: u16) -> OpenSSLClient { + OpenSSLClient { + port: port, + cafile: PathBuf::new(), + extra_args: Vec::new(), + expect_fails: false, + expect_output: Vec::new(), + expect_log: Vec::new(), + } + } + + pub fn arg(&mut self, arg: &str) -> &mut Self { + self.extra_args.push(arg.to_string()); + self + } + + pub fn cafile(&mut self, cafile: &Path) -> &mut Self { + self.cafile = cafile.to_path_buf(); + self + } + + pub fn expect(&mut self, expect: &str) -> &mut Self { + self.expect_output.push(expect.to_string()); + self + } + + pub fn expect_log(&mut self, expect: &str) -> &mut Self { + self.expect_log.push(expect.to_string()); + self + } + + pub fn fails(&mut self) -> &mut Self { + self.expect_fails = true; + self + } + + pub fn go(&mut self) -> Option<()> { + let mut extra_args = Vec::new(); + extra_args.extend(&self.extra_args); + + let mut subp = process::Command::new(openssl_find()); + subp.arg("s_client") + .arg("-tls1_2") + .arg("-host") + .arg("localhost") + .arg("-port") + .arg(self.port.to_string()) + .arg("-CAfile") + .arg(&self.cafile) + .args(&extra_args); + + let output = subp.output() + .unwrap_or_else(|e| panic!("failed to execute: {}", e)); + + let stdout_str = unsafe { String::from_utf8_unchecked(output.stdout.clone()) }; + let stderr_str = unsafe { String::from_utf8_unchecked(output.stderr.clone()) }; + + print!("{}", stdout_str); + print!("{}", stderr_str); + + for expect in &self.expect_output { + let re = Regex::new(expect).unwrap(); + if re.find(&stdout_str).is_none() { + println!("We expected to find '{}' in the following output:", expect); + println!("{:?}", output); + panic!("Test failed"); + } + } + + for expect in &self.expect_log { + let re = Regex::new(expect).unwrap(); + if re.find(&stderr_str).is_none() { + println!("We expected to find '{}' in the following output:", expect); + println!("{:?}", output); + panic!("Test failed"); + } + } + + if self.expect_fails { + assert!(output.status.code().unwrap() != 0); + } else { + assert!(output.status.success()); + } + + Some(()) + } +} diff --git a/tests/curves.rs b/rustls-mio/tests/curves.rs similarity index 100% rename from tests/curves.rs rename to rustls-mio/tests/curves.rs diff --git a/tests/errors.rs b/rustls-mio/tests/errors.rs similarity index 100% rename from tests/errors.rs rename to rustls-mio/tests/errors.rs diff --git a/tests/features.rs b/rustls-mio/tests/features.rs similarity index 100% rename from tests/features.rs rename to rustls-mio/tests/features.rs diff --git a/tests/server_suites.rs b/rustls-mio/tests/server_suites.rs similarity index 100% rename from tests/server_suites.rs rename to rustls-mio/tests/server_suites.rs diff --git a/tests/topsites.rs b/rustls-mio/tests/topsites.rs similarity index 100% rename from tests/topsites.rs rename to rustls-mio/tests/topsites.rs diff --git a/tests/common/mod.rs b/tests/common/mod.rs index bfe3455841c..877e7592775 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,16 +1,6 @@ -use std::env; -use std::net; - use std::fs::{self, File}; use std::io::Write; -use std::path::{Path, PathBuf}; -use std::process; use std::str; -use std::thread; -use std::time; - -use regex; -use self::regex::Regex; use tempfile; macro_rules! embed_files { @@ -88,703 +78,3 @@ embed_files! { (RSA_INTER_KEY, "rsa", "inter.key"); (RSA_INTER_REQ, "rsa", "inter.req"); } - -// For tests which connect to internet servers, don't go crazy. -pub fn polite() { - thread::sleep(time::Duration::from_secs(1)); -} - -// Wait until we can connect to localhost:port. -fn wait_for_port(port: u16) -> Option<()> { - let mut count = 0; - loop { - thread::sleep(time::Duration::from_millis(500)); - if net::TcpStream::connect(("127.0.0.1", port)).is_ok() { - return Some(()); - } - count += 1; - if count == 10 { - return None; - } - } -} - -// Find an unused port -fn unused_port(mut port: u16) -> u16 { - loop { - if net::TcpStream::connect(("127.0.0.1", port)).is_err() { - return port; - } - - port += 1; - } -} - -// Note we skipped this test. -pub fn skipped(why: &str) { - use std::io::{self, Write}; - let mut stdout = io::stdout(); - write!(&mut stdout, - "[ SKIPPED ] because: {}\n -- UNTESTED: ", - why) - .unwrap(); -} - -pub fn tlsserver_find() -> &'static str { - "target/debug/examples/tlsserver" -} - -pub fn tlsclient_find() -> &'static str { - "target/debug/examples/tlsclient" -} - -pub fn openssl_find() -> String { - if let Ok(dir) = env::var("OPENSSL_DIR") { - return format!("{}/bin/openssl", dir); - } - - // We need a homebrew openssl, because OSX comes with - // 0.9.8y or something equally ancient! - if cfg!(target_os = "macos") { - return "/usr/local/opt/openssl/bin/openssl".to_string(); - } - - "openssl".to_string() -} - -fn openssl_supports_option(cmd: &str, opt: &str) -> bool { - let output = process::Command::new(openssl_find()) - .arg(cmd) - .arg("-help") - .output() - .unwrap(); - - String::from_utf8(output.stderr) - .unwrap() - .contains(opt) -} - -// Does openssl s_client support -alpn? -pub fn openssl_client_supports_alpn() -> bool { - openssl_supports_option("s_client", " -alpn ") -} - -// Does openssl s_server support -alpn? -pub fn openssl_server_supports_alpn() -> bool { - openssl_supports_option("s_server", " -alpn ") -} - -// Does openssl s_server support -no_ecdhe? -pub fn openssl_server_supports_no_echde() -> bool { - openssl_supports_option("s_server", " -no_ecdhe ") -} - -pub struct TlsClient { - pub hostname: String, - pub port: u16, - pub http: bool, - pub cafile: Option, - pub client_auth_key: Option, - pub client_auth_certs: Option, - pub cache: Option, - pub suites: Vec, - pub protos: Vec>, - pub no_tickets: bool, - pub no_sni: bool, - pub insecure: bool, - pub verbose: bool, - pub mtu: Option, - pub expect_fails: bool, - pub expect_output: Vec, - pub expect_log: Vec, -} - -impl TlsClient { - pub fn new(hostname: &str) -> TlsClient { - TlsClient { - hostname: hostname.to_string(), - port: 443, - http: true, - cafile: None, - client_auth_key: None, - client_auth_certs: None, - cache: None, - no_tickets: false, - no_sni: false, - insecure: false, - verbose: false, - mtu: None, - suites: Vec::new(), - protos: Vec::new(), - expect_fails: false, - expect_output: Vec::new(), - expect_log: Vec::new(), - } - } - - pub fn client_auth(&mut self, certs: &Path, key: &Path) -> &mut Self { - self.client_auth_key = Some(key.to_path_buf()); - self.client_auth_certs = Some(certs.to_path_buf()); - self - } - - pub fn cafile(&mut self, cafile: &Path) -> &mut TlsClient { - self.cafile = Some(cafile.to_path_buf()); - self - } - - pub fn cache(&mut self, cache: &str) -> &mut TlsClient { - self.cache = Some(cache.to_string()); - self - } - - pub fn no_tickets(&mut self) -> &mut TlsClient { - self.no_tickets = true; - self - } - - pub fn no_sni(&mut self) -> &mut TlsClient { - self.no_sni = true; - self - } - - pub fn insecure(&mut self) -> &mut TlsClient { - self.insecure = true; - self - } - - pub fn verbose(&mut self) -> &mut TlsClient { - self.verbose = true; - self - } - - pub fn mtu(&mut self, mtu: usize) -> &mut TlsClient { - self.mtu = Some(mtu); - self - } - - pub fn port(&mut self, port: u16) -> &mut TlsClient { - self.port = port; - self - } - - pub fn expect(&mut self, expect: &str) -> &mut TlsClient { - self.expect_output.push(expect.to_string()); - self - } - - pub fn expect_log(&mut self, expect: &str) -> &mut TlsClient { - self.verbose = true; - self.expect_log.push(expect.to_string()); - self - } - - pub fn suite(&mut self, suite: &str) -> &mut TlsClient { - self.suites.push(suite.to_string()); - self - } - - pub fn proto(&mut self, proto: &[u8]) -> &mut TlsClient { - self.protos.push(proto.to_vec()); - self - } - - pub fn fails(&mut self) -> &mut TlsClient { - self.expect_fails = true; - self - } - - pub fn go(&mut self) -> Option<()> { - let mtustring; - let portstring = self.port.to_string(); - let mut args = Vec::<&str>::new(); - args.push(&self.hostname); - - args.push("--port"); - args.push(&portstring); - - if self.http { - args.push("--http"); - } - - if self.cache.is_some() { - args.push("--cache"); - args.push(self.cache.as_ref().unwrap()); - } - - if self.no_tickets { - args.push("--no-tickets"); - } - - if self.no_sni { - args.push("--no-sni"); - } - - if self.insecure { - args.push("--insecure"); - } - - if self.cafile.is_some() { - args.push("--cafile"); - args.push(self.cafile.as_ref().unwrap().to_str().unwrap()); - } - - if self.client_auth_key.is_some() { - args.push("--auth-key"); - args.push(self.client_auth_key.as_ref().unwrap().to_str().unwrap()); - } - - if self.client_auth_certs.is_some() { - args.push("--auth-certs"); - args.push(self.client_auth_certs.as_ref().unwrap().to_str().unwrap()); - } - - for suite in &self.suites { - args.push("--suite"); - args.push(suite.as_ref()); - } - - for proto in &self.protos { - args.push("--proto"); - args.push(str::from_utf8(proto.as_ref()).unwrap()); - } - - if self.verbose { - args.push("--verbose"); - } - - if self.mtu.is_some() { - args.push("--mtu"); - mtustring = self.mtu.unwrap().to_string(); - args.push(&mtustring); - } - - let output = process::Command::new(tlsclient_find()) - .args(&args) - .env("SSLKEYLOGFILE", "./sslkeylogfile.txt") - .output() - .unwrap_or_else(|e| panic!("failed to execute: {}", e)); - - let stdout_str = String::from_utf8(output.stdout.clone()) - .unwrap(); - let stderr_str = String::from_utf8(output.stderr.clone()) - .unwrap(); - - for expect in &self.expect_output { - let re = Regex::new(expect).unwrap(); - if re.find(&stdout_str).is_none() { - println!("We expected to find '{}' in the following output:", expect); - println!("{:?}", output); - panic!("Test failed"); - } - } - - for expect in &self.expect_log { - let re = Regex::new(expect).unwrap(); - if re.find(&stderr_str).is_none() { - println!("We expected to find '{}' in the following output:", expect); - println!("{:?}", output); - panic!("Test failed"); - } - } - - if self.expect_fails { - assert!(output.status.code().unwrap() != 0); - } else { - assert!(output.status.success()); - } - - Some(()) - } -} - -pub struct OpenSSLServer { - pub port: u16, - pub http: bool, - pub quiet: bool, - pub key: PathBuf, - pub cert: PathBuf, - pub chain: PathBuf, - pub intermediate: PathBuf, - pub cacert: PathBuf, - pub extra_args: Vec<&'static str>, - pub child: Option, -} - -impl OpenSSLServer { - pub fn new(test_ca: &Path, keytype: &str, start_port: u16) -> OpenSSLServer { - OpenSSLServer { - port: unused_port(start_port), - http: true, - quiet: true, - key: test_ca.join(keytype).join("end.key"), - cert: test_ca.join(keytype).join("end.cert"), - chain: test_ca.join(keytype).join("end.chain"), - cacert: test_ca.join(keytype).join("ca.cert"), - intermediate: test_ca.join(keytype).join("inter.cert"), - extra_args: Vec::new(), - child: None, - } - } - - pub fn new_rsa(test_ca: &Path, start_port: u16) -> OpenSSLServer { - OpenSSLServer::new(test_ca, "rsa", start_port) - } - - pub fn new_ecdsa(test_ca: &Path, start_port: u16) -> OpenSSLServer { - OpenSSLServer::new(test_ca, "ecdsa", start_port) - } - - pub fn partial_chain(&mut self) -> &mut Self { - self.chain = self.intermediate.clone(); - self - } - - pub fn arg(&mut self, arg: &'static str) -> &mut Self { - self.extra_args.push(arg); - self - } - - pub fn run(&mut self) -> &mut Self { - let mut extra_args = Vec::<&'static str>::new(); - extra_args.extend(&self.extra_args); - if self.http { - extra_args.push("-www"); - } - - let mut subp = process::Command::new(openssl_find()); - subp.arg("s_server") - .arg("-accept") - .arg(self.port.to_string()) - .arg("-key") - .arg(&self.key) - .arg("-cert") - .arg(&self.cert) - .arg("-key2") - .arg(&self.key) - .arg("-cert2") - .arg(&self.cert) - .arg("-CAfile") - .arg(&self.chain) - .args(&extra_args); - - if self.quiet { - subp.stdout(process::Stdio::null()) - .stderr(process::Stdio::null()); - } - - let child = subp.spawn() - .expect("cannot run openssl server"); - - let port_up = wait_for_port(self.port); - port_up.expect("server did not come up"); - self.child = Some(child); - - self - } - - pub fn running(&self) -> bool { - self.child.is_some() - } - - pub fn kill(&mut self) { - self.child.as_mut().unwrap().kill().unwrap(); - self.child = None; - } - - pub fn client(&self) -> TlsClient { - let mut c = TlsClient::new("localhost"); - c.port(self.port); - c.cafile(&self.cacert); - c - } -} - -impl Drop for OpenSSLServer { - fn drop(&mut self) { - if self.running() { - self.kill(); - } - } -} - -pub struct TlsServer { - pub port: u16, - pub http: bool, - pub echo: bool, - pub certs: PathBuf, - pub key: PathBuf, - pub cafile: PathBuf, - pub suites: Vec, - pub protos: Vec>, - used_suites: Vec, - used_protos: Vec>, - pub resumes: bool, - pub tickets: bool, - pub client_auth_roots: Option, - pub client_auth_required: bool, - pub verbose: bool, - pub child: Option, -} - -impl TlsServer { - pub fn new(test_ca: &Path, port: u16) -> Self { - Self::new_keytype(test_ca, port, "rsa") - } - - pub fn new_keytype(test_ca: &Path, port: u16, keytype: &str) -> Self { - TlsServer { - port: unused_port(port), - http: false, - echo: false, - key: test_ca.join(keytype).join("end.key"), - certs: test_ca.join(keytype).join("end.fullchain"), - cafile: test_ca.join(keytype).join("ca.cert"), - verbose: false, - suites: Vec::new(), - protos: Vec::new(), - used_suites: Vec::new(), - used_protos: Vec::new(), - resumes: false, - tickets: false, - client_auth_roots: None, - client_auth_required: false, - child: None, - } - } - - pub fn echo_mode(&mut self) -> &mut Self { - self.echo = true; - self.http = false; - self - } - - pub fn http_mode(&mut self) -> &mut Self { - self.echo = false; - self.http = true; - self - } - - pub fn verbose(&mut self) -> &mut Self { - self.verbose = true; - self - } - - pub fn port(&mut self, port: u16) -> &mut Self { - self.port = port; - self - } - - pub fn suite(&mut self, suite: &str) -> &mut Self { - self.suites.push(suite.to_string()); - self - } - - pub fn proto(&mut self, proto: &[u8]) -> &mut Self { - self.protos.push(proto.to_vec()); - self - } - - pub fn resumes(&mut self) -> &mut Self { - self.resumes = true; - self - } - - pub fn tickets(&mut self) -> &mut Self { - self.tickets = true; - self - } - - pub fn client_auth_roots(&mut self, cafile: &Path) -> &mut Self { - self.client_auth_roots = Some(cafile.to_path_buf()); - self - } - - pub fn client_auth_required(&mut self) -> &mut Self { - self.client_auth_required = true; - self - } - - pub fn run(&mut self) { - let portstring = self.port.to_string(); - let mut args = Vec::<&str>::new(); - args.push("--port"); - args.push(&portstring); - args.push("--key"); - args.push(self.key.to_str().unwrap()); - args.push("--certs"); - args.push(self.certs.to_str().unwrap()); - - self.used_suites = self.suites.clone(); - for suite in &self.used_suites { - args.push("--suite"); - args.push(suite.as_ref()); - } - - self.used_protos = self.protos.clone(); - for proto in &self.used_protos { - args.push("--proto"); - args.push(str::from_utf8(proto.as_ref()).unwrap()); - } - - if self.resumes { - args.push("--resumption"); - } - - if self.tickets { - args.push("--tickets"); - } - - if let Some(ref client_auth_roots) = self.client_auth_roots { - args.push("--auth"); - args.push(client_auth_roots.to_str().unwrap()); - - if self.client_auth_required { - args.push("--require-auth"); - } - } - - if self.verbose { - args.push("--verbose"); - } - - if self.http { - args.push("http"); - } else if self.echo { - args.push("echo"); - } else { - assert!(false, "specify http/echo mode"); - } - - println!("args {:?}", args); - - let child = process::Command::new(tlsserver_find()) - .args(&args) - .spawn() - .expect("cannot run tlsserver"); - - wait_for_port(self.port).expect("tlsserver didn't come up"); - self.child = Some(child); - } - - pub fn kill(&mut self) { - self.child.as_mut().unwrap().kill().unwrap(); - self.child = None; - } - - pub fn running(&self) -> bool { - self.child.is_some() - } - - pub fn client(&self) -> OpenSSLClient { - let mut c = OpenSSLClient::new(self.port); - c.cafile(&self.cafile); - c - } -} - -impl Drop for TlsServer { - fn drop(&mut self) { - if self.running() { - self.kill(); - } - } -} - -pub struct OpenSSLClient { - pub port: u16, - pub cafile: PathBuf, - pub extra_args: Vec, - pub expect_fails: bool, - pub expect_output: Vec, - pub expect_log: Vec, -} - -impl OpenSSLClient { - pub fn new(port: u16) -> OpenSSLClient { - OpenSSLClient { - port: port, - cafile: PathBuf::new(), - extra_args: Vec::new(), - expect_fails: false, - expect_output: Vec::new(), - expect_log: Vec::new(), - } - } - - pub fn arg(&mut self, arg: &str) -> &mut Self { - self.extra_args.push(arg.to_string()); - self - } - - pub fn cafile(&mut self, cafile: &Path) -> &mut Self { - self.cafile = cafile.to_path_buf(); - self - } - - pub fn expect(&mut self, expect: &str) -> &mut Self { - self.expect_output.push(expect.to_string()); - self - } - - pub fn expect_log(&mut self, expect: &str) -> &mut Self { - self.expect_log.push(expect.to_string()); - self - } - - pub fn fails(&mut self) -> &mut Self { - self.expect_fails = true; - self - } - - pub fn go(&mut self) -> Option<()> { - let mut extra_args = Vec::new(); - extra_args.extend(&self.extra_args); - - let mut subp = process::Command::new(openssl_find()); - subp.arg("s_client") - .arg("-tls1_2") - .arg("-host") - .arg("localhost") - .arg("-port") - .arg(self.port.to_string()) - .arg("-CAfile") - .arg(&self.cafile) - .args(&extra_args); - - let output = subp.output() - .unwrap_or_else(|e| panic!("failed to execute: {}", e)); - - let stdout_str = unsafe { String::from_utf8_unchecked(output.stdout.clone()) }; - let stderr_str = unsafe { String::from_utf8_unchecked(output.stderr.clone()) }; - - print!("{}", stdout_str); - print!("{}", stderr_str); - - for expect in &self.expect_output { - let re = Regex::new(expect).unwrap(); - if re.find(&stdout_str).is_none() { - println!("We expected to find '{}' in the following output:", expect); - println!("{:?}", output); - panic!("Test failed"); - } - } - - for expect in &self.expect_log { - let re = Regex::new(expect).unwrap(); - if re.find(&stderr_str).is_none() { - println!("We expected to find '{}' in the following output:", expect); - println!("{:?}", output); - panic!("Test failed"); - } - } - - if self.expect_fails { - assert!(output.status.code().unwrap() != 0); - } else { - assert!(output.status.success()); - } - - Some(()) - } -}