1use crate::compiler::function::EnumVariant;
2use crate::compiler::prelude::*;
3use hmac::{Hmac as HmacHasher, Mac};
4use sha_2::{Sha224, Sha256, Sha384, Sha512};
5use sha1::Sha1;
6use std::sync::LazyLock;
7
8macro_rules! hmac {
9 ($algorithm:ty, $key:expr_2021, $val:expr_2021) => {{
10 let mut mac =
11 <HmacHasher<$algorithm>>::new_from_slice($key.as_ref()).expect("key is bytes");
12 mac.update($val.as_ref());
13 let result = mac.finalize();
14 let code_bytes = result.into_bytes();
15 code_bytes.to_vec()
16 }};
17}
18
19static DEFAULT_ALGORITHM: LazyLock<Value> = LazyLock::new(|| Value::Bytes(Bytes::from("SHA-256")));
20
21static ALGORITHM_ENUM: &[EnumVariant] = &[
22 EnumVariant {
23 value: "SHA1",
24 description: "SHA1 algorithm",
25 },
26 EnumVariant {
27 value: "SHA-224",
28 description: "SHA-224 algorithm",
29 },
30 EnumVariant {
31 value: "SHA-256",
32 description: "SHA-256 algorithm",
33 },
34 EnumVariant {
35 value: "SHA-384",
36 description: "SHA-384 algorithm",
37 },
38 EnumVariant {
39 value: "SHA-512",
40 description: "SHA-512 algorithm",
41 },
42];
43
44static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
45 vec![
46 Parameter::required(
47 "value",
48 kind::BYTES,
49 "The string to calculate the HMAC for.",
50 ),
51 Parameter::required(
52 "key",
53 kind::BYTES,
54 "The string to use as the cryptographic key.",
55 ),
56 Parameter::optional("algorithm", kind::BYTES, "The hashing algorithm to use.")
57 .default(&DEFAULT_ALGORITHM)
58 .enum_variants(ALGORITHM_ENUM),
59 ]
60});
61
62fn hmac(value: Value, key: Value, algorithm: &Value) -> Resolved {
63 let value = value.try_bytes()?;
64 let key = key.try_bytes()?;
65 let algorithm = algorithm.try_bytes_utf8_lossy()?.as_ref().to_uppercase();
66
67 let code_bytes = match algorithm.as_str() {
68 "SHA1" => hmac!(Sha1, key, value),
69 "SHA-224" => hmac!(Sha224, key, value),
70 "SHA-256" => hmac!(Sha256, key, value),
71 "SHA-384" => hmac!(Sha384, key, value),
72 "SHA-512" => hmac!(Sha512, key, value),
73 _ => return Err(format!("Invalid algorithm: {algorithm}").into()),
74 };
75
76 Ok(Value::Bytes(Bytes::from(code_bytes)))
77}
78
79#[derive(Clone, Copy, Debug)]
80pub struct Hmac;
81
82impl Function for Hmac {
83 fn identifier(&self) -> &'static str {
84 "hmac"
85 }
86
87 fn usage(&self) -> &'static str {
88 indoc! {"
89 Calculates a [HMAC](https://en.wikipedia.org/wiki/HMAC) of the `value` using the given `key`.
90 The hashing `algorithm` used can be optionally specified.
91
92 For most use cases, the resulting bytestream should be encoded into a hex or base64
93 string using either [encode_base16](/docs/reference/vrl/functions/#encode_base16) or
94 [encode_base64](/docs/reference/vrl/functions/#encode_base64).
95
96 This function is infallible if either the default `algorithm` value or a recognized-valid compile-time
97 `algorithm` string literal is used. Otherwise, it is fallible.
98 "}
99 }
100
101 fn category(&self) -> &'static str {
102 Category::Cryptography.as_ref()
103 }
104
105 fn return_kind(&self) -> u16 {
106 kind::BYTES
107 }
108
109 fn parameters(&self) -> &'static [Parameter] {
110 PARAMETERS.as_slice()
111 }
112
113 fn examples(&self) -> &'static [Example] {
114 &[
115 example! {
116 title: "Calculate message HMAC (defaults: SHA-256), encoding to a base64 string",
117 source: r#"encode_base64(hmac("Hello there", "super-secret-key"))"#,
118 result: Ok("eLGE8YMviv85NPXgISRUZxstBNSU47JQdcXkUWcClmI="),
119 },
120 example! {
121 title: "Calculate message HMAC using SHA-224, encoding to a hex-encoded string",
122 source: r#"encode_base16(hmac("Hello there", "super-secret-key", algorithm: "SHA-224"))"#,
123 result: Ok("42fccbc2b7d22a143b92f265a8046187558a94d11ddbb30622207e90"),
124 },
125 example! {
126 title: "Calculate message HMAC using SHA1, encoding to a base64 string",
127 source: r#"encode_base64(hmac("Hello there", "super-secret-key", algorithm: "SHA1"))"#,
128 result: Ok("MiyBIHO8Set9+6crALiwkS0yFPE="),
129 },
130 example! {
131 title: "Calculate message HMAC using a variable hash algorithm",
132 source: indoc! {r#"
133 .hash_algo = "SHA-256"
134 hmac_bytes, err = hmac("Hello there", "super-secret-key", algorithm: .hash_algo)
135 if err == null {
136 .hmac = encode_base16(hmac_bytes)
137 }
138 "#},
139 result: Ok("78b184f1832f8aff3934f5e0212454671b2d04d494e3b25075c5e45167029662"),
140 },
141 ]
142 }
143
144 fn compile(
145 &self,
146 _state: &state::TypeState,
147 _ctx: &mut FunctionCompileContext,
148 arguments: ArgumentList,
149 ) -> Compiled {
150 let value = arguments.required("value");
151 let key = arguments.required("key");
152 let algorithm = arguments.optional("algorithm");
153
154 Ok(HmacFn {
155 value,
156 key,
157 algorithm,
158 }
159 .as_expr())
160 }
161}
162
163#[derive(Debug, Clone)]
164struct HmacFn {
165 value: Box<dyn Expression>,
166 key: Box<dyn Expression>,
167 algorithm: Option<Box<dyn Expression>>,
168}
169
170impl FunctionExpression for HmacFn {
171 fn resolve(&self, ctx: &mut Context) -> Resolved {
172 let value = self.value.resolve(ctx)?;
173 let key = self.key.resolve(ctx)?;
174 let algorithm = self
175 .algorithm
176 .map_resolve_with_default(ctx, || DEFAULT_ALGORITHM.clone())?;
177
178 hmac(value, key, &algorithm)
179 }
180
181 fn type_def(&self, state: &state::TypeState) -> TypeDef {
182 let valid_algorithms = ["SHA1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"];
183
184 let mut valid_static_algo = false;
185 if let Some(algorithm) = self.algorithm.as_ref() {
186 if let Some(algorithm) = algorithm.resolve_constant(state)
187 && let Ok(algorithm) = algorithm.try_bytes_utf8_lossy()
188 {
189 let algorithm = algorithm.to_uppercase();
190 valid_static_algo = valid_algorithms.contains(&algorithm.as_str());
191 }
192 } else {
193 valid_static_algo = true;
194 }
195
196 if valid_static_algo {
197 TypeDef::bytes().infallible()
198 } else {
199 TypeDef::bytes().fallible()
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use crate::value;
208
209 test_function![
210 hmac => Hmac;
211
212 hmac {
213 args: func_args![key: "super-secret-key", value: "Hello there"],
214 want: Ok(value!(b"x\xb1\x84\xf1\x83/\x8a\xff94\xf5\xe0!$Tg\x1b-\x04\xd4\x94\xe3\xb2Pu\xc5\xe4Qg\x02\x96b")),
215 tdef: TypeDef::bytes().infallible(),
216 }
217
218 hmac_sha1 {
219 args: func_args![key: "super-secret-key", value: "Hello there", algorithm: "SHA1"],
220 want: Ok(value!(b"2,\x81 s\xbcI\xeb}\xfb\xa7+\x00\xb8\xb0\x91-2\x14\xf1")),
221 tdef: TypeDef::bytes().infallible(),
222 }
223
224 hmac_sha224 {
225 args: func_args![key: "super-secret-key", value: "Hello there", algorithm: "SHA-224"],
226 want: Ok(value!(b"B\xfc\xcb\xc2\xb7\xd2*\x14;\x92\xf2e\xa8\x04a\x87U\x8a\x94\xd1\x1d\xdb\xb3\x06\" ~\x90")),
227 tdef: TypeDef::bytes().infallible(),
228 }
229
230 hmac_sha384 {
231 args: func_args![key: "super-secret-key", value: "Hello there", algorithm: "SHA-384"],
232 want: Ok(value!(b"\xe2Q7\xc4\xd7\xde\xa2\xcc\xb9&#`\xf5s\x88M[\x81\x8f=\x0d\xb7\x92\x976?fB\x94\xf3\x88\xf0\xf9\xb5\x8c\x04\xc1\x1d\x88\x06\xb5`\xb8\x0d\xe0?\xed\x0d")),
233 tdef: TypeDef::bytes().infallible(),
234 }
235
236 hmac_sha512 {
237 args: func_args![key: "super-secret-key", value: "Hello there", algorithm: "SHA-512"],
238 want: Ok(value!(b" \xc9*\x07k\"\xf3C+\xfe\x91\x8d\xfeC\x14\xd0$<\x85\x08d:\xb1\xd7\xd7y\xa5e\x84\x81\xce/\xd4\x08!\x04@\x10\xe9x\xc16Q\x7fX\xff\xc8\xe6\xc1\xf2X0s\x88X0<\xf0\xa7\x10s\xc6\x0e\x96")),
239 tdef: TypeDef::bytes().infallible(),
240 }
241 ];
242}