diff options
author | Alex Auvolat <alex@adnab.me> | 2023-03-09 14:23:40 +0100 |
---|---|---|
committer | Alex Auvolat <alex@adnab.me> | 2023-03-09 14:23:40 +0100 |
commit | 52926d6152686ccf864fe281c03482b62fb6a749 (patch) | |
tree | ba60cb639554dc3d002b99b39bf61c80ab128d33 /src/main.rs | |
parent | 7c9839f900171d88a4fd4cb6d7c5dc556817aace (diff) | |
download | wgautomesh-52926d6152686ccf864fe281c03482b62fb6a749.tar.gz wgautomesh-52926d6152686ccf864fe281c03482b62fb6a749.zip |
gossip encryption (fix #4)
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 64 |
1 files changed, 56 insertions, 8 deletions
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<String>, + gossip_secret_file: Option<String>, /// 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<SocketAddr>, 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<SocketAd struct Daemon { config: Config, + gossip_key: xsalsa20poly1305::Key, our_pubkey: Pubkey, listen_port: u16, socket: UdpSocket, @@ -166,11 +182,14 @@ enum Gossip { impl Daemon { fn new(config: Config) -> Result<Self> { + 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<Vec<u8>> { + 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(); |