switch (verify_error)
{
case X509_V_OK:
+ {
+ X509 *cert = SSL_get_peer_certificate (serv->ssl);
+ int hostname_err;
+ if ((hostname_err = _SSL_check_hostname(cert, serv->hostname)) != 0)
+ {
+ snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname? (%d)%s",
+ hostname_err, serv->accept_invalid_cert ? " -- Ignored" : "");
+ if (serv->accept_invalid_cert)
+ EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0);
+ else
+ goto conn_fail;
+ }
+ break;
+ }
/* snprintf (buf, sizeof (buf), "* Verify OK (?)"); */
/* EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0); */
- break;
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
snprintf (buf, sizeof (buf), "%s.? (%d)",
X509_verify_cert_error_string (verify_error),
verify_error);
+conn_fail:
EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, buf, NULL, NULL,
NULL, 0);
#include "inet.h" /* make it first to avoid macro redefinitions */
#include <openssl/ssl.h> /* SSL_() */
#include <openssl/err.h> /* ERR_() */
+#include <openssl/x509v3.h>
#ifdef WIN32
#include <openssl/rand.h> /* RAND_seed() */
#include "../../config-win32.h" /* HAVE_SNPRINTF */
#include <glib.h>
#include <glib/gprintf.h>
+#include <gio/gio.h>
#include "util.h"
/* If openssl was built without ec */
SSL_free (ssl);
ERR_remove_state (0); /* free state buffer */
}
+
+/* Hostname validation code based on OpenBSD's libtls. */
+
+static int
+_SSL_match_hostname (const char *cert_hostname, const char *hostname)
+{
+ const char *cert_domain, *domain, *next_dot;
+
+ if (g_ascii_strcasecmp (cert_hostname, hostname) == 0)
+ return 0;
+
+ /* Wildcard match? */
+ if (cert_hostname[0] == '*')
+ {
+ /*
+ * Valid wildcards:
+ * - "*.domain.tld"
+ * - "*.sub.domain.tld"
+ * - etc.
+ * Reject "*.tld".
+ * No attempt to prevent the use of eg. "*.co.uk".
+ */
+ cert_domain = &cert_hostname[1];
+ /* Disallow "*" */
+ if (cert_domain[0] == '\0')
+ return -1;
+ /* Disallow "*foo" */
+ if (cert_domain[0] != '.')
+ return -1;
+ /* Disallow "*.." */
+ if (cert_domain[1] == '.')
+ return -1;
+ next_dot = strchr (&cert_domain[1], '.');
+ /* Disallow "*.bar" */
+ if (next_dot == NULL)
+ return -1;
+ /* Disallow "*.bar.." */
+ if (next_dot[1] == '.')
+ return -1;
+
+ domain = strchr (hostname, '.');
+
+ /* No wildcard match against a hostname with no domain part. */
+ if (domain == NULL || strlen(domain) == 1)
+ return -1;
+
+ if (g_ascii_strcasecmp (cert_domain, domain) == 0)
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+_SSL_check_subject_altname (X509 *cert, const char *host)
+{
+ STACK_OF(GENERAL_NAME) *altname_stack = NULL;
+ GInetAddress *addr;
+ GSocketFamily family;
+ int type = GEN_DNS;
+ int count, i;
+ int rv = -1;
+
+ altname_stack = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL);
+ if (altname_stack == NULL)
+ return -1;
+
+ addr = g_inet_address_new_from_string (host);
+ if (addr != NULL)
+ {
+ family = g_inet_address_get_family (addr);
+ if (family == G_SOCKET_FAMILY_IPV4 || family == G_SOCKET_FAMILY_IPV6)
+ type = GEN_IPADD;
+ }
+
+ count = sk_GENERAL_NAME_num(altname_stack);
+ for (i = 0; i < count; i++)
+ {
+ GENERAL_NAME *altname;
+
+ altname = sk_GENERAL_NAME_value (altname_stack, i);
+
+ if (altname->type != type)
+ continue;
+
+ if (type == GEN_DNS)
+ {
+ unsigned char *data;
+ int format;
+
+ format = ASN1_STRING_type (altname->d.dNSName);
+ if (format == V_ASN1_IA5STRING)
+ {
+ data = ASN1_STRING_data (altname->d.dNSName);
+
+ if (ASN1_STRING_length (altname->d.dNSName) != (int)strlen(data))
+ {
+ g_warning("NUL byte in subjectAltName, probably a malicious certificate.\n");
+ rv = -2;
+ break;
+ }
+
+ if (_SSL_match_hostname (data, host) == 0)
+ {
+ rv = 0;
+ break;
+ }
+ }
+ else
+ g_warning ("unhandled subjectAltName dNSName encoding (%d)\n", format);
+
+ }
+ else if (type == GEN_IPADD)
+ {
+ unsigned char *data;
+ const guint8 *addr_bytes;
+ int datalen, addr_len;
+
+ datalen = ASN1_STRING_length (altname->d.iPAddress);
+ data = ASN1_STRING_data (altname->d.iPAddress);
+
+ addr_bytes = g_inet_address_to_bytes (addr);
+ addr_len = (int)g_inet_address_get_native_size (addr);
+
+ if (datalen == addr_len && memcmp (data, addr_bytes, addr_len) == 0)
+ {
+ rv = 0;
+ break;
+ }
+ }
+ }
+
+ if (addr != NULL)
+ g_object_unref (addr);
+ sk_GENERAL_NAME_free (altname_stack);
+ return rv;
+}
+
+static int
+_SSL_check_common_name (X509 *cert, const char *host)
+{
+ X509_NAME *name;
+ char *common_name = NULL;
+ int common_name_len;
+ int rv = -1;
+ GInetAddress *addr;
+
+ name = X509_get_subject_name (cert);
+ if (name == NULL)
+ return -1;
+
+ common_name_len = X509_NAME_get_text_by_NID (name, NID_commonName, NULL, 0);
+ if (common_name_len < 0)
+ return -1;
+
+ common_name = calloc (common_name_len + 1, 1);
+ if (common_name == NULL)
+ return -1;
+
+ X509_NAME_get_text_by_NID (name, NID_commonName, common_name, common_name_len + 1);
+
+ /* NUL bytes in CN? */
+ if (common_name_len != (int)strlen(common_name))
+ {
+ g_warning ("NUL byte in Common Name field, probably a malicious certificate.\n");
+ rv = -2;
+ goto out;
+ }
+
+ if ((addr = g_inet_address_new_from_string (host)) != NULL)
+ {
+ /*
+ * We don't want to attempt wildcard matching against IP
+ * addresses, so perform a simple comparison here.
+ */
+ if (g_strcmp0 (common_name, host) == 0)
+ rv = 0;
+ else
+ rv = -1;
+
+ g_object_unref (addr);
+ }
+ else if (_SSL_match_hostname (common_name, host) == 0)
+ rv = 0;
+
+out:
+ free(common_name);
+ return rv;
+}
+
+int
+_SSL_check_hostname (X509 *cert, const char *host)
+{
+ int rv;
+
+ rv = _SSL_check_subject_altname (cert, host);
+ if (rv == 0 || rv == -2)
+ return rv;
+
+ return _SSL_check_common_name (cert, host);
+}
\ No newline at end of file