diff --git a/Cargo.lock b/Cargo.lock index 24b48f5..96c7fc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[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.4" @@ -28,6 +38,20 @@ dependencies = [ "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 = "ahash" version = "0.8.11" @@ -882,6 +906,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "bstr" version = "1.11.3" @@ -1180,9 +1213,19 @@ 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 = "ctrlc" version = "3.4.5" @@ -1260,6 +1303,7 @@ dependencies = [ "block-buffer", "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -1336,6 +1380,7 @@ dependencies = [ "hyper-util", "log", "mlua", + "ncr", "tokio", ] @@ -1539,6 +1584,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1655,6 +1710,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "1.2.0" @@ -1943,6 +2007,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ + "block-padding", "generic-array", ] @@ -2146,6 +2211,24 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "ncr" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e905418cb5217c1e6947e8d90e0cfd7468352dbe4e6d34a0a6e7ed51e8edfcf2" +dependencies = [ + "aes", + "aes-gcm", + "base64 0.21.7", + "cfb8", + "cipher", + "hmac", + "pbkdf2", + "phf", + "rand", + "sha1", +] + [[package]] name = "nix" version = "0.29.0" @@ -2306,6 +2389,12 @@ version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "overload" version = "0.1.1" @@ -2341,6 +2430,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2366,6 +2464,24 @@ dependencies = [ "indexmap 2.7.1", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.9" @@ -2425,6 +2541,18 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2867,6 +2995,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2955,6 +3094,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -3432,6 +3577,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[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 = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 9ed8633..123e7ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ hyper = { version = "1", features = ["server"] } hyper-util = "0" log = { version = "0" } mlua = { version = "0", features = ["async", "luajit", "send"] } +ncr = { version = "0", features = ["cfb8", "ecb", "gcm"] } tokio = { version = "1", features = ["macros"] } [features] diff --git a/README.md b/README.md index 50a8ac9..fd019b3 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ A Minecraft bot with Lua scripting support, written in Rust with [azalea](https: - Listening to in-game events - Pathfinding (from azalea) - Entity and chest interaction +- NoChatReports encryption ## Usage diff --git a/src/commands.rs b/src/commands.rs index ddc8c48..f745e80 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -4,6 +4,8 @@ use crate::{ }; use azalea::{brigadier::prelude::*, chat::ChatPacket, prelude::*}; use futures::lock::Mutex; +use mlua::{Function, Table}; +use ncr::utils::prepend_header; pub type Ctx = CommandContext>; @@ -11,16 +13,24 @@ pub struct CommandSource { pub client: Client, pub message: ChatPacket, pub state: State, + pub ncr_options: Option, } impl CommandSource { pub fn reply(&self, message: &str) { - for chunk in message + for mut chunk in message .chars() .collect::>() - .chunks(236) + .chunks(if self.ncr_options.is_some() { 150 } else { 236 }) .map(|chars| chars.iter().collect::()) { + if let (Some(options), Ok(encrypt)) = ( + &self.ncr_options, + self.state.lua.globals().get::("ncr_encrypt"), + ) && let Ok(encrypted) = encrypt.call::((options, prepend_header(&chunk))) + { + chunk = encrypted + } self.client.chat( &(if self.message.is_whisper() && let Some(username) = self.message.username() diff --git a/src/events.rs b/src/events.rs index aefb21e..30800b3 100644 --- a/src/events.rs +++ b/src/events.rs @@ -9,7 +9,8 @@ use azalea::{prelude::*, protocol::packets::game::ClientboundGamePacket}; use hyper::{server::conn::http1, service::service_fn}; use hyper_util::rt::TokioIo; use log::{debug, error, info, trace}; -use mlua::IntoLuaMulti; +use mlua::{Function, IntoLuaMulti, Table}; +use ncr::utils::trim_header; use std::process::exit; use tokio::net::TcpListener; @@ -20,32 +21,44 @@ pub async fn handle_event(client: Client, event: Event, state: State) -> anyhow: call_listeners(&state, "add_player", Player::from(player_info)).await; } Event::Chat(message) => { + let globals = state.lua.globals(); + let (sender, mut content) = message.split_sender_and_content(); let formatted_message = message.message(); info!("{}", formatted_message.to_ansi()); - if message.is_whisper() - && let (Some(sender), content) = message.split_sender_and_content() - && state - .lua - .globals() - .get::>("Owners")? - .contains(&sender) - { - if let Err(error) = state.commands.execute( - content, - CommandSource { - client: client.clone(), - message: message.clone(), - state: state.clone(), + if let Some(sender) = sender { + let mut ncr_options = None; + if let (Ok(options), Ok(decrypt)) = ( + globals.get::
("NcrOptions"), + globals.get::("ncr_decrypt"), + ) && let Ok(decrypted) = + decrypt.call::((options.clone(), content.clone())) + && let Ok(trimmed) = trim_header(&decrypted) + { + ncr_options = Some(options); + content = trimmed.to_owned(); + info!("Decrypted message from {sender}: {content}"); + } + + if message.is_whisper() && globals.get::>("Owners")?.contains(&sender) { + if let Err(error) = state.commands.execute( + content, + CommandSource { + client: client.clone(), + message: message.clone(), + state: state.clone(), + ncr_options: ncr_options.clone(), + } + .into(), + ) { + CommandSource { + client, + message, + state: state.clone(), + ncr_options, + } + .reply(&format!("{error:?}")); } - .into(), - ) { - CommandSource { - client, - message, - state: state.clone(), - } - .reply(&format!("{error:?}")); } } diff --git a/src/lua/mod.rs b/src/lua/mod.rs index 8355834..16e3221 100644 --- a/src/lua/mod.rs +++ b/src/lua/mod.rs @@ -4,6 +4,7 @@ pub mod container; pub mod direction; pub mod events; pub mod logging; +pub mod nochatreports; pub mod player; pub mod system; pub mod vec3; @@ -39,6 +40,7 @@ pub fn register_functions( block::register_functions(lua, globals)?; events::register_functions(lua, globals, event_listeners)?; logging::register_functions(lua, globals)?; + nochatreports::register_functions(lua, globals)?; system::register_functions(lua, globals) } diff --git a/src/lua/nochatreports/crypt.rs b/src/lua/nochatreports/crypt.rs new file mode 100644 index 0000000..2a6096b --- /dev/null +++ b/src/lua/nochatreports/crypt.rs @@ -0,0 +1,41 @@ +macro_rules! crypt_with { + ($op:ident, $encoding:expr, $key:expr, $text:expr, $algo:ident) => { + match $encoding { + 1 => $algo::::$op($text, $key), + 2 => $algo::::$op($text, $key), + _ => $algo::::$op($text, $key), + } + .map_err(|error| Error::external(error.to_string()))? + }; +} + +#[macro_export] +macro_rules! crypt { + ($op:ident, $encoding:expr, $options:expr, $text:expr) => { + match $options.get("encryption").unwrap_or_default() { + 1 => CaesarEncryption::$op(&$text, &$options.get("key")?) + .map_err(|error| Error::external(error.to_string()))?, + 2 => crypt_with!( + $op, + $encoding, + &$options.get::>("key")?.inner, + &$text, + EcbEncryption + ), + 3 => crypt_with!( + $op, + $encoding, + &$options.get::>("key")?.inner, + &$text, + GcmEncryption + ), + _ => crypt_with!( + $op, + $encoding, + &$options.get::>("key")?.inner, + &$text, + Cfb8Encryption + ), + } + }; +} diff --git a/src/lua/nochatreports/key.rs b/src/lua/nochatreports/key.rs new file mode 100644 index 0000000..adbc7fe --- /dev/null +++ b/src/lua/nochatreports/key.rs @@ -0,0 +1,12 @@ +use mlua::UserData; + +pub struct AesKey { + pub inner: ncr::AesKey, +} + +impl UserData for AesKey { + fn add_fields>(f: &mut F) { + f.add_field_method_get("base64", |_, this| Ok(this.inner.encode_base64())); + f.add_field_method_get("bytes", |_, this| Ok(this.inner.as_ref().to_vec())); + } +} diff --git a/src/lua/nochatreports/mod.rs b/src/lua/nochatreports/mod.rs new file mode 100644 index 0000000..e95049a --- /dev/null +++ b/src/lua/nochatreports/mod.rs @@ -0,0 +1,81 @@ +#[macro_use] +pub mod crypt; +pub mod key; + +use key::AesKey; +use mlua::{Error, Lua, Result, Table, UserDataRef}; +use ncr::{ + encoding::{Base64Encoding, Base64rEncoding, NewBase64rEncoding}, + encryption::{CaesarEncryption, Cfb8Encryption, EcbEncryption, Encryption, GcmEncryption}, + utils::{prepend_header, trim_header}, +}; + +pub fn register_functions(lua: &Lua, globals: &Table) -> Result<()> { + globals.set( + "ncr_aes_key_from_passphrase", + lua.create_function(|_, passphrase: Vec| { + Ok(AesKey { + inner: ncr::AesKey::gen_from_passphrase(&passphrase), + }) + })?, + )?; + + globals.set( + "ncr_aes_key_from_base64", + lua.create_function(|_, base64: String| { + Ok(AesKey { + inner: ncr::AesKey::decode_base64(&base64) + .map_err(|error| Error::external(error.to_string()))?, + }) + })?, + )?; + + globals.set( + "ncr_generate_random_aes_key", + lua.create_function(|_, (): ()| { + Ok(AesKey { + inner: ncr::AesKey::gen_random_key(), + }) + })?, + )?; + + globals.set( + "ncr_encrypt", + lua.create_function(|_, (options, plaintext): (Table, String)| { + Ok(crypt!( + encrypt, + options.get("encoding").unwrap_or_default(), + options, + plaintext + )) + })?, + )?; + + globals.set( + "ncr_decrypt", + lua.create_function(|_, (options, ciphertext): (Table, String)| { + Ok(crypt!( + decrypt, + options.get("encoding").unwrap_or_default(), + options, + ciphertext + )) + })?, + )?; + + globals.set( + "ncr_prepend_header", + lua.create_function(|_, text: String| Ok(prepend_header(&text)))?, + )?; + + globals.set( + "ncr_trim_header", + lua.create_function(|_, text: String| { + Ok(trim_header(&text) + .map_err(|error| Error::external(error.to_string()))? + .to_owned()) + })?, + )?; + + Ok(()) +}