From df97f472fad4ec7c2621d2e13db837a591742d9e Mon Sep 17 00:00:00 2001 From: Abhijeet Rastogi Date: Sat, 13 May 2023 20:04:45 -0700 Subject: MINOR: ssl: add new sample ssl_c_r_dn 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 | 14 +++++++++ include/haproxy/ssl_utils.h | 1 + reg-tests/ssl/ssl_client_samples.vtc | 2 ++ src/ssl_sample.c | 57 ++++++++++++++++++++++++++++++++++++ src/ssl_utils.c | 27 +++++++++++++++++ 5 files changed, 101 insertions(+) diff --git a/doc/configuration.txt b/doc/configuration.txt index aabbe8e2b..43e4bffeb 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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([[,[,]]]) : 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 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 + 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([[,[,]]]) : string When the incoming connection was made over an SSL/TLS transport layer, returns the full distinguished name of the subject of the certificate diff --git a/include/haproxy/ssl_utils.h b/include/haproxy/ssl_utils.h index d6db0874b..3391efd38 100644 --- a/include/haproxy/ssl_utils.h +++ b/include/haproxy/ssl_utils.h @@ -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); diff --git a/reg-tests/ssl/ssl_client_samples.vtc b/reg-tests/ssl/ssl_client_samples.vtc index 81a52abeb..62956f1f7 100644 --- a/reg-tests/ssl/ssl_client_samples.vtc +++ b/reg-tests/ssl/ssl_client_samples.vtc @@ -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" diff --git a/src/ssl_sample.c b/src/ssl_sample.c index 5c6ad1ca2..582b7134c 100644 --- a/src/ssl_sample.c +++ b/src/ssl_sample.c @@ -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 }, diff --git a/src/ssl_utils.c b/src/ssl_utils.c index 836f05461..03d43410a 100644 --- a/src/ssl_utils.c +++ b/src/ssl_utils.c @@ -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 -- cgit v1.2.1