feat: added connection and authentication

client can now generate a server binary, and try to connect to it and
get capabilities
This commit is contained in:
Andrew Rioux
2023-09-02 22:29:13 -04:00
parent cda6388596
commit 4449a771e2
12 changed files with 606 additions and 67 deletions

View File

@@ -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"

View File

@@ -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<Response> {
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<Vec<u8>> {
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(())
}

View File

@@ -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()

View File

@@ -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,
}

View File

@@ -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"

View File

@@ -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<u8>,
}
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<u8>),
CdDone,
@@ -76,14 +71,23 @@ pub mod messages {
DownloadFileSegment(u64, u64, Vec<u8>),
}
#[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,

View File

@@ -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"

View File

@@ -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<Capabilities> {
let userent = get_username(uid)?;
Ok(Capabilities {
operating_system: if cfg!(target_os = "linux") {
OperatingSystem::Linux
} else {
OperatingSystem::Windows
},
docker_container,
docker_breakout,
setuid,

View File

@@ -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<EthernetPacket>,
}
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<EthernetPacket> {
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<EthernetPacket> {
let raw_data = rmp_serde::to_vec(&data)?;
self.encrypt_and_sign(&raw_data)
}
fn try_decrypt_and_verify(&self, data: &[u8]) -> anyhow::Result<Vec<u8>> {
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<Command> {
let data = self.try_decrypt_and_verify(data)?;
Ok(rmp_serde::from_slice(&data)?)
}
}
pub fn spawn_connection_handler(
capabilities: Arc<Capabilities>,
sign_pubkey: Arc<ed25519_dalek::PublicKey>,
enc_pubkey: Arc<ecies_ed25519::PublicKey>,
packet_sender: Sender<EthernetPacket>,
connection_packet: EthernetPacket,
connection_killer: Sender<(Ipv4Addr, u16)>,
) -> ConnectionHandle {
ConnectionHandle {}
) -> anyhow::Result<ConnectionHandle> {
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<F: Fn()>(
capabilities: Arc<Capabilities>,
packet_handler: Receiver<EthernetPacket>,
conninfo: ConnectionInformation,
packet_sender: Sender<EthernetPacket>,
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(())
}

View File

@@ -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<Mutex<HashMap<(Ipv4Addr, u16), ConnectionHandle>>> =
Arc::new(Mutex::new(HashMap::new()));
let connections: Mutex<HashMap<(Ipv4Addr, u16), ConnectionHandle>> = Mutex::new(HashMap::new());
let (send_eth_packet, recv_eth_packet) = channel::<EthernetPacket>();
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(())
}