TLS

Rust

Certificates

The certificates used in this section were discussed in the section TL: General . They are

Openssl crate

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"] }
  

Openssl client talking to server with CA signed certificate

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));
}

Openssl client using private CA

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

Openssl client using self-signed certificates

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.

Openssl server using private CA

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 Certificate chains

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");
  

Rustls crate

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"
  

Rustls client talking to server with CA signed certificate

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();
}

Rustls client using private CA

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
Creative Commons License
" 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/ .

If you like this book, please contribute using PayPal