From 81f908089327b3a55a0a1eea2ee5ffa16a515169 Mon Sep 17 00:00:00 2001 From: Micah Date: Sun, 2 Jun 2024 15:21:12 -0700 Subject: [PATCH] Implement hashing and hmac again --- Cargo.lock | 48 +++++++ crates/lune-std-serde/Cargo.toml | 10 ++ crates/lune-std-serde/src/hash.rs | 216 ++++++++++++++++++++++++++++++ crates/lune-std-serde/src/lib.rs | 13 ++ 4 files changed, 287 insertions(+) create mode 100644 crates/lune-std-serde/src/hash.rs diff --git a/Cargo.lock b/Cargo.lock index e21c206..13ae8cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -273,6 +273,7 @@ dependencies = [ "cc", "cfg-if", "constant_time_eq 0.3.0", + "digest", ] [[package]] @@ -1339,6 +1340,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1582,13 +1592,20 @@ name = "lune-std-serde" version = "0.1.0" dependencies = [ "async-compression", + "blake3", "bstr", + "digest", + "hmac", "lune-utils", "lz4", + "md-5", "mlua", "serde", "serde_json", "serde_yaml", + "sha1 0.10.6", + "sha2", + "sha3", "tokio", "toml", ] @@ -1666,6 +1683,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.7.2" @@ -2665,6 +2692,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" diff --git a/crates/lune-std-serde/Cargo.toml b/crates/lune-std-serde/Cargo.toml index 91786ff..ab7bec0 100644 --- a/crates/lune-std-serde/Cargo.toml +++ b/crates/lune-std-serde/Cargo.toml @@ -29,6 +29,16 @@ serde_json = { version = "1.0", features = ["preserve_order"] } serde_yaml = "0.9" toml = { version = "0.8", features = ["preserve_order"] } +digest = "0.10.7" +hmac = "0.12.1" +md-5 = "0.10.6" +sha1 = "0.10.6" +sha2 = "0.10.8" +sha3 = "0.10.8" +# This feature MIGHT break due to the unstable nature of the digest crate. +# Check before updating it. +blake3 = { version = "1.5.0", features = ["traits-preview"] } + tokio = { version = "1", default-features = false, features = [ "rt", "io-util", diff --git a/crates/lune-std-serde/src/hash.rs b/crates/lune-std-serde/src/hash.rs new file mode 100644 index 0000000..30febf3 --- /dev/null +++ b/crates/lune-std-serde/src/hash.rs @@ -0,0 +1,216 @@ +use bstr::BString; +use md5::Md5; +use mlua::prelude::*; + +use blake3::Hasher as Blake3; +use sha1::Sha1; +use sha2::{Sha224, Sha256, Sha384, Sha512}; +use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; + +pub struct HashOptions { + algorithm: HashAlgorithm, + message: BString, + secret: Option, + seed: Option, +} + +#[derive(Debug, Clone, Copy)] +enum HashAlgorithm { + Md5, + Sha1, + // SHA-2 variants + Sha2_224, + Sha2_256, + Sha2_384, + Sha2_512, + // SHA-3 variants + Sha3_224, + Sha3_256, + Sha3_384, + Sha3_512, + // Blake3 + Blake3, +} + +impl HashAlgorithm { + pub fn list_all_as_string() -> String { + [ + "md5", "sha1", "sha224", "sha256", "sha384", "sha512", "sha3-224", "sha3-256", + "sha3-384", "sha3-512", "blake3", + ] + .join(", ") + } +} + +impl HashOptions { + /** + Computes the hash for the `message` using whatever `algorithm` is + contained within this struct. + */ + #[inline] + #[must_use = "hashing a message is useless without using the resulting hash"] + pub fn hash(self) -> Vec { + use digest::Digest; + + let message = self.message; + match self.algorithm { + HashAlgorithm::Md5 => Md5::digest(message).to_vec(), + HashAlgorithm::Sha1 => Sha1::digest(message).to_vec(), + HashAlgorithm::Sha2_224 => Sha224::digest(message).to_vec(), + HashAlgorithm::Sha2_256 => Sha256::digest(message).to_vec(), + HashAlgorithm::Sha2_384 => Sha384::digest(message).to_vec(), + HashAlgorithm::Sha2_512 => Sha512::digest(message).to_vec(), + + HashAlgorithm::Sha3_224 => Sha3_224::digest(message).to_vec(), + HashAlgorithm::Sha3_256 => Sha3_256::digest(message).to_vec(), + HashAlgorithm::Sha3_384 => Sha3_384::digest(message).to_vec(), + HashAlgorithm::Sha3_512 => Sha3_512::digest(message).to_vec(), + + HashAlgorithm::Blake3 => Blake3::digest(message).to_vec(), + } + } + + /** + Computes the HMAC for the `message` using whatever `algorithm` and + `secret` are contained within this struct. + + # Errors + + If the `secret` is not provided or is otherwise invalid. + */ + #[inline] + pub fn hmac(self) -> LuaResult> { + use hmac::{Hmac, Mac, SimpleHmac}; + + let secret = self + .secret + .ok_or_else(|| LuaError::FromLuaConversionError { + from: "nil", + to: "string or buffer", + message: Some("Argument #3 missing or nil".to_string()), + })?; + + /* + These macros exist to remove what would ultimately be dozens of + repeating lines. Essentially, there's several step to processing + HMacs, which expands into the 3 lines you see below. However, + the Hmac struct is specialized towards eager block-based processes. + In order to support anything else, like blake3, there's a second + type named `SimpleHmac`. This results in duplicate macros like + there are below. + */ + macro_rules! hmac { + ($Type:ty) => {{ + let mut mac: Hmac<$Type> = Hmac::new_from_slice(&secret).into_lua_err()?; + mac.update(&self.message); + Ok(mac.finalize().into_bytes().to_vec()) + }}; + } + macro_rules! hmac_no_blocks { + ($Type:ty) => {{ + let mut mac: SimpleHmac<$Type> = + SimpleHmac::new_from_slice(&secret).into_lua_err()?; + mac.update(&self.message); + Ok(mac.finalize().into_bytes().to_vec()) + }}; + } + + match self.algorithm { + HashAlgorithm::Md5 => hmac!(Md5), + HashAlgorithm::Sha1 => hmac!(Sha1), + + HashAlgorithm::Sha2_224 => hmac!(Sha224), + HashAlgorithm::Sha2_256 => hmac!(Sha256), + HashAlgorithm::Sha2_384 => hmac!(Sha384), + HashAlgorithm::Sha2_512 => hmac!(Sha512), + + HashAlgorithm::Sha3_224 => hmac!(Sha3_224), + HashAlgorithm::Sha3_256 => hmac!(Sha3_256), + HashAlgorithm::Sha3_384 => hmac!(Sha3_384), + HashAlgorithm::Sha3_512 => hmac!(Sha3_512), + + HashAlgorithm::Blake3 => hmac_no_blocks!(Blake3), + } + } +} + +impl<'lua> FromLua<'lua> for HashAlgorithm { + fn from_lua(value: LuaValue<'lua>, _lua: &'lua Lua) -> LuaResult { + if let LuaValue::String(str) = value { + /* + Casing tends to vary for algorithms, so rather than force + people to remember it we'll just accept any casing. + */ + let str = str.to_str()?.to_ascii_lowercase(); + match str.as_str() { + "md5" => Ok(Self::Md5), + "sha1" => Ok(Self::Sha1), + + "sha224" => Ok(Self::Sha2_224), + "sha256" => Ok(Self::Sha2_256), + "sha384" => Ok(Self::Sha2_384), + "sha512" => Ok(Self::Sha2_512), + + "sha3-224" => Ok(Self::Sha3_224), + "sha3-256" => Ok(Self::Sha3_256), + "sha3-384" => Ok(Self::Sha3_384), + "sha3-512" => Ok(Self::Sha3_512), + + "blake3" => Ok(Self::Blake3), + + _ => Err(LuaError::FromLuaConversionError { + from: "string", + to: "HashAlgorithm", + message: Some(format!( + "Invalid hashing algorithm '{str}', valid kinds are:\n{}", + HashAlgorithm::list_all_as_string() + )), + }), + } + } else { + Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "HashAlgorithm", + message: None, + }) + } + } +} + +impl<'lua> FromLuaMulti<'lua> for HashOptions { + fn from_lua_multi(mut values: LuaMultiValue<'lua>, lua: &'lua Lua) -> LuaResult { + let algorithm = values + .pop_front() + .map(|value| HashAlgorithm::from_lua(value, lua)) + .transpose()? + .ok_or_else(|| LuaError::FromLuaConversionError { + from: "nil", + to: "HashAlgorithm", + message: Some("Argument #1 missing or nil".to_string()), + })?; + let message = values + .pop_front() + .map(|value| BString::from_lua(value, lua)) + .transpose()? + .ok_or_else(|| LuaError::FromLuaConversionError { + from: "nil", + to: "string or buffer", + message: Some("Argument #2 missing or nil".to_string()), + })?; + let secret = values + .pop_front() + .map(|value| BString::from_lua(value, lua)) + .transpose()?; + let seed = values + .pop_front() + .map(|value| BString::from_lua(value, lua)) + .transpose()?; + + Ok(HashOptions { + algorithm, + message, + secret, + seed, + }) + } +} diff --git a/crates/lune-std-serde/src/lib.rs b/crates/lune-std-serde/src/lib.rs index 4514a75..4147dec 100644 --- a/crates/lune-std-serde/src/lib.rs +++ b/crates/lune-std-serde/src/lib.rs @@ -7,9 +7,11 @@ use lune_utils::TableBuilder; mod compress_decompress; mod encode_decode; +mod hash; pub use self::compress_decompress::{compress, decompress, CompressDecompressFormat}; pub use self::encode_decode::{decode, encode, EncodeDecodeConfig, EncodeDecodeFormat}; +pub use self::hash::HashOptions; /** Creates the `serde` standard library module. @@ -24,6 +26,8 @@ pub fn module(lua: &Lua) -> LuaResult { .with_function("decode", serde_decode)? .with_async_function("compress", serde_compress)? .with_async_function("decompress", serde_decompress)? + .with_function("hash", hash_message)? + .with_function("hmac", hmac_message)? .build_readonly() } @@ -55,3 +59,12 @@ async fn serde_decompress( let bytes = decompress(bs, format).await?; lua.create_string(bytes) } + +fn hash_message(lua: &Lua, options: HashOptions) -> LuaResult { + lua.create_string(options.hash()) +} + +fn hmac_message(lua: &Lua, options: HashOptions) -> LuaResult { + let bytes = options.hmac()?; + lua.create_string(bytes) +}