vrl/stdlib/
hmac.rs

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}