-
Notifications
You must be signed in to change notification settings - Fork 47
/
main.rs
95 lines (75 loc) · 3.04 KB
/
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use async_std::io;
use async_std::net::TcpStream;
use async_std::prelude::*;
use async_std::task;
use async_tls::TlsConnector;
use rustls::ClientConfig;
use rustls_pemfile::certs;
use std::io::{BufReader, Cursor};
use std::net::ToSocketAddrs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use structopt::StructOpt;
#[derive(StructOpt)]
struct Options {
/// The host to connect to
host: String,
/// The port to connect to
#[structopt(short = "p", long = "port", default_value = "443")]
port: u16,
/// The domain to connect to. This may be different from the host!
#[structopt(short = "d", long = "domain")]
domain: Option<String>,
/// A file with a certificate authority chain, allows to connect
/// to certificate authories not included in the default set
#[structopt(short = "c", long = "cafile", parse(from_os_str))]
cafile: Option<PathBuf>,
}
fn main() -> io::Result<()> {
let options = Options::from_args();
// Check if the provided host exists
// TODO: this is blocking
let addr = (options.host.as_str(), options.port)
.to_socket_addrs()?
.next()
.ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?;
// If no domain was passed, the host is also the domain to connect to
let domain = options.domain.unwrap_or(options.host);
// Create a bare bones HTTP GET request
let http_request = format!("GET / HTTP/1.0\r\nHost: {}\r\n\r\n", domain);
let cafile = &options.cafile;
task::block_on(async move {
// Create default connector comes preconfigured with all you need to safely connect
// to remote servers!
let connector = if let Some(cafile) = cafile {
connector_for_ca_file(cafile).await?
} else {
TlsConnector::default()
};
// Open a normal TCP connection, just as you are used to
let tcp_stream = TcpStream::connect(&addr).await?;
// Use the connector to start the handshake process.
// This consumes the TCP stream to ensure you are not reusing it.
// Awaiting the handshake gives you an encrypted
// stream back which you can use like any other.
let mut tls_stream = connector.connect(&domain, tcp_stream).await?;
// We write our crafted HTTP request to it
tls_stream.write_all(http_request.as_bytes()).await?;
// And read it all to stdout
let mut stdout = io::stdout();
io::copy(&mut tls_stream, &mut stdout).await?;
// Voila, we're done here!
Ok(())
})
}
async fn connector_for_ca_file(cafile: &Path) -> io::Result<TlsConnector> {
let mut root_store = rustls::RootCertStore::empty();
let ca_bytes = async_std::fs::read(cafile).await?;
let cert = certs(&mut BufReader::new(Cursor::new(ca_bytes))).unwrap();
debug_assert_eq!((1, 0), root_store.add_parsable_certificates(&cert));
let config = ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth();
Ok(TlsConnector::from(Arc::new(config)))
}