From ghudson at mit.edu Wed Mar 4 02:09:33 2026 From: ghudson at mit.edu (ghudson at mit.edu) Date: Wed, 4 Mar 2026 02:09:33 -0500 (EST) Subject: krb5 commit: Use X509_check_host() to verify KKDCP server cert Message-ID: <20260304070933.282FF1046F6@krbdev.mit.edu> https://github.com/krb5/krb5/commit/f5bbfa4821cf590a4748f96d0e016bc0485e95c4 commit f5bbfa4821cf590a4748f96d0e016bc0485e95c4 Author: Greg Hudson Date: Wed Feb 25 19:05:40 2026 -0500 Use X509_check_host() to verify KKDCP server cert In the k5tls module, rely on X509_check_host() and X509_check_ip_asc(), which were added in OpenSSL 1.0.2, instead of doing our own verification. There is one notable difference in behavior: X509_check_host() admits wildcards with a prefix or suffix (but not both) within the label, like "kdc*.mydomain.com". The old code only allows a wildcard to match a complete label. ticket: 9198 (new) src/plugins/tls/k5tls/openssl.c | 211 ++-------------------------------------- 1 file changed, 6 insertions(+), 205 deletions(-) diff --git a/src/plugins/tls/k5tls/openssl.c b/src/plugins/tls/k5tls/openssl.c index aab67c01c..42d72dc9e 100644 --- a/src/plugins/tls/k5tls/openssl.c +++ b/src/plugins/tls/k5tls/openssl.c @@ -71,218 +71,19 @@ flush_errors(krb5_context context) } } -/* Return the passed-in character, lower-cased if it's an ASCII character. */ -static inline char -ascii_tolower(char p) -{ - if (KRB5_UPPER(p)) - return p + ('a' - 'A'); - return p; -} - -/* - * Check a single label. If allow_wildcard is true, and the presented name - * includes a wildcard, return true and note that we matched a wildcard. - * Otherwise, for both the presented and expected values, do a case-insensitive - * comparison of ASCII characters, and a case-sensitive comparison of - * everything else. - */ -static krb5_boolean -label_match(const char *presented, size_t plen, const char *expected, - size_t elen, krb5_boolean allow_wildcard, krb5_boolean *wildcard) -{ - unsigned int i; - - if (allow_wildcard && plen == 1 && presented[0] == '*') { - *wildcard = TRUE; - return TRUE; - } - - if (plen != elen) - return FALSE; - - for (i = 0; i < elen; i++) { - if (ascii_tolower(presented[i]) != ascii_tolower(expected[i])) - return FALSE; - } - return TRUE; -} - -/* Break up the two names and check them, label by label. */ -static krb5_boolean -domain_match(const char *presented, size_t plen, const char *expected) -{ - const char *p, *q, *r, *s; - int n_label; - krb5_boolean used_wildcard = FALSE; - - n_label = 0; - p = presented; - r = expected; - while (p < presented + plen && *r != '\0') { - q = memchr(p, '.', plen - (p - presented)); - if (q == NULL) - q = presented + plen; - s = r + strcspn(r, "."); - if (!label_match(p, q - p, r, s - r, n_label == 0, &used_wildcard)) - return FALSE; - p = q < presented + plen ? q + 1 : q; - r = *s ? s + 1 : s; - n_label++; - } - if (used_wildcard && n_label <= 2) - return FALSE; - if (p == presented + plen && *r == '\0') - return TRUE; - return FALSE; -} - -/* Fetch the list of subjectAltNames from a certificate. */ -static GENERAL_NAMES * -get_cert_sans(X509 *x) -{ - int ext; - X509_EXTENSION *san_ext; - - ext = X509_get_ext_by_NID(x, NID_subject_alt_name, -1); - if (ext < 0) - return NULL; - san_ext = X509_get_ext(x, ext); - if (san_ext == NULL) - return NULL; - return X509V3_EXT_d2i(san_ext); -} - -/* Fetch a CN value from the subjct name field, returning its length, or -1 if - * there is no subject name or it contains no CN value. */ -static int -get_cert_cn(X509 *x, char *buf, size_t bufsize) -{ - X509_NAME *name; - - name = X509_get_subject_name(x); - if (name == NULL) - return -1; - return X509_NAME_get_text_by_NID(name, NID_commonName, buf, bufsize); -} - -/* Return true if text matches any of the addresses we can recover from x. */ -static krb5_boolean -check_cert_address(X509 *x, const char *text) -{ - char buf[1024]; - GENERAL_NAMES *sans; - GENERAL_NAME *san = NULL; - ASN1_OCTET_STRING *ip; - krb5_boolean found_ip_san = FALSE, matched = FALSE; - int n_sans, i; - int name_length; - struct in_addr sin; - struct in6_addr sin6; - - /* Parse the IP address into an octet string. */ - ip = ASN1_OCTET_STRING_new(); - if (ip == NULL) - return FALSE; - if (inet_pton(AF_INET, text, &sin)) { - ASN1_OCTET_STRING_set(ip, (unsigned char *)&sin, sizeof(sin)); - } else if (inet_pton(AF_INET6, text, &sin6)) { - ASN1_OCTET_STRING_set(ip, (unsigned char *)&sin6, sizeof(sin6)); - } else { - ASN1_OCTET_STRING_free(ip); - return FALSE; - } - - /* Check for matches in ipaddress subjectAltName values. */ - sans = get_cert_sans(x); - if (sans != NULL) { - n_sans = sk_GENERAL_NAME_num(sans); - for (i = 0; i < n_sans; i++) { - san = sk_GENERAL_NAME_value(sans, i); - if (san->type != GEN_IPADD) - continue; - found_ip_san = TRUE; - matched = (ASN1_OCTET_STRING_cmp(ip, san->d.iPAddress) == 0); - if (matched) - break; - } - sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free); - } - ASN1_OCTET_STRING_free(ip); - - if (found_ip_san) - return matched; - - /* Check for a match against the CN value in the peer's subject name. */ - name_length = get_cert_cn(x, buf, sizeof(buf)); - if (name_length >= 0) { - /* Do a string compare to check if it's an acceptable value. */ - return strlen(text) == (size_t)name_length && - strncmp(text, buf, name_length) == 0; - } - - /* We didn't find a match. */ - return FALSE; -} - -/* Return true if expected matches any of the names we can recover from x. */ -static krb5_boolean -check_cert_servername(X509 *x, const char *expected) -{ - char buf[1024]; - GENERAL_NAMES *sans; - GENERAL_NAME *san = NULL; - unsigned char *dnsname; - krb5_boolean found_dns_san = FALSE, matched = FALSE; - int name_length, n_sans, i; - - /* Check for matches in dnsname subjectAltName values. */ - sans = get_cert_sans(x); - if (sans != NULL) { - n_sans = sk_GENERAL_NAME_num(sans); - for (i = 0; i < n_sans; i++) { - san = sk_GENERAL_NAME_value(sans, i); - if (san->type != GEN_DNS) - continue; - found_dns_san = TRUE; - dnsname = NULL; - name_length = ASN1_STRING_to_UTF8(&dnsname, san->d.dNSName); - if (dnsname == NULL) - continue; - matched = domain_match((char *)dnsname, name_length, expected); - OPENSSL_free(dnsname); - if (matched) - break; - } - sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free); - } - - if (matched) - return TRUE; - if (found_dns_san) - return matched; - - /* Check for a match against the CN value in the peer's subject name. */ - name_length = get_cert_cn(x, buf, sizeof(buf)); - if (name_length >= 0) - return domain_match(buf, name_length, expected); - - /* We didn't find a match. */ - return FALSE; -} - static krb5_boolean check_cert_name_or_ip(X509 *x, const char *expected_name) { struct in_addr in; struct in6_addr in6; + int r; if (inet_pton(AF_INET, expected_name, &in) != 0 || - inet_pton(AF_INET6, expected_name, &in6) != 0) { - return check_cert_address(x, expected_name); - } else { - return check_cert_servername(x, expected_name); - } + inet_pton(AF_INET6, expected_name, &in6) != 0) + r = X509_check_ip_asc(x, expected_name, 0); + else + r = X509_check_host(x, expected_name, 0, 0, NULL); + return r == 1; } static int