From 4449a771e234f40194305779c5445123d6dab100 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Sat, 2 Sep 2023 22:29:13 -0400 Subject: [PATCH] feat: added connection and authentication client can now generate a server binary, and try to connect to it and get capabilities --- Cargo.lock | 199 +++++++++++++++++ Makefile.toml | 7 + sparse-05/sparse-05-client/Cargo.toml | 1 + .../src/commands/connect/mod.rs | 117 +++++++++- .../sparse-05-client/src/commands/generate.rs | 11 +- sparse-05/sparse-05-client/src/configs.rs | 2 + sparse-05/sparse-05-common/Cargo.toml | 2 + sparse-05/sparse-05-common/src/lib.rs | 22 +- sparse-05/sparse-05-server/Cargo.toml | 2 + .../sparse-05-server/src/capabilities.rs | 7 +- sparse-05/sparse-05-server/src/connection.rs | 202 +++++++++++++++++- sparse-05/sparse-05-server/src/main.rs | 101 +++++---- 12 files changed, 606 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47ad76e..39c5201 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,60 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher", +] + +[[package]] +name = "aes-gcm" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aes-soft" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +dependencies = [ + "cipher", + "opaque-debug", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher", + "opaque-debug", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -118,6 +172,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array", +] + [[package]] name = "clap" version = "2.34.0" @@ -162,6 +225,31 @@ dependencies = [ "libc", ] +[[package]] +name = "cpuid-bool" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" + +[[package]] +name = "crypto-mac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -184,6 +272,24 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ecies-ed25519" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a882353828f086290bedd0b598e18863a4d2135f8e632c4325ed89a4e5535db" +dependencies = [ + "aes-gcm", + "curve25519-dalek", + "digest", + "hex", + "hkdf", + "rand", + "serde", + "sha2", + "thiserror", + "zeroize", +] + [[package]] name = "ed25519" version = "1.5.3" @@ -393,6 +499,16 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "ghash" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.0" @@ -426,6 +542,32 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "hmac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +dependencies = [ + "crypto-mac", + "digest", +] + [[package]] name = "itoa" version = "1.0.6" @@ -589,6 +731,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "polyval" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" +dependencies = [ + "cpuid-bool", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -735,6 +888,17 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "serde_repr" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "sha2" version = "0.9.9" @@ -800,6 +964,7 @@ name = "sparse-05-client" version = "0.5.0" dependencies = [ "anyhow", + "ecies-ed25519", "ed25519-dalek", "rand", "rmp-serde", @@ -814,9 +979,11 @@ dependencies = [ name = "sparse-05-common" version = "0.1.0" dependencies = [ + "ecies-ed25519", "ed25519-dalek", "rand", "serde", + "serde_repr", ] [[package]] @@ -826,10 +993,12 @@ dependencies = [ "anyhow", "catconf", "cc", + "ecies-ed25519", "ed25519-dalek", "libc", "log", "pcap-sys", + "rand", "rmp-serde", "serde", "simple_logger", @@ -911,6 +1080,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "time" version = "0.3.20" @@ -1004,6 +1193,16 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Makefile.toml b/Makefile.toml index 9140707..cc9bd10 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -19,6 +19,13 @@ workspace = false command = "docker-compose" args = ["run", "build", "build", "${@}"] +[tasks.sparse-05] +workspace = false +script = [ + "docker-compose run build build --bin sparse-05-server ${@}", + "docker-compose run build build --bin sparse-05-client ${@}", +] + [tasks.fmt] command = "cargo" args = ["fmt"] diff --git a/sparse-05/sparse-05-client/Cargo.toml b/sparse-05/sparse-05-client/Cargo.toml index 528a18c..845e21a 100644 --- a/sparse-05/sparse-05-client/Cargo.toml +++ b/sparse-05/sparse-05-client/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = "1.0.75" +ecies-ed25519 = { version = "0.5.1", features = ["serde"] } ed25519-dalek = "1.0.1" rand = "0.7" rmp-serde = "1.1.2" diff --git a/sparse-05/sparse-05-client/src/commands/connect/mod.rs b/sparse-05/sparse-05-client/src/commands/connect/mod.rs index 398f978..f3058cc 100644 --- a/sparse-05/sparse-05-client/src/commands/connect/mod.rs +++ b/sparse-05/sparse-05-client/src/commands/connect/mod.rs @@ -1,17 +1,51 @@ use std::{net::SocketAddr, path::PathBuf, sync::Arc}; -use ed25519_dalek::{Keypair, Signer}; -use sparse_05_common::messages::CONNECT_MESSAGE; +use anyhow::{bail, Context}; +use ed25519_dalek::{Keypair, Signature, Signer, Verifier}; +use rmp_serde::decode::ReadSlice; +use sparse_05_common::messages::{Capabilities, Response, CONNECTED_MESSAGE, CONNECT_MESSAGE}; use tokio::{fs, net::UdpSocket}; use crate::configs::ClientConfig; enum State { + Authenticating, Ready, UploadingFile, DownloadingFile, } +struct Connection { + config: ClientConfig, + foreign_sign_pubkey: ed25519_dalek::PublicKey, + foreign_enc_pubkey: ecies_ed25519::PublicKey, +} + +impl Connection { + fn decrypt_and_verify(&self, data: &[u8]) -> anyhow::Result { + if data.len() < 65 { + bail!("unable to parse out signature from message"); + } + + let signature: [u8; 64] = data[..64].try_into().unwrap(); + self.foreign_sign_pubkey + .verify(data, &Signature::from(signature))?; + + let data = ecies_ed25519::decrypt(&self.config.enc_privkey, &data[64..])?; + + Ok(rmp_serde::from_slice(&data)?) + } + + fn encrypt_and_sign(&self, data: &[u8]) -> anyhow::Result> { + let mut rng = rand::thread_rng(); + let data = ecies_ed25519::encrypt(&self.foreign_enc_pubkey, data, &mut rng)?; + + let signature = self.config.keypair.sign(&data); + + Ok([&signature.to_bytes(), &*data].concat()) + } +} + pub async fn connect(config: PathBuf, ip: SocketAddr) -> anyhow::Result<()> { let config = fs::read(&config).await?; let config: ClientConfig = rmp_serde::from_slice(&config)?; @@ -21,7 +55,84 @@ pub async fn connect(config: PathBuf, ip: SocketAddr) -> anyhow::Result<()> { let connect_signature = config.keypair.sign(CONNECT_MESSAGE).to_bytes(); let connect_msg = &[&connect_signature, CONNECT_MESSAGE].concat(); - remote.send_to(connect_msg, ip).await?; + let mut counter = 0; + + let (foreign_sign_pubkey, foreign_enc_pubkey, capabilities) = loop { + if counter > 5 { + bail!("could not connect to server"); + } + + remote.send_to(connect_msg, ip).await?; + + let mut buf = [0u8; 2000]; + + match tokio::time::timeout( + std::time::Duration::from_millis(250), + remote.recv_from(&mut buf), + ) + .await + { + Ok(packet) => { + let (count, addr) = packet?; + + if addr != ip { + bail!("received response from wrong source"); + } + + let data = &buf[..count]; + + if data.len() < 65 { + bail!("could not get signature from server"); + } + + let signature: [u8; 64] = data[..64].try_into().unwrap(); + let data = &data[64..]; + + let Ok(foreign_keys) = ecies_ed25519::decrypt(&config.enc_privkey, data) else { + counter += 1; + continue; + }; + + if foreign_keys.len() + < ed25519_dalek::PUBLIC_KEY_LENGTH + ecies_ed25519::PUBLIC_KEY_LENGTH + { + bail!("could not get public keys from the server"); + } + + let foreign_sign_pubkey = ed25519_dalek::PublicKey::from_bytes( + &foreign_keys[..ed25519_dalek::PUBLIC_KEY_LENGTH], + )?; + let foreign_enc_pubkey = ecies_ed25519::PublicKey::from_bytes( + &foreign_keys[ed25519_dalek::PUBLIC_KEY_LENGTH + ..(ed25519_dalek::PUBLIC_KEY_LENGTH + ecies_ed25519::PUBLIC_KEY_LENGTH)], + )?; + + let capabilities: Capabilities = rmp_serde::from_slice( + &foreign_keys + [(ed25519_dalek::PUBLIC_KEY_LENGTH + ecies_ed25519::PUBLIC_KEY_LENGTH)..], + )?; + + foreign_sign_pubkey.verify(data, &Signature::from(signature))?; + + break (foreign_sign_pubkey, foreign_enc_pubkey, capabilities); + } + Err(_) => { + counter += 1; + } + } + }; + + let connection = Connection { + config, + foreign_enc_pubkey, + foreign_sign_pubkey, + }; + + remote + .send_to(&connection.encrypt_and_sign(CONNECTED_MESSAGE)?, ip) + .await?; + + println!("connected! {:?}", capabilities); Ok(()) } diff --git a/sparse-05/sparse-05-client/src/commands/generate.rs b/sparse-05/sparse-05-client/src/commands/generate.rs index 8bdd5a7..4922c16 100644 --- a/sparse-05/sparse-05-client/src/commands/generate.rs +++ b/sparse-05/sparse-05-client/src/commands/generate.rs @@ -16,6 +16,7 @@ pub const SPARSE_SERVER_BINARY: &'static [u8] = pub async fn generate(mut name: PathBuf, port: u16) -> anyhow::Result<()> { let mut csprng = rand::thread_rng(); let keypair = Keypair::generate(&mut csprng); + let (enc_privkey, enc_pubkey) = ecies_ed25519::generate_keypair(&mut csprng); let mut file = fs::OpenOptions::new() .write(true) @@ -28,11 +29,17 @@ pub async fn generate(mut name: PathBuf, port: u16) -> anyhow::Result<()> { file.write_all(CONFIG_SEPARATOR).await?; file.write_all(&port.to_be_bytes()[..]).await?; file.write_all(keypair.public.as_bytes()).await?; + file.write_all(enc_pubkey.as_bytes()).await?; - let config = ClientConfig { keypair, port }; + let config = ClientConfig { + keypair, + enc_privkey, + enc_pubkey, + port, + }; let mut file_part = name.file_name().unwrap().to_owned(); - file_part.push(OsString::from(".conf")); + file_part.push(OsString::from(".scon")); name.pop(); name.push(file_part); let mut file = fs::OpenOptions::new() diff --git a/sparse-05/sparse-05-client/src/configs.rs b/sparse-05/sparse-05-client/src/configs.rs index c4409a5..5daefd5 100644 --- a/sparse-05/sparse-05-client/src/configs.rs +++ b/sparse-05/sparse-05-client/src/configs.rs @@ -4,5 +4,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct ClientConfig { pub keypair: Keypair, + pub enc_pubkey: ecies_ed25519::PublicKey, + pub enc_privkey: ecies_ed25519::SecretKey, pub port: u16, } diff --git a/sparse-05/sparse-05-common/Cargo.toml b/sparse-05/sparse-05-common/Cargo.toml index d7f554f..49f6179 100644 --- a/sparse-05/sparse-05-common/Cargo.toml +++ b/sparse-05/sparse-05-common/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ecies-ed25519 = { version = "0.5.1", features = ["serde"] } ed25519-dalek = { version = "1.0.1", features = ["serde"] } rand = "0.7" serde = { version = "1.0.188", features = ["derive"] } +serde_repr = "0.1" diff --git a/sparse-05/sparse-05-common/src/lib.rs b/sparse-05/sparse-05-common/src/lib.rs index f7af970..72e02f9 100644 --- a/sparse-05/sparse-05-common/src/lib.rs +++ b/sparse-05/sparse-05-common/src/lib.rs @@ -3,17 +3,12 @@ pub const CONFIG_SEPARATOR: &'static [u8] = pub mod messages { pub const CONNECT_MESSAGE: &'static [u8] = b"CONNECT"; + pub const CONNECTED_MESSAGE: &'static [u8] = b"CONNECTED"; use std::{ffi::OsString, path::PathBuf}; - use ed25519_dalek::Signature; use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - pub struct CommandWrapper { - sig: Signature, - command: Vec, - } + use serde_repr::{Deserialize_repr, Serialize_repr}; #[derive(Serialize, Deserialize)] pub enum Command { @@ -61,7 +56,7 @@ pub mod messages { #[derive(Serialize, Deserialize)] pub enum Response { - Connected(Capabilities), + // Connected(Capabilities, ed25519_dalek::PublicKey, ecies_ed25519::PublicKey), SendData(Vec), CdDone, @@ -76,14 +71,23 @@ pub mod messages { DownloadFileSegment(u64, u64, Vec), } - #[derive(Serialize, Deserialize, Debug, Clone, Copy)] + #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy)] + #[repr(u8)] pub enum TransportType { RawUdp, Udp, } + #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Copy)] + #[repr(u8)] + pub enum OperatingSystem { + Windows, + Linux, + } + #[derive(Serialize, Deserialize, Debug)] pub struct Capabilities { + pub operating_system: OperatingSystem, pub docker_container: bool, pub docker_breakout: bool, pub setuid: bool, diff --git a/sparse-05/sparse-05-server/Cargo.toml b/sparse-05/sparse-05-server/Cargo.toml index 406056c..e3d090d 100644 --- a/sparse-05/sparse-05-server/Cargo.toml +++ b/sparse-05/sparse-05-server/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" pcap-sys = { path = "../../pcap-sys" } anyhow = "1.0.70" ed25519-dalek = "1.0.1" +rand = "0.7" log = "0.4.17" simple_logger = "4.1.0" libc = { version = "0.2.147" } @@ -14,6 +15,7 @@ serde = { version = "1.0.188", features = ["derive"] } rmp-serde = "1.1.2" catconf = "0.1.2" sparse-05-common = { version = "0.1.0", path = "../sparse-05-common" } +ecies-ed25519 = { version = "0.5.1", features = ["serde"] } [build-dependencies] cc = "1.0" diff --git a/sparse-05/sparse-05-server/src/capabilities.rs b/sparse-05/sparse-05-server/src/capabilities.rs index f729a66..7612d6e 100644 --- a/sparse-05/sparse-05-server/src/capabilities.rs +++ b/sparse-05/sparse-05-server/src/capabilities.rs @@ -1,6 +1,6 @@ use std::ffi::c_int; -use sparse_05_common::messages::{Capabilities, TransportType}; +use sparse_05_common::messages::{Capabilities, OperatingSystem, TransportType}; const CAP_SETUID: u32 = 1 << 7; const CAP_NET_RAW: u32 = 1 << 13; @@ -71,6 +71,11 @@ fn get_current_capabilities() -> anyhow::Result { let userent = get_username(uid)?; Ok(Capabilities { + operating_system: if cfg!(target_os = "linux") { + OperatingSystem::Linux + } else { + OperatingSystem::Windows + }, docker_container, docker_breakout, setuid, diff --git a/sparse-05/sparse-05-server/src/connection.rs b/sparse-05/sparse-05-server/src/connection.rs index 53c06fc..daf6727 100644 --- a/sparse-05/sparse-05-server/src/connection.rs +++ b/sparse-05/sparse-05-server/src/connection.rs @@ -1,22 +1,214 @@ -use std::{net::Ipv4Addr, sync::mpsc::Sender}; +use std::{ + net::{Ipv4Addr, UdpSocket}, + sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, + }, + thread, +}; +use anyhow::{anyhow, bail, Context}; +use ed25519_dalek::{Keypair, Signature, Signer, Verifier}; use pcap_sys::packets::EthernetPacket; +use sparse_05_common::messages::{Capabilities, Command, Response, CONNECTED_MESSAGE}; #[derive(Clone)] -pub struct ConnectionHandle {} +pub struct ConnectionHandle { + packet_handler_sender: Sender, +} impl ConnectionHandle { pub fn handle_packet(&self, packet: EthernetPacket) -> anyhow::Result<()> { + self.packet_handler_sender.send(packet)?; Ok(()) } } -pub enum State {} +struct ConnectionInformation { + remote_sign_pubkey: ed25519_dalek::PublicKey, + remote_enc_pubkey: ecies_ed25519::PublicKey, + local_sign_keypair: Keypair, + local_enc_pubkey: ecies_ed25519::PublicKey, + local_enc_privkey: ecies_ed25519::SecretKey, + srcmac: [u8; 6], + dstmac: [u8; 6], + srcip: Ipv4Addr, + dstip: Ipv4Addr, + srcport: u16, +} + +impl ConnectionInformation { + fn format_udp_packet(&self, data: &[u8]) -> EthernetPacket { + use pcap_sys::packets::*; + + let udp_packet = UDPPacket::construct(54248, self.srcport, data); + let ip_packet = + IPv4Packet::construct(self.dstip, self.srcip, &Layer4Packet::UDP(udp_packet)); + EthernetPacket::construct(self.dstmac, self.srcmac, &Layer3Packet::IPv4(ip_packet)) + } + + fn encrypt_and_sign(&self, data: &[u8]) -> anyhow::Result { + let mut rng = rand::thread_rng(); + let enc_data = ecies_ed25519::encrypt(&self.remote_enc_pubkey, data, &mut rng)?; + let signature = self.local_sign_keypair.sign(&enc_data); + + Ok(self.format_udp_packet(&[&signature.to_bytes(), &*enc_data].concat())) + } + + fn encrypt_and_sign_resp(&self, data: Response) -> anyhow::Result { + let raw_data = rmp_serde::to_vec(&data)?; + self.encrypt_and_sign(&raw_data) + } + + fn try_decrypt_and_verify(&self, data: &[u8]) -> anyhow::Result> { + if data.len() < 65 { + bail!("packet received is too small"); + } + + let signature: [u8; 64] = data[..64].try_into()?; + let signature = Signature::from(signature); + let rest = &data[64..]; + + self.remote_sign_pubkey.verify(rest, &signature)?; + + Ok(ecies_ed25519::decrypt(&self.local_enc_privkey, rest)?) + } + + fn try_decrypt_and_verify_comm(&self, data: &[u8]) -> anyhow::Result { + let data = self.try_decrypt_and_verify(data)?; + + Ok(rmp_serde::from_slice(&data)?) + } +} pub fn spawn_connection_handler( + capabilities: Arc, + sign_pubkey: Arc, + enc_pubkey: Arc, packet_sender: Sender, connection_packet: EthernetPacket, connection_killer: Sender<(Ipv4Addr, u16)>, -) -> ConnectionHandle { - ConnectionHandle {} +) -> anyhow::Result { + println!("received connection, starting to authenticate"); + + let conninfo = { + use pcap_sys::packets::*; + let packet = connection_packet.pkt(); + + let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()?; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + + let data = udp_pkt.get_data(); + + if data.len() < 71 { + bail!("connection packet was the wrong length"); + } + + let Ok(signature): Result<[u8; 64], _> = data[..64].try_into() else { + bail!("could not get signature from connection"); + }; + + let signature = Signature::from(signature); + let connect = &data[64..]; + + if connect != b"CONNECT" { + bail!("connect magic bytes were wrong"); + } + + sign_pubkey + .verify(connect, &signature) + .context("could not verify the signature of the connection packet")?; + + let mut csprng = rand::thread_rng(); + let local_sign_keypair = Keypair::generate(&mut csprng); + let (local_enc_privkey, local_enc_pubkey) = ecies_ed25519::generate_keypair(&mut csprng); + + ConnectionInformation { + remote_sign_pubkey: (*sign_pubkey).clone(), + remote_enc_pubkey: (*enc_pubkey).clone(), + local_sign_keypair, + local_enc_privkey, + local_enc_pubkey, + srcmac: *packet.source_address(), + dstmac: *packet.destination_address(), + srcip: ip_pkt.source_ip(), + dstip: ip_pkt.dest_ip(), + srcport: udp_pkt.srcport(), + } + }; + let close = { + let ip = conninfo.srcip.clone(); + let port = conninfo.srcport; + move || { + let _ = connection_killer.send((ip, port)); + } + }; + + let (packet_handler_sender, packet_handler) = channel(); + + thread::spawn(move || { + if let Err(e) = authenticate(capabilities, packet_handler, conninfo, packet_sender, close) { + eprintln!("connection thread died: {e:?}"); + } + }); + + Ok(ConnectionHandle { + packet_handler_sender, + }) +} + +fn authenticate( + capabilities: Arc, + packet_handler: Receiver, + conninfo: ConnectionInformation, + packet_sender: Sender, + close: F, +) -> anyhow::Result<()> { + let mut counter = 0; + + loop { + if counter > 5 { + close(); + } + + let next_pkt = conninfo.encrypt_and_sign( + &[ + &conninfo.local_sign_keypair.public.to_bytes(), + &conninfo.local_enc_pubkey.to_bytes(), + &*(rmp_serde::to_vec(&*capabilities)?), + ] + .concat(), + )?; + packet_sender.send(next_pkt)?; + + match packet_handler.recv_timeout(std::time::Duration::from_millis(250)) { + Ok(p) => { + use pcap_sys::packets::*; + let p = p.pkt(); + let Layer3Pkt::IPv4Pkt(ip_pkt) = p.get_layer3_pkt()?; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + + let Ok(data) = conninfo.try_decrypt_and_verify(udp_pkt.get_data()) else { + counter += 1; + continue; + }; + + if data == CONNECTED_MESSAGE { + break; + } + + counter += 1; + } + Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { + counter += 1; + } + Err(e) => Err(e)?, + } + } + + println!("Connection made!"); + + close(); + + Ok(()) } diff --git a/sparse-05/sparse-05-server/src/main.rs b/sparse-05/sparse-05-server/src/main.rs index a12a4d7..73da0ca 100644 --- a/sparse-05/sparse-05-server/src/main.rs +++ b/sparse-05/sparse-05-server/src/main.rs @@ -7,7 +7,6 @@ use std::{ use anyhow::{bail, Context}; use connection::ConnectionHandle; -use ed25519_dalek::PublicKey; use pcap_sys::packets::EthernetPacket; use sparse_05_common::CONFIG_SEPARATOR; @@ -24,26 +23,27 @@ fn main() -> anyhow::Result<()> { .with_module_level("sparse-05-server", log::LevelFilter::Info) .init()?; - let capabilities = capabilities::get_capabilities()?; + let capabilities = Arc::new(capabilities::get_capabilities()?); let config_bytes = catconf::read_from_exe(CONFIG_SEPARATOR, 512)?; - if config_bytes.len() != 34 { + if config_bytes.len() != 66 { bail!("could not load configuration"); } - let (port, pubkey) = { + let (port, conn_pubkey, enc_pubkey) = { let port = u16::from_be_bytes(config_bytes[..2].try_into().unwrap()); - let pubkey = PublicKey::from_bytes(&config_bytes[2..]) - .context("could not parse public key from configuration")?; + let conn_pubkey = ed25519_dalek::PublicKey::from_bytes(&config_bytes[2..34]) + .context("could not parse public signing key from configuration")?; + let enc_pubkey = ecies_ed25519::PublicKey::from_bytes(&config_bytes[34..66]) + .context("could not parse public encryption key from configuration")?; - (port, Arc::new(pubkey)) + (port, Arc::new(conn_pubkey), Arc::new(enc_pubkey)) }; let interface = interface::Interface::new(capabilities.transport, port)?; let (interface_sender, interface_receiver) = interface.split()?; - let connections: Arc>> = - Arc::new(Mutex::new(HashMap::new())); + let connections: Mutex> = Mutex::new(HashMap::new()); let (send_eth_packet, recv_eth_packet) = channel::(); let (kill_connection, kill_connection_recv) = channel::<(Ipv4Addr, u16)>(); @@ -52,46 +52,53 @@ fn main() -> anyhow::Result<()> { if let Err(_) = interface_sender.sendpacket(packet.pkt()) {} }); - thread::spawn({ - let connections = Arc::clone(&connections); - move || loop { - let Ok(connection) = kill_connection_recv.recv() else { continue }; - if let Ok(mut e) = connections.lock() { - e.remove(&connection); + thread::scope(|s| { + s.spawn({ + let connections = &connections; + move || loop { + let Ok(connection) = kill_connection_recv.recv() else { continue }; + if let Ok(mut e) = connections.lock() { + e.remove(&connection); + } } - } + }); + + interface_receiver + .listen(|pkt| { + use pcap_sys::packets::*; + + let pkt = pkt.pkt(); + + let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()?; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + + let connection_handle = &(ip_pkt.dest_ip(), udp_pkt.srcport()); + + if let Ok(mut connections) = connections.lock() { + let connection = connections.get(connection_handle); + + match connection { + None => { + let new_connection = spawn_connection_handler( + Arc::clone(&capabilities), + Arc::clone(&conn_pubkey), + Arc::clone(&enc_pubkey), + send_eth_packet.clone(), + pkt.to_owned(), + kill_connection.clone(), + )?; + connections.insert(*connection_handle, new_connection); + } + Some(conn) => { + conn.handle_packet(pkt.to_owned())?; + } + } + } + + Ok(()) + }) + .expect("packet capture loop failed"); }); - interface_receiver.listen(|pkt| { - use pcap_sys::packets::*; - - let pkt = pkt.pkt(); - - let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; - - let connection_handle = &(ip_pkt.dest_ip(), udp_pkt.srcport()); - - if let Ok(mut connections) = connections.lock() { - let connection = connections.get(connection_handle); - - match connection { - None => { - let new_connection = spawn_connection_handler( - send_eth_packet.clone(), - pkt.to_owned(), - kill_connection.clone(), - ); - connections.insert(*connection_handle, new_connection); - } - Some(conn) => { - conn.handle_packet(pkt.to_owned())?; - } - } - } - - Ok(()) - })?; - Ok(()) }