diff --git a/Cargo.lock b/Cargo.lock index ecb259ca..cbfb7019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "blake2b_simd" version = "0.5.11" @@ -520,6 +529,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -840,6 +850,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "home" version = "0.5.5" @@ -1054,6 +1070,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1113,25 +1138,31 @@ dependencies = [ "anyhow", "async-compression", "async-trait", + "base64 0.21.4", + "blake2", "chrono", "chrono_lc", "clap", "console", "dialoguer", + "digest", "directories", "dunce", "env_logger", "futures-util", "glam", + "hex", "hyper", "hyper-tungstenite", "include_dir", "itertools", "lz4_flex", + "md-5", "mlua", "num-traits", "once_cell", "os_str_bytes", + "paste", "path-clean", "pin-project", "rand", @@ -1147,6 +1178,9 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "sha1 0.10.6", + "sha2", + "sha3", "thiserror", "tokio", "tokio-tungstenite", @@ -1194,6 +1228,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.6.4" @@ -2062,6 +2106,27 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2203,6 +2268,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[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" diff --git a/Cargo.toml b/Cargo.toml index 25871911..b151119f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,6 +98,19 @@ serde_json = { version = "1.0", features = ["preserve_order"] } serde_yaml = "0.9" toml = { version = "0.8", features = ["preserve_order"] } +paste = "1.0.14" + +base64 = "0.21.4" +hex = "0.4.3" +digest = "0.10.7" + +md-5 = "0.10.6" +sha1 = "0.10.6" +sha2 = "0.10.8" +sha3 = "0.10.8" +blake2 = "0.10.6" + + ### NET hyper = { version = "0.14", features = ["full"] } diff --git a/src/lune/builtins/serde/crypto.rs b/src/lune/builtins/serde/crypto.rs new file mode 100644 index 00000000..da76ea36 --- /dev/null +++ b/src/lune/builtins/serde/crypto.rs @@ -0,0 +1,153 @@ +use std::sync::Arc; +use std::sync::Mutex; + +use anyhow::Result; +use base64::{engine::general_purpose as Base64, Engine as _}; +use digest::Digest as _; +use mlua::prelude::*; + +// TODO: Proper error handling, remove unwraps + +macro_rules! impl_hash_algo { + ($($algo:ident: $Type:ty),*) => { + #[derive(Clone)] + pub enum CryptoAlgo { + $( + $algo(Box<$Type>), + )* + } + + impl CryptoAlgo { + pub fn update(&mut self, data: impl AsRef<[u8]>) { + match self { + $( + Self::$algo(hasher) => hasher.update(data), + )* + } + } + + pub fn digest(&mut self, encoding: EncodingKind) -> Result { + let computed = match self { + $( + Self::$algo(hasher) => hasher.clone().finalize_reset().to_vec(), + )* + }; + + match encoding { + EncodingKind::Utf8 => String::from_utf8(computed).map_err(anyhow::Error::from), + EncodingKind::Base64 => Ok(Base64::STANDARD.encode(computed)), + EncodingKind::Hex => Ok(hex::encode(&computed)), + } + } + } + + impl Crypto { + $( + paste::item! { + pub fn [<$algo:snake:lower>](content: Option) -> Self { + let constructed = Self { + algo: Arc::new(Mutex::new(CryptoAlgo::$algo(Box::new($Type::new())))), + }; + + match content { + Some(inner) => constructed.update(inner.to_string()).clone(), + None => constructed, + } + } + } + )* + } + } +} + +// Macro call creates the CryptoAlgo enum and implementations for it +// It also adds a method corresponding to the enum in the `Crypto` struct +impl_hash_algo! { + Sha1: sha1::Sha1, + Sha256: sha2::Sha256, + Sha512: sha2::Sha512, + Md5: md5::Md5, + Blake2s256: blake2::Blake2s256, + Blake2b512: blake2::Blake2b512, + Sha3_256: sha3::Sha3_256, + Sha3_512: sha3::Sha3_512 +} + +#[derive(Clone)] +pub struct Crypto { + algo: Arc>, +} + +#[derive(PartialOrd, PartialEq, Ord, Eq)] +pub enum EncodingKind { + Utf8, + Base64, + Hex, +} + +impl From for EncodingKind { + fn from(value: usize) -> Self { + match value { + 0 => Self::Utf8, + 1 => Self::Base64, + 2 => Self::Hex, + _ => panic!("invalid value"), + } + } +} + +impl From for EncodingKind { + fn from(value: String) -> Self { + match value.to_lowercase().as_str() { + "utf8" => Self::Utf8, + "base64" => Self::Base64, + "hex" => Self::Hex, + &_ => panic!("invalid value"), + } + } +} + +impl FromLua<'_> for EncodingKind { + fn from_lua(value: LuaValue, _: &Lua) -> LuaResult { + match value { + LuaValue::Integer(int) => Ok(EncodingKind::from(int as usize)), + LuaValue::Number(num) => Ok(EncodingKind::from(num as usize)), + LuaValue::String(str) => Ok(EncodingKind::from(str.to_string_lossy().to_string())), + + _ => Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "EncodingKind", + message: Some("value must be a an Integer, Number or String".to_string()), + }), + } + } +} + +trait CryptoResult { + fn update(&self, content: impl AsRef<[u8]>) -> &Self; + fn digest(&self, encoding: EncodingKind) -> Result; +} + +impl CryptoResult for Crypto { + fn update(&self, content: impl AsRef<[u8]>) -> &Crypto { + (self.algo.lock().unwrap()).update(content); + + self + } + + fn digest(&self, encoding: EncodingKind) -> Result { + (*self.algo.lock().unwrap()).digest(encoding) + } +} + +impl LuaUserData for Crypto { + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method("digest", |_, this, encoding| { + this.digest(encoding).map_err(mlua::Error::runtime) + }); + + methods.add_method("update", |_, this, content: String| { + Ok(this.update(content).clone()) + }); + } +} diff --git a/src/lune/builtins/serde/mod.rs b/src/lune/builtins/serde/mod.rs index 4e76bcea..11afdfc5 100644 --- a/src/lune/builtins/serde/mod.rs +++ b/src/lune/builtins/serde/mod.rs @@ -1,9 +1,11 @@ use mlua::prelude::*; pub(super) mod compress_decompress; +pub(super) mod crypto; pub(super) mod encode_decode; use compress_decompress::{compress, decompress, CompressDecompressFormat}; +use crypto::Crypto; use encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat}; use crate::lune::util::TableBuilder; @@ -14,6 +16,38 @@ pub fn create(lua: &'static Lua) -> LuaResult { .with_function("decode", serde_decode)? .with_async_function("compress", serde_compress)? .with_async_function("decompress", serde_decompress)? + .with_value( + "crypto", + TableBuilder::new(lua)? + .with_function("sha1", |_, content: Option| { + Ok(Crypto::sha1(content)) + })? + .with_function("sha256", |_, content: Option| { + Ok(Crypto::sha256(content)) + })? + .with_function("sha512", |_, content: Option| { + Ok(Crypto::sha512(content)) + })? + .with_function("md5", |_, content: Option| Ok(Crypto::md5(content)))? + .with_function("blake2s256", |_, content: Option| { + Ok(Crypto::blake2s256(content)) + })? + .with_function("blake2b512", |_, content: Option| { + Ok(Crypto::blake2b512(content)) + })? + .with_function("sha3", |_, (variant, content): (String, Option)| { + match variant.to_string().as_str() { + "256" => Ok(Crypto::sha3_256(content)), + "512" => Ok(Crypto::sha3_512(content)), + + &_ => Err(LuaError::runtime(format!( + "Expected sha3 variant to be 256-bit or 512-bit, got {}", + variant + ))), + } + })? + .build()?, + )? .build_readonly() }