2020-08-31 14:22:46 +00:00
|
|
|
use argh::FromArgs;
|
2021-09-28 17:01:37 +00:00
|
|
|
use std::convert::TryFrom;
|
2019-05-22 15:57:14 +00:00
|
|
|
use std::fs::File;
|
2020-08-31 14:22:46 +00:00
|
|
|
use std::io;
|
|
|
|
use std::io::BufReader;
|
|
|
|
use std::net::ToSocketAddrs;
|
2019-05-22 15:57:14 +00:00
|
|
|
use std::path::PathBuf;
|
2017-02-22 03:42:32 +00:00
|
|
|
use std::sync::Arc;
|
2020-08-31 14:22:46 +00:00
|
|
|
use tokio::io::{copy, split, stdin as tokio_stdin, stdout as tokio_stdout, AsyncWriteExt};
|
2019-11-27 16:11:02 +00:00
|
|
|
use tokio::net::TcpStream;
|
2021-09-28 17:01:37 +00:00
|
|
|
use tokio_rustls::rustls::{self, OwnedTrustAnchor};
|
2023-03-30 16:13:12 +00:00
|
|
|
use tokio_rustls::TlsConnector;
|
2017-02-22 03:42:32 +00:00
|
|
|
|
2020-08-31 14:22:46 +00:00
|
|
|
/// Tokio Rustls client example
|
|
|
|
#[derive(FromArgs)]
|
2019-05-22 15:57:14 +00:00
|
|
|
struct Options {
|
2020-08-31 14:22:46 +00:00
|
|
|
/// host
|
|
|
|
#[argh(positional)]
|
2019-05-22 15:57:14 +00:00
|
|
|
host: String,
|
|
|
|
|
|
|
|
/// port
|
2020-08-31 14:22:46 +00:00
|
|
|
#[argh(option, short = 'p', default = "443")]
|
2019-05-22 15:57:14 +00:00
|
|
|
port: u16,
|
|
|
|
|
|
|
|
/// domain
|
2020-08-31 14:22:46 +00:00
|
|
|
#[argh(option, short = 'd')]
|
2019-05-22 15:57:14 +00:00
|
|
|
domain: Option<String>,
|
|
|
|
|
|
|
|
/// cafile
|
2020-08-31 14:22:46 +00:00
|
|
|
#[argh(option, short = 'c')]
|
|
|
|
cafile: Option<PathBuf>,
|
2019-05-22 15:57:14 +00:00
|
|
|
}
|
2017-02-22 03:42:32 +00:00
|
|
|
|
2020-08-31 14:22:46 +00:00
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> io::Result<()> {
|
|
|
|
let options: Options = argh::from_env();
|
2017-02-22 03:42:32 +00:00
|
|
|
|
2019-05-22 15:57:14 +00:00
|
|
|
let addr = (options.host.as_str(), options.port)
|
|
|
|
.to_socket_addrs()?
|
|
|
|
.next()
|
|
|
|
.ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?;
|
|
|
|
let domain = options.domain.unwrap_or(options.host);
|
2020-08-31 14:22:46 +00:00
|
|
|
let content = format!("GET / HTTP/1.0\r\nHost: {}\r\n\r\n", domain);
|
2017-02-22 03:42:32 +00:00
|
|
|
|
2021-09-28 17:01:37 +00:00
|
|
|
let mut root_cert_store = rustls::RootCertStore::empty();
|
2019-05-22 15:57:14 +00:00
|
|
|
if let Some(cafile) = &options.cafile {
|
|
|
|
let mut pem = BufReader::new(File::open(cafile)?);
|
2021-09-28 17:01:37 +00:00
|
|
|
let certs = rustls_pemfile::certs(&mut pem)?;
|
|
|
|
let trust_anchors = certs.iter().map(|cert| {
|
|
|
|
let ta = webpki::TrustAnchor::try_from_cert_der(&cert[..]).unwrap();
|
|
|
|
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
|
|
|
ta.subject,
|
|
|
|
ta.spki,
|
|
|
|
ta.name_constraints,
|
|
|
|
)
|
|
|
|
});
|
|
|
|
root_cert_store.add_server_trust_anchors(trust_anchors);
|
2017-02-22 03:42:32 +00:00
|
|
|
} else {
|
2021-09-28 17:01:37 +00:00
|
|
|
root_cert_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,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
));
|
2017-02-22 03:42:32 +00:00
|
|
|
}
|
2021-09-28 17:01:37 +00:00
|
|
|
|
|
|
|
let config = rustls::ClientConfig::builder()
|
|
|
|
.with_safe_defaults()
|
|
|
|
.with_root_certificates(root_cert_store)
|
|
|
|
.with_no_client_auth(); // i guess this was previously the default?
|
2019-05-22 15:57:14 +00:00
|
|
|
let connector = TlsConnector::from(Arc::new(config));
|
|
|
|
|
2020-08-31 14:22:46 +00:00
|
|
|
let stream = TcpStream::connect(&addr).await?;
|
2019-11-27 16:11:02 +00:00
|
|
|
|
2020-08-31 14:22:46 +00:00
|
|
|
let (mut stdin, mut stdout) = (tokio_stdin(), tokio_stdout());
|
2019-05-22 15:57:14 +00:00
|
|
|
|
2021-09-28 17:01:37 +00:00
|
|
|
let domain = rustls::ServerName::try_from(domain.as_str())
|
2020-08-31 14:22:46 +00:00
|
|
|
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid dnsname"))?;
|
2019-05-22 15:57:14 +00:00
|
|
|
|
2020-08-31 14:22:46 +00:00
|
|
|
let mut stream = connector.connect(domain, stream).await?;
|
|
|
|
stream.write_all(content.as_bytes()).await?;
|
2019-05-22 15:57:14 +00:00
|
|
|
|
2020-08-31 14:22:46 +00:00
|
|
|
let (mut reader, mut writer) = split(stream);
|
2019-05-22 15:57:14 +00:00
|
|
|
|
2020-08-31 14:22:46 +00:00
|
|
|
tokio::select! {
|
|
|
|
ret = copy(&mut reader, &mut stdout) => {
|
|
|
|
ret?;
|
|
|
|
},
|
|
|
|
ret = copy(&mut stdin, &mut writer) => {
|
|
|
|
ret?;
|
|
|
|
writer.shutdown().await?
|
|
|
|
}
|
|
|
|
}
|
2019-05-22 15:57:14 +00:00
|
|
|
|
2020-08-31 14:22:46 +00:00
|
|
|
Ok(())
|
2017-02-22 03:42:32 +00:00
|
|
|
}
|