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 | |
parent | 7c9839f900171d88a4fd4cb6d7c5dc556817aace (diff) | |
download | wgautomesh-52926d6152686ccf864fe281c03482b62fb6a749.tar.gz wgautomesh-52926d6152686ccf864fe281c03482b62fb6a749.zip |
gossip encryption (fix #4)
-rw-r--r-- | Cargo.lock | 193 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/main.rs | 64 |
3 files changed, 251 insertions, 8 deletions
@@ -3,6 +3,16 @@ version = 3 [[package]] +name = "aead" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] name = "aho-corasick" version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -18,6 +28,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] name = "attohttpc" version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -56,6 +78,29 @@ dependencies = [ ] [[package]] +name = "blake3" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -68,12 +113,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b" [[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "constant_time_eq" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] name = "env_logger" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -108,6 +207,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] name = "get_if_addrs" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -209,6 +318,15 @@ dependencies = [ ] [[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] name = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -236,12 +354,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] name = "percent-encoding" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -329,6 +464,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] name = "serde" version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -358,6 +502,12 @@ dependencies = [ ] [[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -427,6 +577,12 @@ dependencies = [ ] [[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] name = "unicode-bidi" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -448,6 +604,16 @@ dependencies = [ ] [[package]] +name = "universal-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] name = "url" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -459,6 +625,12 @@ dependencies = [ ] [[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -470,12 +642,14 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode", + "blake3", "get_if_addrs", "igd", "log", "pretty_env_logger", "serde", "toml", + "xsalsa20poly1305", "xxhash-rust", ] @@ -547,7 +721,26 @@ dependencies = [ ] [[package]] +name = "xsalsa20poly1305" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "472c385ee974833d7e59979eeb74175d56774be3768b5bcc581337e21396bda3" +dependencies = [ + "aead", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] name = "xxhash-rust" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" @@ -14,6 +14,8 @@ pretty_env_logger = "0.4.0" serde = { version = "1.0", features = ["derive"] } bincode = "1.3" toml = { version = "0.7", default-features = false, features = ["parse"] } +xsalsa20poly1305 = "0.9" +blake3 = "1.3" igd = { version = "0.12", default-features = false } get_if_addrs = "0.5" 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(); |