gossip encryption (fix #4)

This commit is contained in:
Alex 2023-03-09 14:23:40 +01:00
parent 7c9839f900
commit 52926d6152
3 changed files with 251 additions and 8 deletions

193
Cargo.lock generated
View file

@ -2,6 +2,16 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.20" version = "0.7.20"
@ -17,6 +27,18 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" 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]] [[package]]
name = "attohttpc" name = "attohttpc"
version = "0.16.3" version = "0.16.3"
@ -55,6 +77,29 @@ dependencies = [
"serde", "serde",
] ]
[[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]] [[package]]
name = "bytes" name = "bytes"
version = "1.4.0" version = "1.4.0"
@ -67,12 +112,66 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b" checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 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]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.7.1" version = "0.7.1"
@ -107,6 +206,16 @@ version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 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]] [[package]]
name = "get_if_addrs" name = "get_if_addrs"
version = "0.5.3" version = "0.5.3"
@ -208,6 +317,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.6" version = "1.0.6"
@ -235,12 +353,29 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.2.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 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]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.17"
@ -328,6 +463,15 @@ version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "salsa20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
"cipher",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.154" version = "1.0.154"
@ -357,6 +501,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@ -426,6 +576,12 @@ dependencies = [
"winnow", "winnow",
] ]
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.11" version = "0.3.11"
@ -447,6 +603,16 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[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]] [[package]]
name = "url" name = "url"
version = "2.3.1" version = "2.3.1"
@ -458,6 +624,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@ -470,12 +642,14 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
"blake3",
"get_if_addrs", "get_if_addrs",
"igd", "igd",
"log", "log",
"pretty_env_logger", "pretty_env_logger",
"serde", "serde",
"toml", "toml",
"xsalsa20poly1305",
"xxhash-rust", "xxhash-rust",
] ]
@ -546,8 +720,27 @@ dependencies = [
"xml-rs", "xml-rs",
] ]
[[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]] [[package]]
name = "xxhash-rust" name = "xxhash-rust"
version = "0.8.6" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70"
[[package]]
name = "zeroize"
version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"

View file

@ -14,6 +14,8 @@ pretty_env_logger = "0.4.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
bincode = "1.3" bincode = "1.3"
toml = { version = "0.7", default-features = false, features = ["parse"] } toml = { version = "0.7", default-features = false, features = ["parse"] }
xsalsa20poly1305 = "0.9"
blake3 = "1.3"
igd = { version = "0.12", default-features = false } igd = { version = "0.12", default-features = false }
get_if_addrs = "0.5" get_if_addrs = "0.5"

View file

@ -34,6 +34,9 @@ struct Config {
interface: Pubkey, interface: Pubkey,
/// The port to use for gossip inside the Wireguard mesh (must be the same on all nodes) /// The port to use for gossip inside the Wireguard mesh (must be the same on all nodes)
gossip_port: u16, gossip_port: u16,
/// The secret to use to authenticate nodes between them
gossip_secret: Option<String>,
gossip_secret_file: Option<String>,
/// Enable LAN discovery /// Enable LAN discovery
#[serde(default)] #[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)?; let config_str = std::fs::read_to_string(config_path)?;
toml::from_str(&config_str)? 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() Daemon::new(config)?.run()
} }
@ -96,6 +106,11 @@ fn fasthash(data: &[u8]) -> u64 {
h.digest() 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)>)> { fn wg_dump(config: &Config) -> Result<(Pubkey, u16, Vec<(Pubkey, Option<SocketAddr>, u64)>)> {
let output = Command::new("wg") let output = Command::new("wg")
.args(["show", &config.interface, "dump"]) .args(["show", &config.interface, "dump"])
@ -134,6 +149,7 @@ fn wg_dump(config: &Config) -> Result<(Pubkey, u16, Vec<(Pubkey, Option<SocketAd
struct Daemon { struct Daemon {
config: Config, config: Config,
gossip_key: xsalsa20poly1305::Key,
our_pubkey: Pubkey, our_pubkey: Pubkey,
listen_port: u16, listen_port: u16,
socket: UdpSocket, socket: UdpSocket,
@ -166,11 +182,14 @@ enum Gossip {
impl Daemon { impl Daemon {
fn new(config: Config) -> Result<Self> { 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 (our_pubkey, listen_port, _peers) = wg_dump(&config)?;
let socket = UdpSocket::bind(SocketAddr::new("0.0.0.0".parse()?, config.gossip_port))?; let socket = UdpSocket::bind(SocketAddr::new("0.0.0.0".parse()?, config.gossip_port))?;
socket.set_broadcast(true)?; socket.set_broadcast(true)?;
Ok(Daemon { Ok(Daemon {
config, config,
gossip_key,
our_pubkey, our_pubkey,
listen_port, listen_port,
socket, socket,
@ -186,7 +205,7 @@ impl Daemon {
error!("Error initializing wireguard peers: {}", e); 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() { for peer in self.config.peers.iter() {
let addr = SocketAddr::new(peer.address, self.config.gossip_port); let addr = SocketAddr::new(peer.address, self.config.gossip_port);
if let Err(e) = self.socket.send_to(&request, addr) { if let Err(e) = self.socket.send_to(&request, addr) {
@ -264,7 +283,7 @@ impl Daemon {
} }
Gossip::Request => { Gossip::Request => {
for (pubkey, endpoints) in state.gossip.iter() { for (pubkey, endpoints) in state.gossip.iter() {
let packet = bincode::serialize(&Gossip::Announce { let packet = self.make_packet(&Gossip::Announce {
pubkey: pubkey.clone(), pubkey: pubkey.clone(),
endpoints: endpoints.clone(), endpoints: endpoints.clone(),
})?; })?;
@ -287,12 +306,24 @@ impl Daemon {
} }
fn recv_gossip(&self) -> Result<(SocketAddr, Gossip)> { fn recv_gossip(&self) -> Result<(SocketAddr, Gossip)> {
use xsalsa20poly1305::{
aead::{Aead, KeyInit},
XSalsa20Poly1305, NONCE_SIZE,
};
let mut buf = vec![0u8; 1500]; let mut buf = vec![0u8; 1500];
let (amt, src) = self.socket.recv_from(&mut buf)?; 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); debug!("RECV {}\t{:?}", src, gossip);
Ok((src, gossip)) Ok((src, gossip))
} }
@ -309,7 +340,7 @@ impl Daemon {
} }
fn lan_broadcast_iter(&self) -> Result<()> { fn lan_broadcast_iter(&self) -> Result<()> {
let packet = bincode::serialize(&Gossip::LanBroadcast { let packet = self.make_packet(&Gossip::LanBroadcast {
pubkey: self.our_pubkey.clone(), pubkey: self.our_pubkey.clone(),
listen_port: self.listen_port, listen_port: self.listen_port,
})?; })?;
@ -365,6 +396,23 @@ impl Daemon {
Ok(()) 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 { struct State {
@ -374,7 +422,7 @@ struct State {
impl State { impl State {
fn send_gossip(&self, daemon: &Daemon, gossip: Gossip) -> Result<()> { fn send_gossip(&self, daemon: &Daemon, gossip: Gossip) -> Result<()> {
let packet = bincode::serialize(&gossip)?; let packet = daemon.make_packet(&gossip)?;
let now = time(); let now = time();