Skip to content

Commit

Permalink
Implement hashing and hmac again
Browse files Browse the repository at this point in the history
  • Loading branch information
Dekkonot committed Jun 2, 2024
1 parent cf513c6 commit 81f9080
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 0 deletions.
48 changes: 48 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions crates/lune-std-serde/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
216 changes: 216 additions & 0 deletions crates/lune-std-serde/src/hash.rs
Original file line number Diff line number Diff line change
@@ -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<BString>,
seed: Option<BString>,
}

#[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<u8> {
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<Vec<u8>> {
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<Self> {
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<Self> {
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,
})
}
}
13 changes: 13 additions & 0 deletions crates/lune-std-serde/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -24,6 +26,8 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
.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()
}

Expand Down Expand Up @@ -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<LuaString> {
lua.create_string(options.hash())
}

fn hmac_message(lua: &Lua, options: HashOptions) -> LuaResult<LuaString> {
let bytes = options.hmac()?;
lua.create_string(bytes)
}

0 comments on commit 81f9080

Please sign in to comment.