Skip to main content

jpx_core/extensions/
hash.rs

1//! Cryptographic hash functions.
2
3use std::collections::HashSet;
4
5use serde_json::Value;
6
7use crate::functions::Function;
8use crate::interpreter::SearchResult;
9use crate::registry::register_if_enabled;
10use crate::{Context, Runtime, arg, defn};
11
12use crc32fast::Hasher as Crc32Hasher;
13use hmac::{Hmac, Mac};
14use md5::{Digest, Md5};
15use sha1::Sha1;
16use sha2::{Sha256, Sha512};
17
18// Type aliases for HMAC variants
19type HmacMd5 = Hmac<Md5>;
20type HmacSha1 = Hmac<Sha1>;
21type HmacSha256 = Hmac<Sha256>;
22type HmacSha512 = Hmac<Sha512>;
23
24/// Register hash functions with the runtime, filtered by the enabled set.
25pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
26    // Hash functions
27    register_if_enabled(runtime, "md5", enabled, Box::new(Md5Fn::new()));
28    register_if_enabled(runtime, "sha1", enabled, Box::new(Sha1Fn::new()));
29    register_if_enabled(runtime, "sha256", enabled, Box::new(Sha256Fn::new()));
30    register_if_enabled(runtime, "sha512", enabled, Box::new(Sha512Fn::new()));
31
32    // HMAC functions
33    register_if_enabled(runtime, "hmac_md5", enabled, Box::new(HmacMd5Fn::new()));
34    register_if_enabled(runtime, "hmac_sha1", enabled, Box::new(HmacSha1Fn::new()));
35    register_if_enabled(
36        runtime,
37        "hmac_sha256",
38        enabled,
39        Box::new(HmacSha256Fn::new()),
40    );
41    register_if_enabled(
42        runtime,
43        "hmac_sha512",
44        enabled,
45        Box::new(HmacSha512Fn::new()),
46    );
47
48    // Checksum functions
49    register_if_enabled(runtime, "crc32", enabled, Box::new(Crc32Fn::new()));
50}
51
52// =============================================================================
53// md5(string) -> string (hex-encoded MD5 hash)
54// =============================================================================
55
56defn!(Md5Fn, vec![arg!(string)], None);
57
58impl Function for Md5Fn {
59    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
60        self.signature.validate(args, ctx)?;
61
62        let input = args[0].as_str().ok_or_else(|| {
63            crate::JmespathError::from_ctx(
64                ctx,
65                crate::ErrorReason::Parse("Expected string argument".to_owned()),
66            )
67        })?;
68
69        let mut hasher = Md5::new();
70        hasher.update(input.as_bytes());
71        let result = hasher.finalize();
72        let hex_string = format!("{:x}", result);
73
74        Ok(Value::String(hex_string))
75    }
76}
77
78// =============================================================================
79// sha1(string) -> string (hex-encoded SHA-1 hash)
80// =============================================================================
81
82defn!(Sha1Fn, vec![arg!(string)], None);
83
84impl Function for Sha1Fn {
85    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
86        self.signature.validate(args, ctx)?;
87
88        let input = args[0].as_str().ok_or_else(|| {
89            crate::JmespathError::from_ctx(
90                ctx,
91                crate::ErrorReason::Parse("Expected string argument".to_owned()),
92            )
93        })?;
94
95        let mut hasher = Sha1::new();
96        hasher.update(input.as_bytes());
97        let result = hasher.finalize();
98        let hex_string = format!("{:x}", result);
99
100        Ok(Value::String(hex_string))
101    }
102}
103
104// =============================================================================
105// sha256(string) -> string (hex-encoded SHA-256 hash)
106// =============================================================================
107
108defn!(Sha256Fn, vec![arg!(string)], None);
109
110impl Function for Sha256Fn {
111    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
112        self.signature.validate(args, ctx)?;
113
114        let input = args[0].as_str().ok_or_else(|| {
115            crate::JmespathError::from_ctx(
116                ctx,
117                crate::ErrorReason::Parse("Expected string argument".to_owned()),
118            )
119        })?;
120
121        let mut hasher = Sha256::new();
122        hasher.update(input.as_bytes());
123        let result = hasher.finalize();
124        let hex_string = format!("{:x}", result);
125
126        Ok(Value::String(hex_string))
127    }
128}
129
130// =============================================================================
131// sha512(string) -> string (hex-encoded SHA-512 hash)
132// =============================================================================
133
134defn!(Sha512Fn, vec![arg!(string)], None);
135
136impl Function for Sha512Fn {
137    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
138        self.signature.validate(args, ctx)?;
139
140        let input = args[0].as_str().ok_or_else(|| {
141            crate::JmespathError::from_ctx(
142                ctx,
143                crate::ErrorReason::Parse("Expected string argument".to_owned()),
144            )
145        })?;
146
147        let mut hasher = Sha512::new();
148        hasher.update(input.as_bytes());
149        let result = hasher.finalize();
150        let hex_string = format!("{:x}", result);
151
152        Ok(Value::String(hex_string))
153    }
154}
155
156// =============================================================================
157// hmac_md5(text, key) -> string (hex-encoded HMAC-MD5)
158// =============================================================================
159
160defn!(HmacMd5Fn, vec![arg!(string), arg!(string)], None);
161
162impl Function for HmacMd5Fn {
163    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
164        self.signature.validate(args, ctx)?;
165
166        let text = args[0].as_str().ok_or_else(|| {
167            crate::JmespathError::from_ctx(
168                ctx,
169                crate::ErrorReason::Parse("Expected string for text argument".to_owned()),
170            )
171        })?;
172
173        let key = args[1].as_str().ok_or_else(|| {
174            crate::JmespathError::from_ctx(
175                ctx,
176                crate::ErrorReason::Parse("Expected string for key argument".to_owned()),
177            )
178        })?;
179
180        let mut mac =
181            HmacMd5::new_from_slice(key.as_bytes()).expect("HMAC can take key of any size");
182        mac.update(text.as_bytes());
183        let result = mac.finalize();
184        let hex_string = format!("{:x}", result.into_bytes());
185
186        Ok(Value::String(hex_string))
187    }
188}
189
190// =============================================================================
191// hmac_sha1(text, key) -> string (hex-encoded HMAC-SHA1)
192// =============================================================================
193
194defn!(HmacSha1Fn, vec![arg!(string), arg!(string)], None);
195
196impl Function for HmacSha1Fn {
197    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
198        self.signature.validate(args, ctx)?;
199
200        let text = args[0].as_str().ok_or_else(|| {
201            crate::JmespathError::from_ctx(
202                ctx,
203                crate::ErrorReason::Parse("Expected string for text argument".to_owned()),
204            )
205        })?;
206
207        let key = args[1].as_str().ok_or_else(|| {
208            crate::JmespathError::from_ctx(
209                ctx,
210                crate::ErrorReason::Parse("Expected string for key argument".to_owned()),
211            )
212        })?;
213
214        let mut mac =
215            HmacSha1::new_from_slice(key.as_bytes()).expect("HMAC can take key of any size");
216        mac.update(text.as_bytes());
217        let result = mac.finalize();
218        let hex_string = format!("{:x}", result.into_bytes());
219
220        Ok(Value::String(hex_string))
221    }
222}
223
224// =============================================================================
225// hmac_sha256(text, key) -> string (hex-encoded HMAC-SHA256)
226// =============================================================================
227
228defn!(HmacSha256Fn, vec![arg!(string), arg!(string)], None);
229
230impl Function for HmacSha256Fn {
231    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
232        self.signature.validate(args, ctx)?;
233
234        let text = args[0].as_str().ok_or_else(|| {
235            crate::JmespathError::from_ctx(
236                ctx,
237                crate::ErrorReason::Parse("Expected string for text argument".to_owned()),
238            )
239        })?;
240
241        let key = args[1].as_str().ok_or_else(|| {
242            crate::JmespathError::from_ctx(
243                ctx,
244                crate::ErrorReason::Parse("Expected string for key argument".to_owned()),
245            )
246        })?;
247
248        let mut mac =
249            HmacSha256::new_from_slice(key.as_bytes()).expect("HMAC can take key of any size");
250        mac.update(text.as_bytes());
251        let result = mac.finalize();
252        let hex_string = format!("{:x}", result.into_bytes());
253
254        Ok(Value::String(hex_string))
255    }
256}
257
258// =============================================================================
259// hmac_sha512(text, key) -> string (hex-encoded HMAC-SHA512)
260// =============================================================================
261
262defn!(HmacSha512Fn, vec![arg!(string), arg!(string)], None);
263
264impl Function for HmacSha512Fn {
265    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
266        self.signature.validate(args, ctx)?;
267
268        let text = args[0].as_str().ok_or_else(|| {
269            crate::JmespathError::from_ctx(
270                ctx,
271                crate::ErrorReason::Parse("Expected string for text argument".to_owned()),
272            )
273        })?;
274
275        let key = args[1].as_str().ok_or_else(|| {
276            crate::JmespathError::from_ctx(
277                ctx,
278                crate::ErrorReason::Parse("Expected string for key argument".to_owned()),
279            )
280        })?;
281
282        let mut mac =
283            HmacSha512::new_from_slice(key.as_bytes()).expect("HMAC can take key of any size");
284        mac.update(text.as_bytes());
285        let result = mac.finalize();
286        let hex_string = format!("{:x}", result.into_bytes());
287
288        Ok(Value::String(hex_string))
289    }
290}
291
292// =============================================================================
293// crc32(string) -> number (CRC32 checksum as integer)
294// =============================================================================
295
296defn!(Crc32Fn, vec![arg!(string)], None);
297
298impl Function for Crc32Fn {
299    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
300        self.signature.validate(args, ctx)?;
301
302        let input = args[0].as_str().ok_or_else(|| {
303            crate::JmespathError::from_ctx(
304                ctx,
305                crate::ErrorReason::Parse("Expected string argument".to_owned()),
306            )
307        })?;
308
309        let mut hasher = Crc32Hasher::new();
310        hasher.update(input.as_bytes());
311        let checksum = hasher.finalize();
312
313        Ok(Value::Number(serde_json::Number::from(checksum)))
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use crate::Runtime;
320    use serde_json::json;
321
322    fn setup_runtime() -> Runtime {
323        Runtime::builder()
324            .with_standard()
325            .with_all_extensions()
326            .build()
327    }
328
329    // =========================================================================
330    // Hash function tests
331    // =========================================================================
332
333    #[test]
334    fn test_md5() {
335        let runtime = setup_runtime();
336        let expr = runtime.compile("md5(@)").unwrap();
337        let data = json!("hello");
338        let result = expr.search(&data).unwrap();
339        assert_eq!(result, json!("5d41402abc4b2a76b9719d911017c592"));
340    }
341
342    #[test]
343    fn test_md5_empty() {
344        let runtime = setup_runtime();
345        let expr = runtime.compile("md5(@)").unwrap();
346        let data = json!("");
347        let result = expr.search(&data).unwrap();
348        assert_eq!(result, json!("d41d8cd98f00b204e9800998ecf8427e"));
349    }
350
351    #[test]
352    fn test_sha1() {
353        let runtime = setup_runtime();
354        let expr = runtime.compile("sha1(@)").unwrap();
355        let data = json!("hello");
356        let result = expr.search(&data).unwrap();
357        assert_eq!(result, json!("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"));
358    }
359
360    #[test]
361    fn test_sha256() {
362        let runtime = setup_runtime();
363        let expr = runtime.compile("sha256(@)").unwrap();
364        let data = json!("hello");
365        let result = expr.search(&data).unwrap();
366        assert_eq!(
367            result,
368            json!("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
369        );
370    }
371
372    #[test]
373    fn test_sha512() {
374        let runtime = setup_runtime();
375        let expr = runtime.compile("sha512(@)").unwrap();
376        let data = json!("hello");
377        let result = expr.search(&data).unwrap();
378        assert_eq!(
379            result,
380            json!(
381                "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
382            )
383        );
384    }
385
386    #[test]
387    fn test_sha512_empty() {
388        let runtime = setup_runtime();
389        let expr = runtime.compile("sha512(@)").unwrap();
390        let data = json!("");
391        let result = expr.search(&data).unwrap();
392        assert_eq!(
393            result,
394            json!(
395                "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
396            )
397        );
398    }
399
400    // =========================================================================
401    // HMAC function tests
402    // =========================================================================
403
404    #[test]
405    fn test_hmac_md5() {
406        let runtime = setup_runtime();
407        let expr = runtime.compile("hmac_md5(@, `\"secret\"`)").unwrap();
408        let data = json!("hello");
409        let result = expr.search(&data).unwrap();
410        assert_eq!(result, json!("bade63863c61ed0b3165806ecd6acefc"));
411    }
412
413    #[test]
414    fn test_hmac_sha1() {
415        let runtime = setup_runtime();
416        let expr = runtime.compile("hmac_sha1(@, `\"secret\"`)").unwrap();
417        let data = json!("hello");
418        let result = expr.search(&data).unwrap();
419        assert_eq!(result, json!("5112055c05f944f85755efc5cd8970e194e9f45b"));
420    }
421
422    #[test]
423    fn test_hmac_sha256() {
424        let runtime = setup_runtime();
425        let expr = runtime.compile("hmac_sha256(@, `\"secret\"`)").unwrap();
426        let data = json!("hello");
427        let result = expr.search(&data).unwrap();
428        assert_eq!(
429            result,
430            json!("88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b")
431        );
432    }
433
434    #[test]
435    fn test_hmac_sha512() {
436        let runtime = setup_runtime();
437        let expr = runtime.compile("hmac_sha512(@, `\"secret\"`)").unwrap();
438        let data = json!("hello");
439        let result = expr.search(&data).unwrap();
440        assert_eq!(
441            result,
442            json!(
443                "db1595ae88a62fd151ec1cba81b98c39df82daae7b4cb9820f446d5bf02f1dcfca6683d88cab3e273f5963ab8ec469a746b5b19086371239f67d1e5f99a79440"
444            )
445        );
446    }
447
448    #[test]
449    fn test_hmac_sha256_empty_message() {
450        let runtime = setup_runtime();
451        let expr = runtime.compile("hmac_sha256(@, `\"key\"`)").unwrap();
452        let data = json!("");
453        let result = expr.search(&data).unwrap();
454        assert_eq!(
455            result,
456            json!("5d5d139563c95b5967b9bd9a8c9b233a9dedb45072794cd232dc1b74832607d0")
457        );
458    }
459
460    #[test]
461    fn test_hmac_sha256_empty_key() {
462        let runtime = setup_runtime();
463        let expr = runtime.compile("hmac_sha256(@, `\"\"`)").unwrap();
464        let data = json!("hello");
465        let result = expr.search(&data).unwrap();
466        // HMAC with empty key is valid
467        assert!(!result.as_str().unwrap().is_empty());
468    }
469
470    // =========================================================================
471    // CRC32 tests
472    // =========================================================================
473
474    #[test]
475    fn test_crc32() {
476        let runtime = setup_runtime();
477        let expr = runtime.compile("crc32(@)").unwrap();
478        let data = json!("hello");
479        let result = expr.search(&data).unwrap();
480        assert_eq!(result, json!(907060870));
481    }
482
483    #[test]
484    fn test_crc32_empty() {
485        let runtime = setup_runtime();
486        let expr = runtime.compile("crc32(@)").unwrap();
487        let data = json!("");
488        let result = expr.search(&data).unwrap();
489        assert_eq!(result, json!(0));
490    }
491}