//! End-to-end loopback test for the TLS-443 / TCP fallback transport: real outer rustls TLS over //! plain TCP on 127.0.0.1, full inner Aura mutual-auth handshake, packet echo. //! //! Also covers: //! * A custom (non-default) ALPN advertisement. //! * The "accept-any" outer-cert guarantee: the client connects with an outer SNI that does NOT //! match the server's outer-TLS certificate, the outer TLS handshake completes anyway (because //! the client uses [`AcceptAnyServerCert`]), and the inner Aura mutual auth still succeeds. use aura_pki::AuraCa; use aura_proto::{ClientConfig, PacketConnection, ServerConfig}; use aura_transport::{TcpClient, TcpOpts, TcpServer}; const SERVER_NAME: &str = "localhost"; const CLIENT_ID: &str = "client-tcp"; /// Mint a fresh CA + server("localhost") + client("client-tcp") and build the proto configs. fn make_configs() -> (ServerConfig, ClientConfig) { let ca = AuraCa::generate("Aura Test CA").expect("generate CA"); let server = ca .issue_server_cert(SERVER_NAME) .expect("issue server cert"); let client = ca.issue_client_cert(CLIENT_ID).expect("issue client cert"); let ca_pem = ca.ca_cert_pem(); let scfg = ServerConfig { ca_cert_pem: ca_pem.clone(), server_cert_pem: server.cert_pem, server_key_pem: server.key_pem, }; let ccfg = ClientConfig { ca_cert_pem: ca_pem, client_cert_pem: client.cert_pem, client_key_pem: client.key_pem, server_name: SERVER_NAME.to_string(), }; (scfg, ccfg) } /// Drive a single loopback handshake + 3-packet echo. `client_sni` is the OUTER TLS SNI the client /// presents; it is independent of the server cert (the client uses an accept-any verifier). async fn run_case(opts: TcpOpts, client_sni: &str) { let (scfg, ccfg) = make_configs(); let server = TcpServer::bind("127.0.0.1:0".parse().unwrap(), scfg, opts.clone()) .await .expect("bind server"); let addr = server.local_addr().expect("local addr"); let server_task = tokio::spawn(async move { let conn = server.accept().await.expect("server handshake"); assert_eq!(conn.peer_id(), Some(CLIENT_ID), "verified client id"); // Echo three packets back to the client. for _ in 0..3 { let pkt = conn.recv_packet().await.expect("server recv"); conn.send_packet(&pkt).await.expect("server echo"); } }); let client = TcpClient::connect(addr, client_sni, ccfg, opts) .await .expect("client handshake"); assert_eq!( client.peer_id(), Some(SERVER_NAME), "inner handshake verified the server CN" ); // Exchange packets of varying sizes (incl. a large one) and assert the echo matches. for i in 0..3u16 { let payload = vec![(i as u8).wrapping_add(1); 100 + (i as usize) * 600]; // 100, 700, 1300 bytes client.send_packet(&payload).await.expect("client send"); let echoed = client.recv_packet().await.expect("client recv"); assert_eq!(echoed, payload, "round-trip payload mismatch"); } server_task.await.expect("server task"); } /// Baseline: default ALPN advert (`h2`, `http/1.1`), outer SNI matches the server cert SAN. #[tokio::test] async fn tcp_loopback_end_to_end() { run_case(TcpOpts::default(), SERVER_NAME).await; } /// A custom ALPN list still negotiates and runs the handshake. #[tokio::test] async fn tcp_loopback_with_custom_alpn() { let opts = TcpOpts { alpn: Some(vec![b"http/1.1".to_vec()]), }; run_case(opts, SERVER_NAME).await; } /// The client uses [`AcceptAnyServerCert`] on the outer TLS layer, so an outer SNI that has nothing /// to do with the server's real certificate must still complete the TLS handshake; the inner Aura /// mutual auth then proves identity. This is the security model: outer = camouflage, inner = trust. #[tokio::test] async fn tcp_loopback_outer_sni_mismatch_still_connects() { run_case(TcpOpts::default(), "definitely-not-the-server.example").await; }