use crate::compiler::function::EnumVariant;
use crate::compiler::prelude::*;
use hmac::{Hmac as HmacHasher, Mac};
use sha_2::{Sha224, Sha256, Sha384, Sha512};
use sha1::Sha1;
use std::sync::LazyLock;
macro_rules! hmac {
($algorithm:ty, $key:expr_2021, $val:expr_2021) => {{
let mut mac =
<HmacHasher<$algorithm>>::new_from_slice($key.as_ref()).expect("key is bytes");
mac.update($val.as_ref());
let result = mac.finalize();
let code_bytes = result.into_bytes();
code_bytes.to_vec()
}};
}
static DEFAULT_ALGORITHM: LazyLock<Value> = LazyLock::new(|| Value::Bytes(Bytes::from("SHA-256")));
static ALGORITHM_ENUM: &[EnumVariant] = &[
EnumVariant {
value: "SHA1",
description: "SHA1 algorithm",
},
EnumVariant {
value: "SHA-224",
description: "SHA-224 algorithm",
},
EnumVariant {
value: "SHA-256",
description: "SHA-256 algorithm",
},
EnumVariant {
value: "SHA-384",
description: "SHA-384 algorithm",
},
EnumVariant {
value: "SHA-512",
description: "SHA-512 algorithm",
},
];
static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
vec![
Parameter::required(
"value",
kind::BYTES,
"The string to calculate the HMAC for.",
),
Parameter::required(
"key",
kind::BYTES,
"The string to use as the cryptographic key.",
),
Parameter::optional("algorithm", kind::BYTES, "The hashing algorithm to use.")
.default(&DEFAULT_ALGORITHM)
.enum_variants(ALGORITHM_ENUM),
]
});
fn hmac(value: Value, key: Value, algorithm: &Value) -> Resolved {
let value = value.try_bytes()?;
let key = key.try_bytes()?;
let algorithm = algorithm.try_bytes_utf8_lossy()?.as_ref().to_uppercase();
let code_bytes = match algorithm.as_str() {
"SHA1" => hmac!(Sha1, key, value),
"SHA-224" => hmac!(Sha224, key, value),
"SHA-256" => hmac!(Sha256, key, value),
"SHA-384" => hmac!(Sha384, key, value),
"SHA-512" => hmac!(Sha512, key, value),
_ => return Err(format!("Invalid algorithm: {algorithm}").into()),
};
Ok(Value::Bytes(Bytes::from(code_bytes)))
}
#[derive(Clone, Copy, Debug)]
pub struct Hmac;
impl Function for Hmac {
fn identifier(&self) -> &'static str {
"hmac"
}
fn usage(&self) -> &'static str {
indoc! {"
Calculates a [HMAC](https://en.wikipedia.org/wiki/HMAC) of the `value` using the given `key`.
The hashing `algorithm` used can be optionally specified.
For most use cases, the resulting bytestream should be encoded into a hex or base64
string using either [encode_base16](/docs/reference/vrl/functions/#encode_base16) or
[encode_base64](/docs/reference/vrl/functions/#encode_base64).
This function is infallible if either the default `algorithm` value or a recognized-valid compile-time
`algorithm` string literal is used. Otherwise, it is fallible.
"}
}
fn category(&self) -> &'static str {
Category::Cryptography.as_ref()
}
fn return_kind(&self) -> u16 {
kind::BYTES
}
fn parameters(&self) -> &'static [Parameter] {
PARAMETERS.as_slice()
}
fn examples(&self) -> &'static [Example] {
&[
example! {
title: "Calculate message HMAC (defaults: SHA-256), encoding to a base64 string",
source: r#"encode_base64(hmac("Hello there", "super-secret-key"))"#,
result: Ok("eLGE8YMviv85NPXgISRUZxstBNSU47JQdcXkUWcClmI="),
},
example! {
title: "Calculate message HMAC using SHA-224, encoding to a hex-encoded string",
source: r#"encode_base16(hmac("Hello there", "super-secret-key", algorithm: "SHA-224"))"#,
result: Ok("42fccbc2b7d22a143b92f265a8046187558a94d11ddbb30622207e90"),
},
example! {
title: "Calculate message HMAC using SHA1, encoding to a base64 string",
source: r#"encode_base64(hmac("Hello there", "super-secret-key", algorithm: "SHA1"))"#,
result: Ok("MiyBIHO8Set9+6crALiwkS0yFPE="),
},
example! {
title: "Calculate message HMAC using a variable hash algorithm",
source: indoc! {r#"
.hash_algo = "SHA-256"
hmac_bytes, err = hmac("Hello there", "super-secret-key", algorithm: .hash_algo)
if err == null {
.hmac = encode_base16(hmac_bytes)
}
"#},
result: Ok("78b184f1832f8aff3934f5e0212454671b2d04d494e3b25075c5e45167029662"),
},
]
}
fn compile(
&self,
_state: &state::TypeState,
_ctx: &mut FunctionCompileContext,
arguments: ArgumentList,
) -> Compiled {
let value = arguments.required("value");
let key = arguments.required("key");
let algorithm = arguments.optional("algorithm");
Ok(HmacFn {
value,
key,
algorithm,
}
.as_expr())
}
}
#[derive(Debug, Clone)]
struct HmacFn {
value: Box<dyn Expression>,
key: Box<dyn Expression>,
algorithm: Option<Box<dyn Expression>>,
}
impl FunctionExpression for HmacFn {
fn resolve(&self, ctx: &mut Context) -> Resolved {
let value = self.value.resolve(ctx)?;
let key = self.key.resolve(ctx)?;
let algorithm = self
.algorithm
.map_resolve_with_default(ctx, || DEFAULT_ALGORITHM.clone())?;
hmac(value, key, &algorithm)
}
fn type_def(&self, state: &state::TypeState) -> TypeDef {
let valid_algorithms = ["SHA1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"];
let mut valid_static_algo = false;
if let Some(algorithm) = self.algorithm.as_ref() {
if let Some(algorithm) = algorithm.resolve_constant(state)
&& let Ok(algorithm) = algorithm.try_bytes_utf8_lossy()
{
let algorithm = algorithm.to_uppercase();
valid_static_algo = valid_algorithms.contains(&algorithm.as_str());
}
} else {
valid_static_algo = true;
}
if valid_static_algo {
TypeDef::bytes().infallible()
} else {
TypeDef::bytes().fallible()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value;
test_function![
hmac => Hmac;
hmac {
args: func_args![key: "super-secret-key", value: "Hello there"],
want: Ok(value!(b"x\xb1\x84\xf1\x83/\x8a\xff94\xf5\xe0!$Tg\x1b-\x04\xd4\x94\xe3\xb2Pu\xc5\xe4Qg\x02\x96b")),
tdef: TypeDef::bytes().infallible(),
}
hmac_sha1 {
args: func_args![key: "super-secret-key", value: "Hello there", algorithm: "SHA1"],
want: Ok(value!(b"2,\x81 s\xbcI\xeb}\xfb\xa7+\x00\xb8\xb0\x91-2\x14\xf1")),
tdef: TypeDef::bytes().infallible(),
}
hmac_sha224 {
args: func_args![key: "super-secret-key", value: "Hello there", algorithm: "SHA-224"],
want: Ok(value!(b"B\xfc\xcb\xc2\xb7\xd2*\x14;\x92\xf2e\xa8\x04a\x87U\x8a\x94\xd1\x1d\xdb\xb3\x06\" ~\x90")),
tdef: TypeDef::bytes().infallible(),
}
hmac_sha384 {
args: func_args![key: "super-secret-key", value: "Hello there", algorithm: "SHA-384"],
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")),
tdef: TypeDef::bytes().infallible(),
}
hmac_sha512 {
args: func_args![key: "super-secret-key", value: "Hello there", algorithm: "SHA-512"],
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")),
tdef: TypeDef::bytes().infallible(),
}
];
}