diff options
Diffstat (limited to 'src/common/ssl.c')
-rw-r--r-- | src/common/ssl.c | 237 |
1 files changed, 222 insertions, 15 deletions
diff --git a/src/common/ssl.c b/src/common/ssl.c index cfa9b6cf..f4e23665 100644 --- a/src/common/ssl.c +++ b/src/common/ssl.c @@ -25,18 +25,29 @@ #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() */ #endif -#include "../../config.h" +#include "config.h" #include <time.h> /* asctime() */ #include <string.h> /* strncpy() */ #include "ssl.h" /* struct cert_info */ #include <glib.h> #include <glib/gprintf.h> +#include <gio/gio.h> #include "util.h" +/* If openssl was built without ec */ +#ifndef SSL_OP_SINGLE_ECDH_USE +#define SSL_OP_SINGLE_ECDH_USE 0 +#endif + +#ifndef SSL_OP_NO_COMPRESSION +#define SSL_OP_NO_COMPRESSION 0 +#endif + /* globals */ static struct chiper_info chiper_info; /* static buffer for _SSL_get_cipher_info() */ static char err_buf[256]; /* generic error buffer */ @@ -69,32 +80,29 @@ __SSL_critical_error (char *funcname) /* +++++ SSL functions +++++ */ SSL_CTX * -_SSL_context_init (void (*info_cb_func), int server) +_SSL_context_init (void (*info_cb_func)) { SSL_CTX *ctx; -#ifdef WIN32 - int i, r; -#endif SSLeay_add_ssl_algorithms (); SSL_load_error_strings (); - ctx = SSL_CTX_new (server ? SSLv23_server_method() : SSLv23_client_method ()); + ctx = SSL_CTX_new (SSLv23_client_method ()); SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH); SSL_CTX_set_timeout (ctx, 300); + SSL_CTX_set_options (ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3 + |SSL_OP_NO_COMPRESSION + |SSL_OP_SINGLE_DH_USE|SSL_OP_SINGLE_ECDH_USE + |SSL_OP_NO_TICKET + |SSL_OP_CIPHER_SERVER_PREFERENCE); + +#if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined (OPENSSL_NO_COMP) /* workaround for OpenSSL 0.9.8 */ + sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); +#endif /* used in SSL_connect(), SSL_accept() */ SSL_CTX_set_info_callback (ctx, info_cb_func); -#ifdef WIN32 - /* under win32, OpenSSL needs to be seeded with some randomness */ - for (i = 0; i < 128; i++) - { - r = rand (); - RAND_seed ((unsigned char *)&r, sizeof (r)); - } -#endif - return(ctx); } @@ -329,3 +337,202 @@ _SSL_close (SSL * ssl) 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_pop_free (altname_stack, GENERAL_NAME_free); + 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 = g_malloc0 (common_name_len + 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: + g_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); +} |