]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: sample: optional AAD parameter support to aes_gcm_enc/dec
authorWilliam Lallemand <wlallemand@haproxy.com>
Thu, 30 Oct 2025 18:02:13 +0000 (19:02 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Fri, 31 Oct 2025 11:27:38 +0000 (12:27 +0100)
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.

doc/configuration.txt
reg-tests/converter/aes_gcm.vtc [new file with mode: 0644]
src/ssl_sample.c

index 32c49509827d8fe19e4e11d0ca69516a8a495f0b..ef2b5945096bb4a20514999ef354c3098ca7ceea 100644 (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),\
diff --git a/reg-tests/converter/aes_gcm.vtc b/reg-tests/converter/aes_gcm.vtc
new file mode 100644 (file)
index 0000000..6c96938
--- /dev/null
@@ -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
index 4cf1c6d1d80b6c422090d4cfe1051430c2aa02cb..230deafc391f7e100082f1638d54d72b0a4586de 100644 (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  },