From 21d91856a3d982e15c351ae322a2cbc392d84767 Mon Sep 17 00:00:00 2001 From: Erica Marigold Date: Wed, 11 Oct 2023 21:33:15 -0700 Subject: [PATCH] feat: initial crypto library rust-side implementation --- Cargo.lock | 60 ++++++++- Cargo.toml | 5 + src/lune/builtins/serde/crypto.rs | 211 ++++++++++++++++++++++++++++++ src/lune/builtins/serde/mod.rs | 1 + 4 files changed, 270 insertions(+), 7 deletions(-) create mode 100644 src/lune/builtins/serde/crypto.rs diff --git a/Cargo.lock b/Cargo.lock index ecb259c..b34516d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -840,6 +840,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" @@ -1113,6 +1119,7 @@ dependencies = [ "anyhow", "async-compression", "async-trait", + "base64 0.21.4", "chrono", "chrono_lc", "clap", @@ -1123,11 +1130,13 @@ dependencies = [ "env_logger", "futures-util", "glam", + "hex", "hyper", "hyper-tungstenite", "include_dir", "itertools", "lz4_flex", + "md-5", "mlua", "num-traits", "once_cell", @@ -1143,6 +1152,7 @@ dependencies = [ "rbx_xml", "regex", "reqwest", + "ring 0.17.3", "rustyline", "serde", "serde_json", @@ -1194,6 +1204,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" @@ -1780,12 +1800,26 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e" +dependencies = [ + "cc", + "getrandom 0.2.10", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + [[package]] name = "rmp" version = "0.8.12" @@ -1861,7 +1895,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", - "ring", + "ring 0.16.20", "rustls-webpki", "sct", ] @@ -1881,8 +1915,8 @@ version = "0.101.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -1941,8 +1975,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -2127,6 +2161,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "standback" version = "0.2.17" @@ -2639,6 +2679,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" diff --git a/Cargo.toml b/Cargo.toml index 2587191..a571e58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,6 +98,11 @@ serde_json = { version = "1.0", features = ["preserve_order"] } serde_yaml = "0.9" toml = { version = "0.8", features = ["preserve_order"] } +ring = "0.17.3" +base64 = "0.21.4" +hex = "0.4.3" +md-5 = "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 0000000..7a95937 --- /dev/null +++ b/src/lune/builtins/serde/crypto.rs @@ -0,0 +1,211 @@ +use anyhow::Result; +use base64::{engine::general_purpose as Base64, Engine as _}; +use ring::digest::{self, digest, Digest}; + +#[derive(Debug, Clone, Copy)] +pub struct Crypto; +#[derive(Debug, Clone)] +pub struct CryptoResult +where + T: AsRef<[u8]>, + C: AsRef<[u8]>, +{ + algo: CryptoAlgo, + content: Option, + computed: Option, +} + +#[derive(Clone, Debug)] +pub enum CryptoAlgo { + Sha256, + Sha512, + // We shouldn't be able to Pass Hmac(Hmac), would there be a way to limit this? + Hmac(Box), + Md5, +} + +impl Crypto { + pub fn sha256(content: Option) -> CryptoResult + where + T: ToString, + { + let content = content.map(|data| data.to_string()); + + CryptoResult { + algo: CryptoAlgo::Sha256, + content, + computed: None, + } + } + + pub fn sha512(content: Option) -> CryptoResult + where + T: ToString, + { + let content = content.map(|data| data.to_string()); + + CryptoResult { + algo: CryptoAlgo::Sha512, + content, + computed: None, + } + } + + pub fn hmac(content: Option, algo: CryptoAlgo) -> CryptoResult + where + T: ToString, + { + let content = content.map(|data| data.to_string()); + + CryptoResult { + algo: CryptoAlgo::Hmac(Box::new(algo)), + content, + computed: None, + } + } +} + +trait FromCryptoAlgo { + fn from_crypto_algo(value: CryptoAlgo) -> &'static Self; +} + +trait FromCryptoAlgoOwned { + fn from_crypto_algo(value: CryptoAlgo) -> Self; +} + +impl FromCryptoAlgo for ring::digest::Algorithm { + fn from_crypto_algo(value: CryptoAlgo) -> &'static Self { + match &value { + CryptoAlgo::Sha256 => &digest::SHA256, + CryptoAlgo::Sha512 => &digest::SHA512, + _ => panic!(), + } + } +} + +impl FromCryptoAlgoOwned for ring::hmac::Algorithm { + fn from_crypto_algo(value: CryptoAlgo) -> Self { + let val: ring::hmac::Algorithm = match value { + CryptoAlgo::Hmac(algo) => match *algo { + CryptoAlgo::Sha256 => ring::hmac::HMAC_SHA256, + CryptoAlgo::Sha512 => ring::hmac::HMAC_SHA512, + CryptoAlgo::Hmac(_) => panic!("Hmac(Hmac) is not allowed!"), + CryptoAlgo::Md5 => ring::hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, + }, + _ => panic!("invalid type"), + }; + + val + } +} + +pub enum EncodingKind { + Utf8, + Base64, + Hex, +} + +// Note that compute and digest declared here are identical to those of the below implementation +// Quite a bit of boilerplate, is there any way to avoid this without using derive macros? +impl CryptoResult { + pub fn update(&mut self, content: String) -> Self { + self.content = Some(content); + + (*self).to_owned() + } + + pub fn compute(&mut self) -> Self { + let content = match &self.content { + Some(inner) => inner.to_owned(), + None => "".to_string(), + }; + + match self.algo { + CryptoAlgo::Hmac(_) => { + let rng = ring::rand::SystemRandom::new(); + let key = ring::hmac::Key::generate( + ring::hmac::Algorithm::from_crypto_algo(self.algo.clone()), + &rng, + ) + .expect("failed to generate random key"); + + self.computed = Some(ring::hmac::sign(&key, content.as_bytes())); + } + _ => panic!("Invalid implementation"), + }; + + (*self).to_owned() + } + + pub fn digest(&self, encoding: EncodingKind) -> Result { + let computed = self.computed.ok_or(anyhow::Error::msg( + "compute the hash first before trying to obtain a digest", + ))?; + + match encoding { + EncodingKind::Utf8 => { + String::from_utf8(computed.as_ref().to_vec()).map_err(anyhow::Error::from) + } + EncodingKind::Base64 => Ok(Base64::STANDARD.encode(computed)), + EncodingKind::Hex => Ok(hex::encode(computed.as_ref())), + } + } +} + +impl CryptoResult { + pub fn update(&mut self, content: String) -> Self { + self.content = Some(content); + + (*self).to_owned() + } + + pub fn compute(&mut self) -> Self { + let content = match &self.content { + Some(inner) => inner.to_owned(), + None => "".to_string(), + }; + + match self.algo { + CryptoAlgo::Sha256 | CryptoAlgo::Sha512 => { + self.computed = Some(digest( + ring::digest::Algorithm::from_crypto_algo(self.algo.clone()), + content.as_bytes(), + )) + } + CryptoAlgo::Hmac(_) => unreachable!(), + CryptoAlgo::Md5 => todo!(), + }; + + (*self).to_owned() + } + + pub fn digest(&self, encoding: EncodingKind) -> Result { + let computed = self.computed.ok_or(anyhow::Error::msg( + "compute the hash first before trying to obtain a digest", + ))?; + + match encoding { + EncodingKind::Utf8 => { + String::from_utf8(computed.as_ref().to_vec()).map_err(anyhow::Error::from) + } + EncodingKind::Base64 => Ok(Base64::STANDARD.encode(computed)), + EncodingKind::Hex => Ok(hex::encode(computed.as_ref())), + } + } +} + +pub fn test() { + println!( + "{}", + Crypto::sha256::<&str>(None /* or Some("some string!") */) + .update("some string!".to_string()) + .compute() + .digest(EncodingKind::Hex) + .unwrap() + ); + + Crypto::hmac(Some("test"), CryptoAlgo::Sha256) + .compute() + .digest(EncodingKind::Base64) + .unwrap(); +} diff --git a/src/lune/builtins/serde/mod.rs b/src/lune/builtins/serde/mod.rs index 4e76bce..985ea1d 100644 --- a/src/lune/builtins/serde/mod.rs +++ b/src/lune/builtins/serde/mod.rs @@ -1,6 +1,7 @@ use mlua::prelude::*; pub(super) mod compress_decompress; +pub(super) mod crypto; pub(super) mod encode_decode; use compress_decompress::{compress, decompress, CompressDecompressFormat};