Skip to main content

tls_get/
tls_get.rs

1//! An HTTPS GET over a real TCP connection, using the `purecrypto` TLS 1.3
2//! client. By default the server certificate is verified against the system
3//! trust store; pass `--insecure` to skip certificate verification.
4//!
5//! Run with: `cargo run --example tls_get [-- --insecure]`
6//!
7//! Note: chain verification requires every certificate's key (and its issuer's
8//! signing key) to be RSA or ECDSA P-256 — the algorithms this library
9//! implements. Hosts anchored through a P-384 CA (example.org via Cloudflare,
10//! at the time of writing) cannot be verified yet and need `--insecure`.
11
12use 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    // Drive the handshake to completion.
39    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
100/// Loads the system CA bundle into a trust store, skipping any certificate
101/// whose key type this library does not parse (e.g. Ed25519/P-384 roots).
102fn 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}