]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: ssl: add curve2name converter
authorWilliam Lallemand <wlallemand@irq6.net>
Tue, 6 Oct 2026 12:59:43 +0000 (14:59 +0200)
committerWilliam Lallemand <wlallemand@irq6.net>
Tue, 6 Oct 2026 13:39:56 +0000 (15:39 +0200)
Add a string converter to normalize curve names to IANA, ANSI X9.62
or NIST notation. Match both the requested format and input names
case-insensitively, and resolve old ecdh_* aliases to canonical names.

Keep the original string unchanged when no curve matches or the
requested naming convention has no corresponding name. The converter
uses the curve table independently of the SSL library's available NIDs.

Document the supported formats and add regression coverage for curve
aliases, mixed-case formats and unchanged-string fallbacks. The build
and all three SSL curve regression tests pass.

doc/configuration.txt
reg-tests/ssl/curve2name.vtc [new file with mode: 0644]
src/ssl_utils.c

index d7d25929660e146dca113f08736d317de835d2ce..a2d27804bb097ab37ea4e780737bc13cd5e0fae8 100644 (file)
@@ -21599,6 +21599,7 @@ concat([start[,var[,end]]])                        string       string
 cpl                                                integer      integer
 crc32([avalanche])                                 binary       integer
 crc32c([avalanche])                                binary       integer
+curve2name(format)                                 string       string
 cut_crlf                                           string       string
 da-csv-conv(prop[,prop*])                          string       string
 date                                               string       integer
@@ -22039,6 +22040,28 @@ crc32c([<avalanche>])
   not be used for security purposes as a 32-bit hash is trivial to break. See
   also "djb2", "sdbm", "wt6", "crc32" and the "hash-type" directive.
 
+curve2name(<format>)
+  Converts a curve name to the naming convention selected by <format>. The
+  following formats are supported:
+
+    - iana: registered TLS group name, such as "secp256r1" or "x25519".
+      Names are listed in the IANA TLS Supported Groups registry:
+      https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8
+    - ansi: ANSI X9.62 curve name, such as "prime256v1".
+    - nist: NIST curve name, such as "P-256".
+
+  The format is mandatory and case-insensitive. Input names are matched
+  case-insensitively against the known names and compatibility aliases. This
+  converter requires SSL support.
+
+  The original input is returned unchanged if it is unknown or the curve has
+  no name in the requested convention. For example,
+  "prime256v1,curve2name(iana)" produces "secp256r1", and
+  "secp256r1,curve2name(nist)" produces "P-256".
+
+  Example:
+    http-response set-header X-Curve %[ssl_fc_curve,curve2name(iana)]
+
 cut_crlf
   Cuts the string representation of the input sample on the first carriage
   return ('\r') or newline ('\n') character found. Only the string length is
diff --git a/reg-tests/ssl/curve2name.vtc b/reg-tests/ssl/curve2name.vtc
new file mode 100644 (file)
index 0000000..d6a92fd
--- /dev/null
@@ -0,0 +1,58 @@
+#REGTEST_TYPE=devel
+
+varnishtest "Convert curve names between naming conventions"
+feature ignore_unknown_macro
+feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL)'"
+
+haproxy h1 -conf {
+    defaults
+        mode http
+        timeout connect 5s
+        timeout client 5s
+        timeout server 5s
+
+    frontend fe
+        bind "fd@${fe}"
+        http-request return status 200 hdr x-iana %[req.hdr(curve),curve2name(IANA)] hdr x-ansi %[req.hdr(curve),curve2name(ANSI)] hdr x-nist %[req.hdr(curve),curve2name(NiSt)]
+} -start
+
+client c1 -connect ${h1_fe_sock} {
+    txreq -hdr "curve: prime256v1"
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-iana == "secp256r1"
+    expect resp.http.x-ansi == "prime256v1"
+    expect resp.http.x-nist == "P-256"
+
+    txreq -hdr "curve: p-192"
+    rxresp
+    expect resp.http.x-iana == "secp192r1"
+    expect resp.http.x-ansi == "prime192v1"
+    expect resp.http.x-nist == "P-192"
+
+    txreq -hdr "curve: secp384r1"
+    rxresp
+    expect resp.http.x-iana == "secp384r1"
+    expect resp.http.x-ansi == "secp384r1"
+    expect resp.http.x-nist == "P-384"
+
+    txreq -hdr "curve: X25519"
+    rxresp
+    expect resp.http.x-iana == "x25519"
+    expect resp.http.x-ansi == "X25519"
+    expect resp.http.x-nist == "X25519"
+
+    txreq -hdr "curve: ecdh_x448"
+    rxresp
+    expect resp.http.x-iana == "x448"
+
+    txreq -hdr "curve: MLKEM768"
+    rxresp
+    expect resp.http.x-iana == "MLKEM768"
+
+    txreq -hdr "curve: secp256r1-extra"
+    rxresp
+    expect resp.http.x-iana == "secp256r1-extra"
+    expect resp.http.x-ansi == "secp256r1-extra"
+    expect resp.http.x-nist == "secp256r1-extra"
+} -run
index ee8e5c398ce78b2edc2325f1005ccde10ae23ab2..15f190431202a624c8d72dc3b3abb82559922c3d 100644 (file)
 
 
 #include <haproxy/api.h>
