MINOR: sample: optional AAD parameter support to aes_gcm_enc/dec

The aes_gcm_enc() and aes_gcm_dec() sample converters now accept an
optional fifth argument for Additional Authenticated Data (AAD). When
provided, the AAD value is base64-decoded and used during AES-GCM
encryption or decryption. Both string and variable forms are supported.

This enables use cases that require authentication of additional data.
This commit is contained in:
William Lallemand 2025-10-30 19:02:13 +01:00
parent 73b5d331cc
commit 1d859bdaa2
3 changed files with 134 additions and 20 deletions

View File

@ -20260,8 +20260,8 @@ The following keywords are supported:
51d.single(prop[,prop*]) string string
add(value) integer integer
add_item(delim[,var[,suff]]) string string
aes_gcm_dec(bits,nonce,key,aead_tag) binary binary
aes_gcm_enc(bits,nonce,key,aead_tag) binary binary
aes_gcm_dec(bits,nonce,key,aead_tag[,aad]) binary binary
aes_gcm_enc(bits,nonce,key,aead_tag[,aad]) binary binary
and(value) integer integer
b64dec string binary
base2 binary string
@ -20456,25 +20456,27 @@ add_item(<delim>[,<var>[,<suff>]])
http-request set-var(req.tagged) 'var(req.tagged),add_item(",",req.score1),add_item(",",req.score2)'
http-request set-var(req.tagged) 'var(req.tagged),add_item(",",,(site1))' if src,in_table(site1)
aes_gcm_dec(<bits>,<nonce>,<key>,<aead_tag>)
Decrypts the raw byte input using the AES128-GCM, AES192-GCM or
AES256-GCM algorithm, depending on the <bits> parameter. All other parameters
need to be base64 encoded and the returned result is in raw byte format.
If the <aead_tag> validation fails, the converter doesn't return any data.
The <nonce>, <key> and <aead_tag> can either be strings or variables. This
converter requires at least OpenSSL 1.0.1.
aes_gcm_dec(<bits>,<nonce>,<key>,<aead_tag>[,<aad>])
Decrypts the raw byte input using the AES128-GCM, AES192-GCM or AES256-GCM
algorithm, depending on the <bits> parameter. All other parameters need to be
base64 encoded and the returned result is in raw byte format. If the
<aead_tag> or <aad> validation fails, the converter doesn't return any data.
The <aad> parameter is optional. The <nonce>, <key>, <aead_tag> and <aad> can
either be strings or variables. This converter requires at least OpenSSL
1.0.1.
Example:
http-response set-header X-Decrypted-Text %[var(txn.enc),\
aes_gcm_dec(128,txn.nonce,Zm9vb2Zvb29mb29wZm9vbw==,txn.aead_tag)]
aes_gcm_enc(<bits>,<nonce>,<key>,<aead_tag>)
aes_gcm_enc(<bits>,<nonce>,<key>,<aead_tag>[,<aad>])
Encrypts the raw byte input using the AES128-GCM, AES192-GCM or
AES256-GCM algorithm, depending on the <bits> parameter. <nonce> and <key>
parameters must be base64 encoded. Last parameter, <aead_tag>, must be a
variable. The AEAD tag will be stored base64 encoded into that variable.
The returned result is in raw byte format. The <nonce> and <key> can either
be strings or variables. This converter requires at least OpenSSL 1.0.1.
AES256-GCM algorithm, depending on the <bits> parameter. <nonce>, <key> and
<aad> parameters must be base64 encoded. Parameter <aead_tag> must be a
variable. The AEAD tag will be stored base64 encoded into that variable. The
<aad> parameter is optional. The returned result is in raw byte format. The
<nonce>, <key> and <aad> can either be strings or variables. This converter
requires at least OpenSSL 1.0.1.
Example:
http-response set-header X-Encrypted-Text %[var(txn.plain),\

View File

@ -0,0 +1,78 @@
varnishtest "aes_gcm converter Test"
feature ignore_unknown_macro
server s1 {
rxreq
txresp -hdr "Connection: close"
} -repeat 2 -start
haproxy h1 -conf {
global
.if feature(THREAD)
thread-groups 1
.endif
# WT: limit false-positives causing "HTTP header incomplete" due to
# idle server connections being randomly used and randomly expiring
# under us.
tune.idle-pool.shared off
defaults
mode http
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
frontend fe
bind "fd@${fe}"
http-request set-var(txn.plain) str("Hello from HAProxy AES-GCM")
http-request set-var(txn.nonce) str("MTIzNDU2Nzg5MDEy")
http-request set-var(txn.key) str("Zm9vb2Zvb29mb29vb29vbw==")
# AES-GCM enc wth vars + dec with strings
http-request set-var(txn.encrypted1) var(txn.plain),aes_gcm_enc(128,txn.nonce,txn.key,txn.aead_tag1),base64
http-after-response set-header X-Encrypted1 %[var(txn.encrypted1)]
http-after-response set-header X-AEAD-Tag1 %[var(txn.aead_tag1)]
http-request set-var(txn.decrypted1) var(txn.encrypted1),b64dec,aes_gcm_dec(128,"MTIzNDU2Nzg5MDEy","Zm9vb2Zvb29mb29vb29vbw==",txn.aead_tag1)
http-after-response set-header X-Decrypted1 %[var(txn.decrypted1)]
# AES-GCM enc wth strings + dec with vars
http-request set-var(txn.encrypted2) var(txn.plain),aes_gcm_enc(128,"MTIzNDU2Nzg5MDEy","Zm9vb2Zvb29mb29vb29vbw==",txn.aead_tag2),base64
http-after-response set-header X-Encrypted2 %[var(txn.encrypted2)]
http-after-response set-header X-AEAD-Tag2 %[var(txn.aead_tag2)]
http-request set-var(txn.decrypted2) var(txn.encrypted2),b64dec,aes_gcm_dec(128,txn.nonce,txn.key,txn.aead_tag2)
http-after-response set-header X-Decrypted2 %[var(txn.decrypted2)]
# AES-GCM + AAD enc wth vars + dec with strings
http-request set-var(txn.aad) str("dGVzdAo=")
http-request set-var(txn.encrypted3) var(txn.plain),aes_gcm_enc(128,txn.nonce,txn.key,txn.aead_tag3,txn.aad),base64
http-after-response set-header X-Encrypted3 %[var(txn.encrypted3)]
http-after-response set-header X-AEAD-Tag3 %[var(txn.aead_tag3)]
http-request set-var(txn.decrypted3) var(txn.encrypted3),b64dec,aes_gcm_dec(128,"MTIzNDU2Nzg5MDEy","Zm9vb2Zvb29mb29vb29vbw==",txn.aead_tag3,"dGVzdAo=")
http-after-response set-header X-Decrypted3 %[var(txn.decrypted3)]
# AES-GCM + AAD enc wth strings + enc with strings
http-request set-var(txn.encrypted4) var(txn.plain),aes_gcm_enc(128,"MTIzNDU2Nzg5MDEy","Zm9vb2Zvb29mb29vb29vbw==",txn.aead_tag4,"dGVzdAo="),base64
http-after-response set-header X-Encrypted4 %[var(txn.encrypted4)]
http-after-response set-header X-AEAD-Tag4 %[var(txn.aead_tag4)]
http-request set-var(txn.decrypted4) var(txn.encrypted4),b64dec,aes_gcm_dec(128,txn.nonce,txn.key,txn.aead_tag3,txn.aad)
http-after-response set-header X-Decrypted4 %[var(txn.decrypted4)]
default_backend be
backend be
server s1 ${s1_addr}:${s1_port}
} -start
client c1 -connect ${h1_fe_sock} {
txreq
rxresp
expect resp.http.x-decrypted1 == "Hello from HAProxy AES-GCM"
expect resp.http.x-decrypted2 == "Hello from HAProxy AES-GCM"
expect resp.http.x-decrypted3 == "Hello from HAProxy AES-GCM"
expect resp.http.x-decrypted4 == "Hello from HAProxy AES-GCM"
} -run

View File

@ -250,7 +250,12 @@ static int check_aes_gcm(struct arg *args, struct sample_conv *conv,
memprintf(err, "failed to parse aead_tag : %s", *err);
return 0;
}
if (args[4].type) {
if (!sample_check_arg_base64(&args[4], err)) {
memprintf(err, "failed to parse aad : %s", *err);
return 0;
}
}
return 1;
}
@ -281,8 +286,8 @@ static int check_aes_gcm(struct arg *args, struct sample_conv *conv,
/* Arguments: AES size in bits, nonce, key, tag. The last three arguments are base64 encoded */
static int sample_conv_aes_gcm(const struct arg *arg_p, struct sample *smp, void *private)
{
struct sample nonce, key, aead_tag;
struct buffer *smp_trash = NULL, *smp_trash_alloc = NULL;
struct sample nonce, key, aead_tag, aad;
struct buffer *smp_trash = NULL, *smp_trash_alloc = NULL, *aad_trash = NULL;
EVP_CIPHER_CTX *ctx = NULL;
int size, ret, dec;
@ -355,6 +360,33 @@ static int sample_conv_aes_gcm(const struct arg *arg_p, struct sample *smp, void
if (!sample_conv_aes_gcm_init(dec, ctx, NULL, NULL, (unsigned char *) key.data.u.str.area, NULL))
goto err;
/* if there's an AAD parameter */
if (arg_p[4].type) {
smp_set_owner(&aad, smp->px, smp->sess, smp->strm, smp->opt);
if (!sample_conv_var2smp_str(&arg_p[4], &aad))
goto err;
/* if stored in a variable, the base64 decode was not done in check_aes_gcm() */
if (arg_p[4].type == ARGT_VAR) {
int aad_len;
aad_trash = alloc_trash_chunk();
if (!aad_trash)
return 0;
aad_len = base64dec(aad.data.u.str.area, aad.data.u.str.data, aad_trash->area, aad_trash->size);
if (aad_len < 0)
goto err;
aad_trash->data = aad_len;
aad.data.u.str = *aad_trash;
}
if (!sample_conv_aes_gcm_update(dec, ctx, NULL, (int *)&smp_trash->data,
(unsigned char *)aad.data.u.str.area, (int)aad.data.u.str.data))
goto err;
}
if (!sample_conv_aes_gcm_update(dec, ctx, (unsigned char *) smp_trash->area, (int *) &smp_trash->data,
(unsigned char *) smp_trash_alloc->area, (int) smp_trash_alloc->data))
goto err;
@ -409,12 +441,14 @@ static int sample_conv_aes_gcm(const struct arg *arg_p, struct sample *smp, void
smp_dup(smp);
free_trash_chunk(smp_trash_alloc);
free_trash_chunk(smp_trash);
free_trash_chunk(aad_trash);
EVP_CIPHER_CTX_free(ctx);
return 1;
err:
free_trash_chunk(smp_trash_alloc);
free_trash_chunk(smp_trash);
free_trash_chunk(aad_trash);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
@ -2655,8 +2689,8 @@ INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
static struct sample_conv_kw_list sample_conv_kws = {ILH, {
{ "sha2", sample_conv_sha2, ARG1(0, SINT), smp_check_sha2, SMP_T_BIN, SMP_T_BIN },
#ifdef EVP_CIPH_GCM_MODE
{ "aes_gcm_enc", sample_conv_aes_gcm, ARG4(4,SINT,STR,STR,STR), check_aes_gcm, SMP_T_BIN, SMP_T_BIN },
{ "aes_gcm_dec", sample_conv_aes_gcm, ARG4(4,SINT,STR,STR,STR), check_aes_gcm, SMP_T_BIN, SMP_T_BIN },
{ "aes_gcm_enc", sample_conv_aes_gcm, ARG5(4,SINT,STR,STR,STR,STR), check_aes_gcm, SMP_T_BIN, SMP_T_BIN },
{ "aes_gcm_dec", sample_conv_aes_gcm, ARG5(4,SINT,STR,STR,STR,STR), check_aes_gcm, SMP_T_BIN, SMP_T_BIN },
#endif
{ "x509_v_err_str", sample_conv_x509_v_err, 0, NULL, SMP_T_SINT, SMP_T_STR },
{ "digest", sample_conv_crypto_digest, ARG1(1,STR), check_crypto_digest, SMP_T_BIN, SMP_T_BIN },