Hyper and TLS

For audioserve update I wanted use new hyper with tokio-tls. Actually it required a bit of documentation reading, but finally turned our quite straightforward.

Here are supportive functions (in tls module):

use futures::{future, stream::{StreamExt, TryStreamExt}};
use hyper::server::accept::{from_stream, Accept};
use native_tls::Identity;
use std::fs::File;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use tokio::net::{TcpListener, TcpStream};
use tokio_tls::TlsStream;

type Error = Box<dyn std::error::Error + 'static>;

pub struct TlsConfig {
    key_file: PathBuf,
    key_password: String
}

impl TlsConfig {
    pub fn new<P:Into<PathBuf>, S:Into<String>>(key_file: P, pass: S) -> Self {
        TlsConfig{
            key_file: key_file.into(),
            key_password: pass.into()
        }
    }
}

fn load_private_key<P>(file: P, pass: &str) -> Result<Identity, Error>
where
    P: AsRef<Path>,
{
    let mut bytes = vec![];
    let mut f = File::open(file)?;
    f.read_to_end(&mut bytes)?;
    let key = Identity::from_pkcs12(&bytes, pass)?;
    Ok(key)
}

pub async fn tls_acceptor(
    addr: &std::net::SocketAddr,
    ssl: &TlsConfig,
) -> Result<impl Accept<Conn = TlsStream<TcpStream>, Error = io::Error>, Error> {
    let private_key = load_private_key(&ssl.key_file, &ssl.key_password)?;
    let tls_cx = native_tls::TlsAcceptor::builder(private_key).build()?;
    let tls_cx = tokio_tls::TlsAcceptor::from(tls_cx);
    let stream = TcpListener::bind(addr).await?.and_then(move |s| {
        let acceptor = tls_cx.clone();
        async move {
            let conn = acceptor.accept(s).await;
            conn.map_err(|e| {
                error!("Error when accepting TLS connection {}", e);
                io::Error::new(io::ErrorKind::Other, e)
            })
        }
    })
    .filter(|i| future::ready(i.is_ok())); // Need to filter out errors as they will stop server to accept connections

    Ok(from_stream(stream))
}

Main trick is to filter out TLS accept errors – because an error in stream will stop the stream and new connections will not be accepted then. (And TLS errors are happening often – you may connect with just plain HTTP or client does not trust certificate, etc.).

With above function server can be started like this:

#[macro_use]
extern crate log;
use std::convert::Infallible;
use hyper::{Request, Response, Body, Server, service::{make_service_fn, service_fn}};
use tls::{TlsConfig, tls_acceptor};

mod tls;

type Error = Box<dyn std::error::Error + 'static>;

#[tokio::main]
pub async fn main() -> Result<(), Error> {
     env_logger::init();

    let addr = ([127, 0, 0, 1], 3000).into();
    let tls_config = TlsConfig::new("/home/ivan/tls_key/audioserve.p12", "mypass");
    let incoming = tls_acceptor(&addr,&tls_config).await?;

    let make_svc = make_service_fn(|conn: &tokio_tls::TlsStream<tokio::net::TcpStream>| {
        let peer = conn.get_ref().peer_addr().unwrap();
        async move { Ok::<_, Infallible>(service_fn(move|req| {
            debug!("Request from {} for path {}", peer, req.uri());
            futures::future::ok::<_, Infallible>(Response::new(Body::from("Hello World!")))
        })) }
    });

    let server = Server::builder(incoming).serve(make_svc);
    info!("Listening on https://{}", addr);
    server.await?;
    Ok(())
}

Leave a Reply

Your email address will not be published. Required fields are marked *