+#include <haproxy/arg.h>
 #include <haproxy/buf-t.h>
 #include <haproxy/chunk.h>
 #include <haproxy/openssl-compat.h>
+#include <haproxy/sample.h>
+#include <haproxy/tools.h>
 #include <haproxy/ssl_sock.h>
 #include <haproxy/ssl_utils.h>
 
@@ -1154,3 +1157,68 @@ int curves2nid(const char *curve)
        return -1;
 }
 
+
+/* Validate the requested curve naming convention. */
+static int check_curve2name(struct arg *args, struct sample_conv *conv,
+                            const char *file, int line, char **err)
+{
+       const char *format = args[0].data.str.area;
+
+       if (!strcasecmp(format, "iana") ||
+           !strcasecmp(format, "ansi") || !strcasecmp(format, "nist"))
+               return 1;
+
+       memprintf(err, "expected 'iana', 'ansi' or 'nist'");
+       return 0;
+}
+
+/* Compare a sample string without requiring a terminating NUL. */
+static int curve_name_matches(const struct buffer *input, const char *name)
+{
+       return name && input->data == strlen(name) &&
+              !strncasecmp(input->area, name, input->data);
+}
+
+static int sample_conv_curve2name(const struct arg *args, struct sample *smp,
+                                  void *private)
+{
+       struct curve *item, *canonical;
+       const char *format = args[0].data.str.area;
+       const char *name = NULL;
+
+       for (item = curves_list; item->curve_id; item++) {
+               if (curve_name_matches(&smp->data.u.str, item->ansi) ||
+                   curve_name_matches(&smp->data.u.str, item->iana) ||
+                   curve_name_matches(&smp->data.u.str, item->nist))
+                       break;
+       }
+       if (!item->curve_id)
+               return 1;
+
+       /* Resolve compatibility aliases to the first entry for the group. */
+       for (canonical = curves_list; canonical->curve_id != item->curve_id; canonical++)
+               ;
+
+       if (!strcasecmp(format, "iana"))
+               name = canonical->iana;
+       else if (!strcasecmp(format, "ansi"))
+               name = canonical->ansi;
+       else if (!strcasecmp(format, "nist"))
+               name = canonical->nist;
+
+       if (!name)
+               return 1;
+
+       smp->data.u.str.area = (char *)name;
+       smp->data.u.str.data = strlen(name);
+       smp->data.u.str.size = 0;
+       smp->flags |= SMP_F_CONST;
+       return 1;
+}
+
+static struct sample_conv_kw_list curve_conv_kws = { ILH, {
+       { "curve2name", sample_conv_curve2name, ARG1(1,STR), check_curve2name, SMP_T_STR, SMP_T_STR },
+       { NULL, NULL, 0, 0, 0 },
+}};
+
+INITCALL1(STG_REGISTER, sample_register_convs, &curve_conv_kws);