]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: ssl/sample: ssl_c_san returns a comma separated list of SAN
authorWilliam Lallemand <wlallemand@haproxy.org>
Mon, 27 Feb 2023 21:16:06 +0000 (22:16 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Tue, 9 Jul 2024 11:57:18 +0000 (13:57 +0200)
The ssl_c_san sample fetch returns a list of Subject Alt Name which was
presented by the client certificate.

The format is the same as the "openssl x509 -text" command, it's a
Description: Value list separated by commas.
The format is directly generated by the GENERAL_NAME_print() openssl
function.

https://github.com/openssl/openssl/blob/openssl-3.0/crypto/x509/v3_san.c#L207

Example:
    IP Address:127.0.0.1, IP Address:127.0.0.2, IP Address:127.0.0.3, URI:http://docs.haproxy.org/2.7/, DNS:ca.tests.haproxy.com

doc/configuration.txt
src/ssl_sample.c

index 20738b31c19aa73f92c194b6026b071f7ec429cb..f760c39e965d8a2fc5ad2dcd954a3f29a3a58d37 100644 (file)
@@ -22920,6 +22920,7 @@ ssl_c_notafter                                     string
 ssl_c_notbefore                                    string
 ssl_c_r_dn([<entry>[,<occ>[,<format>]]])           string
 ssl_c_s_dn([<entry>[,<occ>[,<format>]]])           string
+ssl_c_san                                          string
 ssl_c_serial                                       binary
 ssl_c_sha1                                         binary
 ssl_c_sig_alg                                      string
@@ -23264,6 +23265,23 @@ ssl_c_s_dn([<entry>[,<occ>[,<format>]]]) : string
   If you'd like to modify the format only you can specify an empty string
   and zero for the first two parameters. Example: ssl_c_s_dn(,0,rfc2253)
 
+ssl_c_san : string
+  When the incoming connection was made over an SSL/TLS transport layer, and was
+  provided with a client certificate. Returns a string of comma separated
+  Subject Alt Name fields contained into the provided certificate.
+
+  This can be used to inspect the client certificate.
+
+  Example:
+
+    acl is_valid_client_cert ssl_c_used && ! ssl_c_verify
+    http-request set-header X-SSL-Client-SAN %[ssl_c_san] if is_valid_client_cert
+
+  will results in:
+
+    X-SSL-Client-SAN: IP Address:127.0.0.1, IP Address:127.0.0.2, IP Address:127.0.0.3, URI:http://docs.haproxy.org/2.7/, DNS:ca.tests.haproxy.com
+
+
 ssl_c_serial : binary
   Returns the serial of the certificate presented by the client when the
   incoming connection was made over an SSL/TLS transport layer. When used for
index 0757c12d7ff4650fb1d400a4d2f4b0f44a8982c9..debc9dcbc0586462346afeac3b96875106c45e90 100644 (file)
@@ -997,6 +997,82 @@ out:
        return ret;
 }
 
+/*
+ *  returns a string of comma separated SAN in a client certificate, Use "GENERAL_NAME_print"
+ *  Example:  "IP Address:127.0.0.1, IP Address:127.0.0.2, IP Address:127.0.0.3, URI:http://docs.haproxy.org/2.7/, DNS:ca.tests.haproxy.com"
+ */
+static int
+smp_fetch_ssl_x_san(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+       int cert_peer = (kw[4] == 'c' || kw[4] == 's') ? 1 : 0;
+       int conn_server = (kw[4] == 's') ? 1 : 0;
+       STACK_OF(GENERAL_NAME) *names;
+       X509 *crt = NULL;
+       int ret = 0;
+       struct buffer *smp_trash;
+       struct connection *conn;
+       SSL *ssl;
+       int i, read;
+       BIO *bio = NULL;
+
+       if (conn_server)
+               conn = smp->strm ? sc_conn(smp->strm->scb) : NULL;
+       else
+               conn = objt_conn(smp->sess->origin);
+
+       ssl = ssl_sock_get_ssl_object(conn);
+       if (!ssl)
+               return 0;
+
+       if (conn->flags & CO_FL_WAIT_XPRT && !conn->err_code) {
+               smp->flags |= SMP_F_MAY_CHANGE;
+               return 0;
+       }
+
+       if (cert_peer)
+               crt = ssl_sock_get_peer_certificate(ssl);
+       else
+               crt = SSL_get_certificate(ssl);
+       if (!crt)
+               goto out;
+
+       names = X509_get_ext_d2i(crt, NID_subject_alt_name, NULL, NULL);
+       if (!names)
+               goto out;
+
+       smp_trash = get_trash_chunk();
+
+        bio = BIO_new(BIO_s_mem());
+       if (!bio)
+               goto out;
+
+       for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
+               GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
+               if (i != 0)
+                       BIO_puts(bio, ", ");
+               GENERAL_NAME_print(bio, name);
+       }
+
+       sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
+
+       read = BIO_read(bio, smp_trash->area, smp_trash->size-1);
+       if (read <= 0) /* nothing to read, prevent negative array index */
+               goto out;
+       smp_trash->area[read] = '\0';
+       smp_trash->data = read;
+
+       smp->flags = SMP_F_VOL_SESS;
+       smp->data.type = SMP_T_STR;
+       smp->data.u.str = *smp_trash;
+       ret = 1;
+out:
+       /* SSL_get_peer_certificate, it increase X509 * ref count */
+       if (cert_peer && crt)
+               X509_free(crt);
+       BIO_free(bio);
+       return ret;
+}
+
 /* string, returns notbefore date in ASN1_UTCTIME format.
  * The 5th keyword char is used to know if SSL_get_certificate or SSL_get_peer_certificate
  * should be use.
@@ -2343,6 +2419,7 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
        { "ssl_c_r_dn",             smp_fetch_ssl_r_dn,           ARG3(0,STR,SINT,STR),val_dnfmt,    SMP_T_STR,  SMP_USE_L5CLI },
 #endif
        { "ssl_c_sig_alg",          smp_fetch_ssl_x_sig_alg,      0,                   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+       { "ssl_c_san",              smp_fetch_ssl_x_san,          0,                   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
        { "ssl_c_s_dn",             smp_fetch_ssl_x_s_dn,         ARG3(0,STR,SINT,STR),val_dnfmt,    SMP_T_STR,  SMP_USE_L5CLI },
        { "ssl_c_serial",           smp_fetch_ssl_x_serial,       0,                   NULL,    SMP_T_BIN,  SMP_USE_L5CLI },
        { "ssl_c_sha1",             smp_fetch_ssl_x_sha1,         0,                   NULL,    SMP_T_BIN,  SMP_USE_L5CLI },