]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: ssl: add new sample ssl_c_r_dn
authorAbhijeet Rastogi <abhijeet.1989@gmail.com>
Sun, 14 May 2023 03:04:45 +0000 (20:04 -0700)
committerWilliam Lallemand <wlallemand@haproxy.org>
Mon, 15 May 2023 08:48:05 +0000 (10:48 +0200)
This patch addresses #1514, adds the ability to fetch DN of the root
ca that was in the chain when client certificate was verified during SSL
handshake.

doc/configuration.txt
include/haproxy/ssl_utils.h
reg-tests/ssl/ssl_client_samples.vtc
src/ssl_sample.c
src/ssl_utils.c

index aabbe8e2bddd4a72968a1fd61d23e899fa3479be..43e4bffebbd35e260afd97fdbeeea206c86840e9 100644 (file)
@@ -20768,6 +20768,20 @@ ssl_c_notbefore : string
   YYMMDDhhmmss[Z] when the incoming connection was made over an SSL/TLS
   transport layer.
 
+ssl_c_r_dn([<entry>[,<occ>[,<format>]]]) : string
+  When the incoming connection was made over an SSL/TLS transport layer, and is
+  successfully validated with the configured ca-file, returns the full
+  distinguished name of the root CA of the certificate presented by the client
+  when no <entry> is specified, or the value of the first given entry found from
+  the beginning of the DN. If a positive/negative occurrence number is specified
+  as the optional second argument, it returns the value of the nth given entry
+  value from the beginning/end of the DN. For instance, "ssl_c_r_dn(OU,2)" the
+  second organization unit, and "ssl_c_r_dn(CN)" retrieves the common name. The
+  <format> parameter allows you to receive the DN suitable for consumption by
+  different protocols. Currently supported is rfc2253 for LDAP v3. 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_r_dn(,0,rfc2253)
+
 ssl_c_s_dn([<entry>[,<occ>[,<format>]]]) : string
   When the incoming connection was made over an SSL/TLS transport layer,
   returns the full distinguished name of the subject of the certificate
index d6db0874b17dd78d586c1dd05bd95244b49b49b5..3391efd38bdfcf5b527d9e76990405df3b5505d0 100644 (file)
@@ -39,6 +39,7 @@ int ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos,
 int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct buffer *out);
 int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out);
 X509* ssl_sock_get_peer_certificate(SSL *ssl);
+X509* ssl_sock_get_verified_chain_root(SSL *ssl);
 unsigned int openssl_version_parser(const char *version);
 void exclude_tls_grease(char *input, int len, struct buffer *output);
 int x509_v_err_str_to_int(const char *str);
