The certificates used in this section were discussed in the section TL: General . They are
The most popular crate for dealing with TLS seems to be
openssl
.
Using it will require the following to be added to the dependency section
of Cargo.toml
:
openssl = { version = "0.10", features = ["vendored"] }
The openssl crate has a simple example of connecting to a TLS server and getting some information. Slightly adapted to the other examples in this chapter, it is TLSGetHead.rs:
// Based on https://docs.rs/openssl/0.10.29/openssl/ssl/index.html
use openssl::ssl::{SslMethod, SslConnector};
use std::io::{Read, Write};
use std::net::TcpStream;
fn main() {
let connector = SslConnector::builder(SslMethod::tls()).unwrap().build();
let stream = TcpStream::connect("www.verisign.com:443").unwrap();
let mut stream = connector.connect("www.verisign.com", stream).unwrap();
stream.write_all(b"HEAD / HTTP/1.0\r\n\r\n").unwrap();
let mut res = vec![];
stream.read_to_end(&mut res).unwrap();
println!("{}", String::from_utf8_lossy(&res));
}
A client requires a bit more work of splitting the builder out from
creating a connector. The builder then has the function
set_ca_file()
called before building
the connector.
The echo client program talking to a server using
my own CA with certificate file
CA-cert.pem
needs to load the CA-signed certificate
as its CA file. The program is
/TLSEchoClient.rs:
use std::io::{self, Read, Write, BufRead};
use openssl::ssl::{SslMethod, SslConnector};
use std::net::TcpStream;
use std::env;
use std::str;
use std::io::stdout;
fn main() -> std::io::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Usage {} hostname", args[0]);
std::process::exit(1);
}
let hostname = &args[1];
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
// turn off all checking - dangerous
//builder.set_verify(openssl::ssl::SslVerifyMode::NONE);
// Working with my own CA
builder.set_ca_file("../../certs/CA-cert.pem")
.expect("Can't find file");
// this works with self-signed cert
//builder.set_ca_file("../../certs/self-signed-cert.pem")
// .expect("Can't find file");
let connector = builder.build();
let stream = TcpStream::connect(hostname.to_string() + &":1200")
.expect("Couldn't connect to the server...");
let mut stream = connector.connect(hostname, stream).unwrap();
print!("Enter line:"); stdout().flush().expect("Flush failed");
let stdin = io::stdin();
for line in stdin.lock().lines() {
let line = line.unwrap();
println!("Read line '{}'", line);
if &line == "BYE" {
break;
}
stream.write(line.as_bytes())
.expect("Error on write");
let mut echo_bytes: [u8; 2048] = [0; 2048];
stream.read(&mut echo_bytes[..])
.expect("Error on read");
let echo = str::from_utf8(&echo_bytes).unwrap();
println!("Echoed '{}'", echo);
print!("Enter line:"); stdout().flush().expect("Flush failed");
}
Ok(())
} // the stream is closed here
The previous echo client program had lines commented out for a self-signed certificate. Uncomment this and comment out the CA-signed certificate and the client works with a self-signed server.
The server needs to set up an SSL Acceptor with the private key and the certificate file set to the CA signed certificate. It then sets up a TCP listener on the port (here 1200) and then wraps that in the acceptor. From then on it can read and write to the stream.
The server is TLSEchoServer.rs:
// From https://gist.github.com/fortruce/828bcc3499eb291e7e17
//use openssl::ssl::{SslMethod, SslConnector};
use openssl::ssl::{SslMethod, SslAcceptor, SslStream, SslFiletype};
//use std::net::TcpStream;
use std::net::{TcpListener, TcpStream};
use std::thread;
use std::io::Read;
use std::io::Write;
use std::sync::Arc;
fn handle_client(mut stream: SslStream<TcpStream>) {
println!("Connected");
// read 2048 bytes at a time from stream echoing back to stream
loop {
let mut read = [0; 2048];
match stream.read(&mut read) {
Ok(n) => {
if n == 0 {
// connection was closed
break;
}
stream.write(&read[0..n]).unwrap();
}
Err(_err) => {
//panic!(err);
break;
}
}
}
println!("Disconnected");
}
fn main() {
let mut acceptor = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap();
//let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
acceptor.set_private_key_file("../../certs/key.pem", SslFiletype::PEM).unwrap();
acceptor.set_certificate_file("../../certs/CA-signed-cert.pem", SslFiletype::PEM).unwrap();
//acceptor.set_certificate_chain_file("certs.pem").unwrap();
acceptor.check_private_key().unwrap();
let acceptor = Arc::new(acceptor.build());
let listener = TcpListener::bind("[::]:1200").unwrap();
for stream in listener.incoming() {
match stream {
Ok(stream) => {
let acceptor = acceptor.clone();
thread::spawn(move || {
let stream = acceptor.accept(stream).unwrap();
handle_client(stream);
});
}
Err(_) => {
println!("Error");
}
}
}
}
Openssl has an explicit function call to make on the builder to include an intermediate certificate file:
builder.set_certificate_chain_file("intermediate-cert.pem")
.expect("Can't find file");
Another crate gaining popularity is
rustls
.
It is purely Rust, not relying on the openssl
C library.
Using it will require the following to be added to the dependency section
of Cargo.toml
:
rustls = "0.18"
webpki = "0.21.0"
webpki-roots = "0.20"
custom_error = "1.7.1"
It has two sets of examples. The first at rustls/rustls/examples/ uses standard I/O. The second set at rustls/rustls-mio/examples/ uses the asynchronous mio crate.
The Cargo.toml
file needs the following dependencies:
rustls = "0.18"
webpki = "0.21.0"
webpki-roots = "0.20"
A client using this crate does not have a default set of trusted certificates. The "standard" way of getting such a set is to use the crate wbpki-roots which contains the set of root CA's recognised by Mozilla.
A client using the simpler way to get HEAD information from a server with a Mozilla recognised CA is RustlsGetHead.rs:
use std::sync::Arc;
//use std::sync::{Arc, Mutex};
//use std::process;
//use mio;
use std::net::TcpStream;
use webpki;
use rustls;
//use rustls::Session;
//use std::net::SocketAddr;
//use std::str;
//use std::io;
//use std::fs;
//use std::collections;
//use std::io::{Read, Write, BufReader};
use std::io::{Read, Write};
use std::io::stdout;
fn main() {
let hostname = "www.verisign.com";
let hostnameport = "www.verisign.com:443";
let dns_name = webpki::DNSNameRef::try_from_ascii_str(hostname).unwrap();
let mut sock = TcpStream::connect(hostnameport).unwrap();
let mut config = rustls::ClientConfig::new();
config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
let mut tls_session = rustls::ClientSession::new(&Arc::new(config),
dns_name);
let mut tls = rustls::Stream::new(&mut tls_session, &mut sock);
//let mut tlsclient = TlsClient::new(sock, "www.verisign.com:443", config);
let http_req = "HEAD / HTTP/1.0\r\n\r\n";
tls.write(http_req.as_bytes()).expect("Write fail");
/*
while (!tls_session.wants_write()) {
println!("Waiting to wtite");
}
let num_wrote = tls_session.write_tls(&mut sock).expect("Cant write TLS");
println!("{}", num_wrote.to_string());
*/
/*
while (!tls_session.wants_read()) {
println!("Waiting to read");
}
println!("Ready to read");
tls_session.read_tls(&mut sock).expect("Cant read TLS");
println!("Read tls");
tls_session.process_new_packets().expect("Cant split packets");
let mut buf = Vec::new();
//(&mut io::stdin()
tls_session.read_to_end(&mut buf).unwrap();
println!("{}", String::from_utf8_lossy(&buf));
*/
let mut plaintext = Vec::new();
tls.read_to_end(&mut plaintext).unwrap();
stdout().write_all(&plaintext).unwrap();
}
The client needs to set up a configuration with the CA certificate loaded into the root store. It then creates a session using that configuration and wraps the TCP stream in a Rustls stream. Reads and writes are then done using the stream.
The client is RustLSEchoClient.rs:
// Based on https://github.com/ctz/rustls/blob/main/rustls/examples/simpleclient.rs
use std::sync::Arc;
use std::net::TcpStream;
use std::io::{self, Read, Write, BufRead, stdout, BufReader};
use std::fs::File;
use std::env;
use std::str;
fn main() -> std::io::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Usage {} hostname", args[0]);
std::process::exit(1);
}
let hostname = &args[1];
let mut config = rustls::ClientConfig::new();
//add in our root certificate
let mut cert = File::open("../../certs/CA-cert.pem").expect("Cant open file");
let mut cert = BufReader::new(&mut cert);
config.root_store.add_pem_file(&mut cert).expect("Cant add to root store");
let dns_name = webpki::DNSNameRef::try_from_ascii_str(hostname).unwrap();
let mut sess = rustls::ClientSession::new(&Arc::new(config), dns_name);
let mut sock = TcpStream::connect(hostname.to_string() + &":1200").unwrap();
let mut stream = rustls::Stream::new(&mut sess, &mut sock);
print!("Enter line:"); stdout().flush().expect("print err");
let stdin = io::stdin();
for line in stdin.lock().lines() {
let line = line.unwrap();
println!("Read line: '{}'", line);
if &line == "BYE" {
break;
}
stream.write(line.as_bytes())
.expect("Error on write");
let mut echo_bytes: [u8; 2048] = [0; 2048];
stream.read(&mut echo_bytes[..])
.expect("Error on read");
let echo = str::from_utf8(&echo_bytes).unwrap();
println!("Echoed: '{}'", echo);
print!("Enter line:"); stdout().flush().expect("pritn err");
}
Ok(())
}
Copyright © Jan Newmarch, jan@newmarch.name
" Network Programming using Java, Go, Python, Rust, JavaScript and Julia"
by
Jan Newmarch
is licensed under a
Creative Commons Attribution-ShareAlike 4.0 International License
.
Based on a work at
https://jan.newmarch.name/NetworkProgramming/
.