/
githubmirror
/
sudo
Обзор
Документация
Войти
/
githubmirror
/
sudo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
lib/iolog/hostcheck.c
272 строки
8 KB
Theo Buehler
Use accessor for getting an ASN1_STRING's length
22 ноя 2025, 19:19
22 ноя 2025, 19:19
f97a34b
Код
Авторство
О чём код?
/* * Copyright (c) 2020 Laszlo Orban <laszlo.orban@oneidentity.com> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <config.h> #if defined(HAVE_OPENSSL) # if defined(HAVE_WOLFSSL) # include <wolfssl/options.h> # endif # include <sys/types.h> # include <sys/socket.h> # include <netinet/in.h> # include <arpa/inet.h> # include <stdlib.h> # include <string.h> # define NEED_INET_NTOP /* to expose sudo_inet_ntop in sudo_compat.h */ # include <sudo_compat.h> # include <sudo_debug.h> # include <sudo_util.h> # include <hostcheck.h> #ifndef INET_ADDRSTRLEN # define INET_ADDRSTRLEN 16 #endif #ifndef INET6_ADDRSTRLEN # define INET6_ADDRSTRLEN 46 #endif #if !defined(HAVE_ASN1_STRING_GET0_DATA) && !defined(HAVE_WOLFSSL) # define ASN1_STRING_get0_data(x) ASN1_STRING_data(x) #endif /* !HAVE_ASN1_STRING_GET0_DATA && !HAVE_WOLFSSL */ /** * @brief Compares the given hostname with a DNS entry in a certificate. * * The certificate DNS name can contain wildcards in the left-most label. * A wildcard can match only one label. * Accepted names: * - foo.bar.example.com * - *.example.com * - *.bar.example.com * * @param hostname peer's name * @param certname_asn1 hostname in the certificate * * @return MatchFound * MatchNotFound * MalformedCertificate */ static HostnameValidationResult validate_name(const char *hostname, ASN1_STRING *certname_asn1) { const char *certname_s = (const char *)ASN1_STRING_get0_data(certname_asn1); size_t certname_len = (size_t)ASN1_STRING_length(certname_asn1); size_t hostname_len = strlen(hostname); debug_decl(validate_name, SUDO_DEBUG_UTIL); /* Make sure there isn't an embedded NUL character in certname */ if (memchr(certname_s, '\0', certname_len) != NULL) { debug_return_int(MalformedCertificate); } /* Remove last '.' from hostname if it exists */ if (hostname_len != 0 && hostname[hostname_len - 1] == '.') { --hostname_len; } sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "comparing %.*s to %.*s in cert", (int)hostname_len, hostname, (int)certname_len, certname_s); /* Skip the first label if wildcard */ if (certname_len > 2 && certname_s[0] == '*' && certname_s[1] == '.') { while (hostname_len != 0) { --hostname_len; if (*hostname++ == '.') { break; } } certname_s += 2; certname_len -= 2; } /* Compare expected hostname with the DNS name */ if (certname_len != hostname_len) { debug_return_int(MatchNotFound); } if (strncasecmp(hostname, certname_s, hostname_len) != 0) { debug_return_int(MatchNotFound); } debug_return_int(MatchFound); } /** * @brief Matches a hostname with the cert's CN. * * @param hostname remote peer's name or NULL if no name * @param cert peer's X509 certificate * * @return MatchFound * MatchNotFound * MalformedCertificate * Error */ static HostnameValidationResult matches_common_name(const char *hostname, X509 *cert) { X509_NAME_ENTRY *common_name_entry = NULL; ASN1_STRING *common_name_asn1 = NULL; int common_name_loc; debug_decl(matches_common_name, SUDO_DEBUG_UTIL); if (hostname == NULL) { debug_return_int(MatchNotFound); } /* Find the CN field's index in the Subject field of the certificate */ common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(cert), NID_commonName, -1); if (common_name_loc < 0) { debug_return_int(Error); } /* Extract the CN field */ common_name_entry = X509_NAME_get_entry(X509_get_subject_name(cert), common_name_loc); if (common_name_entry == NULL) { debug_return_int(Error); } /* Compare expected hostname with the CN */ common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); if (common_name_asn1 == NULL) { debug_return_int(Error); } if (validate_name(hostname, common_name_asn1) == MatchFound) { debug_return_int(MatchFound); } debug_return_int(MatchNotFound); } /** * @brief Matches a hostname or ipaddr with the cert's corresponding SAN field. * * SAN can have different fields. For hostname matching, the GEN_DNS field is * used. For IP address matching, the GEN_IPADD field is used. * Since SAN is an X503 v3 extension, it may not be preseent in the cert. * * @param hostname remote peer's name or NULL if no name * @param ipaddr remote peer's IP address * @param cert peer's X509 certificate * * @return MatchFound * MatchNotFound * NoSANPresent * MalformedCertificate * Error */ static HostnameValidationResult matches_subject_alternative_name(const char *hostname, const char *ipaddr, X509 *cert) { HostnameValidationResult ret = MatchNotFound; STACK_OF(GENERAL_NAME) *san_names; int i, san_names_nb; debug_decl(matches_subject_alternative_name, SUDO_DEBUG_UTIL); /* Try to extract the names within the SAN extension from the certificate */ san_names = X509_get_ext_d2i((X509 *) cert, NID_subject_alt_name, NULL, NULL); if (san_names == NULL) { debug_return_int(NoSANPresent); } san_names_nb = sk_GENERAL_NAME_num(san_names); /* Check each name within the extension */ for (i = 0; i < san_names_nb; i++) { const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i); if (current_name->type == GEN_DNS && hostname != NULL) { /* Compare expected hostname with the DNS name */ if (validate_name(hostname, current_name->d.dNSName) == MatchFound) { ret = MatchFound; break; } } else if (current_name->type == GEN_IPADD && ipaddr != NULL) { const unsigned char *san_ip = ASN1_STRING_get0_data(current_name->d.iPAddress); #if defined(HAVE_STRUCT_IN6_ADDR) char san_ip_str[INET6_ADDRSTRLEN]; #else char san_ip_str[INET_ADDRSTRLEN]; #endif /* IPV4 address */ if (ASN1_STRING_length(current_name->d.iPAddress) == 4) { if (inet_ntop(AF_INET, san_ip, san_ip_str, INET_ADDRSTRLEN) == NULL) { ret = MalformedCertificate; break; } #if defined(HAVE_STRUCT_IN6_ADDR) /* IPV6 address */ } else if (ASN1_STRING_length(current_name->d.iPAddress) == 16) { if (inet_ntop(AF_INET6, san_ip, san_ip_str, INET6_ADDRSTRLEN) == NULL) { ret = MalformedCertificate; break; } #endif } else { ret = MalformedCertificate; break; } if (strcasecmp(ipaddr, san_ip_str) == 0) { ret = MatchFound; break; } } } sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); debug_return_int(ret); } /** * @brief Perform hostname/IP validation on the given X509 certificate. * * According to RFC 6125 section 6.4.4, if the SAN field is present, it * must be checked first. The certificate's CN field must only be checked * if no SAN field is present. * * @param cert X509 certificate * @param hostname remote peer's name or NULL if no name * @param ipaddr remote peer's IP address * * @return MatchFound * MatchNotFound * MalformedCertificate * Error */ HostnameValidationResult validate_hostname(X509 *cert, const char *hostname, const char *ipaddr) { HostnameValidationResult ret; debug_decl(validate_hostname, SUDO_DEBUG_UTIL); /* Check SAN first if exists */ ret = matches_subject_alternative_name(hostname, ipaddr, cert); /* RFC 6125 section 6.4.4 says only check CN if no SAN name is present */ if (ret == NoSANPresent) { ret = matches_common_name(hostname, cert); } debug_return_int(ret); } #endif /* HAVE_OPENSSL */