index 81a52abebf1b8786a9e95c76e474451de53b33c6..62956f1f74699bb968e0201fb09d333408adb1ef 100644 (file)
@@ -42,6 +42,7 @@ haproxy h1 -conf {
         http-response add-header x-ssl-sig_alg %[ssl_c_sig_alg]
         http-response add-header x-ssl-i_dn %[ssl_c_i_dn]
         http-response add-header x-ssl-s_dn %[ssl_c_s_dn]
+        http-response add-header x-ssl-r_dn %[ssl_c_r_dn]
         http-response add-header x-ssl-s_serial %[ssl_c_serial,hex]
         http-response add-header x-ssl-key_alg %[ssl_c_key_alg]
         http-response add-header x-ssl-version %[ssl_c_version]
@@ -64,6 +65,7 @@ client c1 -connect ${h1_clearlst_sock} {
     expect resp.http.x-ssl-sig_alg == "RSA-SHA256"
     expect resp.http.x-ssl-i_dn == "/C=FR/ST=Some-State/O=HAProxy Technologies/CN=HAProxy Technologies CA Test Client Auth"
     expect resp.http.x-ssl-s_dn  == "/C=FR/O=HAProxy Technologies Test/CN=client1"
+    expect resp.http.x-ssl-r_dn  == "/C=FR/ST=Some-State/O=HAProxy Technologies/CN=HAProxy Technologies CA Test Client Auth"
     expect resp.http.x-ssl-s_serial == "02"
     expect resp.http.x-ssl-key_alg == "rsaEncryption"
     expect resp.http.x-ssl-version == "1"
index 5c6ad1ca2075ec090844fd64ecc210fb9199ece5..582b7134c393c41d0be2ec9d31e5c6b56e3ad5a2 100644 (file)
@@ -538,6 +538,62 @@ smp_fetch_ssl_fc_has_crt(const struct arg *args, struct sample *smp, const char
        return 1;
 }
 
+/* string, returns a string of a formatted full dn \C=..\O=..\OU=.. \CN=.. of the
+ * client certificate's root CA.
+ */
+static int
+smp_fetch_ssl_r_dn(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+       X509 *crt = NULL;
+       X509_NAME *name;
+       int ret = 0;
+       struct buffer *smp_trash;
+       struct connection *conn;
+       SSL *ssl;
+
+       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;
+       }
+
+       crt = ssl_sock_get_verified_chain_root(ssl);
+       if (!crt)
+               goto out;
+
+       name = X509_get_subject_name(crt);
+       if (!name)
+               goto out;
+
+       smp_trash = get_trash_chunk();
+       if (args[0].type == ARGT_STR && args[0].data.str.data > 0) {
+               int pos = 1;
+
+               if (args[1].type == ARGT_SINT)
+                       pos = args[1].data.sint;
+
+               if (ssl_sock_get_dn_entry(name, &args[0].data.str, pos, smp_trash) <= 0)
+                       goto out;
+       }
+       else if (args[2].type == ARGT_STR && args[2].data.str.data > 0) {
+               if (ssl_sock_get_dn_formatted(name, &args[2].data.str, smp_trash) <= 0)
+                       goto out;
+       }
+       else if (ssl_sock_get_dn_oneline(name, smp_trash) <= 0)
+               goto out;
+
+       smp->flags = SMP_F_VOL_SESS;
+       smp->data.type = SMP_T_STR;
+       smp->data.u.str = *smp_trash;
+       ret = 1;
+out:
+       return ret;
+}
+
 /* binary, returns a certificate in a binary chunk (der/raw).
  * The 5th keyword char is used to know if SSL_get_certificate or SSL_get_peer_certificate
  * should be use.
@@ -2142,6 +2198,7 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
        { "ssl_c_key_alg",          smp_fetch_ssl_x_key_alg,      0,                   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
        { "ssl_c_notafter",         smp_fetch_ssl_x_notafter,     0,                   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
        { "ssl_c_notbefore",        smp_fetch_ssl_x_notbefore,    0,                   NULL,    SMP_T_STR,  SMP_USE_L5CLI },
+       { "ssl_c_r_dn",             smp_fetch_ssl_r_dn,           ARG3(0,STR,SINT,STR),val_dnfmt,    SMP_T_STR,  SMP_USE_L5CLI },
        { "ssl_c_sig_alg",          smp_fetch_ssl_x_sig_alg,      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 },
index 836f05461e385e81be387f3a7492121f0d90afd5..03d43410a6c220bf47e75fe7703b247b9e24bb8e 100644 (file)
@@ -317,6 +317,33 @@ X509* ssl_sock_get_peer_certificate(SSL *ssl)
        return cert;
 }
 
+/*
+ * This function fetches the x509* for the root CA of client certificate
+ * from the verified chain. We use the SSL_get0_verified_chain and get the
+ * last certificate in the x509 stack.
+ *
+ * Returns NULL in case of failure.
+*/
+X509* ssl_sock_get_verified_chain_root(SSL *ssl)
+{
+       STACK_OF(X509) *chain = NULL;
+       X509 *crt = NULL;
+       int i;
+
+       chain = SSL_get0_verified_chain(ssl);
+       if (!chain)
+               return NULL;
+
+       for (i = 0; i < sk_X509_num(chain); i++) {
+               crt = sk_X509_value(chain, i);
+
+               if (X509_check_issued(crt, crt) == X509_V_OK)
+                       break;
+       }
+
+       return crt;
+}
+
 /*
  * Take an OpenSSL version in text format and return a numeric openssl version
  * Return 0 if it failed to parse the version