testing updates

This commit is contained in:
Joseph Birr-Pixton 2016-06-03 02:28:28 +01:00
parent 078802cdd7
commit 82bdabbb0a
10 changed files with 353 additions and 85 deletions

View File

@ -14,5 +14,6 @@ rustc-serialize = "0.3"
log = "0.3.6"
[dev-dependencies]
env_logger = "0.3.3"
mio = "0.5.1"
docopt = "0.6"

View File

@ -4,10 +4,13 @@ use std::process;
extern crate mio;
use mio::tcp::TcpStream;
use std::net::SocketAddr;
use std::str;
use std::io;
use std::io::{Read, Write, BufReader};
extern crate env_logger;
extern crate rustc_serialize;
extern crate docopt;
use docopt::Docopt;
@ -73,9 +76,40 @@ impl io::Read for TlsClient {
}
}
fn find_suite(name: &str) -> Option<&'static rustls::suites::SupportedCipherSuite> {
for suite in rustls::suites::DEFAULT_CIPHERSUITES.iter() {
let sname = format!("{:?}", suite.suite).to_lowercase();
if sname == name.to_string().to_lowercase() {
return Some(suite);
}
}
None
}
fn lookup_suites(suites: &Vec<String>) -> Vec<&'static rustls::suites::SupportedCipherSuite> {
let mut out = Vec::new();
for csname in suites {
let scs = find_suite(csname);
match scs {
Some(s) => out.push(s),
None => panic!("cannot look up ciphersuite '{}'", csname)
}
}
out
}
impl TlsClient {
fn new(sock: TcpStream, hostname: &str, cafile: &str) -> TlsClient {
fn new(sock: TcpStream, hostname: &str, cafile: &str, suites: &Vec<String>) -> TlsClient {
let mut config = rustls::client::ClientConfig::default();
if suites.len() != 0 {
config.ciphersuites = lookup_suites(suites);
}
let certfile = std::fs::File::open(cafile)
.unwrap();
let mut reader = BufReader::new(certfile);
@ -110,6 +144,7 @@ impl TlsClient {
if rc.unwrap() == 0 {
println!("EOF");
self.closing = true;
self.clean_closure = true;
return;
}
@ -181,7 +216,7 @@ before making the connection. --http replaces this with a
basic HTTP GET request for /.
Usage:
tlsclient [-p PORT] [--http] [--cafile CAFILE] <hostname>
tlsclient [--verbose] [-p PORT] [--http] [--cafile CAFILE] [--suite SUITE...] <hostname>
tlsclient --version
tlsclient --help
@ -189,6 +224,9 @@ Options:
-p, --port PORT Connect to PORT. Default is 443.
--http Send a basic HTTP GET request for /.
--cafile CAFILE Read root certificates from CAFILE.
--suite SUITE Disable default cipher suite list, and use
SUITE instead.
--verbose Emit log output.
--version Show tool version.
--help Show this screen.
";
@ -197,13 +235,26 @@ Options:
struct Args {
flag_port: Option<u16>,
flag_http: bool,
flag_verbose: bool,
flag_suite: Vec<String>,
flag_cafile: Option<String>,
arg_hostname: String
}
fn main() {
fn lookup_ipv4(host: &str, port: u16) -> SocketAddr {
use std::net::ToSocketAddrs;
let addrs = (host, port).to_socket_addrs().unwrap();
for addr in addrs {
if let SocketAddr::V4(_) = addr {
return addr.clone();
}
}
unreachable!("Cannot lookup address");
}
fn main() {
let version = env!("CARGO_PKG_NAME").to_string() + ", version: " + env!("CARGO_PKG_VERSION");
let args: Args = Docopt::new(USAGE)
@ -212,17 +263,19 @@ fn main() {
.and_then(|d| d.decode())
.unwrap_or_else(|e| e.exit());
let port = args.flag_port.unwrap_or(443);
if args.flag_verbose {
let mut logger = env_logger::LogBuilder::new();
logger.parse("debug");
logger.init().unwrap();
}
let addr = (args.arg_hostname.as_str(), port).to_socket_addrs()
.unwrap()
.next()
.unwrap();
let port = args.flag_port.unwrap_or(443);
let addr = lookup_ipv4(args.arg_hostname.as_str(), port);
let cafile = args.flag_cafile.unwrap_or("/etc/ssl/certs/ca-certificates.crt".to_string());
let sock = TcpStream::connect(&addr).unwrap();
let mut tlsclient = TlsClient::new(sock, &args.arg_hostname, &cafile);
let mut tlsclient = TlsClient::new(sock, &args.arg_hostname, &cafile, &args.flag_suite);
if args.flag_http {
let httpreq = format!("GET / HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", args.arg_hostname);

View File

@ -8,6 +8,7 @@ use msgs::handshake::ClientExtension;
use msgs::handshake::{SupportedSignatureAlgorithms, SupportedMandatedSignatureAlgorithms};
use msgs::handshake::{EllipticCurveList, SupportedCurves};
use msgs::handshake::{ECPointFormatList, SupportedPointFormats};
use msgs::handshake::ServerKeyExchangePayload;
use msgs::ccs::ChangeCipherSpecPayload;
use client::{ClientSession, ConnState};
use suites;
@ -156,6 +157,11 @@ fn handle_server_kx(sess: &mut ClientSession, m: &Message) -> Result<ConnState,
sess.handshake_data.server_kx_sig = decoded_kx.get_sig();
decoded_kx.encode_params(&mut sess.handshake_data.server_kx_params);
match decoded_kx {
ServerKeyExchangePayload::ECDHE(ecdhe) => info!("ECDHE curve is {:?}", ecdhe.params.curve_params),
_ => ()
}
Ok(ConnState::ExpectServerHelloDone)
}

View File

@ -14,6 +14,6 @@ mod verify;
mod handshake;
mod server_hs;
mod client_hs;
mod suites;
pub mod suites;
pub mod server;
pub mod client;

View File

@ -15,4 +15,4 @@ keyUsage = cRLSign, keyCertSign, digitalSignature, nonRepudiation,keyEnciphermen
[ alt_names ]
DNS.1 = testserver.com
DNS.2 = second.testserver.com
DNS.3 = localhost

View File

@ -1,12 +0,0 @@
#!/bin/sh
openssl s_server -www -accept 8443 -key test/end.key -cert test/end.cert -CAfile test/end.chain -msg -debug -state > server.log 2>&1 &
server=$!
sleep 1
./target/debug/s_client > client.log 2>&1 &
client=$!
sleep 5
kill $server
kill $client

View File

@ -4,69 +4,12 @@
* each test.
*/
use std::process;
#[allow(dead_code)]
mod common;
use common::{TlsClient, polite};
struct SClientTest {
hostname: String,
expect_fails: bool,
expect_output: Option<String>
}
fn connect(hostname: &str) -> SClientTest {
SClientTest {
hostname: hostname.to_string(),
expect_fails: false,
expect_output: None
}
}
impl SClientTest {
fn expect(&mut self, expect: &str) -> &mut SClientTest {
self.expect_output = Some(expect.to_string());
self
}
fn fails(&mut self) -> &mut SClientTest {
self.expect_fails = true;
self
}
fn go(&mut self) -> Option<()> {
println!("cwd {:?}",
process::Command::new("pwd")
.output()
.unwrap());
let output = process::Command::new("target/debug/examples/tlsclient")
.arg("--http")
.arg(&self.hostname)
.output()
.unwrap_or_else(|e| { panic!("failed to execute: {}", e) });
if self.expect_fails {
assert!(output.status.code().unwrap() != 0);
} else {
assert!(output.status.success());
}
let stdout_str = String::from_utf8(output.stdout.clone()).unwrap();
if self.expect_output.is_some() && stdout_str.find(self.expect_output.as_ref().unwrap()).is_none() {
println!("We expected to find '{}' in the following output:", self.expect_output.as_ref().unwrap());
println!("{:?}", output);
panic!("Test failed");
}
Some(())
}
}
/* For tests which connect to internet servers, don't go crazy. */
fn polite() {
use std::thread;
use std::time;
thread::sleep(time::Duration::from_secs(1));
fn connect(hostname: &str) -> TlsClient {
TlsClient::new(hostname)
}
#[test]

219
tests/common/mod.rs Normal file
View File

@ -0,0 +1,219 @@
use std::process;
use std::thread;
use std::time;
use std::net;
/* For tests which connect to internet servers, don't go crazy. */
pub fn polite() {
thread::sleep(time::Duration::from_secs(1));
}
pub struct TlsClient {
pub hostname: String,
pub port: u16,
pub http: bool,
pub cafile: Option<String>,
pub suites: Vec<String>,
pub verbose: bool,
pub expect_fails: bool,
pub expect_output: Option<String>,
pub expect_log: Option<String>
}
impl TlsClient {
pub fn new(hostname: &str) -> TlsClient {
TlsClient {
hostname: hostname.to_string(),
port: 443,
http: true,
cafile: None,
verbose: false,
suites: Vec::new(),
expect_fails: false,
expect_output: None,
expect_log: None
}
}
pub fn cafile(&mut self, cafile: &str) -> &mut TlsClient {
self.cafile = Some(cafile.to_string());
self
}
pub fn verbose(&mut self) -> &mut TlsClient {
self.verbose = true;
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 = Some(expect.to_string());
self
}
pub fn expect_log(&mut self, expect: &str) -> &mut TlsClient {
self.expect_log = Some(expect.to_string());
self
}
pub fn suite(&mut self, suite: &str) -> &mut TlsClient {
self.suites.push(suite.to_string());
self
}
pub fn fails(&mut self) -> &mut TlsClient {
self.expect_fails = true;
self
}
pub fn go(&mut self) -> Option<()> {
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.cafile.is_some() {
args.push("--cafile");
args.push(self.cafile.as_ref().unwrap());
}
for suite in &self.suites {
args.push("--suite");
args.push(suite.as_ref());
}
if self.verbose {
args.push("--verbose");
}
let output = process::Command::new("target/debug/examples/tlsclient")
.args(&args)
.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();
if self.expect_output.is_some() && stdout_str.find(self.expect_output.as_ref().unwrap()).is_none() {
println!("We expected to find '{}' in the following output:", self.expect_output.as_ref().unwrap());
println!("{:?}", output);
panic!("Test failed");
}
if self.expect_log.is_some() && stderr_str.find(self.expect_log.as_ref().unwrap()).is_none() {
println!("We expected to find '{}' in the following output:", self.expect_log.as_ref().unwrap());
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 key: String,
pub cert: String,
pub chain: String,
pub extra_args: Vec<&'static str>,
pub child: Option<process::Child>
}
fn unused_port(mut port: u16) -> u16 {
loop {
if let Err(_) = net::TcpStream::connect(("127.0.0.1", port)) {
return port;
}
port += 1;
}
}
impl OpenSSLServer {
pub fn new(start_port: u16) -> OpenSSLServer {
OpenSSLServer {
port: unused_port(start_port),
http: true,
key: "test-ca/end.key".to_string(),
cert: "test-ca/end.cert".to_string(),
chain: "test-ca/end.chain".to_string(),
extra_args: Vec::new(),
child: None
}
}
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");
}
println!("args = {:?}", extra_args);
let child = process::Command::new("openssl")
.arg("s_server")
.arg("-accept").arg(self.port.to_string())
.arg("-key").arg(&self.key)
.arg("-cert").arg(&self.cert)
.arg("-CAfile").arg(&self.chain)
.args(&extra_args)
.stdout(process::Stdio::null())
.stderr(process::Stdio::null())
.spawn()
.expect("cannot run openssl server");
self.wait_for_port().expect("server did not come up");
self.child = Some(child);
self
}
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("test-ca/ca.cert");
c
}
fn wait_for_port(&self) -> Option<()> {
let mut count = 0;
loop {
thread::sleep(time::Duration::from_millis(100));
if let Ok(_) = net::TcpStream::connect(("127.0.0.1", self.port)) {
return Some(())
}
count += 1;
if count == 10 {
return None
}
}
}
}

29
tests/curves.rs Normal file
View File

@ -0,0 +1,29 @@
/* Engineer a handshake using each curve. */
#[allow(dead_code)]
mod common;
use common::OpenSSLServer;
#[test]
fn curve_nistp256() {
let mut server = OpenSSLServer::new(8300);
server.arg("-named_curve").arg("prime256v1");
server.run();
server.client()
.verbose()
.expect_log("ECDHE curve is ECParameters { curve_type: NamedCurve, named_curve: secp256r1 }")
.go();
server.kill();
}
#[test]
fn curve_nistp384() {
let mut server = OpenSSLServer::new(8400);
server.arg("-named_curve").arg("secp384r1");
server.run();
server.client()
.verbose()
.expect_log("ECDHE curve is ECParameters { curve_type: NamedCurve, named_curve: secp384r1 }")
.go();
server.kill();
}

29
tests/suites.rs Normal file
View File

@ -0,0 +1,29 @@
/* Engineer a handshake using each suite. */
#[allow(dead_code)]
mod common;
use common::OpenSSLServer;
#[test]
fn ecdhe_rsa_aes_128_gcm_sha256() {
let mut server = OpenSSLServer::new(8100);
server.run();
server.client()
.verbose()
.suite("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
.expect("Ciphers common between both SSL end points:\nECDHE-RSA-AES128-GCM-SHA256")
.go();
server.kill();
}
#[test]
fn ecdhe_rsa_aes_256_gcm_sha384() {
let mut server = OpenSSLServer::new(8200);
server.run();
server.client()
.verbose()
.suite("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
.expect("Ciphers common between both SSL end points:\nECDHE-RSA-AES256-GCM-SHA384")
.go();
server.kill();
}