diff --git a/Cargo.lock b/Cargo.lock index 78b033f..cff54fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,41 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "attohttpc" version = "0.16.3" @@ -24,6 +59,7 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" name = "bevnet" version = "0.2.0" dependencies = [ + "aes-gcm", "base64", "igd", "local-ip-address", @@ -51,6 +87,45 @@ 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", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +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 = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "either" version = "1.9.0" @@ -72,6 +147,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.12" @@ -83,6 +168,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "http" version = "0.2.11" @@ -117,6 +212,15 @@ dependencies = [ "xmltree", ] +[[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.10" @@ -172,12 +276,30 @@ dependencies = [ "syn 1.0.109", ] +[[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.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -252,6 +374,12 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -309,6 +437,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -330,6 +464,16 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "url" version = "2.5.0" @@ -341,6 +485,12 @@ dependencies = [ "percent-encoding", ] +[[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" diff --git a/crates/bevnet/Cargo.toml b/crates/bevnet/Cargo.toml index 865a4b0..0399afe 100644 --- a/crates/bevnet/Cargo.toml +++ b/crates/bevnet/Cargo.toml @@ -13,5 +13,6 @@ workspace = true [dependencies] local-ip-address = "0.5.7" +aes-gcm = "0.10.3" base64 = "0.21.7" igd = "0.12.1" diff --git a/crates/bevnet/src/lib.rs b/crates/bevnet/src/lib.rs index 52ee2df..b169f49 100644 --- a/crates/bevnet/src/lib.rs +++ b/crates/bevnet/src/lib.rs @@ -4,6 +4,8 @@ use std::collections::LinkedList; use std::io::{self, Read, Write}; use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpListener, TcpStream}; +use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng}; +use aes_gcm::{Aes128Gcm, Key, Nonce}; use base64::prelude::*; use igd::{Gateway, PortMappingProtocol}; use local_ip_address::local_ip; @@ -18,8 +20,9 @@ use local_ip_address::local_ip; /// use bevnet::{Connection, Listener}; /// /// # fn main() -> io::Result<()> { -/// let listener = Listener::bind("127.0.0.1:23732")?; -/// let mut connection = Connection::connect("127.0.0.1:23732")?; +/// let secret_key = Connection::generate_key(); +/// let listener = Listener::bind("127.0.0.1:23732", &secret_key)?; +/// let mut connection = Connection::connect("127.0.0.1:23732", &secret_key)?; /// /// // The accept operation is not blocking. So we need to loop here. /// let mut server_connection; @@ -60,6 +63,9 @@ pub struct Connection { /// `None` if the message length is not yet received. receive_message_len: Option, + /// The nonce used for encryption. + receive_message_nonce: Option>, + /// The length of the received byte block. /// /// Used by [Connection::receive_partial] to determine if the block is @@ -68,18 +74,23 @@ pub struct Connection { /// The buffer used to receive a byte block. receive_buffer: Vec, + + /// The secret key used for encryption. + secret_key: Aes128Gcm, } impl Connection { /// Creates a new [Connection] from a [TcpStream]. - fn new(stream: TcpStream) -> io::Result { + fn new(stream: TcpStream, secret_key: &Key) -> io::Result { stream.set_nonblocking(true)?; Ok(Self { stream, send_buffers: LinkedList::new(), receive_message_len: None, + receive_message_nonce: None, receive_filled_len: 0, receive_buffer: Vec::new(), + secret_key: Aes128Gcm::new(secret_key), }) } @@ -105,7 +116,10 @@ impl Connection { ) })?), ); - Self::new(TcpStream::connect(address)?) + Self::new( + TcpStream::connect(address)?, + Key::::from_slice(&data[6..]), + ) } /// Sends a message over the connection. @@ -115,6 +129,15 @@ impl Connection { /// /// This function is not blocking. pub fn send(&mut self, message: &[u8]) -> io::Result { + // Encrypt the message. + let nonce = Aes128Gcm::generate_nonce(OsRng); + let message = self.secret_key.encrypt(&nonce, message).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed to encrypt message: {}", e), + ) + })?; + // Get the length of the message as a u16. let message_len: u16 = match message.len().try_into() { Ok(len) => len, @@ -127,10 +150,10 @@ impl Connection { }; // Add a new buffer to the send queue. - let mut buffer = Vec::with_capacity(message_len as usize + 2); - buffer.extend(message_len.to_ne_bytes()); - buffer.extend(message); - self.send_buffers.push_back((0, buffer)); + self.send_buffers + .push_back((0, message_len.to_ne_bytes().to_vec())); + self.send_buffers.push_back((0, nonce.to_vec())); + self.send_buffers.push_back((0, message)); // Update the connection. self.update() @@ -193,6 +216,12 @@ impl Connection { let receive_buffer = &mut self.receive_buffer[start_index..start_index + len]; let received_len = self.stream.read(receive_buffer); self.receive_filled_len += match received_len { + Ok(0) => { + return Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + "connection closed by remote peer", + )); + } Ok(n) => n, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(false), Err(ref e) if e.kind() == io::ErrorKind::Interrupted => return Ok(false), @@ -212,7 +241,7 @@ impl Connection { /// If no message is available, returns `None`. /// /// This function is not blocking. - pub fn receive(&mut self) -> io::Result> { + pub fn receive(&mut self) -> io::Result>> { // Receiving the message length. let message_len = match self.receive_message_len { Some(message_len) => message_len, @@ -234,13 +263,45 @@ impl Connection { } }; + if self.receive_message_nonce.is_none() { + // If the nonce is not received yet, return `None`. + if !self.receive_partial(12)? { + return Ok(None); + } + + // Setting the nonce. + self.receive_message_nonce = Some(self.receive_buffer[..12].to_vec()); + } + // Receiving the message. if !self.receive_partial(message_len)? { return Ok(None); } + let message = &self.receive_buffer[..message_len as usize]; + + // Getting the nonce. + let nonce = self + .receive_message_nonce + .as_ref() + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing nonce"))?; + + // Decrypting the message. + let message = self + .secret_key + .decrypt(Nonce::from_slice(nonce), message) + .map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed to decrypt message: {}", e), + ) + })?; + + // Resetting the message length and nonce. + self.receive_message_len = None; + self.receive_message_nonce = None; // Returning the message. - Ok(Some(&self.receive_buffer[..message_len as usize])) + Ok(Some(message)) } } @@ -252,8 +313,9 @@ impl Connection { /// use bevnet::{Connection, Listener}; /// /// # fn main() -> io::Result<()> { -/// let listener = Listener::new()?; -/// let mut connection = Connection::connect("127.0.0.1:23732")?; +/// let secret_key = Connection::generate_key(); +/// let listener = Listener::new(&secret_key)?; +/// let mut connection = Connection::connect(listener.connection_string())?; /// /// // The accept operation is not blocking. So we need to loop here. /// let mut server_connection; @@ -266,7 +328,7 @@ impl Connection { /// # Ok(()) /// # } /// ``` -pub struct Listener(TcpListener, Gateway, SocketAddrV4); +pub struct Listener(TcpListener, Gateway, SocketAddrV4, Key); impl Listener { /// Creates a new listener. @@ -291,6 +353,7 @@ impl Listener { listener, gateway, SocketAddrV4::new(external_address, opened_port), + Aes128Gcm::generate_key(OsRng), )) } @@ -299,7 +362,7 @@ impl Listener { /// This function is not blocking. pub fn accept(&self) -> io::Result> { match self.0.accept() { - Ok((stream, _)) => Connection::new(stream).map(Some), + Ok((stream, _)) => Connection::new(stream, &self.3).map(Some), Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None), Err(ref e) if e.kind() == io::ErrorKind::Interrupted => Ok(None), Err(e) => Err(e),