Created
May 14, 2023 18:38
-
-
Save shadyabhi/c7095e6f416de2f98155d49d35836860 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| From d9694aeac3099741353c3cf50c27526591012e28 Mon Sep 17 00:00:00 2001 | |
| From: Abhijeet Rastogi <[email protected]> | |
| Date: Sat, 13 May 2023 20:04:45 -0700 | |
| Subject: [PATCH] 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([<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 | |
| 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 | |
| -- | |
| 2.34.1 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment