From d4d3e5ee1c9edb67844b693be0202ee5968d61c3 Mon Sep 17 00:00:00 2001 From: John Mark Bell Date: Thu, 23 Feb 2006 15:06:54 +0000 Subject: [project @ 2006-02-23 15:06:53 by jmb] Handle invalid SSL certificates better - UI still needs work. Modify fetch callback data parameter type to remove compiler warnings. Constify things. Lose global ssl_verify_certificates option. Fix issue when closing a dialog without input focus. svn path=/import/netsurf/; revision=2092 --- content/certdb.c | 154 ++++++++++++++++++++++++++++++++++ content/certdb.h | 18 ++++ content/content.h | 13 ++- content/fetch.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++++--- content/fetch.h | 22 ++++- content/fetchcache.c | 26 ++++-- 6 files changed, 438 insertions(+), 24 deletions(-) create mode 100644 content/certdb.c create mode 100644 content/certdb.h (limited to 'content') diff --git a/content/certdb.c b/content/certdb.c new file mode 100644 index 000000000..78c6ec04f --- /dev/null +++ b/content/certdb.c @@ -0,0 +1,154 @@ +/* + * This file is part of NetSurf, http://netsurf.sourceforge.net/ + * Licensed under the GNU General Public License, + * http://www.opensource.org/licenses/gpl-license + * Copyright 2006 John M Bell + */ + +/** \file + * HTTPS certificate verification database (implementation) + * + * URLs of servers with invalid SSL certificates are stored hashed by + * canonical root URI (absoluteURI with no abs_path part - see RFC 2617) + * for fast lookup. + */ +#include +#include +#include +#include +#include "netsurf/utils/config.h" +#include "netsurf/content/certdb.h" +#define NDEBUG +#include "netsurf/utils/log.h" +#include "netsurf/utils/url.h" + +#define HASH_SIZE 77 + +#ifdef WITH_SSL + +struct cert_entry { + char *root_url; /**< Canonical root URL */ + struct cert_entry *next; +}; + +static struct cert_entry *cert_table[HASH_SIZE]; + +static unsigned int certdb_hash(const char *s); +static void certdb_dump(void); + +/** + * Insert an entry into the database + * + * \param url Absolute URL to resource + * \return true on success, false on error. + */ +bool certdb_insert(const char *url) +{ + char *canon; + unsigned int hash; + struct cert_entry *entry; + url_func_result ret; + + assert(url); + + LOG(("Adding '%s'", url)); + + ret = url_canonical_root(url, &canon); + if (ret != URL_FUNC_OK) + return false; + + LOG(("'%s'", canon)); + + hash = certdb_hash(canon); + + /* Look for existing entry */ + for (entry = cert_table[hash]; entry; entry = entry->next) { + if (strcmp(entry->root_url, canon) == 0) { + free(canon); + return true; + } + } + + /* not found => create new */ + entry = malloc(sizeof(struct cert_entry)); + if (!entry) { + free(canon); + return false; + } + + entry->root_url = canon; + entry->next = cert_table[hash]; + cert_table[hash] = entry; + + return true; +} + +/** + * Retrieve certificate details for an URL from the database + * + * \param url Absolute URL to consider + * \return certificate details, or NULL if none found. + */ +const char *certdb_get(const char *url) +{ + char *canon; + struct cert_entry *entry; + url_func_result ret; + + assert(url); + + LOG(("Searching for '%s'", url)); + + certdb_dump(); + + ret = url_canonical_root(url, &canon); + if (ret != URL_FUNC_OK) + return NULL; + + /* Find cert entry */ + for (entry = cert_table[certdb_hash(canon)]; entry; + entry = entry->next) { + if (strcmp(entry->root_url, canon) == 0) { + free(canon); + return entry->root_url; + } + } + + return NULL; +} + +/** + * Hash function for keys. + */ +unsigned int certdb_hash(const char *s) +{ + unsigned int i, z = 0, m; + if (!s) + return 0; + + m = strlen(s); + + for (i = 0; i != m && s[i]; i++) + z += s[i] & 0x1f; /* lower 5 bits, case insensitive */ + return z % HASH_SIZE; +} + +/** + * Dump contents of auth db to stderr + */ +void certdb_dump(void) +{ +#ifndef NDEBUG + int i; + struct cert_entry *e; + + for (i = 0; i != HASH_SIZE; i++) { + LOG(("%d:", i)); + for (e = cert_table[i]; e; e = e->next) { + LOG(("\t%s", e->root_url)); + } + } +#endif +} + +#endif diff --git a/content/certdb.h b/content/certdb.h new file mode 100644 index 000000000..28aa88664 --- /dev/null +++ b/content/certdb.h @@ -0,0 +1,18 @@ +/* + * This file is part of NetSurf, http://netsurf.sourceforge.net/ + * Licensed under the GNU General Public License, + * http://www.opensource.org/licenses/gpl-license + * Copyright 2006 John M Bell + */ + +/** \file + * HTTPS certificate verification database (interface) + */ + +#ifndef _NETSURF_CONTENT_CERTDB_H_ +#define _NETSURF_CONTENT_CERTDB_H_ + +bool certdb_insert(const char *url); +const char *certdb_get(const char *url); + +#endif diff --git a/content/content.h b/content/content.h index f40888009..21c4f399c 100644 --- a/content/content.h +++ b/content/content.h @@ -139,6 +139,7 @@ struct cache_data; struct content; struct fetch; struct object_params; +struct ssl_cert_info; /** Used in callbacks to indicate what has occurred. */ @@ -154,7 +155,10 @@ typedef enum { CONTENT_MSG_NEWPTR, /**< address of structure has changed */ CONTENT_MSG_REFRESH, /**< wants refresh */ #ifdef WITH_AUTH - CONTENT_MSG_AUTH /**< authentication required */ + CONTENT_MSG_AUTH, /**< authentication required */ +#endif +#ifdef WITH_SSL + CONTENT_MSG_SSL /**< SSL cert verify failed */ #endif } content_msg; @@ -175,8 +179,13 @@ union content_msg_data { /** Dimensions to plot object with. */ float object_width, object_height; } redraw; - char *auth_realm; /**< Realm, for CONTENT_MSG_AUTH. */ + const char *auth_realm; /**< Realm, for CONTENT_MSG_AUTH. */ int delay; /**< Minimum delay, for CONTENT_MSG_REFRESH */ + struct { + /** Certificate chain (certs[0] == server) */ + const struct ssl_cert_info *certs; + unsigned long num; /**< Number of certs in chain */ + } ssl; }; /** Linked list of users of a content. */ diff --git a/content/fetch.c b/content/fetch.c index 0d9ccd583..25202340b 100644 --- a/content/fetch.c +++ b/content/fetch.c @@ -31,9 +31,15 @@ #endif #include "curl/curl.h" #include "netsurf/utils/config.h" +#ifdef WITH_SSL +#include "openssl/ssl.h" +#endif #ifdef WITH_AUTH #include "netsurf/content/authdb.h" #endif +#ifdef WITH_SSL +#include "netsurf/content/certdb.h" +#endif #include "netsurf/content/fetch.h" #include "netsurf/desktop/options.h" #include "netsurf/render/form.h" @@ -46,10 +52,18 @@ bool fetch_active; /**< Fetches in progress, please call fetch_poll(). */ +#ifdef WITH_SSL +/** SSL certificate info */ +struct cert_info { + X509 *cert; /**< Pointer to certificate */ + long err; /**< OpenSSL error code */ +}; +#endif + /** Information for a single fetch. */ struct fetch { CURL * curl_handle; /**< cURL handle if being fetched, or 0. */ - void (*callback)(fetch_msg msg, void *p, const char *data, + void (*callback)(fetch_msg msg, void *p, const void *data, unsigned long size); /**< Callback function. */ bool had_headers; /**< Headers have been processed. */ @@ -70,6 +84,10 @@ struct fetch { struct cache_data cachedata; /**< Cache control data */ time_t last_modified; /**< If-Modified-Since time */ time_t file_etag; /**< ETag for local objects */ +#ifdef WITH_SSL +#define MAX_CERTS 10 + struct cert_info cert_data[MAX_CERTS]; /**< HTTPS certificate data */ +#endif struct fetch *queue_prev; /**< Previous fetch for this host. */ struct fetch *queue_next; /**< Next fetch for this host. */ struct fetch *prev; /**< Previous active fetch in ::fetch_list. */ @@ -86,6 +104,9 @@ static char fetch_progress_buffer[256]; /**< Progress buffer for cURL */ static char fetch_proxy_userpwd[100]; /**< Proxy authentication details. */ static CURLcode fetch_set_options(struct fetch *f); +#ifdef WITH_SSL +static CURLcode fetch_sslctxfun(CURL *curl_handle, SSL_CTX *sslctx, void *p); +#endif static void fetch_free(struct fetch *f); static void fetch_stop(struct fetch *f); static void fetch_done(CURL *curl_handle, CURLcode result); @@ -98,6 +119,10 @@ static size_t fetch_curl_header(char *data, size_t size, size_t nmemb, static bool fetch_process_headers(struct fetch *f); static struct curl_httppost *fetch_post_convert( struct form_successful_control *control); +#ifdef WITH_SSL +static int fetch_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx); +static int fetch_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *parm); +#endif /** @@ -148,14 +173,6 @@ void fetch_init(void) if (option_ca_bundle) SETOPT(CURLOPT_CAINFO, option_ca_bundle); - if (!option_ssl_verify_certificates) { - /* disable verification of SSL certificates. - * security? we've heard of it... - */ - SETOPT(CURLOPT_SSL_VERIFYPEER, 0L); - SETOPT(CURLOPT_SSL_VERIFYHOST, 0L); - } - return; curl_easy_setopt_failed: @@ -207,7 +224,7 @@ void fetch_quit(void) */ struct fetch * fetch_start(char *url, char *referer, - void (*callback)(fetch_msg msg, void *p, const char *data, + void (*callback)(fetch_msg msg, void *p, const void *data, unsigned long size), void *p, bool only_2xx, char *post_urlenc, struct form_successful_control *post_multipart, bool cookies, @@ -288,6 +305,9 @@ struct fetch * fetch_start(char *url, char *referer, fetch->cachedata.last_modified = 0; fetch->last_modified = 0; fetch->file_etag = 0; +#ifdef WITH_SSL + memset(fetch->cert_data, 0, sizeof(fetch->cert_data)); +#endif fetch->queue_prev = 0; fetch->queue_next = 0; fetch->prev = 0; @@ -473,10 +493,37 @@ CURLcode fetch_set_options(struct fetch *f) } } +#ifdef WITH_SSL + if (certdb_get(f->url) != NULL) { + /* Disable certificate verification */ + SETOPT(CURLOPT_SSL_VERIFYPEER, 0L); + SETOPT(CURLOPT_SSL_VERIFYHOST, 0L); + } else { + /* do verification */ + SETOPT(CURLOPT_SSL_CTX_FUNCTION, fetch_sslctxfun); + SETOPT(CURLOPT_SSL_CTX_DATA, f); + } +#endif + return CURLE_OK; } +#ifdef WITH_SSL +/** + * cURL SSL setup callback + */ + +CURLcode fetch_sslctxfun(CURL *curl_handle, SSL_CTX *sslctx, void *parm) +{ + SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, fetch_verify_callback); + SSL_CTX_set_cert_verify_callback(sslctx, fetch_cert_verify_callback, + parm); + return CURLE_OK; +} +#endif + + /** * Abort a fetch. */ @@ -577,6 +624,10 @@ void fetch_stop(struct fetch *f) void fetch_free(struct fetch *f) { +#ifdef WITH_SSL + int i; +#endif + if (f->curl_handle) curl_easy_cleanup(f->curl_handle); free(f->url); @@ -590,6 +641,15 @@ void fetch_free(struct fetch *f) if (f->post_multipart) curl_formfree(f->post_multipart); free(f->cachedata.etag); + +#ifdef WITH_SSL + for (i = 0; i < MAX_CERTS && f->cert_data[i].cert; i++) { + f->cert_data[i].cert->references--; + if (f->cert_data[i].cert->references == 0) + X509_free(f->cert_data[i].cert); + } +#endif + free(f); } @@ -641,13 +701,20 @@ void fetch_done(CURL *curl_handle, CURLcode result) { bool finished = false; bool error = false; +#ifdef WITH_SSL + bool cert = false; +#endif bool abort; struct fetch *f; void *p; - void (*callback)(fetch_msg msg, void *p, const char *data, + void (*callback)(fetch_msg msg, void *p, const void *data, unsigned long size); CURLcode code; struct cache_data cachedata; +#ifdef WITH_SSL + struct cert_info certs[MAX_CERTS]; + memset(certs, 0, sizeof(certs)); +#endif /* find the structure associated with this fetch */ code = curl_easy_getinfo(curl_handle, CURLINFO_PRIVATE, &f); @@ -669,6 +736,14 @@ void fetch_done(CURL *curl_handle, CURLcode result) /* CURLE_WRITE_ERROR occurs when fetch_curl_data * returns 0, which we use to abort intentionally */ ; +#ifdef WITH_SSL + else if (result == CURLE_SSL_PEER_CERTIFICATE || + result == CURLE_SSL_CACERT) { + memcpy(certs, f->cert_data, sizeof(certs)); + memset(f->cert_data, 0, sizeof(f->cert_data)); + cert = true; + } +#endif else error = true; @@ -685,9 +760,91 @@ void fetch_done(CURL *curl_handle, CURLcode result) if (abort) ; /* fetch was aborted: no callback */ else if (finished) { - callback(FETCH_FINISHED, p, (const char *)&cachedata, 0); + callback(FETCH_FINISHED, p, &cachedata, 0); free(cachedata.etag); } +#ifdef WITH_SSL + else if (cert) { + int i; + BIO *mem; + BUF_MEM *buf; + struct ssl_cert_info ssl_certs[MAX_CERTS]; + + for (i = 0; i < MAX_CERTS && certs[i].cert; i++) { + ssl_certs[i].version = + X509_get_version(certs[i].cert); + + mem = BIO_new(BIO_s_mem()); + ASN1_TIME_print(mem, + X509_get_notBefore(certs[i].cert)); + BIO_get_mem_ptr(mem, &buf); + BIO_set_close(mem, BIO_NOCLOSE); + BIO_free(mem); + snprintf(ssl_certs[i].not_before, + min(sizeof ssl_certs[i].not_before, + (unsigned) buf->length + 1), + "%s", buf->data); + BUF_MEM_free(buf); + + mem = BIO_new(BIO_s_mem()); + ASN1_TIME_print(mem, + X509_get_notAfter(certs[i].cert)); + BIO_get_mem_ptr(mem, &buf); + BIO_set_close(mem, BIO_NOCLOSE); + BIO_free(mem); + snprintf(ssl_certs[i].not_after, + min(sizeof ssl_certs[i].not_after, + (unsigned) buf->length + 1), + "%s", buf->data); + BUF_MEM_free(buf); + + ssl_certs[i].sig_type = + X509_get_signature_type(certs[i].cert); + ssl_certs[i].serial = + ASN1_INTEGER_get( + X509_get_serialNumber(certs[i].cert)); + mem = BIO_new(BIO_s_mem()); + X509_NAME_print_ex(mem, + X509_get_issuer_name(certs[i].cert), + 0, XN_FLAG_SEP_CPLUS_SPC | + XN_FLAG_DN_REV | XN_FLAG_FN_NONE); + BIO_get_mem_ptr(mem, &buf); + BIO_set_close(mem, BIO_NOCLOSE); + BIO_free(mem); + snprintf(ssl_certs[i].issuer, + min(sizeof ssl_certs[i].issuer, + (unsigned) buf->length + 1), + "%s", buf->data); + BUF_MEM_free(buf); + + mem = BIO_new(BIO_s_mem()); + X509_NAME_print_ex(mem, + X509_get_subject_name(certs[i].cert), + 0, XN_FLAG_SEP_CPLUS_SPC | + XN_FLAG_DN_REV | XN_FLAG_FN_NONE); + BIO_get_mem_ptr(mem, &buf); + BIO_set_close(mem, BIO_NOCLOSE); + BIO_free(mem); + snprintf(ssl_certs[i].subject, + min(sizeof ssl_certs[i].subject, + (unsigned) buf->length + 1), + "%s", buf->data); + BUF_MEM_free(buf); + + ssl_certs[i].cert_type = + X509_certificate_type(certs[i].cert, + X509_get_pubkey(certs[i].cert)); + + /* and clean up */ + certs[i].cert->references--; + if (certs[i].cert->references == 0) + X509_free(certs[i].cert); + } + + callback(FETCH_CERT_ERR, p, &ssl_certs, i); + + } +#endif else if (error) callback(FETCH_ERROR, p, fetch_error_buffer, 0); } @@ -1096,7 +1253,7 @@ bool fetch_can_fetch(const char *url) */ void fetch_change_callback(struct fetch *fetch, - void (*callback)(fetch_msg msg, void *p, const char *data, + void (*callback)(fetch_msg msg, void *p, const void *data, unsigned long size), void *p) { @@ -1106,6 +1263,52 @@ void fetch_change_callback(struct fetch *fetch, } +#ifdef WITH_SSL +/** + * OpenSSL Certificate verification callback + * Stores certificate details in fetch struct. + */ + +int fetch_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + X509 *cert = X509_STORE_CTX_get_current_cert(x509_ctx); + int depth = X509_STORE_CTX_get_error_depth(x509_ctx); + int err = X509_STORE_CTX_get_error(x509_ctx); + struct fetch *f = X509_STORE_CTX_get_app_data(x509_ctx); + + /* save the certificate by incrementing the reference count and + * keeping a pointer */ + if (depth < MAX_CERTS && !f->cert_data[depth].cert) { + f->cert_data[depth].cert = cert; + f->cert_data[depth].err = err; + cert->references++; + } + + return preverify_ok; +} + + +/** + * OpenSSL certificate chain verification callback + * Verifies certificate chain, setting up context for fetch_verify_callback + */ + +int fetch_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *parm) +{ + int ok; + + /* Store fetch struct in context for verify callback */ + ok = X509_STORE_CTX_set_app_data(x509_ctx, parm); + + /* and verify the certificate chain */ + if (ok) + ok = X509_verify_cert(x509_ctx); + + return ok; +} +#endif + + /** * testing framework */ diff --git a/content/fetch.h b/content/fetch.h index 633a87131..5dd2d80cb 100644 --- a/content/fetch.h +++ b/content/fetch.h @@ -25,7 +25,10 @@ typedef enum { FETCH_REDIRECT, FETCH_NOTMODIFIED, #ifdef WITH_AUTH - FETCH_AUTH + FETCH_AUTH, +#endif +#ifdef WITH_SSL + FETCH_CERT_ERR, #endif } fetch_msg; @@ -46,12 +49,25 @@ struct cache_data { time_t last_modified; /**< Last-Modified: response header */ }; +#ifdef WITH_SSL +struct ssl_cert_info { + long version; /**< Certificate version */ + char not_before[32]; /**< Valid from date */ + char not_after[32]; /**< Valid to date */ + int sig_type; /**< Signature type */ + long serial; /**< Serial number */ + char issuer[256]; /**< Issuer details */ + char subject[256]; /**< Subject details */ + int cert_type; /**< Certificate type */ +}; +#endif + extern bool fetch_active; extern CURLM *fetch_curl_multi; void fetch_init(void); struct fetch * fetch_start(char *url, char *referer, - void (*callback)(fetch_msg msg, void *p, const char *data, + void (*callback)(fetch_msg msg, void *p, const void *data, unsigned long size), void *p, bool only_2xx, char *post_urlenc, struct form_successful_control *post_multipart, @@ -63,7 +79,7 @@ const char *fetch_filetype(const char *unix_path); char *fetch_mimetype(const char *ro_path); bool fetch_can_fetch(const char *url); void fetch_change_callback(struct fetch *fetch, - void (*callback)(fetch_msg msg, void *p, const char *data, + void (*callback)(fetch_msg msg, void *p, const void *data, unsigned long size), void *p); diff --git a/content/fetchcache.c b/content/fetchcache.c index 62fa07e3f..47f24e89c 100644 --- a/content/fetchcache.c +++ b/content/fetchcache.c @@ -33,13 +33,13 @@ static char error_page[1000]; static regex_t re_content_type; -static void fetchcache_callback(fetch_msg msg, void *p, const char *data, +static void fetchcache_callback(fetch_msg msg, void *p, const void *data, unsigned long size); static char *fetchcache_parse_type(const char *s, char **params[]); static void fetchcache_error_page(struct content *c, const char *error); static void fetchcache_cache_update(struct content *c, const struct cache_data *data); -static void fetchcache_notmodified(struct content *c, const char *data); +static void fetchcache_notmodified(struct content *c, const void *data); /** @@ -307,7 +307,7 @@ void fetchcache_go(struct content *content, char *referer, * This is called when the status of a fetch changes. */ -void fetchcache_callback(fetch_msg msg, void *p, const char *data, +void fetchcache_callback(fetch_msg msg, void *p, const void *data, unsigned long size) { bool res; @@ -375,7 +375,7 @@ void fetchcache_callback(fetch_msg msg, void *p, const char *data, break; case FETCH_ERROR: - LOG(("FETCH_ERROR, '%s'", data)); + LOG(("FETCH_ERROR, '%s'", (const char *)data)); c->fetch = 0; if (c->no_error_pages) { c->status = CONTENT_STATUS_ERROR; @@ -425,7 +425,7 @@ void fetchcache_callback(fetch_msg msg, void *p, const char *data, #ifdef WITH_AUTH case FETCH_AUTH: /* data -> string containing the Realm */ - LOG(("FETCH_AUTH, '%s'", data)); + LOG(("FETCH_AUTH, '%s'", (const char *)data)); c->fetch = 0; msg_data.auth_realm = data; content_broadcast(c, CONTENT_MSG_AUTH, msg_data); @@ -434,6 +434,20 @@ void fetchcache_callback(fetch_msg msg, void *p, const char *data, c->status = CONTENT_STATUS_ERROR; break; #endif + +#ifdef WITH_SSL + case FETCH_CERT_ERR: + c->fetch = 0; + /* set the status to ERROR so that the content is + * destroyed in content_clean() */ + c->status = CONTENT_STATUS_ERROR; + + msg_data.ssl.certs = data; + msg_data.ssl.num = size; + content_broadcast(c, CONTENT_MSG_SSL, msg_data); + break; +#endif + default: assert(0); } @@ -597,7 +611,7 @@ void fetchcache_cache_update(struct content *c, * Not modified callback handler */ -void fetchcache_notmodified(struct content *c, const char *data) +void fetchcache_notmodified(struct content *c, const void *data) { struct content *fb; union content_msg_data msg_data; -- cgit v1.2.3