From: William Lallemand Date: Thu, 30 Oct 2025 18:02:13 +0000 (+0100) Subject: MINOR: sample: optional AAD parameter support to aes_gcm_enc/dec X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1d859bdaa2ac42193d2cb5f1198d42fe8bae4ba2;p=thirdparty%2Fhaproxy.git 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. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 32c495098..ef2b59450 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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([,[,]]) 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(,,,) - Decrypts the raw byte input using the AES128-GCM, AES192-GCM or - AES256-GCM algorithm, depending on the parameter. All other parameters - need to be base64 encoded and the returned result is in raw byte format. - If the validation fails, the converter doesn't return any data. - The , and can either be strings or variables. This - converter requires at least OpenSSL 1.0.1. +aes_gcm_dec(,,,[,]) + Decrypts the raw byte input using the AES128-GCM, AES192-GCM or AES256-GCM + algorithm, depending on the parameter. All other parameters need to be + base64 encoded and the returned result is in raw byte format. If the + or validation fails, the converter doesn't return any data. + The parameter is optional. The , , and 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(,,,) +aes_gcm_enc(,,,[,]) Encrypts the raw byte input using the AES128-GCM, AES192-GCM or - AES256-GCM algorithm, depending on the parameter. and - parameters must be base64 encoded. Last parameter, , must be a - variable. The AEAD tag will be stored base64 encoded into that variable. - The returned result is in raw byte format. The and can either - be strings or variables. This converter requires at least OpenSSL 1.0.1. + AES256-GCM algorithm, depending on the parameter. , and + parameters must be base64 encoded. Parameter must be a + variable. The AEAD tag will be stored base64 encoded into that variable. The + parameter is optional. The returned result is in raw byte format. The + , and 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),\ diff --git a/reg-tests/converter/aes_gcm.vtc b/reg-tests/converter/aes_gcm.vtc new file mode 100644 index 000000000..6c969389a --- /dev/null +++ b/reg-tests/converter/aes_gcm.vtc @@ -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 diff --git a/src/ssl_sample.c b/src/ssl_sample.c index 4cf1c6d1d..230deafc3 100644 --- a/src/ssl_sample.c +++ b/src/ssl_sample.c @@ -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 },