From 83f5fe8265cbf6a715e1645d157b7538b211a4b1 Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Thu, 9 Dec 2021 20:54:54 +0100 Subject: [PATCH] Implement TLS handling --- examples/bffh.dhall | 10 +++-- src/config.rs | 7 ++++ src/connection.rs | 97 +++++++++++++++++++++++++++++++++++++++++---- src/db/user.rs | 2 +- src/error.rs | 10 +++++ src/server.rs | 46 ++++++++++++++++++++- 6 files changed, 159 insertions(+), 13 deletions(-) diff --git a/examples/bffh.dhall b/examples/bffh.dhall index 84c5f70..4a57c51 100644 --- a/examples/bffh.dhall +++ b/examples/bffh.dhall @@ -22,10 +22,10 @@ { cmd = "./examples/fail-actor.sh" }} } - --, init_connections = [] : List { machine : Text, initiator : Text } - , init_connections = [{ machine = "Testmachine", initiator = "Initiator" }] - , initiators = --{=} - { Initiator = { module = "Dummy", params = { uid = "Testuser" } } } + , init_connections = [] : List { machine : Text, initiator : Text } + --, init_connections = [{ machine = "Testmachine", initiator = "Initiator" }] + , initiators = {=} + --{ Initiator = { module = "Dummy", params = { uid = "Testuser" } } } , listens = [ { address = "127.0.0.1", port = Some 59661 } , { address = "::1", port = Some 59661 } @@ -74,4 +74,6 @@ ] } } +, certfile = "examples/self-signed-cert.pem" +, keyfile = "examples/self-signed-key.pem" } diff --git a/src/config.rs b/src/config.rs index b653c1b..79f1a0c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -52,6 +52,10 @@ pub struct Config { pub db_path: PathBuf, pub roles: HashMap, + + /// Path to a certificate chain to be used + pub certfile: PathBuf, + pub keyfile: PathBuf, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -123,6 +127,9 @@ impl Default for Config { db_path: PathBuf::from("/run/bffh/database"), roles: HashMap::new(), + + certfile: PathBuf::from("/etc/bffh/pub.crt"), + keyfile: PathBuf::from("/etc/bffh/priv.key"), } } } diff --git a/src/connection.rs b/src/connection.rs index e2cbcd5..967ced3 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -1,20 +1,27 @@ -use futures::FutureExt; +use std::fmt::Debug; +use std::ops::DerefMut; +use futures::{AsyncRead, AsyncWrite, FutureExt}; use std::future::Future; +use std::io::{IoSlice, IoSliceMut}; +use std::pin::Pin; +use std::rc::Rc; use std::sync::Arc; +use std::task::{Context, Poll}; +use async_rustls::server::TlsStream; use slog::Logger; use smol::lock::Mutex; -use smol::net::TcpStream; use crate::api::Bootstrap; use crate::error::Result; use capnp_rpc::{rpc_twoparty_capnp, twoparty}; +use futures_util::{pin_mut, ready}; use crate::schema::connection_capnp; -use crate::db::access::{AccessControl, PermRule, RoleIdentifier}; +use crate::db::access::{PermRule, RoleIdentifier}; use crate::db::user::UserId; use crate::db::Databases; use crate::network::Network; @@ -75,14 +82,17 @@ impl ConnectionHandler { Self { log, db, network } } - pub fn handle(&mut self, stream: TcpStream) -> impl Future> { - info!(self.log, "New connection from on {:?}", stream); + pub fn handle(&mut self, stream: TlsStream) + -> impl Future> + { + let conn = Connection::new(stream); + let boots = Bootstrap::new(self.log.new(o!()), self.db.clone(), self.network.clone()); let rpc: connection_capnp::bootstrap::Client = capnp_rpc::new_client(boots); let network = twoparty::VatNetwork::new( - stream.clone(), - stream, + conn.clone(), + conn, rpc_twoparty_capnp::Side::Server, Default::default(), ); @@ -92,3 +102,76 @@ impl ConnectionHandler { rpc_system.map(|r| r.map_err(Into::into)) } } + +struct Connection { + inner: Rc>>, +} + +impl Connection { + pub fn new(stream: TlsStream) -> Self { + Self { + inner: Rc::new(Mutex::new(stream)), + } + } +} + +impl Clone for Connection { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone() + } + } +} + + +impl AsyncRead for Connection { + fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { + let f = self.inner.lock(); + pin_mut!(f); + let mut guard = ready!(f.poll(cx)); + let stream = guard.deref_mut(); + Pin::new(stream).poll_read(cx, buf) + } + + fn poll_read_vectored(self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>]) -> Poll> { + let f = self.inner.lock(); + pin_mut!(f); + let mut guard = ready!(f.poll(cx)); + let stream = guard.deref_mut(); + Pin::new(stream).poll_read_vectored(cx, bufs) + } +} + +impl AsyncWrite for Connection { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + let f = self.inner.lock(); + pin_mut!(f); + let mut guard = ready!(f.poll(cx)); + let stream = guard.deref_mut(); + Pin::new(stream).poll_write(cx, buf) + } + + fn poll_write_vectored(self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>]) -> Poll> { + let f = self.inner.lock(); + pin_mut!(f); + let mut guard = ready!(f.poll(cx)); + let stream = guard.deref_mut(); + Pin::new(stream).poll_write_vectored(cx, bufs) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let f = self.inner.lock(); + pin_mut!(f); + let mut guard = ready!(f.poll(cx)); + let stream = guard.deref_mut(); + Pin::new(stream).poll_flush(cx) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let f = self.inner.lock(); + pin_mut!(f); + let mut guard = ready!(f.poll(cx)); + let stream = guard.deref_mut(); + Pin::new(stream).poll_close(cx) + } +} \ No newline at end of file diff --git a/src/db/user.rs b/src/db/user.rs index b212647..3a200dd 100644 --- a/src/db/user.rs +++ b/src/db/user.rs @@ -134,7 +134,7 @@ pub fn load_file>(path: P) -> Result> { } pub fn init(log: Logger, _config: &Config, env: Arc) -> Result { - let mut flags = lmdb::DatabaseFlags::empty(); + let flags = lmdb::DatabaseFlags::empty(); let db = env.create_db(Some("userdb"), flags)?; debug!(&log, "Opened user db successfully."); diff --git a/src/error.rs b/src/error.rs index 801b435..e61cb00 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,6 +29,7 @@ pub enum Error { BadVersion((u32,u32)), Argon2(argon2::Error), EventNetwork(network::Error), + RustTls(rustls::TLSError), Denied, } @@ -83,6 +84,9 @@ impl fmt::Display for Error { Error::EventNetwork(e) => { e.fmt(f) } + Error::RustTls(e) => { + write!(f, "TLS Error: {}", e) + } } } } @@ -171,4 +175,10 @@ impl From for Error { } } +impl From for Error { + fn from(e: rustls::TLSError) -> Error { + Error::RustTls(e) + } +} + pub(crate) type Result = std::result::Result; diff --git a/src/server.rs b/src/server.rs index c51e8fb..4b9c500 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,3 +1,4 @@ +use std::fs::File; use slog::Logger; use crate::config; @@ -13,10 +14,15 @@ use smol::Executor; use futures::prelude::*; use std::io; +use std::io::BufReader; use std::sync::Arc; use std::os::unix::io::AsRawFd; +use std::path::Path; +use async_rustls::TlsAcceptor; +use rustls::{Certificate, KeyLogFile, NoClientAuth, PrivateKey, ServerConfig}; +use rustls_pemfile::Item; use signal_hook::low_level::pipe as sigpipe; use crate::db::Databases; @@ -46,6 +52,33 @@ pub fn serve_api_connections(log: Arc, config: Config, db: Databases, nw io::Result::Ok(LoopResult::Stop) }); + info!(log, "Reading certificate chain file"); + let mut certfp = BufReader::new(File::open(&config.certfile)?); + let certs = rustls_pemfile::certs(&mut certfp)? + .into_iter() + .map(Certificate) + .collect(); + info!(log, "Reading private key file"); + let mut keyfp = BufReader::new(File::open(&config.keyfile)?); + let mut tls_config = ServerConfig::new(Arc::new(NoClientAuth)); + tls_config.key_log = Arc::new(KeyLogFile::new()); + if let Some(path) = std::env::var_os("SSLKEYLOGFILE") { + let path = Path::new(&path); + warn!(log, "TLS SECRET LOGGING ENABLED! This will write all connection secrets to file {}!", + path.display()); + } + match rustls_pemfile::read_one(&mut keyfp)? { + Some(rustls_pemfile::Item::PKCS8Key(key) | rustls_pemfile::Item::RSAKey(key)) => { + let key = PrivateKey(key); + tls_config.set_single_cert(certs, key)?; + } + _ => { + error!(log, "private key file must contain a PEM-encoded private key"); + return Ok(()); + } + } + let tls_acceptor: TlsAcceptor = Arc::new(tls_config).into(); + // Bind to each address in config.listens. // This is a Stream over Futures so it will do absolutely nothing unless polled to completion let listeners_s: futures::stream::Collect<_, Vec> @@ -97,18 +130,29 @@ pub fn serve_api_connections(log: Arc, config: Config, db: Databases, nw inner_log.new(o!()) }; + let db = db.clone(); let network = network.clone(); let tlog = inner_log.new(o!()); + + let tls_acceptor_clone = tls_acceptor.clone(); std::thread::spawn(move || { + let tls_acceptor = tls_acceptor_clone; let local_ex = LocalExecutor::new(); + info!(tlog, "New connection from on {:?}", socket); let mut handler = connection::ConnectionHandler::new(tlog, db, network); // We handle the error using map_err - let f = handler.handle(socket) + let log2 = log.clone(); + let f = tls_acceptor.accept(socket) .map_err(move |e| { error!(log, "Error occured during protocol handling: {}", e); }) + .and_then(|stream| { + handler.handle(stream).map_err(move |e| { + error!(log2, "Error occured during protocol handling: {}", e); + }) + }) // Void any and all results since pool.spawn allows no return value. .map(|_| ());