diff --git a/Cargo.lock b/Cargo.lock index baa355dc..ae2ac0ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -873,6 +873,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1277,6 +1278,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.9" @@ -1689,6 +1699,7 @@ dependencies = [ "anyhow", "bitflags", "fastrand", + "hmac", "javy-test-macros", "quickcheck", "rmp-serde", @@ -1698,6 +1709,7 @@ dependencies = [ "serde", "serde-transcode", "serde_json", + "sha2", "simd-json", ] @@ -2967,6 +2979,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swc_atoms" version = "0.6.7" diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 8836b9e2..2fdc6133 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -31,6 +31,7 @@ bitflags! { const JAVY_STREAM_IO = 1 << 2; const REDIRECT_STDOUT_TO_STDERR = 1 << 3; const TEXT_ENCODING = 1 << 4; + const CRYPTO = 1 << 5; } } @@ -44,5 +45,6 @@ mod tests { assert!(Config::JAVY_STREAM_IO == Config::from_bits(1 << 2).unwrap()); assert!(Config::REDIRECT_STDOUT_TO_STDERR == Config::from_bits(1 << 3).unwrap()); assert!(Config::TEXT_ENCODING == Config::from_bits(1 << 4).unwrap()); + assert!(Config::CRYPTO == Config::from_bits(1 << 5).unwrap()); } } diff --git a/crates/core/src/runtime.rs b/crates/core/src/runtime.rs index b973bd7b..416e39d0 100644 --- a/crates/core/src/runtime.rs +++ b/crates/core/src/runtime.rs @@ -12,7 +12,8 @@ pub(crate) fn new(shared_config: SharedConfig) -> Result { // we're disabling this temporarily. It will be enabled once we have a // fix forward. .override_json_parse_and_stringify(false) - .javy_json(false); + .javy_json(false) + .crypto(shared_config.contains(SharedConfig::CRYPTO)); Runtime::new(std::mem::take(config)) } diff --git a/crates/javy/Cargo.toml b/crates/javy/Cargo.toml index a8257dcb..253648a0 100644 --- a/crates/javy/Cargo.toml +++ b/crates/javy/Cargo.toml @@ -24,6 +24,8 @@ quickcheck = "1" bitflags = { workspace = true } fastrand = "2.1.0" simd-json = { version = "0.13.10", optional = true, default-features = false, features = ["big-int-as-float", "serde_impl"] } +sha2 = "0.10.8" +hmac = "0.12.1" [dev-dependencies] javy-test-macros = { path = "../test-macros/" } diff --git a/crates/javy/src/apis/crypto/mod.rs b/crates/javy/src/apis/crypto/mod.rs new file mode 100644 index 00000000..2fac230c --- /dev/null +++ b/crates/javy/src/apis/crypto/mod.rs @@ -0,0 +1,88 @@ +use crate::quickjs::{context::Intrinsic, qjs, Ctx, Function, Object, String as JSString, Value}; +use crate::{hold, hold_and_release, to_js_error, val_to_string, Args}; +use anyhow::{bail, Error, Result}; + +use hmac::{Hmac, Mac}; +use sha2::Sha256; + +/// An implemetation of crypto APIs to optimize fuel. +/// Currently, hmacSHA256 is the only function implemented. +pub struct Crypto; + +impl Intrinsic for Crypto { + unsafe fn add_intrinsic(ctx: std::ptr::NonNull) { + register(Ctx::from_raw(ctx)).expect("`Crypto` APIs to succeed") + } +} +fn register(this: Ctx<'_>) -> Result<()> { + let globals = this.globals(); + + // let crypto_obj = Object::new(cx)?; + let crypto_obj = Object::new(this.clone())?; + + crypto_obj.set( + "hmacSHA256", + Function::new(this.clone(), |this, args| { + let (this, args) = hold_and_release!(this, args); + hmac_sha256(hold!(this.clone(), args)).map_err(|e| to_js_error(this, e)) + }), + )?; + + globals.set("crypto", crypto_obj)?; + + Ok::<_, Error>(()) +} +/// hmac_sha256 applies the HMAC algorithm using sha256 for hashing. +/// Arg[0] - secret +/// Arg[1] - message +/// returns - hex encoded string of hmac. +fn hmac_sha256(args: Args<'_>) -> Result> { + let (cx, args) = args.release(); + + if args.len() != 2 { + bail!("Wrong number of arguments. Expected 2. Got {}", args.len()); + } + + let js_string_secret = val_to_string(&cx, args[0].clone())?; + let js_string_message = val_to_string(&cx, args[1].clone())?; + + /// Create alias for HMAC-SHA256 + type HmacSha256 = Hmac; + + let mut mac = HmacSha256::new_from_slice(js_string_secret.as_bytes()) + .expect("HMAC can take key of any size"); + mac.update(js_string_message.as_bytes()); + + let result = mac.finalize(); + let code_bytes = result.into_bytes(); + let code: String = format!("{code_bytes:x}"); + let js_string = JSString::from_str(cx, &code); + Ok(Value::from_string(js_string?)) +} + +#[cfg(test)] +mod tests { + use crate::{quickjs::Value, Config, Runtime}; + use anyhow::{Error, Result}; + + #[test] + fn test_text_encoder_decoder() -> Result<()> { + let mut config = Config::default(); + config.crypto(true); + let runtime = Runtime::new(config)?; + + runtime.context().with(|this| { + let result: Value<'_> = this.eval( + r#" + let expectedHex = "97d2a569059bbcd8ead4444ff99071f4c01d005bcefe0d3567e1be628e5fdcd9"; + let result = crypto.hmacSHA256("my secret and secure key", "input message"); + expectedHex === result; + "#, + )?; + + assert!(result.as_bool().unwrap()); + Ok::<_, Error>(()) + })?; + Ok(()) + } +} diff --git a/crates/javy/src/apis/mod.rs b/crates/javy/src/apis/mod.rs index 67979d7e..d440d6c5 100644 --- a/crates/javy/src/apis/mod.rs +++ b/crates/javy/src/apis/mod.rs @@ -57,6 +57,7 @@ //! //! Disabled by default. pub(crate) mod console; +pub(crate) mod crypto; #[cfg(feature = "json")] pub(crate) mod json; pub(crate) mod random; @@ -64,6 +65,7 @@ pub(crate) mod stream_io; pub(crate) mod text_encoding; pub(crate) use console::*; +pub(crate) use crypto::*; #[cfg(feature = "json")] pub(crate) use json::*; pub(crate) use random::*; diff --git a/crates/javy/src/config.rs b/crates/javy/src/config.rs index 6f542642..c678f53c 100644 --- a/crates/javy/src/config.rs +++ b/crates/javy/src/config.rs @@ -19,6 +19,7 @@ bitflags! { const OPERATORS = 1 << 12; const BIGNUM_EXTENSION = 1 << 13; const TEXT_ENCODING = 1 << 14; + const CRYPTO = 1 << 15; } } @@ -179,6 +180,14 @@ impl Config { self } + /// Whether the `crypto` intrinsic will be available. + /// Enabled by default. + // #[cfg(feature = "crypto")] + pub fn crypto(&mut self, enable: bool) -> &mut Self { + self.intrinsics.set(JSIntrinsics::CRYPTO, enable); + self + } + /// Enables whether the output of console.log will be redirected to /// `stderr`. pub fn redirect_stdout_to_stderr(&mut self, enable: bool) -> &mut Self { diff --git a/crates/javy/src/runtime.rs b/crates/javy/src/runtime.rs index 22d2f9ab..306a715c 100644 --- a/crates/javy/src/runtime.rs +++ b/crates/javy/src/runtime.rs @@ -1,7 +1,7 @@ // use crate::quickjs::JSContextRef; use super::from_js_error; use crate::{ - apis::{Console, NonStandardConsole, Random, StreamIO, TextEncoding}, + apis::{Console, Crypto, NonStandardConsole, Random, StreamIO, TextEncoding}, config::{JSIntrinsics, JavyIntrinsics}, Config, }; @@ -144,6 +144,11 @@ impl Runtime { JavyJson::add_intrinsic(ctx.as_raw()) } } + + if intrinsics.contains(JSIntrinsics::CRYPTO) { + // #[cfg(feature = "crypto")] + unsafe { Crypto::add_intrinsic(ctx.as_raw()) } + } }); Ok(ManuallyDrop::new(context))