From 52926d6152686ccf864fe281c03482b62fb6a749 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Thu, 9 Mar 2023 14:23:40 +0100 Subject: gossip encryption (fix #4) --- src/main.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index b913068..1730edc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,9 @@ struct Config { interface: Pubkey, /// The port to use for gossip inside the Wireguard mesh (must be the same on all nodes) gossip_port: u16, + /// The secret to use to authenticate nodes between them + gossip_secret: Option, + gossip_secret_file: Option, /// Enable LAN discovery #[serde(default)] @@ -71,11 +74,18 @@ fn main() -> Result<()> { ), }; - let config: Config = { + let mut config: Config = { let config_str = std::fs::read_to_string(config_path)?; toml::from_str(&config_str)? }; + if let Some(f) = &config.gossip_secret_file { + if config.gossip_secret.is_some() { + bail!("both gossip_secret and gossip_secret_file are given in config file"); + } + config.gossip_secret = Some(std::fs::read_to_string(f)?); + } + Daemon::new(config)?.run() } @@ -96,6 +106,11 @@ fn fasthash(data: &[u8]) -> u64 { h.digest() } +fn kdf(secret: &str) -> xsalsa20poly1305::Key { + let hash = blake3::hash(format!("wgautomesh: {}", secret).as_bytes()); + hash.as_bytes().clone().into() +} + fn wg_dump(config: &Config) -> Result<(Pubkey, u16, Vec<(Pubkey, Option, u64)>)> { let output = Command::new("wg") .args(["show", &config.interface, "dump"]) @@ -134,6 +149,7 @@ fn wg_dump(config: &Config) -> Result<(Pubkey, u16, Vec<(Pubkey, Option Result { + let gossip_key = kdf(config.gossip_secret.as_deref().unwrap_or_default()); + let (our_pubkey, listen_port, _peers) = wg_dump(&config)?; let socket = UdpSocket::bind(SocketAddr::new("0.0.0.0".parse()?, config.gossip_port))?; socket.set_broadcast(true)?; Ok(Daemon { config, + gossip_key, our_pubkey, listen_port, socket, @@ -186,7 +205,7 @@ impl Daemon { error!("Error initializing wireguard peers: {}", e); } - let request = bincode::serialize(&Gossip::Request)?; + let request = self.make_packet(&Gossip::Request)?; for peer in self.config.peers.iter() { let addr = SocketAddr::new(peer.address, self.config.gossip_port); if let Err(e) = self.socket.send_to(&request, addr) { @@ -264,7 +283,7 @@ impl Daemon { } Gossip::Request => { for (pubkey, endpoints) in state.gossip.iter() { - let packet = bincode::serialize(&Gossip::Announce { + let packet = self.make_packet(&Gossip::Announce { pubkey: pubkey.clone(), endpoints: endpoints.clone(), })?; @@ -287,12 +306,24 @@ impl Daemon { } fn recv_gossip(&self) -> Result<(SocketAddr, Gossip)> { + use xsalsa20poly1305::{ + aead::{Aead, KeyInit}, + XSalsa20Poly1305, NONCE_SIZE, + }; + let mut buf = vec![0u8; 1500]; let (amt, src) = self.socket.recv_from(&mut buf)?; - if !self.config.peers.iter().any(|x| x.address == src.ip()) { - bail!("Received message from unexpected peer: {}", src); + + if amt < NONCE_SIZE { + bail!("invalid packet"); } - let gossip = bincode::deserialize(&buf[..amt])?; + + let cipher = XSalsa20Poly1305::new(&self.gossip_key); + let plaintext = cipher + .decrypt(buf[..NONCE_SIZE].try_into().unwrap(), &buf[NONCE_SIZE..amt]) + .map_err(|e| anyhow!("decrypt error: {}", e))?; + + let gossip = bincode::deserialize(&plaintext)?; debug!("RECV {}\t{:?}", src, gossip); Ok((src, gossip)) } @@ -309,7 +340,7 @@ impl Daemon { } fn lan_broadcast_iter(&self) -> Result<()> { - let packet = bincode::serialize(&Gossip::LanBroadcast { + let packet = self.make_packet(&Gossip::LanBroadcast { pubkey: self.our_pubkey.clone(), listen_port: self.listen_port, })?; @@ -365,6 +396,23 @@ impl Daemon { Ok(()) } + + fn make_packet(&self, gossip: &Gossip) -> Result> { + use xsalsa20poly1305::{ + aead::{Aead, KeyInit, OsRng}, + XSalsa20Poly1305, + }; + + let plaintext = bincode::serialize(&gossip)?; + + let cipher = XSalsa20Poly1305::new(&self.gossip_key); + let nonce = XSalsa20Poly1305::generate_nonce(&mut OsRng); + let ciphertext = cipher + .encrypt(&nonce, &plaintext[..]) + .map_err(|e| anyhow!("encrypt error: {}", e))?; + + Ok([&nonce[..], &ciphertext[..]].concat()) + } } struct State { @@ -374,7 +422,7 @@ struct State { impl State { fn send_gossip(&self, daemon: &Daemon, gossip: Gossip) -> Result<()> { - let packet = bincode::serialize(&gossip)?; + let packet = daemon.make_packet(&gossip)?; let now = time(); -- cgit v1.2.3