tls-server-v2 #49
95
Cargo.lock
generated
95
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
10
README.md
10
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.
|
||||
|
@ -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();
|
||||
|
@ -1,12 +1,15 @@
|
||||
mod http;
|
||||
mod tls;
|
||||
|
||||
use std::fs::File;
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
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 +27,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 +42,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,17 +56,81 @@ 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 http_handler(trigger_socket: &File, stream: TcpStream) {
|
||||
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: UnixStream) {
|
||||
// imports
|
||||
use nix::sys::socket::{sendmsg, ControlMessage, MsgFlags};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
@ -96,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));
|
||||
}
|
||||
|
@ -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": [
|
||||
{
|
||||
|
173
examples/tls/tls.rs
Normal file
173
examples/tls/tls.rs
Normal file
@ -0,0 +1,173 @@
|
||||
use std::fs::File;
|
||||
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,
|
||||
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();
|
||||
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 tls_conn, &mut stream, &mut socket).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(events) = to_poll[1].revents() {
|
||||
if events.contains(PollFlags::POLLIN) {
|
||||
handle_new_data(&mut tls_conn, &mut socket, &mut stream).unwrap();
|
||||
}
|
||||
|
||||
if events.contains(PollFlags::POLLHUP) {
|
||||
println!("response writer hung up, exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exitcode::OK
|
||||
}
|
||||
|
||||
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 = 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(());
|
||||
}
|
||||
|
||||
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(
|
||||
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(());
|
||||
}
|
||||
|
||||
tls_conn.writer().write_all(&buf[0..read])?;
|
||||
tls_conn.write_tls(stream)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn non_blocking_read(reader: &mut impl io::Read, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match reader.read(buf) {
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::WouldBlock {
|
||||
Ok(0)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
Ok(n) => Ok(n),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_config(cert: File, key: File) -> Arc<rustls::ServerConfig> {
|
||||
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<rustls::Certificate> {
|
||||
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)");
|
||||
}
|
Loading…
Reference in New Issue
Block a user