//! Connections, plain and TLS, against local servers with TEST-ONLY certificates (M4a spec, //! section 5): the right CA is trusted through `ca_file`; an unknown CA and a wrong name are //! refused, so verification is on. Do not edit. #[path = "support/tls_server.rs"] mod tls_server; use std::io::{BufRead, BufReader, Write}; use std::time::Duration; use gatewayd::config::ServerUrl; use gatewayd::net::{Connector, NetError, Stream}; use tls_server::{echo_line, fixture, serve, server_config}; const T: Duration = Duration::from_secs(5); fn url(tls: bool, host: &str, port: u16) -> ServerUrl { ServerUrl { tls, host: host.to_string(), port, } } fn round_trip(mut stream: Stream) -> String { stream.write_all(b"hello over the wire\n").unwrap(); stream.flush().unwrap(); let mut line = String::new(); BufReader::new(stream).read_line(&mut line).unwrap(); line } #[test] fn plain_tcp() { let addr = serve(None, echo_line); let c = Connector::new(url(false, "127.0.0.1", addr.port()), None).unwrap(); let stream = c.connect(T).unwrap(); assert!(matches!(stream, Stream::Plain(_))); assert_eq!(round_trip(stream), "hello over the wire\n"); } #[test] fn tls_to_a_server_signed_by_the_ca_file() { let addr = serve(Some(server_config("server")), echo_line); for host in ["localhost", "127.0.0.1"] { let c = Connector::new(url(true, host, addr.port()), Some(&fixture("test-ca.pem"))).unwrap(); let stream = c.connect(T).unwrap_or_else(|e| panic!("{host}: {e}")); assert!(matches!(stream, Stream::Tls(_))); assert_eq!(round_trip(stream), "hello over the wire\n", "{host}"); } } #[test] fn an_unknown_ca_is_refused_at_connect() { let addr = serve(Some(server_config("other-server")), echo_line); let c = Connector::new( url(true, "localhost", addr.port()), Some(&fixture("test-ca.pem")), ) .unwrap(); match c.connect(T) { Err(NetError::Tls(why)) => assert!(why.to_lowercase().contains("certificate"), "{why}"), Err(e) => panic!("expected a TLS error, got {e}"), Ok(_) => panic!("a server signed by an unknown CA was accepted"), } } #[test] fn a_wrong_name_is_refused_at_connect() { let addr = serve(Some(server_config("wrong-name")), echo_line); let c = Connector::new( url(true, "localhost", addr.port()), Some(&fixture("test-ca.pem")), ) .unwrap(); assert!( matches!(c.connect(T), Err(NetError::Tls(_))), "a certificate for wrong.example was accepted for localhost" ); } #[test] fn tls_to_a_plain_server_fails_and_does_not_hang() { let addr = serve(None, |_conn| std::thread::sleep(Duration::from_secs(30))); let c = Connector::new( url(true, "localhost", addr.port()), Some(&fixture("test-ca.pem")), ) .unwrap(); let started = std::time::Instant::now(); assert!(c.connect(Duration::from_millis(300)).is_err()); assert!(started.elapsed() < Duration::from_secs(3)); } #[test] fn a_bad_ca_file_is_an_error_before_any_connection() { let missing = fixture("no-such.pem"); assert!(matches!( Connector::new(url(true, "localhost", 1), Some(&missing)), Err(NetError::Roots(_)) )); let not_pem = fixture("README.md"); assert!(matches!( Connector::new(url(true, "localhost", 1), Some(¬_pem)), Err(NetError::Roots(_)) )); // A plain server needs no certificates at all. assert!(Connector::new(url(false, "localhost", 1), Some(&missing)).is_ok()); } #[test] fn nothing_listening_is_a_connect_error() { let port = { let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); l.local_addr().unwrap().port() }; let c = Connector::new(url(false, "127.0.0.1", port), None).unwrap(); assert!(matches!(c.connect(T), Err(NetError::Connect(_)))); }