// 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 // use base64; use env_logger; use rustls; use rustls::internal::msgs::codec::{Codec, Reader}; use rustls::internal::msgs::persist; use rustls::quic::{self, ClientQuicExt, QuicExt, ServerQuicExt}; use rustls::server::ClientHello; use rustls::{CipherSuite, ProtocolVersion}; use rustls::{ClientConnection, Connection, ServerConnection}; use std::convert::TryInto; use std::env; use std::fs; use std::io; use std::io::BufReader; use std::io::{Read, Write}; use std::net; use std::process; use std::sync::Arc; use std::time::SystemTime; 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, max_fragment: Option, resumes: usize, verify_peer: bool, require_any_client_cert: bool, offer_no_client_cas: bool, tickets: bool, resume_with_tickets_disabled: bool, queue_data: bool, queue_data_on_resume: bool, only_write_one_byte_after_handshake: bool, only_write_one_byte_after_handshake_on_resume: bool, shut_down_after_handshake: bool, check_close_notify: bool, host_name: String, use_sni: bool, send_sct: bool, key_file: String, cert_file: String, protocols: Vec, support_tls13: bool, support_tls12: bool, min_version: Option, max_version: Option, server_ocsp_response: Vec, server_sct_list: Vec, use_signing_scheme: u16, curves: Option>, export_keying_material: usize, export_keying_material_label: String, export_keying_material_context: String, export_keying_material_context_used: bool, read_size: usize, quic_transport_params: Vec, expect_quic_transport_params: Vec, enable_early_data: bool, expect_ticket_supports_early_data: bool, expect_accept_early_data: bool, expect_reject_early_data: bool, expect_version: u16, resumption_delay: u32, } impl Options { fn new() -> Self { Options { port: 0, server: false, max_fragment: None, resumes: 0, verify_peer: false, tickets: true, resume_with_tickets_disabled: false, host_name: "example.com".to_string(), use_sni: false, send_sct: false, queue_data: false, queue_data_on_resume: false, only_write_one_byte_after_handshake: false, only_write_one_byte_after_handshake_on_resume: false, shut_down_after_handshake: false, check_close_notify: 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, server_ocsp_response: vec![], server_sct_list: vec![], use_signing_scheme: 0, curves: None, export_keying_material: 0, export_keying_material_label: "".to_string(), export_keying_material_context: "".to_string(), export_keying_material_context_used: false, read_size: 512, quic_transport_params: vec![], expect_quic_transport_params: vec![], enable_early_data: false, expect_ticket_supports_early_data: false, expect_accept_early_data: false, expect_reject_early_data: false, expect_version: 0, resumption_delay: 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) } fn tls12_supported(&self) -> bool { self.support_tls12 && self.version_allowed(ProtocolVersion::TLSv1_2) } fn supported_versions(&self) -> Vec<&'static rustls::SupportedProtocolVersion> { let mut versions = vec![]; if self.tls12_supported() { versions.push(&rustls::version::TLS12); } if self.tls13_supported() { versions.push(&rustls::version::TLS13); } versions } } fn load_cert(filename: &str) -> Vec { let certfile = fs::File::open(filename).expect("cannot open certificate file"); let mut reader = BufReader::new(certfile); rustls_pemfile::certs(&mut reader) .unwrap() .iter() .map(|v| rustls::Certificate(v.clone())) .collect() } fn load_key(filename: &str) -> rustls::PrivateKey { let keyfile = fs::File::open(filename).expect("cannot open private key file"); let mut reader = BufReader::new(keyfile); let keys = rustls_pemfile::pkcs8_private_keys(&mut reader).unwrap(); assert!(keys.len() == 1); rustls::PrivateKey(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 DummyClientAuth { mandatory: bool, } impl rustls::server::ClientCertVerifier for DummyClientAuth { fn offer_client_auth(&self) -> bool { true } fn client_auth_mandatory(&self) -> Option { Some(self.mandatory) } fn client_auth_root_subjects(&self) -> Option { Some(rustls::DistinguishedNames::new()) } fn verify_client_cert( &self, _end_entity: &rustls::Certificate, _intermediates: &[rustls::Certificate], _now: SystemTime, ) -> Result { Ok(rustls::server::ClientCertVerified::assertion()) } } struct DummyServerAuth { send_sct: bool, } impl rustls::client::ServerCertVerifier for DummyServerAuth { fn verify_server_cert( &self, _end_entity: &rustls::Certificate, _certs: &[rustls::Certificate], _hostname: &rustls::ServerName, _scts: &mut dyn Iterator, _ocsp: &[u8], _now: SystemTime, ) -> Result { Ok(rustls::client::ServerCertVerified::assertion()) } fn request_scts(&self) -> bool { self.send_sct } } struct FixedSignatureSchemeSigningKey { key: Arc, scheme: rustls::SignatureScheme, } impl rustls::sign::SigningKey for FixedSignatureSchemeSigningKey { fn choose_scheme( &self, offered: &[rustls::SignatureScheme], ) -> Option> { if offered.contains(&self.scheme) { self.key.choose_scheme(&[self.scheme]) } else { self.key.choose_scheme(&[]) } } fn algorithm(&self) -> rustls::SignatureAlgorithm { self.key.algorithm() } } struct FixedSignatureSchemeServerCertResolver { resolver: Arc, scheme: rustls::SignatureScheme, } impl rustls::server::ResolvesServerCert for FixedSignatureSchemeServerCertResolver { fn resolve(&self, client_hello: ClientHello) -> Option> { let mut certkey = self.resolver.resolve(client_hello)?; Arc::make_mut(&mut certkey).key = Arc::new(FixedSignatureSchemeSigningKey { key: certkey.key.clone(), scheme: self.scheme, }); Some(certkey) } } struct FixedSignatureSchemeClientCertResolver { resolver: Arc, scheme: rustls::SignatureScheme, } impl rustls::client::ResolvesClientCert for FixedSignatureSchemeClientCertResolver { fn resolve( &self, acceptable_issuers: &[&[u8]], sigschemes: &[rustls::SignatureScheme], ) -> Option> { if !sigschemes.contains(&self.scheme) { quit(":NO_COMMON_SIGNATURE_ALGORITHMS:"); } let mut certkey = self .resolver .resolve(acceptable_issuers, sigschemes)?; Arc::make_mut(&mut certkey).key = Arc::new(FixedSignatureSchemeSigningKey { key: certkey.key.clone(), scheme: self.scheme, }); Some(certkey) } fn has_certs(&self) -> bool { self.resolver.has_certs() } } fn lookup_scheme(scheme: u16) -> rustls::SignatureScheme { match scheme { 0x0401 => rustls::SignatureScheme::RSA_PKCS1_SHA256, 0x0501 => rustls::SignatureScheme::RSA_PKCS1_SHA384, 0x0601 => rustls::SignatureScheme::RSA_PKCS1_SHA512, 0x0403 => rustls::SignatureScheme::ECDSA_NISTP256_SHA256, 0x0503 => rustls::SignatureScheme::ECDSA_NISTP384_SHA384, 0x0804 => rustls::SignatureScheme::RSA_PSS_SHA256, 0x0805 => rustls::SignatureScheme::RSA_PSS_SHA384, 0x0806 => rustls::SignatureScheme::RSA_PSS_SHA512, 0x0807 => rustls::SignatureScheme::ED25519, // TODO: add support for Ed448 // 0x0808 => rustls::SignatureScheme::ED448, _ => { println_err!("Unsupported signature scheme {:04x}", scheme); process::exit(BOGO_NACK); } } } fn lookup_kx_group(group: u16) -> &'static rustls::SupportedKxGroup { match group { 0x001d => &rustls::kx_group::X25519, 0x0017 => &rustls::kx_group::SECP256R1, 0x0018 => &rustls::kx_group::SECP384R1, _ => { println_err!("Unsupported kx group {:04x}", group); process::exit(BOGO_NACK); } } } struct ServerCacheWithResumptionDelay { delay: u32, storage: Arc, } impl ServerCacheWithResumptionDelay { fn new(delay: u32) -> Arc { Arc::new(Self { delay, storage: rustls::server::ServerSessionMemoryCache::new(32), }) } } fn align_time() { /* we don't have an injectable clock source in rustls' public api, and * resumption timing is in seconds resolution, so tests that use * resumption_delay tend to be flickery if the seconds time ticks * during this. * * this function delays until a fresh second ticks, which alleviates * this. gross! */ use std::{thread, time}; fn sample() -> u64 { time::SystemTime::now() .duration_since(time::SystemTime::UNIX_EPOCH) .unwrap() .as_secs() } let start_secs = sample(); while start_secs == sample() { thread::sleep(time::Duration::from_millis(20)); } } impl rustls::server::StoresServerSessions for ServerCacheWithResumptionDelay { fn put(&self, key: Vec, value: Vec) -> bool { let mut ssv = persist::ServerSessionValue::read_bytes(&value).unwrap(); ssv.creation_time_sec -= self.delay as u64; self.storage .put(key, ssv.get_encoding()) } fn get(&self, key: &[u8]) -> Option> { self.storage.get(key) } fn take(&self, key: &[u8]) -> Option> { self.storage.take(key) } fn can_cache(&self) -> bool { self.storage.can_cache() } } fn make_server_cfg(opts: &Options) -> Arc { let client_auth = if opts.verify_peer || opts.offer_no_client_cas || opts.require_any_client_cert { Arc::new(DummyClientAuth { mandatory: opts.require_any_client_cert, }) } else { rustls::server::NoClientAuth::new() }; let cert = load_cert(&opts.cert_file); let key = load_key(&opts.key_file); let kx_groups = if let Some(curves) = &opts.curves { curves .iter() .map(|curveid| lookup_kx_group(*curveid)) .collect() } else { rustls::ALL_KX_GROUPS.to_vec() }; let mut cfg = rustls::ServerConfig::builder() .with_safe_default_cipher_suites() .with_kx_groups(&kx_groups) .with_protocol_versions(&opts.supported_versions()) .unwrap() .with_client_cert_verifier(client_auth) .with_single_cert_with_ocsp_and_sct( cert.clone(), key, opts.server_ocsp_response.clone(), opts.server_sct_list.clone(), ) .unwrap(); cfg.session_storage = ServerCacheWithResumptionDelay::new(opts.resumption_delay); cfg.max_fragment_size = opts.max_fragment; if opts.use_signing_scheme > 0 { let scheme = lookup_scheme(opts.use_signing_scheme); cfg.cert_resolver = Arc::new(FixedSignatureSchemeServerCertResolver { resolver: cfg.cert_resolver.clone(), scheme, }); } if opts.tickets { cfg.ticketer = rustls::Ticketer::new().unwrap(); } else if opts.resumes == 0 { cfg.session_storage = Arc::new(rustls::server::NoServerSessionStorage {}); } if !opts.protocols.is_empty() { cfg.alpn_protocols = opts .protocols .iter() .map(|proto| proto.as_bytes().to_vec()) .collect::>(); } if opts.enable_early_data { // see kMaxEarlyDataAccepted in boringssl, which bogo validates cfg.max_early_data_size = 14336; cfg.send_half_rtt_data = true; } Arc::new(cfg) } struct ClientCacheWithoutKxHints { delay: u32, storage: Arc, } impl ClientCacheWithoutKxHints { fn new(delay: u32) -> Arc { Arc::new(ClientCacheWithoutKxHints { delay, storage: rustls::client::ClientSessionMemoryCache::new(32), }) } } impl rustls::client::StoresClientSessions for ClientCacheWithoutKxHints { fn put(&self, key: Vec, value: Vec) -> bool { if key.len() > 2 && key[0] == b'k' && key[1] == b'x' { return true; } let mut reader = Reader::init(&value[2..]); let csv = CipherSuite::read_bytes(&value[..2]) .and_then(|suite| { persist::ClientSessionValue::read(&mut reader, suite, &rustls::ALL_CIPHER_SUITES) }) .unwrap(); let value = match csv { persist::ClientSessionValue::Tls13(mut tls13) => { tls13.common.rewind_epoch(self.delay); tls13.get_encoding() } persist::ClientSessionValue::Tls12(mut tls12) => { tls12.common.rewind_epoch(self.delay); tls12.get_encoding() } }; self.storage.put(key, value) } fn get(&self, key: &[u8]) -> Option> { self.storage.get(key) } } fn make_client_cfg(opts: &Options) -> Arc { let kx_groups = if let Some(curves) = &opts.curves { curves .iter() .map(|curveid| lookup_kx_group(*curveid)) .collect() } else { rustls::ALL_KX_GROUPS.to_vec() }; let cfg = rustls::ClientConfig::builder() .with_safe_default_cipher_suites() .with_kx_groups(&kx_groups) .with_protocol_versions(&opts.supported_versions()) .expect("inconsistent settings") .with_custom_certificate_verifier(Arc::new(DummyServerAuth { send_sct: opts.send_sct, })); let mut cfg = 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); cfg.with_single_cert(cert, key).unwrap() } else { cfg.with_no_client_auth() }; if !opts.cert_file.is_empty() && opts.use_signing_scheme > 0 { let scheme = lookup_scheme(opts.use_signing_scheme); cfg.client_auth_cert_resolver = Arc::new(FixedSignatureSchemeClientCertResolver { resolver: cfg.client_auth_cert_resolver.clone(), scheme, }); } let persist = ClientCacheWithoutKxHints::new(opts.resumption_delay); cfg.session_storage = persist; cfg.enable_sni = opts.use_sni; cfg.max_fragment_size = opts.max_fragment; if !opts.protocols.is_empty() { cfg.alpn_protocols = opts .protocols .iter() .map(|proto| proto.as_bytes().to_vec()) .collect(); } if opts.enable_early_data { cfg.enable_early_data = true; } Arc::new(cfg) } fn quit(why: &str) -> ! { println_err!("{}", why); process::exit(0) } fn quit_err(why: &str) -> ! { println_err!("{}", why); process::exit(1) } fn handle_err(err: rustls::Error) -> ! { use rustls::Error; use rustls::{AlertDescription, ContentType}; use std::{thread, time}; println!("TLS error: {:?}", err); thread::sleep(time::Duration::from_millis(100)); match err { Error::InappropriateHandshakeMessage { .. } | Error::InappropriateMessage { .. } => { quit(":UNEXPECTED_MESSAGE:") } Error::AlertReceived(AlertDescription::RecordOverflow) => { quit(":TLSV1_ALERT_RECORD_OVERFLOW:") } Error::AlertReceived(AlertDescription::HandshakeFailure) => quit(":HANDSHAKE_FAILURE:"), Error::AlertReceived(AlertDescription::ProtocolVersion) => quit(":WRONG_VERSION:"), Error::AlertReceived(AlertDescription::InternalError) => { quit(":PEER_ALERT_INTERNAL_ERROR:") } Error::CorruptMessagePayload(ContentType::Alert) => quit(":BAD_ALERT:"), Error::CorruptMessagePayload(ContentType::ChangeCipherSpec) => { quit(":BAD_CHANGE_CIPHER_SPEC:") } Error::CorruptMessagePayload(ContentType::Handshake) => quit(":BAD_HANDSHAKE_MSG:"), Error::CorruptMessagePayload(ContentType::Unknown(42)) => quit(":GARBAGE:"), Error::CorruptMessage => quit(":GARBAGE:"), Error::DecryptError => quit(":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"), Error::PeerIncompatibleError(_) => quit(":INCOMPATIBLE:"), Error::PeerMisbehavedError(s) if s == "too much early_data received" => { quit(":TOO_MUCH_READ_EARLY_DATA:") } Error::PeerMisbehavedError(_) => quit(":PEER_MISBEHAVIOUR:"), Error::NoCertificatesPresented => quit(":NO_CERTS:"), Error::AlertReceived(AlertDescription::UnexpectedMessage) => quit(":BAD_ALERT:"), Error::AlertReceived(AlertDescription::DecompressionFailure) => { quit_err(":SSLV3_ALERT_DECOMPRESSION_FAILURE:") } Error::InvalidCertificateEncoding => quit(":CANNOT_PARSE_LEAF_CERT:"), Error::InvalidCertificateSignature => quit(":BAD_SIGNATURE:"), Error::InvalidCertificateSignatureType => quit(":WRONG_SIGNATURE_TYPE:"), Error::PeerSentOversizedRecord => quit(":DATA_LENGTH_TOO_LONG:"), _ => { println_err!("unhandled error: {:?}", err); quit(":FIXME:") } } } fn flush(sess: &mut Connection, conn: &mut net::TcpStream) { while sess.wants_write() { match sess.write_tls(conn) { Err(err) => { println!("IO error: {:?}", err); process::exit(0); } Ok(_) => {} } } conn.flush().unwrap(); } fn client(conn: &mut Connection) -> &mut ClientConnection { conn.try_into().unwrap() } fn server(conn: &mut Connection) -> &mut ServerConnection { match conn { Connection::Server(s) => s, _ => panic!("Connection is not a ServerConnection"), } } fn exec(opts: &Options, mut sess: Connection, count: usize) { let mut sent_message = false; if opts.queue_data || (opts.queue_data_on_resume && count > 0) { if count > 0 && opts.enable_early_data { let len = client(&mut sess) .early_data() .expect("0rtt not available") .write(b"hello") .expect("0rtt write failed"); sess.writer() .write_all(&b"hello"[len..]) .unwrap(); sent_message = true; } else if !opts.only_write_one_byte_after_handshake { let _ = sess.writer().write_all(b"hello"); sent_message = true; } } let addrs = [ net::SocketAddr::from((net::Ipv6Addr::LOCALHOST, opts.port)), net::SocketAddr::from((net::Ipv4Addr::LOCALHOST, opts.port)), ]; let mut conn = net::TcpStream::connect(&addrs[..]).expect("cannot connect"); let mut sent_shutdown = false; let mut sent_exporter = false; let mut quench_writes = false; loop { if !quench_writes { flush(&mut sess, &mut conn); } if sess.wants_read() { match sess.read_tls(&mut conn) { Ok(_) => {} Err(ref err) if err.kind() == io::ErrorKind::ConnectionReset => {} Err(err) => panic!("invalid read: {}", err), }; if let Err(err) = sess.process_new_packets() { flush(&mut sess, &mut conn); /* send any alerts before exiting */ handle_err(err); } } if opts.server && opts.enable_early_data { if let Some(ref mut ed) = server(&mut sess).early_data() { let mut data = Vec::new(); let data_len = ed .read_to_end(&mut data) .expect("cannot read early_data"); for b in data.iter_mut() { *b ^= 0xff; } sess.writer() .write_all(&data[..data_len]) .expect("cannot echo early_data in 1rtt data"); } } if !sess.is_handshaking() && opts.export_keying_material > 0 && !sent_exporter { let mut export = Vec::new(); export.resize(opts.export_keying_material, 0u8); sess.export_keying_material( &mut export, opts.export_keying_material_label .as_bytes(), if opts.export_keying_material_context_used { Some( opts.export_keying_material_context .as_bytes(), ) } else { None }, ) .unwrap(); sess.writer() .write_all(&export) .unwrap(); sent_exporter = true; } if !sess.is_handshaking() && opts.only_write_one_byte_after_handshake && !sent_message { println!("writing message and then only one byte of its tls frame"); flush(&mut sess, &mut conn); sess.writer() .write_all(b"hello") .unwrap(); sent_message = true; let mut one_byte = [0u8]; let mut cursor = io::Cursor::new(&mut one_byte[..]); sess.write_tls(&mut cursor).unwrap(); conn.write(&one_byte).expect("IO error"); quench_writes = true; } if opts.enable_early_data && !opts.server && !sess.is_handshaking() && count > 0 { if opts.expect_accept_early_data && !client(&mut sess).is_early_data_accepted() { quit_err("Early data was not accepted, but we expect the opposite"); } else if opts.expect_reject_early_data && client(&mut sess).is_early_data_accepted() { quit_err("Early data was accepted, but we expect the opposite"); } if opts.expect_version == 0x0304 { match sess.protocol_version() { Some(ProtocolVersion::TLSv1_3) | Some(ProtocolVersion::Unknown(0x7f17)) => {} _ => quit_err("wrong protocol version"), } } } if !sess.is_handshaking() && !opts .expect_quic_transport_params .is_empty() { let their_transport_params = sess .quic_transport_parameters() .expect("missing peer quic transport params"); assert_eq!(opts.expect_quic_transport_params, their_transport_params); } let mut buf = [0u8; 1024]; let len = match sess .reader() .read(&mut buf[..opts.read_size]) { Ok(0) => { if opts.check_close_notify { println!("close notify ok"); } println!("EOF (tls)"); return; } Ok(len) => len, Err(err) if err.kind() == io::ErrorKind::WouldBlock => 0, Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { if opts.check_close_notify { quit_err(":CLOSE_WITHOUT_CLOSE_NOTIFY:"); } println!("EOF (tcp)"); return; } Err(err) => panic!("unhandled read error {:?}", err), }; if opts.shut_down_after_handshake && !sent_shutdown && !sess.is_handshaking() { sess.send_close_notify(); sent_shutdown = true; } if quench_writes && len > 0 { println!("unquenching writes after {:?}", len); quench_writes = false; } for b in buf.iter_mut() { *b ^= 0xff; } sess.writer() .write_all(&buf[..len]) .unwrap(); } } fn main() { let mut args: Vec<_> = env::args().collect(); env_logger::init(); args.remove(0); if !args.is_empty() && args[0] == "-is-handshaker-supported" { println!("No"); process::exit(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-send-fragment" => { let max_fragment = args.remove(0).parse::().unwrap(); opts.max_fragment = Some(max_fragment + 5); // ours includes header } "-read-size" => { let rdsz = args.remove(0).parse::().unwrap(); opts.read_size = rdsz; } "-tls13-variant" => { let variant = args.remove(0).parse::().unwrap(); if variant != 1 { println!("NYI TLS1.3 variant selection: {:?} {:?}", arg, variant); process::exit(BOGO_NACK); } } "-no-ticket" => { opts.tickets = false; } "-on-resume-no-ticket" => { opts.resume_with_tickets_disabled = true; } "-signing-prefs" => { let alg = args.remove(0).parse::().unwrap(); opts.use_signing_scheme = alg; } "-max-cert-list" | "-expect-curve-id" | "-expect-resume-curve-id" | "-expect-peer-signature-algorithm" | "-expect-peer-verify-pref" | "-expect-advertised-alpn" | "-expect-alpn" | "-on-initial-expect-alpn" | "-on-resume-expect-alpn" | "-on-retry-expect-alpn" | "-expect-server-name" | "-expect-ocsp-response" | "-expect-signed-cert-timestamps" | "-expect-certificate-types" | "-expect-client-ca-list" | "-on-retry-expect-early-data-reason" | "-on-resume-expect-early-data-reason" | "-on-initial-expect-early-data-reason" | "-on-initial-expect-cipher" | "-on-resume-expect-cipher" | "-on-retry-expect-cipher" | "-expect-ticket-age-skew" | "-handshaker-path" | "-application-settings" | "-expect-msg-callback" => { println!("not checking {} {}; NYI", arg, args.remove(0)); } "-expect-secure-renegotiation" | "-expect-no-session-id" | "-enable-ed25519" | "-expect-hrr" | "-expect-no-hrr" | "-on-resume-expect-no-offer-early-data" | "-key-update" | //< we could implement an API for this "-expect-tls13-downgrade" | "-expect-session-id" => { println!("not checking {}; NYI", arg); } "-export-keying-material" => { opts.export_keying_material = args.remove(0).parse::().unwrap(); } "-export-label" => { opts.export_keying_material_label = args.remove(0); } "-export-context" => { opts.export_keying_material_context = args.remove(0); } "-use-export-context" => { opts.export_keying_material_context_used = true; } "-quic-transport-params" => { opts.quic_transport_params = base64::decode(args.remove(0).as_bytes()) .expect("invalid base64"); } "-expect-quic-transport-params" => { opts.expect_quic_transport_params = base64::decode(args.remove(0).as_bytes()) .expect("invalid base64"); } "-ocsp-response" => { opts.server_ocsp_response = base64::decode(args.remove(0).as_bytes()) .expect("invalid base64"); } "-signed-cert-timestamps" => { opts.server_sct_list = base64::decode(args.remove(0).as_bytes()) .expect("invalid base64"); if opts.server_sct_list.len() == 2 && opts.server_sct_list[0] == 0x00 && opts.server_sct_list[1] == 0x00 { quit(":INVALID_SCT_LIST:"); } } "-select-alpn" => { opts.protocols.push(args.remove(0)); } "-require-any-client-certificate" => { opts.require_any_client_cert = true; } "-verify-peer" => { opts.verify_peer = true; } "-shim-writes-first" => { opts.queue_data = true; } "-read-with-unfinished-write" => { opts.queue_data = true; opts.only_write_one_byte_after_handshake = true; } "-shim-shuts-down" => { opts.shut_down_after_handshake = true; } "-check-close-notify" => { opts.check_close_notify = true; } "-host-name" => { opts.host_name = args.remove(0); opts.use_sni = true; } "-advertise-alpn" => { opts.protocols = split_protocols(&args.remove(0)); } "-use-null-client-ca-list" => { opts.offer_no_client_cas = true; } "-enable-signed-cert-timestamps" => { opts.send_sct = true; } "-enable-early-data" => { opts.tickets = false; opts.enable_early_data = true; } "-on-resume-shim-writes-first" => { opts.queue_data_on_resume = true; } "-on-resume-read-with-unfinished-write" => { opts.queue_data_on_resume = true; opts.only_write_one_byte_after_handshake_on_resume = true; } "-expect-ticket-supports-early-data" => { opts.expect_ticket_supports_early_data = true; } "-expect-accept-early-data" | "-on-resume-expect-accept-early-data" => { opts.expect_accept_early_data = true; } "-expect-early-data-reason" | "-on-resume-expect-reject-early-data-reason" => { let reason = args.remove(0); match reason.as_str() { "disabled" | "protocol_version" => { opts.expect_reject_early_data = true; } _ => { println!("NYI early data reason: {}", reason); process::exit(1); } } } "-expect-reject-early-data" | "-on-resume-expect-reject-early-data" => { opts.expect_reject_early_data = true; } "-expect-version" => { opts.expect_version = args.remove(0).parse::().unwrap(); } "-curves" => { let curve = args.remove(0).parse::().unwrap(); if let Some(mut curves) = opts.curves.take() { curves.push(curve); } else { opts.curves = Some(vec![ curve ]); } } "-resumption-delay" => { opts.resumption_delay = args.remove(0).parse::().unwrap(); align_time(); } // defaults: "-enable-all-curves" | "-renegotiate-ignore" | "-no-tls11" | "-no-tls1" | "-no-ssl3" | "-handoff" | "-decline-alpn" | "-expect-no-session" | "-expect-session-miss" | "-expect-extended-master-secret" | "-expect-ticket-renewal" | "-enable-ocsp-stapling" | // internal openssl details: "-async" | "-implicit-handshake" | "-use-old-client-cert-callback" | "-use-early-callback" => {} // Not implemented things "-dtls" | "-cipher" | "-psk" | "-renegotiate-freely" | "-false-start" | "-fallback-scsv" | "-fail-early-callback" | "-fail-cert-callback" | "-install-ddos-callback" | "-advertise-npn" | "-verify-fail" | "-expect-channel-id" | "-send-channel-id" | "-select-next-proto" | "-expect-verify-result" | "-send-alert" | "-digest-prefs" | "-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-early-data-info" | "-expect-cipher-aes" | "-retain-only-sha256-client-cert-initial" | "-use-client-ca-list" | "-expect-draft-downgrade" | "-allow-unknown-alpn-protos" | "-on-initial-tls13-variant" | "-on-initial-expect-curve-id" | "-on-resume-export-early-keying-material" | "-on-resume-enable-early-data" | "-export-early-keying-material" | "-handshake-twice" | "-on-resume-verify-fail" | "-reverify-on-resume" | "-verify-prefs" | "-no-op-extra-handshake" | "-expect-peer-cert-file" | "-no-rsa-pss-rsae-certs" | "-ignore-tls13-downgrade" | "-on-initial-expect-peer-cert-file" => { println!("NYI option {:?}", arg); process::exit(BOGO_NACK); } _ => { println!("unhandled option {:?}", arg); process::exit(1); } } } println!("opts {:?}", opts); let mut 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 }; fn make_session( opts: &Options, scfg: &Option>, ccfg: &Option>, ) -> Connection { if opts.server { let scfg = Arc::clone(scfg.as_ref().unwrap()); let s = if opts.quic_transport_params.is_empty() { rustls::ServerConnection::new(scfg).unwrap() } else { rustls::ServerConnection::new_quic( scfg, quic::Version::V1, opts.quic_transport_params.clone(), ) .unwrap() }; s.into() } else { let server_name = opts .host_name .as_str() .try_into() .unwrap(); let ccfg = Arc::clone(ccfg.as_ref().unwrap()); let c = if opts.quic_transport_params.is_empty() { rustls::ClientConnection::new(ccfg, server_name) } else { rustls::ClientConnection::new_quic( ccfg, quic::Version::V1, server_name, opts.quic_transport_params.clone(), ) } .unwrap(); c.into() } } for i in 0..opts.resumes + 1 { let sess = make_session(&opts, &server_cfg, &client_cfg); exec(&opts, sess, i); if opts.resume_with_tickets_disabled { opts.tickets = false; server_cfg = Some(make_server_cfg(&opts)); } } }