From a2cbe9dc54690df833894eda498124865604b5f4 Mon Sep 17 00:00:00 2001 From: Jake Hillion Date: Mon, 23 May 2022 07:07:18 +0100 Subject: [PATCH 1/3] no-op tls handler --- README.md | 10 ++++++ examples/tls/main.rs | 82 +++++++++++++++++++++++++++++++++++++----- examples/tls/spec.json | 48 ++++++++++++++++++++++--- examples/tls/tls.rs | 12 +++++++ 4 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 examples/tls/tls.rs diff --git a/README.md b/README.md index c29320e..4ba51d8 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,16 @@ To run this example: cargo build --example pipes target/debug/clone-shim -s examples/pipes/spec.json target/debug/examples/pipes +### examples/pipes + +The pipes example shows some of the power of the shim by using pipes. The process "pipe_sender" sends two messages down a pipe that it's given by the shim. These two messages each spawn a completely isolated process, "pipe_receiver", that receives that message. + +To run this example: + + cargo build + cargo build --example tls + target/debug/clone-shim -s examples/tls/spec.json target/debug/examples/tls + ## Debugging the shim The shim can be debugged as with most processes, but it is exceptionally forky. Breaking before a clone in `rust-gdb` then running `set follow-fork-mode child` is often necessary. The best approach is to go in with a plan of attack. diff --git a/examples/tls/main.rs b/examples/tls/main.rs index 0087586..dda71b4 100644 --- a/examples/tls/main.rs +++ b/examples/tls/main.rs @@ -1,4 +1,5 @@ mod http; +mod tls; use std::fs::File; use std::net::{TcpListener, TcpStream}; @@ -7,6 +8,7 @@ fn main() { match std::env::args().next() { Some(s) => match s.as_str() { "connection_listener" => connection_listener_entrypoint(), + "tls_handler" => tls_handler_entrypoint(), "http_handler" => http_handler_entrypoint(), _ => unimplemented!(), @@ -24,12 +26,12 @@ fn connection_listener_entrypoint() { let _entrypoint = args.next(); - let http_handler_trigger = args.next(); - let http_handler_trigger: RawFd = http_handler_trigger - .expect("request handler required") + let tls_handler_trigger = args.next(); + let tls_handler_trigger: RawFd = tls_handler_trigger + .expect("tls handler trigger required") .parse() - .expect("tcp listener should be a file descriptor"); - let http_handler_trigger = unsafe { File::from_raw_fd(http_handler_trigger) }; + .expect("tls handler trigger should be a file descriptor"); + let tls_handler_trigger = unsafe { File::from_raw_fd(tls_handler_trigger) }; let tcp_listener = args.next(); let tcp_listener: RawFd = tcp_listener @@ -39,7 +41,7 @@ fn connection_listener_entrypoint() { let tcp_listener = unsafe { TcpListener::from_raw_fd(tcp_listener) }; // actual function body - fn connection_listener(http_handler_trigger: File, tcp_listener: TcpListener) -> i32 { + fn connection_listener(tls_handler_trigger: File, tcp_listener: TcpListener) -> i32 { println!("connection_listener entered"); // handle incoming connections @@ -53,14 +55,78 @@ fn connection_listener_entrypoint() { }; println!("received a new connection"); - http_handler(&http_handler_trigger, stream); + tls_handler(&tls_handler_trigger, stream); } exitcode::OK } // run function - std::process::exit(connection_listener(http_handler_trigger, tcp_listener)); + std::process::exit(connection_listener(tls_handler_trigger, tcp_listener)); +} + +fn tls_handler(trigger_socket: &File, stream: TcpStream) { + // imports + use nix::sys::socket::{sendmsg, ControlMessage, MsgFlags}; + use std::os::unix::io::AsRawFd; + + // send file descriptor(s) + let sockfd = trigger_socket.as_raw_fd(); + let fds = [stream.as_raw_fd()]; + + sendmsg::<()>( + sockfd, + &[], + &[ControlMessage::ScmRights(&fds)], + MsgFlags::empty(), + None, + ) + .unwrap(); +} + +fn tls_handler_entrypoint() { + // imports + use std::os::unix::io::{FromRawFd, RawFd}; + + // argument parsing + let mut args = std::env::args(); + + let _entrypoint = args.next(); + + let http_handler_trigger = args.next(); + let http_handler_trigger: RawFd = http_handler_trigger + .expect("http handler trigger required") + .parse() + .expect("http handler trigger should be a file descriptor"); + let http_handler_trigger = unsafe { File::from_raw_fd(http_handler_trigger) }; + + let tls_cert_file = args.next(); + let tls_cert_file: RawFd = tls_cert_file + .expect("tls cert file required") + .parse() + .expect("tls cert file should be a file descriptor"); + let tls_cert_file = unsafe { File::from_raw_fd(tls_cert_file) }; + + let tls_key_file = args.next(); + let tls_key_file: RawFd = tls_key_file + .expect("tls key file required") + .parse() + .expect("tls key file should be a file descriptor"); + let tls_key_file = unsafe { File::from_raw_fd(tls_key_file) }; + + let stream = args.next(); + let stream: RawFd = stream + .expect("request stream required") + .parse() + .expect("request stream should be a file descriptor"); + let stream = unsafe { TcpStream::from_raw_fd(stream) }; + + std::process::exit(tls::handler( + http_handler_trigger, + tls_cert_file, + tls_key_file, + stream, + )); } fn http_handler(trigger_socket: &File, stream: TcpStream) { diff --git a/examples/tls/spec.json b/examples/tls/spec.json index 6a4817f..72bd86d 100644 --- a/examples/tls/spec.json +++ b/examples/tls/spec.json @@ -1,6 +1,44 @@ { "entrypoints": { "connection_listener": { + "args": [ + "Entrypoint", + { + "FileSocket": { + "Tx": "tls" + } + }, + { + "TcpListener": { + "addr": "0.0.0.0:8443" + } + } + ], + "environment": [ + { + "Filesystem": { + "host_path": "/lib/x86_64-linux-gnu/libgcc_s.so.1", + "environment_path": "/lib/libgcc_s.so.1" + } + }, + { + "Filesystem": { + "host_path": "/lib/x86_64-linux-gnu/libc.so.6", + "environment_path": "/lib/libc.so.6" + } + }, + { + "Filesystem": { + "host_path": "/lib64/ld-linux-x86-64.so.2", + "environment_path": "/lib64/ld-linux-x86-64.so.2" + } + } + ] + }, + "tls_handler": { + "trigger": { + "FileSocket": "tls" + }, "args": [ "Entrypoint", { @@ -9,10 +47,12 @@ } }, { - "TcpListener": { - "addr": "0.0.0.0:8443" - } - } + "File": "/etc/ssl/certs/example.com.pem" + }, + { + "File": "/etc/ssl/private/example.com.key" + }, + "Trigger" ], "environment": [ { diff --git a/examples/tls/tls.rs b/examples/tls/tls.rs new file mode 100644 index 0000000..74bf5aa --- /dev/null +++ b/examples/tls/tls.rs @@ -0,0 +1,12 @@ +use std::fs::File; +use std::net::TcpStream; + +pub(crate) fn handler( + http_trigger_socket: File, + _cert: File, + _key: File, + stream: TcpStream, +) -> i32 { + super::http_handler(&http_trigger_socket, stream); + exitcode::OK +} -- 2.46.0 From 20c2f8d5f73d003e3038aa2515c8c8758c6bfe5b Mon Sep 17 00:00:00 2001 From: Jake Hillion Date: Mon, 23 May 2022 18:26:55 +0100 Subject: [PATCH 2/3] forward tls handler --- examples/tls/http.rs | 4 +-- examples/tls/main.rs | 5 +-- examples/tls/tls.rs | 83 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/examples/tls/http.rs b/examples/tls/http.rs index 0688f37..82c6e52 100644 --- a/examples/tls/http.rs +++ b/examples/tls/http.rs @@ -1,9 +1,9 @@ use std::fs; use std::io::{Read, Write}; -use std::net::TcpStream; +use std::os::unix::net::UnixStream; use std::path::PathBuf; -pub(super) fn handler(mut stream: TcpStream) -> i32 { +pub(super) fn handler(mut stream: UnixStream) -> i32 { println!("entered http handler"); let mut buf = Vec::new(); diff --git a/examples/tls/main.rs b/examples/tls/main.rs index dda71b4..94b75f0 100644 --- a/examples/tls/main.rs +++ b/examples/tls/main.rs @@ -3,6 +3,7 @@ mod tls; use std::fs::File; use std::net::{TcpListener, TcpStream}; +use std::os::unix::net::UnixStream; fn main() { match std::env::args().next() { @@ -129,7 +130,7 @@ fn tls_handler_entrypoint() { )); } -fn http_handler(trigger_socket: &File, stream: TcpStream) { +fn http_handler(trigger_socket: &File, stream: UnixStream) { // imports use nix::sys::socket::{sendmsg, ControlMessage, MsgFlags}; use std::os::unix::io::AsRawFd; @@ -162,7 +163,7 @@ fn http_handler_entrypoint() { .expect("request stream required") .parse() .expect("request stream should be a file descriptor"); - let stream = unsafe { TcpStream::from_raw_fd(stream) }; + let stream = unsafe { UnixStream::from_raw_fd(stream) }; std::process::exit(http::handler(stream)); } diff --git a/examples/tls/tls.rs b/examples/tls/tls.rs index 74bf5aa..62dfd2c 100644 --- a/examples/tls/tls.rs +++ b/examples/tls/tls.rs @@ -1,12 +1,91 @@ use std::fs::File; +use std::io::{self, ErrorKind, Read, Write}; use std::net::TcpStream; +use std::os::unix::io::AsRawFd; +use std::os::unix::net::UnixStream; + +use nix::poll::{poll, PollFd, PollFlags}; + +const BUFFER_SIZE: usize = 4096; pub(crate) fn handler( http_trigger_socket: File, _cert: File, _key: File, - stream: TcpStream, + mut stream: TcpStream, ) -> i32 { - super::http_handler(&http_trigger_socket, stream); + let (mut socket, far_socket) = UnixStream::pair().unwrap(); + + super::http_handler(&http_trigger_socket, far_socket); + + stream.set_nonblocking(true).unwrap(); + socket.set_nonblocking(true).unwrap(); + + let mut to_poll = [ + PollFd::new(stream.as_raw_fd(), PollFlags::POLLIN), + PollFd::new(socket.as_raw_fd(), PollFlags::POLLIN), + ]; + + loop { + println!("starting polling"); + poll(&mut to_poll, -1).unwrap(); + + if let Some(events) = to_poll[0].revents() { + if events.contains(PollFlags::POLLIN) { + handle_encrypted_data(&mut stream, &mut socket).unwrap(); + } + } + + if let Some(events) = to_poll[1].revents() { + if events.contains(PollFlags::POLLIN) { + handle_new_data(&mut socket, &mut stream).unwrap(); + } + + if events.contains(PollFlags::POLLHUP) { + println!("response writer hung up, exiting"); + break; + } + } + } + exitcode::OK } + +fn handle_encrypted_data(stream: &mut impl Read, socket: &mut impl Write) -> io::Result<()> { + let mut buf = [0_u8; BUFFER_SIZE]; + + loop { + let read = non_blocking_read(stream, &mut buf)?; + if read == 0 { + return Ok(()); + } + + socket.write_all(&buf[0..read]).unwrap(); + } +} + +fn handle_new_data(socket: &mut impl Read, stream: &mut impl Write) -> io::Result<()> { + let mut buf = [0_u8; BUFFER_SIZE]; + + loop { + let read = non_blocking_read(socket, &mut buf)?; + if read == 0 { + return Ok(()); + } + + stream.write_all(&buf[0..read]).unwrap(); + } +} + +fn non_blocking_read(reader: &mut impl io::Read, buf: &mut [u8]) -> io::Result { + match reader.read(buf) { + Err(e) => { + if e.kind() == ErrorKind::WouldBlock { + Ok(0) + } else { + Err(e) + } + } + Ok(n) => Ok(n), + } +} -- 2.46.0 From 71888d40e71be0c217612319181a6a8ccd9dc35f Mon Sep 17 00:00:00 2001 From: Jake Hillion Date: Mon, 23 May 2022 19:42:07 +0100 Subject: [PATCH 3/3] added tls processing --- Cargo.lock | 95 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 ++ examples/tls/tls.rs | 106 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 192 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 091c3f0..86a3f61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + [[package]] name = "atty" version = "0.2.14" @@ -28,6 +34,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.3.2" @@ -61,6 +73,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + [[package]] name = "cfg-if" version = "1.0.0" @@ -106,6 +124,7 @@ dependencies = [ name = "clone-shim" version = "0.1.0" dependencies = [ + "anyhow", "clap 3.1.15", "close_fds", "criterion", @@ -116,6 +135,8 @@ dependencies = [ "libc", "log", "nix", + "rustls", + "rustls-pemfile", "serde", "serde_json", "tempfile", @@ -427,6 +448,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + [[package]] name = "oorandom" version = "11.1.3" @@ -551,6 +578,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -560,6 +602,27 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +dependencies = [ + "base64", +] + [[package]] name = "ryu" version = "1.0.9" @@ -581,6 +644,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "semver" version = "1.0.6" @@ -628,6 +701,12 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.10.0" @@ -725,6 +804,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "walkdir" version = "2.3.2" @@ -800,6 +885,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 97634c3..7d53d83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,12 @@ tempfile = "3.3" [dev-dependencies] criterion = "0.3" +anyhow = "1" # examples/tls httparse = "1" +rustls = "0.20" +rustls-pemfile = "1" [[bench]] name = "clone3" diff --git a/examples/tls/tls.rs b/examples/tls/tls.rs index 62dfd2c..39cae5c 100644 --- a/examples/tls/tls.rs +++ b/examples/tls/tls.rs @@ -1,21 +1,29 @@ use std::fs::File; -use std::io::{self, ErrorKind, Read, Write}; +use std::io::{self, BufReader, ErrorKind, Read, Write}; use std::net::TcpStream; use std::os::unix::io::AsRawFd; use std::os::unix::net::UnixStream; +use std::sync::Arc; use nix::poll::{poll, PollFd, PollFlags}; +use rustls::ServerConnection; + +use anyhow::Context; + const BUFFER_SIZE: usize = 4096; pub(crate) fn handler( http_trigger_socket: File, - _cert: File, - _key: File, + cert: File, + key: File, mut stream: TcpStream, ) -> i32 { let (mut socket, far_socket) = UnixStream::pair().unwrap(); + let config = make_config(cert, key); + let mut tls_conn = rustls::ServerConnection::new(config).unwrap(); + super::http_handler(&http_trigger_socket, far_socket); stream.set_nonblocking(true).unwrap(); @@ -32,13 +40,13 @@ pub(crate) fn handler( if let Some(events) = to_poll[0].revents() { if events.contains(PollFlags::POLLIN) { - handle_encrypted_data(&mut stream, &mut socket).unwrap(); + handle_encrypted_data(&mut tls_conn, &mut stream, &mut socket).unwrap(); } } if let Some(events) = to_poll[1].revents() { if events.contains(PollFlags::POLLIN) { - handle_new_data(&mut socket, &mut stream).unwrap(); + handle_new_data(&mut tls_conn, &mut socket, &mut stream).unwrap(); } if events.contains(PollFlags::POLLHUP) { @@ -51,29 +59,61 @@ pub(crate) fn handler( exitcode::OK } -fn handle_encrypted_data(stream: &mut impl Read, socket: &mut impl Write) -> io::Result<()> { - let mut buf = [0_u8; BUFFER_SIZE]; +fn handle_encrypted_data( + tls_conn: &mut ServerConnection, + stream: &mut (impl Read + Write), + socket: &mut impl Write, +) -> anyhow::Result<()> { + println!("handling newly received encrypted data"); loop { - let read = non_blocking_read(stream, &mut buf)?; + let read = match tls_conn.read_tls(stream) { + Err(e) => { + if e.kind() == ErrorKind::WouldBlock { + 0 + } else { + return Err(e).context("io error reading from stream"); + } + } + Ok(n) => n, + }; + if read == 0 { return Ok(()); } - socket.write_all(&buf[0..read]).unwrap(); + let process_result = tls_conn.process_new_packets(); + let write_tls_result = tls_conn.write_tls(stream); + + let io_state = process_result.context("tls processing failure")?; + write_tls_result.context("tls write failure")?; + + if io_state.plaintext_bytes_to_read() > 0 { + let mut reader = tls_conn + .reader() + .take(io_state.plaintext_bytes_to_read() as u64); + + std::io::copy(&mut reader, socket)?; + } } } -fn handle_new_data(socket: &mut impl Read, stream: &mut impl Write) -> io::Result<()> { - let mut buf = [0_u8; BUFFER_SIZE]; +fn handle_new_data( + tls_conn: &mut ServerConnection, + socket: &mut impl Read, + stream: &mut impl Write, +) -> anyhow::Result<()> { + println!("handling new data to encrypt"); + let mut buf = [0_u8; BUFFER_SIZE]; loop { let read = non_blocking_read(socket, &mut buf)?; if read == 0 { return Ok(()); } - stream.write_all(&buf[0..read]).unwrap(); + tls_conn.writer().write_all(&buf[0..read])?; + tls_conn.write_tls(stream)?; } } @@ -89,3 +129,45 @@ fn non_blocking_read(reader: &mut impl io::Read, buf: &mut [u8]) -> io::Result Ok(n), } } + +fn make_config(cert: File, key: File) -> Arc { + let certs = load_certs(cert); + let privkey = load_private_key(key); + + let config = rustls::ServerConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + .expect("inconsistent cipher-suites/versions specified") + .with_no_client_auth() + .with_single_cert(certs, privkey) + .expect("bad certificates/private key"); + + Arc::new(config) +} + +fn load_certs(certfile: File) -> Vec { + let mut reader = BufReader::new(certfile); + + rustls_pemfile::certs(&mut reader) + .unwrap() + .iter() + .map(|v| rustls::Certificate(v.clone())) + .collect() +} + +fn load_private_key(keyfile: File) -> rustls::PrivateKey { + let mut reader = BufReader::new(keyfile); + + loop { + match rustls_pemfile::read_one(&mut reader).expect("cannot parse private key .pem file") { + Some(rustls_pemfile::Item::RSAKey(key)) => return rustls::PrivateKey(key), + Some(rustls_pemfile::Item::PKCS8Key(key)) => return rustls::PrivateKey(key), + Some(rustls_pemfile::Item::ECKey(key)) => return rustls::PrivateKey(key), + None => break, + _ => {} + } + } + + panic!("no keys found (encrypted keys not supported)"); +} -- 2.46.0