use std::net::TcpStream;
use std::process::{Child, Command};
use std::{env, panic, thread};
const HOST: &str = "localhost";
const PORT: &str = "8443";
#[test]
#[ignore] fn client_server_integration() {
let server_addr = format!("{HOST}:{PORT}");
if TcpStream::connect(&server_addr).is_ok() {
panic!("cannot run tests; something is already listening on {server_addr}");
}
let valgrind = env::var("VALGRIND").ok();
let standard_server = TestCase {
name: "Standard client/server tests",
server_opts: ServerOptions {
valgrind: valgrind.clone(),
env: vec![],
},
client_tests: standard_client_tests(valgrind.clone()),
};
let vectored_server = TestCase {
name: "Vectored server tests",
server_opts: ServerOptions {
valgrind: valgrind.clone(),
env: vec![("VECTORED_IO", "1")],
},
client_tests: standard_client_tests(valgrind.clone()),
};
let keylogfile_server = TestCase {
name: "SSLKEYLOG server",
server_opts: ServerOptions {
valgrind: valgrind.clone(),
env: vec![("SSLKEYLOGFILE", "/tmp/rustls-ffi.server.key")],
},
client_tests: standard_client_tests(valgrind.clone()),
};
let stderrkeylog_server = TestCase {
name: "STDERRKEYLOG server",
server_opts: ServerOptions {
valgrind: valgrind.clone(),
env: vec![("STDERRKEYLOG", "1")],
},
client_tests: standard_client_tests(valgrind.clone()),
};
let mandatory_client_auth_server = TestCase {
name: "Mandatory client auth tests",
server_opts: ServerOptions {
valgrind: valgrind.clone(),
env: vec![("AUTH_CERT", "testdata/minica.pem")],
},
client_tests: vec![
ClientTest {
name: "No client auth",
valgrind: valgrind.clone(),
env: vec![("CA_FILE", "testdata/minica.pem")],
expect_error: true, },
ClientTest {
name: "Valid client auth",
valgrind: valgrind.clone(),
env: vec![
("CA_FILE", "testdata/minica.pem"),
("AUTH_CERT", "testdata/localhost/cert.pem"),
("AUTH_KEY", "testdata/localhost/key.pem"),
],
expect_error: false,
},
],
};
let mandatory_client_auth_server_with_crls = TestCase {
name: "Mandatory client auth w/ CRLs tests",
server_opts: ServerOptions {
valgrind: valgrind.clone(),
env: vec![
("AUTH_CERT", "testdata/minica.pem"),
("AUTH_CRL", "testdata/test.crl.pem"),
],
},
client_tests: vec![
ClientTest {
name: "Valid client auth",
valgrind: valgrind.clone(),
env: vec![
("CA_FILE", "testdata/minica.pem"),
("AUTH_CERT", "testdata/example.com/cert.pem"),
("AUTH_KEY", "testdata/example.com/key.pem"),
],
expect_error: false,
},
ClientTest {
name: "Revoked client auth",
valgrind: valgrind.clone(),
env: vec![
("CA_FILE", "testdata/minica.pem"),
("AUTH_CERT", "testdata/localhost/cert.pem"),
("AUTH_KEY", "testdata/localhost/key.pem"),
],
expect_error: true, },
],
};
#[cfg(not(feature = "fips"))]
let custom_ciphersuite = "TLS13_CHACHA20_POLY1305_SHA256";
#[cfg(feature = "fips")]
let custom_ciphersuite = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
let custom_ciphersuites = TestCase {
name: "client/server with limited ciphersuites",
server_opts: ServerOptions {
valgrind: valgrind.clone(),
env: vec![("RUSTLS_CIPHERSUITE", custom_ciphersuite)],
},
client_tests: vec![
ClientTest {
name: "limited ciphersuite, supported by server",
valgrind: valgrind.clone(),
env: vec![
("NO_CHECK_CERTIFICATE", "1"),
("RUSTLS_CIPHERSUITE", custom_ciphersuite),
],
expect_error: false,
},
ClientTest {
name: "limited ciphersuite, not supported by server",
valgrind: valgrind.clone(),
env: vec![
("NO_CHECK_CERTIFICATE", "1"),
("RUSTLS_CIPHERSUITE", "TLS13_AES_128_GCM_SHA256"),
],
expect_error: true, },
],
};
TestCases(vec![
standard_server,
vectored_server,
keylogfile_server,
stderrkeylog_server,
mandatory_client_auth_server,
mandatory_client_auth_server_with_crls,
custom_ciphersuites,
])
.run();
}
fn standard_client_tests(valgrind: Option<String>) -> Vec<ClientTest> {
vec![
ClientTest {
name: "rustls-platform-verifier",
valgrind: valgrind.clone(),
env: vec![("RUSTLS_PLATFORM_VERIFIER", "1")],
expect_error: true,
},
ClientTest {
name: "With CA_FILE",
valgrind: valgrind.clone(),
env: vec![("CA_FILE", "testdata/minica.pem")],
expect_error: false,
},
ClientTest {
name: "No certificate validation",
valgrind: valgrind.clone(),
env: vec![("NO_CHECK_CERTIFICATE", "1")],
expect_error: false,
},
ClientTest {
name: "Client Vectored I/O",
valgrind: valgrind.clone(),
env: vec![("CA_FILE", "testdata/minica.pem"), ("USE_VECTORED", "1")],
expect_error: false,
},
ClientTest {
name: "Client authentication",
valgrind: valgrind.clone(),
env: vec![
("CA_FILE", "testdata/minica.pem"),
("AUTH_CERT", "testdata/localhost/cert.pem"),
("AUTH_KEY", "testdata/localhost/key.pem"),
],
expect_error: false,
},
ClientTest {
name: "SSLKEYLOGFILE",
valgrind: valgrind.clone(),
env: vec![
("CA_FILE", "testdata/minica.pem"),
("SSLKEYLOGFILE", "/tmp/rustls-ffi.client.key"),
],
expect_error: false,
},
ClientTest {
name: "STDERRKEYLOG",
valgrind: valgrind.clone(),
env: vec![("CA_FILE", "testdata/minica.pem"), ("STDERRKEYLOG", "1")],
expect_error: false,
},
]
}
struct ClientTest {
name: &'static str,
valgrind: Option<String>,
env: Vec<(&'static str, &'static str)>,
expect_error: bool,
}
impl ClientTest {
fn run(&self) {
let client_binary = client_binary();
let args = vec![HOST, PORT, "/"];
let (program, args) = match &self.valgrind {
None => (client_binary.as_str(), args),
Some(valgrind) => (
valgrind.as_str(),
[vec![client_binary.as_str()], args].concat(),
),
};
let result = Command::new(program)
.args(args)
.envs(self.env.clone())
.output()
.unwrap_or_else(|e| panic!("failed to run client binary {client_binary}: {e}"));
let passed = result.status.success() != self.expect_error;
if !passed {
println!(
"client test failed. Failed process output:\n {}",
String::from_utf8_lossy(&result.stderr)
);
}
assert!(passed, "client test failed");
}
}
struct ServerOptions {
valgrind: Option<String>,
env: Vec<(&'static str, &'static str)>,
}
impl ServerOptions {
fn run_server(&self) -> Child {
let server_binary = server_binary();
let args = vec!["testdata/localhost/cert.pem", "testdata/localhost/key.pem"];
let (program, args) = match &self.valgrind {
None => (server_binary.as_str(), args),
Some(valgrind) => (
valgrind.as_str(),
[vec![server_binary.as_str()], args].concat(),
),
};
Command::new(program)
.args(args)
.envs(self.env.clone())
.spawn()
.unwrap_or_else(|e| panic!("failed to run server binary {server_binary}: {e}"))
}
}
struct TestCases(Vec<TestCase>);
impl TestCases {
fn run(&self) {
for test_case in &self.0 {
assert!(test_case.run().is_ok(), "client test panicked");
}
}
}
struct TestCase {
name: &'static str,
server_opts: ServerOptions,
client_tests: Vec<ClientTest>,
}
impl TestCase {
fn run(&self) -> thread::Result<()> {
println!("\nRunning {:?}", self.name);
let mut server = self.server_opts.run_server();
let result = panic::catch_unwind(|| {
for client_test in &self.client_tests {
println!("\nRunning client {:?}\n", client_test.name);
client_test.run();
}
});
server.kill().expect("failed to kill server");
server.wait().expect("failed to wait on server");
result
}
}
fn client_binary() -> String {
let custom_client_binary = env::var("CLIENT_BINARY").ok();
#[cfg(not(target_os = "windows"))]
{
custom_client_binary.unwrap_or(format!("{}/client", target_dir()))
}
#[cfg(target_os = "windows")]
{
custom_client_binary.unwrap_or(format!("{}/client.exe", target_dir()))
}
}
fn server_binary() -> String {
let custom_server_binary = env::var("SERVER_BINARY").ok();
#[cfg(not(target_os = "windows"))]
{
custom_server_binary.unwrap_or(format!("{}/server", target_dir()))
}
#[cfg(target_os = "windows")]
{
custom_server_binary.unwrap_or(format!("{}/server.exe", target_dir()))
}
}
fn target_dir() -> String {
env::var("CARGO_TARGET_DIR")
.unwrap_or_else(|_| "target".to_string())
.to_string()
}