Skip to content

Commit

Permalink
Implement fallback to ASN1_STRING_data
Browse files Browse the repository at this point in the history
Fixes #359.
  • Loading branch information
k0ekk0ek committed Jul 29, 2024
1 parent 806118a commit 2c1e088
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 133 deletions.
32 changes: 16 additions & 16 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -359,11 +359,11 @@ AC_DEFUN([CHECK_SSL], [
])
if test x_$withval != x_no; then
AC_MSG_CHECKING(for SSL)
if test -n "$withval"; then
dnl look for openssl install with different version, eg.
dnl in /usr/include/openssl11/openssl/ssl.h
dnl and /usr/lib64/openssl11/libssl.so
dnl with the --with-ssl=/usr/include/openssl11
if test -n "$withval"; then
dnl look for openssl install with different version, eg.
dnl in /usr/include/openssl11/openssl/ssl.h
dnl and /usr/lib64/openssl11/libssl.so
dnl with the --with-ssl=/usr/include/openssl11
if test ! -f "$withval/include/openssl/ssl.h" -a -f "$withval/openssl/ssl.h"; then
ssldir="$withval"
found_ssl="yes"
Expand All @@ -383,7 +383,7 @@ AC_DEFUN([CHECK_SSL], [
fi
fi
fi
fi
fi
if test x_$withval = x_ -o x_$withval = x_yes; then
withval="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/sfw /usr/local /usr /usr/local/opt/openssl"
fi
Expand All @@ -394,12 +394,12 @@ AC_DEFUN([CHECK_SSL], [
if test x_$ssldir != x_/usr; then
CPPFLAGS="$CPPFLAGS -I$ssldir/include";
fi
ssldir_include="$ssldir/include"
if test ! -d "$ssldir/lib" -a -d "$ssldir/lib64"; then
ssldir_lib="$ssldir/lib64"
else
ssldir_lib="$ssldir/lib"
fi
ssldir_include="$ssldir/include"
if test ! -d "$ssldir/lib" -a -d "$ssldir/lib64"; then
ssldir_lib="$ssldir/lib64"
else
ssldir_lib="$ssldir/lib"
fi
break;
fi
done
Expand All @@ -412,9 +412,9 @@ AC_DEFUN([CHECK_SSL], [
if test x_$ssldir != x_/usr; then
LDFLAGS="$LDFLAGS -L$ssldir_lib";
fi
if test x_$ssldir = x_/usr/sfw; then
LDFLAGS="$LDFLAGS -R$ssldir_lib";
fi
if test x_$ssldir = x_/usr/sfw; then
LDFLAGS="$LDFLAGS -R$ssldir_lib";
fi
fi
AC_SUBST(HAVE_SSL)
fi
Expand Down Expand Up @@ -1048,7 +1048,7 @@ if test x$HAVE_SSL = x"yes"; then
SSL_LIBS="-lssl"
AC_SUBST(SSL_LIBS)
AC_CHECK_HEADERS([openssl/ssl.h openssl/err.h openssl/rand.h openssl/ocsp.h openssl/core_names.h openssl/x509v3.h],,, [AC_INCLUDES_DEFAULT])
AC_CHECK_FUNCS([HMAC_CTX_reset HMAC_CTX_new EVP_cleanup ERR_load_crypto_strings OPENSSL_init_crypto CRYPTO_memcmp EC_KEY_new_by_curve_name EVP_MAC_CTX_new EVP_MAC_CTX_set_params EVP_MAC_CTX_get_mac_size SHA1_Init])
AC_CHECK_FUNCS([HMAC_CTX_reset HMAC_CTX_new EVP_cleanup ERR_load_crypto_strings OPENSSL_init_crypto CRYPTO_memcmp EC_KEY_new_by_curve_name EVP_MAC_CTX_new EVP_MAC_CTX_set_params EVP_MAC_CTX_get_mac_size SHA1_Init ASN1_STRING_get0_data])
if test "$ac_cv_func_SHA1_Init" = "yes"; then
ACX_FUNC_DEPRECATED([SHA1_Init], [(void)SHA1_Init(NULL);], [
#include <openssl/sha.h>
Expand Down
227 changes: 111 additions & 116 deletions options.c
Original file line number Diff line number Diff line change
Expand Up @@ -1983,18 +1983,15 @@ acl_check_incoming(struct acl_options* acl, struct query* q,
if (acl->tls_auth_name && q->tls_auth) {
/* we have auth_domain_name in tls_auth */
if (acl->tls_auth_options && acl->tls_auth_options->auth_domain_name) {
q->cert_cn = NULL;
if (!acl_tls_hostname_matches(q->tls_auth, acl->tls_auth_options->auth_domain_name, &(q->cert_cn))) {
if (!acl_tls_hostname_matches(q->tls_auth, acl->tls_auth_options->auth_domain_name)) {
VERBOSITY(3, (LOG_WARNING,
"client cert %s does not match %s %s",
(q->cert_cn?q->cert_cn:"(null)"), acl->tls_auth_name, acl->tls_auth_options->auth_domain_name));
free(q->cert_cn);
"client cert does not match %s %s",
acl->tls_auth_name, acl->tls_auth_options->auth_domain_name));
q->cert_cn = NULL;
return -1;
}
VERBOSITY(5, (LOG_INFO, "%s %s verified",
acl->tls_auth_name, acl->tls_auth_options->auth_domain_name));
free(q->cert_cn);
q->cert_cn = acl->tls_auth_options->auth_domain_name;
} else {
/* nsd gives error on start for this, but check just in case */
Expand Down Expand Up @@ -2206,35 +2203,122 @@ acl_addr_match_range_v6(uint32_t* minval, uint32_t* x, uint32_t* maxval, size_t
#endif /* INET6 */

#ifdef HAVE_SSL
int
acl_tls_hostname_matches(SSL* tls_auth, const char* acl_cert_cn, char** cert_cn)
/* Code in for matches_subject_alternative_name and matches_common_name
* functions is from https://wiki.openssl.org/index.php/Hostname_validation
* with modifications.
*
* Obtained from: https://github.com/iSECPartners/ssl-conservatory
* Copyright (C) 2012, iSEC Partners.
* License: MIT License
* Author: Alban Diquet
*/
static int matches_subject_alternative_name(
const char *hostname, const X509 *cert)
{
int i;
int result = 0;
int san_names_nb = -1;
STACK_OF(GENERAL_NAME) *san_names = NULL;

/* Try to extract the names within the SAN extension from the certificate */
san_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
if (san_names == NULL)
return 0;

san_names_nb = sk_GENERAL_NAME_num(san_names);

/* Check each name within the extension */
for (int i = 0; i < san_names_nb && !result; i++) {
int index, length;
const char *data;
const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
/* Skip non-DNS SAN entries. */
if (current_name->type != GEN_DNS)
continue;
#if HAVE_ASN1_STRING_GET0_DATA
data = (const char *)ASN1_STRING_get0_data(current_name->d.dNSName);
#else
data = (const char *)ASN1_STRING_data(current_name->d.dNSName);
#endif
if (data == NULL)
continue;
/* Make sure there isn't an embedded NUL character in the DNS name */
/* From the man page: In general it cannot be assumed that the data
* returned by ASN1_STRING_data() is null terminated or does not
* contain embedded nulls. */
index = 0;
length = ASN1_STRING_length(current_name->d.dNSName);
while (index < length && data[index] != 0)
index++;
if (index != length)
break; /* Malformed certificate. */
result = strncasecmp(data, hostname, length) == 0;
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);

return result;
}

static int matches_common_name(
const char *hostname, const X509 *cert)
{
int index, length;
int common_name_loc = -1;
char *common_name_str = NULL;
const char *common_name_str = NULL;
X509_NAME *subject_name = NULL;
X509_NAME_ENTRY *common_name_entry = NULL;
ASN1_STRING *common_name_asn1 = NULL;

X509 *client_cert;
*cert_cn = NULL;
if ((subject_name = X509_get_subject_name(cert)) == NULL)
return 0;

# ifdef HAVE_SSL_GET1_PEER_CERTIFICATE
if ((client_cert = SSL_get1_peer_certificate(tls_auth)) == NULL) {
# else
if ((client_cert = SSL_get_peer_certificate(tls_auth)) == NULL) {
# endif
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "CN match fail no peer certificate"));
X509_free(client_cert);
/* Find the position of the CN field in the Subject field of the certificate */
common_name_loc = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
if (common_name_loc < 0)
return 0;
}
if (!acl_cert_cn || !client_cert) {
X509_free(client_cert);

/* Extract the CN field */
common_name_entry = X509_NAME_get_entry(subject_name, common_name_loc);
if (common_name_entry == NULL)
return 0;

/* Convert the CN field to a C string */
common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
if (common_name_asn1 == NULL)
return 0;

#if HAVE_ASN1_STRING_GET0_DATA
common_name_str = (const char *)ASN1_STRING_get0_data(common_name_asn1);
#else
common_name_str = (const char *)ASN1_STRING_data(common_name_asn1);
#endif

/* Make sure there isn't an embedded NUL character in the CN */
index = 0;
length = ASN1_STRING_length(common_name_asn1);
while (index < length && common_name_str[index] != 0)
index++;
if (index != length)
return 0; /* Malformed certificate. */

return strncasecmp(common_name_str, hostname, length) == 0;
}

int
acl_tls_hostname_matches(SSL* tls_auth, const char *acl_cert_cn)
{
int result = 0;
X509 *client_cert;

assert(acl_cert_cn != NULL);

#ifdef HAVE_SSL_GET1_PEER_CERTIFICATE
client_cert = SSL_get1_peer_certificate(tls_auth);
#else
client_cert = SSL_get_peer_certificate(tls_auth);
#endif

if (client_cert == NULL)
return 0;
}

/* OpenSSL provides functions for hostname checking from certificate
* Following code should work but it doesn't.
Expand All @@ -2248,102 +2332,13 @@ acl_tls_hostname_matches(SSL* tls_auth, const char* acl_cert_cn, char** cert_cn)
* const char *peername = X509_VERIFY_PARAM_get0_peername(vpm); // NOT ok
*/

/* Code in rest of function is from
* https://wiki.openssl.org/index.php/Hostname_validation
* with modifications
*
* Obtained from: https://github.com/iSECPartners/ssl-conservatory
* Copyright (C) 2012, iSEC Partners.
* License: MIT License
* Author: Alban Diquet
*/

/* semi follow RFC6125#section-6.4.4 check SAN DNS first */
if (!(result = matches_subject_alternative_name(acl_cert_cn, client_cert)))
result = matches_common_name(acl_cert_cn, client_cert);

/* Try to extract the names within the SAN extension from the certificate */
san_names = X509_get_ext_d2i(client_cert, NID_subject_alt_name, NULL, NULL);
if (san_names != NULL) {
san_names_nb = sk_GENERAL_NAME_num(san_names);

/* Check each name within the extension */
for (i = 0; i < san_names_nb; i++) {
char* dns_name;
const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
/* skip non-DNS SAN entries */
if (current_name->type != GEN_DNS)
continue;
dns_name = (char *) ASN1_STRING_get0_data(current_name->d.dNSName);
if (dns_name == NULL)
continue;
/* Make sure there isn't an embedded NUL character in the DNS name */
if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
DEBUG(DEBUG_XFRD,2, (LOG_WARNING, "SAN Malformed certificate"));
break;
}
if (dns_name != NULL) {
/* certificate SAN DNS match */
if (strcasecmp(dns_name, acl_cert_cn)==0) {
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "SAN %s matches acl", dns_name));
*cert_cn = strdup(dns_name);
X509_free(client_cert);
return 1;
} else {
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "SAN %s does not match acl", dns_name));
/* check with remaining SANs and then continue with normal CN check */
}
}
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
} else {
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "No SAN DNS extension in certificate"));
}

/* no match on SAN, continue with normal CN */
if ((subject_name = X509_get_subject_name(client_cert)) == NULL) {
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "CN get subject failed"));
X509_free(client_cert);
return 0;
}
/* Find the position of the CN field in the Subject field of the certificate */
common_name_loc = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
if (common_name_loc < 0) {
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "CN not found in Subject"));
X509_free(client_cert);
return 0;
}
/* Extract the CN field */
common_name_entry = X509_NAME_get_entry(subject_name, common_name_loc);
if (common_name_entry == NULL) {
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "CN extraction failed"));
X509_free(client_cert);
return 0;
}
/* Convert the CN field to a C string */
common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
if (common_name_asn1 == NULL) {
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "CN name to string convertion failed"));
X509_free(client_cert);
return 0;
}
common_name_str = (char *) ASN1_STRING_get0_data(common_name_asn1);
/* Make sure there isn't an embedded NUL character in the CN */
if ((size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) {
DEBUG(DEBUG_XFRD,2, (LOG_WARNING, "CN Malformed certificate"));
X509_free(client_cert);
return 0;
}
if (common_name_str != NULL) {
/* store it for logging */
*cert_cn = strdup(common_name_str);
/* certificate CN match */
if (strcasecmp(common_name_str, acl_cert_cn)==0) {
X509_free(client_cert);
return 1;
}
}
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "CN from cert %s does not match acl", (common_name_str?common_name_str:"(null)")));
X509_free(client_cert);
return 0;

return result;
}
#endif

Expand Down
2 changes: 1 addition & 1 deletion options.h
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ int acl_addr_matches_host(struct acl_options* acl, struct acl_options* host);
int acl_addr_matches(struct acl_options* acl, struct query* q);
int acl_addr_matches_proxy(struct acl_options* acl, struct query* q);
#ifdef HAVE_SSL
int acl_tls_hostname_matches(SSL* ssl, const char* acl_cert_cn, char** cert_cn);
int acl_tls_hostname_matches(SSL* ssl, const char* acl_cert_cn);
#endif
int acl_key_matches(struct acl_options* acl, struct query* q);
int acl_addr_match_mask(uint32_t* a, uint32_t* b, uint32_t* mask, size_t sz);
Expand Down

0 comments on commit 2c1e088

Please sign in to comment.