summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/certdb.c154
-rw-r--r--content/certdb.h18
-rw-r--r--content/content.h13
-rw-r--r--content/fetch.c229
-rw-r--r--content/fetch.h22
-rw-r--r--content/fetchcache.c26
6 files changed, 438 insertions, 24 deletions
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 <jmb202@ecs.soton.ac.uk>
+ */
+
+/** \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 <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#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 <jmb202@ecs.soton.ac.uk>
+ */
+
+/** \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;