1use purecrypto::tls::{Config, Connection, HandshakeStatus, RootCertStore};
13use std::io::{Read, Write};
14use std::net::TcpStream;
15use std::time::Duration;
16
17const HOST: &str = "example.org";
18
19fn main() {
20 let insecure = std::env::args().any(|a| a == "--insecure");
21
22 let roots = if insecure {
23 RootCertStore::new()
24 } else {
25 load_system_roots()
26 };
27 let cfg = Config::builder()
28 .tls_only()
29 .roots(roots)
30 .server_name(HOST)
31 .verify_certificates(!insecure)
32 .build();
33 let mut conn = Connection::client(&cfg).expect("client config");
34
35 let mut sock = TcpStream::connect((HOST, 443)).expect("TCP connect");
36 sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
37
38 let mut read_buf = [0u8; 8192];
40 loop {
41 let out = conn.pop().unwrap_or_default();
42 if !out.is_empty() {
43 sock.write_all(&out).unwrap();
44 }
45 match conn.handshake().unwrap() {
46 HandshakeStatus::Complete => break,
47 HandshakeStatus::WantWrite => continue,
48 HandshakeStatus::WantRead => {
49 let n = sock.read(&mut read_buf).expect("read");
50 assert!(n > 0, "peer closed during handshake");
51 conn.feed(&read_buf[..n]).expect("feed");
52 }
53 }
54 }
55 eprintln!(
56 "TLS 1.3 handshake with {HOST} complete (certificate {}verified)",
57 if insecure { "NOT " } else { "" }
58 );
59
60 let request = "GET / HTTP/1.1\r\nHost: example.org\r\n\r\n";
61 conn.send(request.as_bytes()).unwrap();
62 let out = conn.pop().unwrap_or_default();
63 sock.write_all(&out).unwrap();
64 sock.flush().unwrap();
65
66 let mut response = Vec::new();
67 loop {
68 match sock.read(&mut read_buf) {
69 Ok(0) => break,
70 Ok(n) => {
71 if conn.feed(&read_buf[..n]).is_err() {
72 break;
73 }
74 let plain = conn.recv().unwrap_or_default();
75 response.extend_from_slice(&plain);
76 if response.len() > 16 * 1024 {
77 break;
78 }
79 }
80 Err(e)
81 if e.kind() == std::io::ErrorKind::WouldBlock
82 || e.kind() == std::io::ErrorKind::TimedOut =>
83 {
84 break;
85 }
86 Err(e) => {
87 eprintln!("read error: {e}");
88 break;
89 }
90 }
91 }
92
93 let text = String::from_utf8_lossy(&response);
94 println!("--- {} bytes received ---", response.len());
95 for line in text.lines().take(15) {
96 println!("{line}");
97 }
98}
99
100fn load_system_roots() -> RootCertStore {
103 const BUNDLE: &str = "/etc/ssl/certs/ca-certificates.crt";
104 let data = std::fs::read_to_string(BUNDLE).expect("read system CA bundle");
105
106 let mut store = RootCertStore::new();
107 let (mut loaded, mut skipped) = (0u32, 0u32);
108 let mut block = String::new();
109 let mut in_cert = false;
110 for line in data.lines() {
111 if line.starts_with("-----BEGIN CERTIFICATE-----") {
112 in_cert = true;
113 block.clear();
114 }
115 if in_cert {
116 block.push_str(line);
117 block.push('\n');
118 }
119 if line.starts_with("-----END CERTIFICATE-----") {
120 in_cert = false;
121 match store.add_pem(&block) {
122 Ok(()) => loaded += 1,
123 Err(_) => skipped += 1,
124 }
125 }
126 }
127 eprintln!("loaded {loaded} trusted roots ({skipped} skipped)");
128 store
129}