// This is a test shim for the BoringSSL-Go ('bogo') TLS // test suite. See bogo/ for this in action. // // https://boringssl.googlesource.com/boringssl/+/master/ssl/test // extern crate rustls; extern crate webpki; extern crate env_logger; use std::env; use std::process; use std::net; use std::fs; use std::io; use std::io::BufReader; use std::io::{Write, Read}; use std::sync::Arc; use rustls::internal::msgs::enums::ProtocolVersion; static BOGO_NACK: i32 = 89; macro_rules! println_err( ($($arg:tt)*) => { { writeln!(&mut ::std::io::stderr(), $($arg)*).unwrap(); } } ); #[derive(Debug)] struct Options { port: u16, server: bool, resumes: usize, require_any_client_cert: bool, offer_no_client_cas: bool, tickets: bool, queue_data: bool, host_name: String, key_file: String, cert_file: String, protocols: Vec, support_tls13: bool, support_tls12: bool, min_version: Option, max_version: Option, expect_curve: u16, } impl Options { fn new() -> Options { Options { port: 0, server: false, resumes: 0, tickets: true, host_name: "example.com".to_string(), queue_data: false, require_any_client_cert: false, offer_no_client_cas: false, key_file: "".to_string(), cert_file: "".to_string(), protocols: vec![], support_tls13: true, support_tls12: true, min_version: None, max_version: None, expect_curve: 0, } } fn version_allowed(&self, vers: ProtocolVersion) -> bool { (self.min_version.is_none() || vers.get_u16() >= self.min_version.unwrap().get_u16()) && (self.max_version.is_none() || vers.get_u16() <= self.max_version.unwrap().get_u16()) } fn tls13_supported(&self) -> bool { self.support_tls13 && (self.version_allowed(ProtocolVersion::TLSv1_3) || self.version_allowed(ProtocolVersion::Unknown(0x7f12))) } fn tls12_supported(&self) -> bool { self.support_tls12 && self.version_allowed(ProtocolVersion::TLSv1_2) } } fn load_cert(filename: &str) -> Vec { let certfile = fs::File::open(filename).expect("cannot open certificate file"); let mut reader = BufReader::new(certfile); rustls::internal::pemfile::certs(&mut reader).unwrap() } fn load_key(filename: &str) -> rustls::PrivateKey { if filename.contains("ecdsa") { println_err!("No ECDSA key support"); process::exit(BOGO_NACK); } let keyfile = fs::File::open(filename).expect("cannot open private key file"); let mut reader = BufReader::new(keyfile); let keys = rustls::internal::pemfile::rsa_private_keys(&mut reader).unwrap(); assert!(keys.len() == 1); keys[0].clone() } fn split_protocols(protos: &str) -> Vec { let mut ret = Vec::new(); let mut offs = 0; while offs < protos.len() { let len = protos.as_bytes()[offs] as usize; let item = protos[offs + 1..offs + 1 + len].to_string(); ret.push(item); offs += 1 + len; } ret } struct NoVerification {} impl rustls::ClientCertVerifier for NoVerification { fn verify_client_cert(&self, _roots: &rustls::RootCertStore, _certs: &[rustls::Certificate]) -> Result<(), rustls::TLSError> { Ok(()) } } impl rustls::ServerCertVerifier for NoVerification { fn verify_server_cert(&self, _roots: &rustls::RootCertStore, _certs: &[rustls::Certificate], _hostname: &str) -> Result<(), rustls::TLSError> { Ok(()) } } fn make_server_cfg(opts: &Options) -> Arc { let mut cfg = rustls::ServerConfig::new(); let persist = rustls::ServerSessionMemoryCache::new(32); cfg.set_persistence(persist); let cert = load_cert(&opts.cert_file); let key = load_key(&opts.key_file.replace(".pem", ".rsa")); cfg.set_single_cert(cert.clone(), key); if opts.offer_no_client_cas || opts.require_any_client_cert { cfg.client_auth_offer = true; cfg.dangerous() .set_certificate_verifier(Arc::new(NoVerification {})); } if opts.require_any_client_cert { cfg.client_auth_mandatory = true; } if opts.tickets { cfg.ticketer = rustls::Ticketer::new(); } if !opts.protocols.is_empty() { cfg.set_protocols(&opts.protocols); } cfg.versions.clear(); if opts.tls12_supported() { cfg.versions.push(ProtocolVersion::TLSv1_2); } if opts.tls13_supported() { cfg.versions.push(ProtocolVersion::TLSv1_3); } Arc::new(cfg) } fn make_client_cfg(opts: &Options) -> Arc { let mut cfg = rustls::ClientConfig::new(); let persist = rustls::ClientSessionMemoryCache::new(32); cfg.set_persistence(persist); cfg.root_store.add(&load_cert("cert.pem")[0]).unwrap(); if !opts.cert_file.is_empty() && !opts.key_file.is_empty() { let cert = load_cert(&opts.cert_file); let key = load_key(&opts.key_file.replace(".pem", ".rsa")); cfg.set_single_client_cert(cert, key); } cfg.dangerous() .set_certificate_verifier(Arc::new(NoVerification {})); if !opts.protocols.is_empty() { cfg.set_protocols(&opts.protocols); } cfg.versions.clear(); if opts.tls12_supported() { cfg.versions.push(ProtocolVersion::TLSv1_2); } if opts.tls13_supported() { cfg.versions.push(ProtocolVersion::TLSv1_3); } Arc::new(cfg) } fn quit(why: &str) -> ! { println_err!("{}", why); process::exit(0) } fn handle_err(err: rustls::TLSError) -> ! { use rustls::TLSError; use rustls::internal::msgs::enums::{AlertDescription, ContentType}; println!("TLS error: {:?}", err); match err { TLSError::InappropriateHandshakeMessage { .. } | TLSError::InappropriateMessage { .. } => quit(":UNEXPECTED_MESSAGE:"), TLSError::AlertReceived(AlertDescription::RecordOverflow) => { quit(":TLSV1_ALERT_RECORD_OVERFLOW:") } TLSError::AlertReceived(AlertDescription::HandshakeFailure) => quit(":HANDSHAKE_FAILURE:"), TLSError::CorruptMessagePayload(ContentType::Alert) => quit(":BAD_ALERT:"), TLSError::CorruptMessagePayload(ContentType::ChangeCipherSpec) => { quit(":BAD_CHANGE_CIPHER_SPEC:") } TLSError::CorruptMessagePayload(ContentType::Handshake) => quit(":BAD_HANDSHAKE_MSG:"), TLSError::CorruptMessagePayload(ContentType::Unknown(42)) => { quit(":GARBAGE:") } TLSError::CorruptMessage => quit(":GARBAGE:"), TLSError::DecryptError => quit(":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"), TLSError::PeerIncompatibleError(_) => quit(":INCOMPATIBLE:"), TLSError::PeerMisbehavedError(_) => quit(":PEER_MISBEHAVIOUR:"), TLSError::NoCertificatesPresented => quit(":NO_CERTS:"), TLSError::AlertReceived(AlertDescription::UnexpectedMessage) => { quit(":BAD_ALERT:") } TLSError::WebPKIError(webpki::Error::InvalidSignatureForPublicKey) => { quit(":BAD_SIGNATURE:") } TLSError::WebPKIError(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey) => { quit(":WRONG_SIGNATURE_TYPE:") } _ => { println_err!("unhandled error: {:?}", err); quit(":FIXME:") } } } fn flush(sess: &mut Box, conn: &mut net::TcpStream) { while sess.wants_write() { sess.write_tls(conn) .expect("write failed"); } conn.flush().unwrap(); } fn exec(opts: &Options, sess: &mut Box) { if opts.queue_data { sess.write_all(b"hello world") .unwrap(); } let mut conn = net::TcpStream::connect(("127.0.0.1", opts.port)).expect("cannot connect"); loop { flush(sess, &mut conn); if sess.wants_read() { let len = sess.read_tls(&mut conn) .expect("read failed"); if len == 0 { println!("EOF (plain)"); return; } if let Err(err) = sess.process_new_packets() { flush(sess, &mut conn); /* send any alerts before exiting */ handle_err(err); } } let mut buf = [0u8; 128]; let len = match sess.read(&mut buf) { Ok(len) => len, Err(ref err) if err.kind() == io::ErrorKind::ConnectionAborted => { println!("EOF (tls)"); return; } Err(err) => panic!("unhandled read error {:?}", err), }; for b in buf.iter_mut() { *b ^= 0xff; } sess.write_all(&buf[..len]).unwrap(); } } fn main() { let mut args: Vec<_> = env::args().collect(); env_logger::init().unwrap(); args.remove(0); println!("options: {:?}", args); let mut opts = Options::new(); while !args.is_empty() { let arg = args.remove(0); match arg.as_ref() { "-port" => { opts.port = args.remove(0).parse::().unwrap(); } "-server" => { opts.server = true; } "-key-file" => { opts.key_file = args.remove(0); } "-cert-file" => { opts.cert_file = args.remove(0); } "-resume-count" => { opts.resumes = args.remove(0).parse::().unwrap(); } "-no-tls13" => { opts.support_tls13 = false; } "-no-tls12" => { opts.support_tls12 = false; } "-min-version" => { let min = args.remove(0).parse::().unwrap(); opts.min_version = Some(ProtocolVersion::Unknown(min)); } "-max-version" => { let max = args.remove(0).parse::().unwrap(); opts.max_version = Some(ProtocolVersion::Unknown(max)); } "-max-cert-list" | "-expect-curve-id" | "-expect-peer-signature-algorithm" | "-expect-advertised-alpn" | "-expect-alpn" | "-expect-server-name" | "-expect-certificate-types" => { println!("not checking {} {}; NYI", arg, args.remove(0)); } "-select-alpn" => { opts.protocols.push(args.remove(0)); } "-require-any-client-certificate" => { opts.require_any_client_cert = true; } "-shim-writes-first" => { opts.queue_data = true; } "-host-name" => { opts.host_name = args.remove(0); } "-advertise-alpn" => { opts.protocols = split_protocols(&args.remove(0)); } "-use-null-client-ca-list" => { opts.offer_no_client_cas = true; } // defaults: "-enable-all-curves" | "-renegotiate-ignore" | "-no-tls11" | "-no-tls1" | "-no-ssl3" | "-decline-alpn" | "-expect-no-session" | "-expect-session-miss" | "-expect-extended-master-secret" | "-expect-ticket-renewal" | // internal openssl details: "-async" | "-implicit-handshake" | "-use-old-client-cert-callback" | "-use-early-callback" => {} // Not implemented things "-dtls" | "-enable-ocsp-stapling" | "-cipher" | "-psk" | "-renegotiate-freely" | "-false-start" | "-fallback-scsv" | "-fail-early-callback" | "-fail-cert-callback" | "-install-ddos-callback" | "-enable-signed-cert-timestamps" | "-ocsp-response" | "-advertise-npn" | "-verify-fail" | "-verify-peer" | "-expect-channel-id" | "-shim-shuts-down" | "-check-close-notify" | "-send-channel-id" | "-select-next-proto" | "-p384-only" | "-expect-verify-result" | "-send-alert" | "-signing-prefs" | "-digest-prefs" | "-export-keying-material" | "-use-exporter-between-reads" | "-ticket-key" | "-tls-unique" | "-enable-server-custom-extension" | "-enable-client-custom-extension" | "-expect-dhe-group-size" | "-use-ticket-callback" | "-enable-grease" | "-enable-channel-id" | "-expect-resume-curve-id" | "-resumption-delay" | "-expect-early-data-info" | "-enable-early-data" | "-expect-cipher-aes" | "-retain-only-sha256-client-cert-initial" | "-expect-peer-cert-file" | "-signed-cert-timestamps" => { println!("NYI option {:?}", arg); process::exit(BOGO_NACK); } _ => { println!("unhandled option {:?}", arg); process::exit(1); } } } println!("opts {:?}", opts); let server_cfg = if opts.server { Some(make_server_cfg(&opts)) } else { None }; let client_cfg = if !opts.server { Some(make_client_cfg(&opts)) } else { None }; let make_session = || { if opts.server { let s = Box::new(rustls::ServerSession::new(server_cfg.as_ref().unwrap())); s as Box } else { let s = Box::new(rustls::ClientSession::new(client_cfg.as_ref().unwrap(), &opts.host_name)); s as Box } }; for _ in 0..opts.resumes + 1 { let mut sess = make_session(); exec(&opts, &mut sess); } }