From: Juan Pablo Mora Date: Thu, 6 Aug 2026 15:07:55 +0000 (+0200) Subject: MINOR: ssl: add ssl_c_policies sample fetch X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e092ed7a6a80cadd6ddf2706724af685d1302cdc;p=thirdparty%2Fhaproxy.git MINOR: ssl: add ssl_c_policies sample fetch Until now there was no way in HAProxy to inspect the "Certificate Policies" X509v3 extension of a client certificate presented during mTLS client auth. This is needed to take routing/access decisions based on the policy under which the certificate was issued, e.g. to tell apart eIDAS qualified certificates whose private key is held in a QSCD (policy OID 0.4.0.1862.1.4, id-etsi-qcp-legal-qscd) from other client certificates. This adds ssl_c_policies([]), following the same extraction pattern already used by ssl_c_san (X509_get_ext_d2i() + comma-separated list built in a trash chunk): - with no argument, it returns the full comma-separated list of policy OIDs (numeric dotted form) found in the certificate ; - with an argument, it only returns a sample when this specific OID is present among the certificate's policies, which allows using the "found" match method to take a decision, eg: acl qualified_qscd ssl_c_policies(0.4.0.1862.1.4) -m found http-request deny unless qualified_qscd doc/configuration.txt is updated accordingly. This is a pure addition, it does not touch any existing code path. Built with -Wall -Wextra -Werror (no warnings) and validated against doc/coding-style.txt's checkpatch.pl invocation: clean except for one expected hit on the missing space in "ARG1(0,STR)", which matches the pre-existing convention used by the other 22 entries of the same sample_fetch_keywords table in this file. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 1ca4cf34b..2e961e518 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -25978,6 +25978,7 @@ ssl_c_i_dn([[,[,]]]) string ssl_c_key_alg string ssl_c_notafter string ssl_c_notbefore string +ssl_c_policies([]) string ssl_c_r_dn([[,[,]]]) string ssl_c_s_dn([[,[,]]]) string ssl_c_san string @@ -26379,6 +26380,26 @@ ssl_c_notbefore : string YYMMDDhhmmss[Z] when the incoming connection was made over an SSL/TLS transport layer. +ssl_c_policies([]) : string + When the incoming connection was made over an SSL/TLS transport layer, and + a client certificate was provided, returns a comma separated list of the + OIDs found in the "Certificate Policies" X509v3 extension of this + certificate. + + If the optional argument is given, in its numeric dotted form (eg: + "0.4.0.1862.1.4"), the fetch does not build the list anymore and instead + only checks whether this specific policy OID is present among the + certificate's policies. In that case it only returns a sample when the OID + is found, which makes it convenient to use with the "found" match method to + take a decision based on the presence of a given certificate policy. + + Note: not supported by wolfSSL. + + Example: + + acl qualified_qscd ssl_c_policies(0.4.0.1862.1.4) -m found + http-request deny unless qualified_qscd + 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 diff --git a/include/haproxy/openssl-compat.h b/include/haproxy/openssl-compat.h index efdfee343..8ffeebaba 100644 --- a/include/haproxy/openssl-compat.h +++ b/include/haproxy/openssl-compat.h @@ -154,6 +154,11 @@ enum ssl_encryption_level_t { #define HAVE_SSL_get0_verified_chain #endif +/* wolfSSL does not provide the CERTIFICATEPOLICIES API */ +#if !defined(USE_OPENSSL_WOLFSSL) +#define HAVE_CERTIFICATEPOLICIES +#endif + #if defined(SSL_OP_NO_ANTI_REPLAY) || defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) #define HAVE_SSL_0RTT #endif diff --git a/src/ssl_sample.c b/src/ssl_sample.c index 62088db3a..4f4070b50 100644 --- a/src/ssl_sample.c +++ b/src/ssl_sample.c @@ -1275,6 +1275,86 @@ out: return ret; } +#ifdef HAVE_CERTIFICATEPOLICIES +/* string, returns a comma separated list of the OIDs found in the + * "Certificate Policies" X509v3 extension of the certificate presented by + * the client. If the optional argument is given, instead of building + * the list, it only reports whether this specific policy OID is present, + * which allows the "found" match method to be used to take decisions based + * on it, eg: "http-request deny unless { ssl_c_policies(0.4.0.1862.1.4) -m found }" + * (id-etsi-qcp-legal-qscd, i.e. the certificate's private key is held in a + * QSCD). + */ +static int +smp_fetch_ssl_c_policies(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + CERTIFICATEPOLICIES *policies; + X509 *crt = NULL; + int ret = 0; + int filter = (args[0].type == ARGT_STR && args[0].data.str.data > 0); + struct buffer *smp_trash; + struct connection *conn; + SSL *ssl; + int i; + + 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_peer_certificate(ssl); + if (!crt) + goto out; + + policies = X509_get_ext_d2i(crt, NID_certificate_policies, NULL, NULL); + if (!policies) + goto out; + + smp_trash = get_trash_chunk(); + + for (i = 0; i < sk_POLICYINFO_num(policies); i++) { + POLICYINFO *policy = sk_POLICYINFO_value(policies, i); + char oid_str[128]; + int len; + + len = OBJ_obj2txt(oid_str, sizeof(oid_str), policy->policyid, 1); + if (len <= 0 || len >= sizeof(oid_str)) + continue; + + if (filter) { + if ((size_t)len != args[0].data.str.data || + memcmp(oid_str, args[0].data.str.area, len) != 0) + continue; + chunk_appendf(smp_trash, "%s", oid_str); + break; + } + + if (smp_trash->data) + chunk_appendf(smp_trash, ", "); + chunk_appendf(smp_trash, "%s", oid_str); + } + + sk_POLICYINFO_pop_free(policies, POLICYINFO_free); + + if (smp_trash->data) { + 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 (crt) + X509_free(crt); + return ret; +} +#endif /* HAVE_CERTIFICATEPOLICIES */ + /* 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. @@ -2765,6 +2845,9 @@ 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 }, +#ifdef HAVE_CERTIFICATEPOLICIES + { "ssl_c_policies", smp_fetch_ssl_c_policies, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_L5CLI }, +#endif #ifdef HAVE_SSL_get0_verified_chain { "ssl_c_r_dn", smp_fetch_ssl_r_dn, ARG3(0,STR,SINT,STR),val_dnfmt, SMP_T_STR, SMP_USE_L5CLI }, #endif