use std::{ io::{Read, Write}, net::TcpStream, str::FromStr, sync::Arc, }; use anyhow::{bail, Result}; use rustls::{ClientConfig, ClientConnection, OwnedTrustAnchor, RootCertStore, StreamOwned}; use trust_dns_resolver::{ config::{ResolverConfig, ResolverOpts}, Resolver, }; fn main() -> Result<()> { let (port, host) = resolve_dns("daeken.dev")?; dbg!(port); dbg!(host); let (port, host) = (443, "example.com".to_owned()); let tls_conf = Arc::new(make_tls_config()); let mut tls_conn = make_tls_connection(tls_conf, &host, port)?; tls_conn.write_all( concat!( "GET / HTTP/1.1\r\n", "Host: example.com\r\n", "Connection: close\r\n", "\r\n" ) .as_bytes(), )?; let mut pt = Vec::new(); tls_conn.read_to_end(&mut pt)?; println!("{}", String::from_utf8(pt)?); Ok(()) } // TODO: How to cache this? fn resolve_dns(server: &str) -> Result<(u16, String)> { let name = format!("_hypercosm._tls.{}", server); let resp = make_dns_client()? .srv_lookup(name)? .into_iter() .collect::>(); // TODO: Ensure exactly 1 result: I think. let srv = &resp[0]; // TODO: Handle priority and weight. let port = srv.port(); // TODO: Should this be ascii or utf8 let name = srv.target().to_ascii(); Ok((port, name)) } fn make_dns_client() -> Result { Ok(Resolver::new( ResolverConfig::cloudflare_tls(), ResolverOpts::default(), )?) } fn make_tls_config() -> ClientConfig { let mut root_store = RootCertStore::empty(); root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { OwnedTrustAnchor::from_subject_spki_name_constraints( ta.subject, ta.spki, ta.name_constraints, ) })); let config = rustls::ClientConfig::builder() .with_safe_defaults() .with_root_certificates(root_store) .with_no_client_auth(); config } fn make_tls_connection( config: Arc, server: &str, port: u16, ) -> Result { let server_name = server.try_into()?; let conn = ClientConnection::new(config, server_name)?; let sock = TcpStream::connect((server, port))?; let stream = StreamOwned::new(conn, sock); Ok(stream) }