#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
# 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
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);
if (memchr(certname_s, '\0', certname_len) != NULL) {
debug_return_int(MalformedCertificate);
}
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);
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;
}
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);
}
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);
}
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);
}
common_name_entry = X509_NAME_get_entry(X509_get_subject_name(cert),
common_name_loc);
if (common_name_entry == NULL) {
debug_return_int(Error);
}
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);
}
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);
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);
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) {
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
if (current_name->d.iPAddress->length == 4) {
if (inet_ntop(AF_INET, san_ip, san_ip_str, INET_ADDRSTRLEN) == NULL) {
ret = MalformedCertificate;
break;
}
#if defined(HAVE_STRUCT_IN6_ADDR)
} else if (current_name->d.iPAddress->length == 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);
}
HostnameValidationResult
validate_hostname(X509 *cert, const char *hostname, const char *ipaddr)
{
HostnameValidationResult ret;
debug_decl(validate_hostname, SUDO_DEBUG_UTIL);
ret = matches_subject_alternative_name(hostname, ipaddr, cert);
if (ret == NoSANPresent) {
ret = matches_common_name(hostname, cert);
}
debug_return_int(ret);
}
#endif