From 960fb74cae15cf00030fda22836284989fe5a1c0 Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Thu, 3 Nov 2022 16:31:50 +0100 Subject: [PATCH] MEDIUM: ssl: {ca,crt}-ignore-err can now use error constant name The ca-ignore-err and crt-ignore-err directives are now able to use the openssl X509_V_ERR constant names instead of the numerical values. This allow a configuration to survive an OpenSSL upgrade, because the numerical ID can change between versions. For example X509_V_ERR_INVALID_CA was 24 in OpenSSL 1 and is 79 in OpenSSL 3. The list of errors must be updated when a new major OpenSSL version is released. --- doc/configuration.txt | 16 ++- include/haproxy/ssl_utils.h | 2 + reg-tests/ssl/ssl_client_auth.vtc | 2 +- src/cfgparse-ssl.c | 43 +++++-- src/ssl_utils.c | 188 ++++++++++++++++++++++++++++++ 5 files changed, 238 insertions(+), 13 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index d3ab65cc8..5d617d74a 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -14084,6 +14084,11 @@ ca-file ca-ignore-err [all|,...] This setting is only available when support for OpenSSL was built in. Sets a comma separated list of errorIDs to ignore during verify at depth > 0. + It could be a numerical ID, or the constant name (X509_V_ERR) which is + available in the OpenSSL documentation: + https://www.openssl.org/docs/manmaster/man3/X509_STORE_CTX_get_error.html#ERROR-CODES + It is recommended to use the constant name as the numerical value can change + in new version of OpenSSL. If set to 'all', all errors are ignored. SSL handshake is not aborted if an error is ignored. @@ -14204,9 +14209,14 @@ crt crt-ignore-err This setting is only available when support for OpenSSL was built in. Sets a - comma separated list of errorIDs to ignore during verify at depth == 0. If - set to 'all', all errors are ignored. SSL handshake is not aborted if an error - is ignored. + comma separated list of errorIDs to ignore during verify at depth == 0. + It could be a numerical ID, or the constant name (X509_V_ERR) which is + available in the OpenSSL documentation: + https://www.openssl.org/docs/manmaster/man3/X509_STORE_CTX_get_error.html#ERROR-CODES + It is recommended to use the constant name as the numerical value can change + in new version of OpenSSL. + If set to 'all', all errors are ignored. SSL handshake is not aborted if an + error is ignored. crt-list This setting is only available when support for OpenSSL was built in. It diff --git a/include/haproxy/ssl_utils.h b/include/haproxy/ssl_utils.h index e14aaf1c6..b6bd9d6a4 100644 --- a/include/haproxy/ssl_utils.h +++ b/include/haproxy/ssl_utils.h @@ -41,6 +41,8 @@ int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out); X509* ssl_sock_get_peer_certificate(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); +const char *x509_v_err_int_to_str(int code); #endif /* _HAPROXY_SSL_UTILS_H */ #endif /* USE_OPENSSL */ diff --git a/reg-tests/ssl/ssl_client_auth.vtc b/reg-tests/ssl/ssl_client_auth.vtc index 0278ec0ea..b8107d443 100644 --- a/reg-tests/ssl/ssl_client_auth.vtc +++ b/reg-tests/ssl/ssl_client_auth.vtc @@ -48,7 +48,7 @@ haproxy h1 -conf { # crt: certificate of the server # ca-file: CA used for client authentication request # crl-file: revocation list for client auth: the client1 certificate is revoked - bind "${tmpdir}/ssl.sock" ssl crt ${testdir}/common.pem ca-file ${testdir}/ca-auth.crt verify optional crt-ignore-err all crl-file ${testdir}/crl-auth.pem + bind "${tmpdir}/ssl.sock" ssl crt ${testdir}/common.pem ca-file ${testdir}/ca-auth.crt verify optional crt-ignore-err X509_V_ERR_CERT_REVOKED,X509_V_ERR_CERT_HAS_EXPIRED crl-file ${testdir}/crl-auth.pem acl cert_expired ssl_c_verify 10 acl cert_revoked ssl_c_verify 23 diff --git a/src/cfgparse-ssl.c b/src/cfgparse-ssl.c index 35780adff..7cee13db0 100644 --- a/src/cfgparse-ssl.c +++ b/src/cfgparse-ssl.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -824,7 +825,10 @@ static int bind_parse_ecdhe(char **args, int cur_arg, struct proxy *px, struct b static int bind_parse_ignore_err(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err) { int code; + char *s1 = NULL, *s2 = NULL; + char *token = NULL; char *p = args[cur_arg + 1]; + char *str; unsigned long long *ignerr = conf->crt_ignerr_bitfield; if (!*p) { @@ -832,6 +836,15 @@ static int bind_parse_ignore_err(char **args, int cur_arg, struct proxy *px, str return ERR_ALERT | ERR_FATAL; } + /* copy the string to be able to dump the complete one in case of + * error, because strtok_r is writing \0 inside. */ + str = strdup(p); + if (!str) { + memprintf(err, "'%s' : Could not allocate memory", args[cur_arg]); + return ERR_ALERT | ERR_FATAL; + + } + if (strcmp(args[cur_arg], "ca-ignore-err") == 0) ignerr = conf->ca_ignerr_bitfield; @@ -840,19 +853,31 @@ static int bind_parse_ignore_err(char **args, int cur_arg, struct proxy *px, str return 0; } - while (p) { - code = atoi(p); - if ((code <= 0) || (code > SSL_MAX_VFY_ERROR_CODE)) { - memprintf(err, "'%s' : ID '%d' out of range (1..%d) in error IDs list '%s'", - args[cur_arg], code, SSL_MAX_VFY_ERROR_CODE, args[cur_arg + 1]); - return ERR_ALERT | ERR_FATAL; + s1 = str; + while ((token = strtok_r(s1, ",", &s2))) { + s1 = NULL; + printf("token: %s\n", token); + if (isdigit((int)*token)) { + code = atoi(token); + if ((code <= 0) || (code > SSL_MAX_VFY_ERROR_CODE)) { + memprintf(err, "'%s' : ID '%d' out of range (1..%d) in error IDs list '%s'", + args[cur_arg], code, SSL_MAX_VFY_ERROR_CODE, args[cur_arg + 1]); + free(str); + return ERR_ALERT | ERR_FATAL; + } + } else { + code = x509_v_err_str_to_int(token); + if (code < 0) { + memprintf(err, "'%s' : error constant '%s' unknown in error IDs list '%s'", + args[cur_arg], token, args[cur_arg + 1]); + free(str); + return ERR_ALERT | ERR_FATAL; + } } cert_ignerr_bitfield_set(ignerr, code); - p = strchr(p, ','); - if (p) - p++; } + free(str); return 0; } diff --git a/src/ssl_utils.c b/src/ssl_utils.c index 0f4a8596c..f832168bd 100644 --- a/src/ssl_utils.c +++ b/src/ssl_utils.c @@ -417,3 +417,191 @@ void exclude_tls_grease(char *input, int len, struct buffer *output) if (output->size - output->data > 0 && len - ptr > 0) output->area[output->data++] = input[ptr]; } + +/* + * The following generates an array in which the X509_V_ERR_* + * codes are populated with there string equivalent. Depending on the version + * of the SSL library, some code does not exist, these will be populated as + * "-1" in the array. + * + * The list was taken from + * https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in + * and must be updated when new constant are introduced. + */ + +/* manual atoi() that only works on the first 10 chars of input (they must all be there) */ +#undef _S +#define _S(x) ((x[0]-'0')*1000000000 + \ + (x[1]-'0')*100000000 + \ + (x[2]-'0')*10000000 + \ + (x[3]-'0')*1000000 + \ + (x[4]-'0')*100000 + \ + (x[5]-'0')*10000 + \ + (x[6]-'0')*1000 + \ + (x[7]-'0')*100 + \ + (x[8]-'0')*10 + \ + (x[9]-'0')*1 + \ + 0) + +/* always prepends the sufficient number of leading zeroes to have 10 chars */ +#undef _R +#define _R(x) (!x[0] ? _S("0000000000" x) : \ + !x[1] ? _S("000000000" x) : \ + !x[2] ? _S("00000000" x) : \ + !x[3] ? _S("0000000" x) : \ + !x[4] ? _S("000000" x) : \ + !x[5] ? _S("00000" x) : \ + !x[6] ? _S("0000" x) : \ + !x[7] ? _S("000" x) : \ + !x[8] ? _S("00" x) : \ + !x[9] ? _S("0" x) : \ + _S("" x)) + +/* returns the value for an integer-defined macro, otherwise -1 + * The extraneous series of "\0" is there to shut up stupid clang which wants to + * evaluate the expression in false branches. + */ +#undef _Q +#define _Q(x) ((#x[0] >= '0' && #x[0] <= '9') ? _R(#x "\0\0\0\0\0\0\0\0\0\0") : -1) +#undef V +#define V(x) { .code = _Q(x), .string = #x } + +static const struct x509_v_codes { + int code; + const char *string; +} x509_v_codes[] = { + V(X509_V_OK), + V(X509_V_ERR_UNSPECIFIED), + V(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT), + V(X509_V_ERR_UNABLE_TO_GET_CRL), + V(X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE), + V(X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE), + V(X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY), + V(X509_V_ERR_CERT_SIGNATURE_FAILURE), + V(X509_V_ERR_CRL_SIGNATURE_FAILURE), + V(X509_V_ERR_CERT_NOT_YET_VALID), + V(X509_V_ERR_CERT_HAS_EXPIRED), + V(X509_V_ERR_CRL_NOT_YET_VALID), + V(X509_V_ERR_CRL_HAS_EXPIRED), + V(X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD), + V(X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD), + V(X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD), + V(X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD), + V(X509_V_ERR_OUT_OF_MEM), + V(X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT), + V(X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN), + V(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY), + V(X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE), + V(X509_V_ERR_CERT_CHAIN_TOO_LONG), + V(X509_V_ERR_CERT_REVOKED), + V(X509_V_ERR_NO_ISSUER_PUBLIC_KEY), + V(X509_V_ERR_PATH_LENGTH_EXCEEDED), + V(X509_V_ERR_INVALID_PURPOSE), + V(X509_V_ERR_CERT_UNTRUSTED), + V(X509_V_ERR_CERT_REJECTED), + V(X509_V_ERR_SUBJECT_ISSUER_MISMATCH), + V(X509_V_ERR_AKID_SKID_MISMATCH), + V(X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH), + V(X509_V_ERR_KEYUSAGE_NO_CERTSIGN), + V(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER), + V(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION), + V(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN), + V(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION), + V(X509_V_ERR_INVALID_NON_CA), + V(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED), + V(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE), + V(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED), + V(X509_V_ERR_INVALID_EXTENSION), + V(X509_V_ERR_INVALID_POLICY_EXTENSION), + V(X509_V_ERR_NO_EXPLICIT_POLICY), + V(X509_V_ERR_DIFFERENT_CRL_SCOPE), + V(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE), + V(X509_V_ERR_UNNESTED_RESOURCE), + V(X509_V_ERR_PERMITTED_VIOLATION), + V(X509_V_ERR_EXCLUDED_VIOLATION), + V(X509_V_ERR_SUBTREE_MINMAX), + V(X509_V_ERR_APPLICATION_VERIFICATION), + V(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE), + V(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX), + V(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX), + V(X509_V_ERR_CRL_PATH_VALIDATION_ERROR), + V(X509_V_ERR_PATH_LOOP), + V(X509_V_ERR_SUITE_B_INVALID_VERSION), + V(X509_V_ERR_SUITE_B_INVALID_ALGORITHM), + V(X509_V_ERR_SUITE_B_INVALID_CURVE), + V(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM), + V(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED), + V(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256), + V(X509_V_ERR_HOSTNAME_MISMATCH), + V(X509_V_ERR_EMAIL_MISMATCH), + V(X509_V_ERR_IP_ADDRESS_MISMATCH), + V(X509_V_ERR_DANE_NO_MATCH), + V(X509_V_ERR_EE_KEY_TOO_SMALL), + V(X509_V_ERR_CA_KEY_TOO_SMALL), + V(X509_V_ERR_CA_MD_TOO_WEAK), + V(X509_V_ERR_INVALID_CALL), + V(X509_V_ERR_STORE_LOOKUP), + V(X509_V_ERR_NO_VALID_SCTS), + V(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION), + V(X509_V_ERR_OCSP_VERIFY_NEEDED), + V(X509_V_ERR_OCSP_VERIFY_FAILED), + V(X509_V_ERR_OCSP_CERT_UNKNOWN), + V(X509_V_ERR_UNSUPPORTED_SIGNATURE_ALGORITHM), + V(X509_V_ERR_SIGNATURE_ALGORITHM_MISMATCH), + V(X509_V_ERR_SIGNATURE_ALGORITHM_INCONSISTENCY), + V(X509_V_ERR_INVALID_CA), + V(X509_V_ERR_PATHLEN_INVALID_FOR_NON_CA), + V(X509_V_ERR_PATHLEN_WITHOUT_KU_KEY_CERT_SIGN), + V(X509_V_ERR_KU_KEY_CERT_SIGN_INVALID_FOR_NON_CA), + V(X509_V_ERR_ISSUER_NAME_EMPTY), + V(X509_V_ERR_SUBJECT_NAME_EMPTY), + V(X509_V_ERR_MISSING_AUTHORITY_KEY_IDENTIFIER), + V(X509_V_ERR_MISSING_SUBJECT_KEY_IDENTIFIER), + V(X509_V_ERR_EMPTY_SUBJECT_ALT_NAME), + V(X509_V_ERR_EMPTY_SUBJECT_SAN_NOT_CRITICAL), + V(X509_V_ERR_CA_BCONS_NOT_CRITICAL), + V(X509_V_ERR_AUTHORITY_KEY_IDENTIFIER_CRITICAL), + V(X509_V_ERR_SUBJECT_KEY_IDENTIFIER_CRITICAL), + V(X509_V_ERR_CA_CERT_MISSING_KEY_USAGE), + V(X509_V_ERR_EXTENSIONS_REQUIRE_VERSION_3), + V(X509_V_ERR_EC_KEY_EXPLICIT_PARAMS), + { 0, NULL }, +}; + +/* + * Return the X509_V_ERR code corresponding to the name of the constant. + * See https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in + * If not found, return -1 + */ +int x509_v_err_str_to_int(const char *str) +{ + int i; + + for (i = 0; x509_v_codes[i].string; i++) { + if (strcmp(str, x509_v_codes[i].string) == 0) { + return x509_v_codes[i].code; + } + } + + return -1; +} + +/* + * Return the constant name corresponding to the X509_V_ERR code + * See https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in + * If not found, return NULL; + */ +const char *x509_v_err_int_to_str(int code) +{ + int i; + + if (code == -1) + return NULL; + + for (i = 0; x509_v_codes[i].string; i++) { + if (x509_v_codes[i].code == code) { + return x509_v_codes[i].string; + } + } + return NULL; +}