From: Daniel Carl Date: Mon, 28 Apr 2014 21:58:21 +0000 (+0200) Subject: Load and save hsts whitelist from file (#79). X-Git-Url: https://git.owens.tech/projects.html/projects.html/git?a=commitdiff_plain;h=8ac9f1fb3d87e9bd356b845076804abe975ac56c;p=vimb.git Load and save hsts whitelist from file (#79). --- diff --git a/src/config.def.h b/src/config.def.h index 92bed30..4440a99 100644 --- a/src/config.def.h +++ b/src/config.def.h @@ -44,6 +44,8 @@ /* to get the hack working */ /* #define FEATURE_HIGH_DPI */ #endif +/* enable HTTP Strict-Transport-Security*/ +#define FEATURE_HSTS /* time in seconds after that message will be removed from inputbox if the diff --git a/src/hsts.c b/src/hsts.c index fa0c253..3d93ff6 100644 --- a/src/hsts.c +++ b/src/hsts.c @@ -18,15 +18,21 @@ */ #include "config.h" +#ifdef FEATURE_HSTS #include "hsts.h" +#include "util.h" #include "main.h" +#include #include #include #include #define HSTS_HEADER_NAME "Strict-Transport-Security" +#define HSTS_FILE_FORMAT "%s\t%s\t%c\n" #define HSTS_PROVIDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), HSTS_TYPE_PROVIDER, HSTSProviderPrivate)) +extern VbCore vb; + /* private interface of the provider */ typedef struct _HSTSProviderPrivate { GHashTable* whitelist; @@ -45,10 +51,11 @@ static gboolean should_secure_host(HSTSProvider *provider, static void process_hsts_header(SoupMessage *msg, gpointer data); static void parse_hsts_header(HSTSProvider *provider, const char *host, const char *header); -static HSTSEntry *get_new_entry(int max_age, gboolean include_sub_domains); static void free_entry(HSTSEntry *entry); static void add_host_entry(HSTSProvider *provider, const char *host, HSTSEntry *entry); +static void add_host_entry_to_file(HSTSProvider *provider, const char *host, + HSTSEntry *entry); static void remove_host_entry(HSTSProvider *provider, const char *host); /* session feature related functions */ static void session_feature_init( @@ -59,6 +66,9 @@ static void request_started(SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg, SoupSocket *socket); static void request_unqueued(SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg); +/* caching related functions */ +static void load_entries(HSTSProvider *provider, const char *file); +static void save_entries(HSTSProvider *provider, const char *file); /** @@ -77,22 +87,29 @@ G_DEFINE_TYPE_WITH_CODE( static void hsts_provider_class_init(HSTSProviderClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); hsts_provider_parent_class = g_type_class_peek_parent(klass); g_type_class_add_private(klass, sizeof(HSTSProviderPrivate)); - G_OBJECT_CLASS(klass)->finalize = hsts_provider_finalize; + object_class->finalize = hsts_provider_finalize; } static void hsts_provider_init(HSTSProvider *self) { - /* Initialize private fields */ + /* initialize private fields */ HSTSProviderPrivate *priv = HSTS_PROVIDER_GET_PRIVATE(self); priv->whitelist = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)free_entry); + + /* load entries from hsts file */ + load_entries(self, vb.files[FILES_HSTS]); } static void hsts_provider_finalize(GObject* obj) { HSTSProviderPrivate *priv = HSTS_PROVIDER_GET_PRIVATE (obj); + /* save all the entries in hsts file */ + save_entries(HSTS_PROVIDER(obj), vb.files[FILES_HSTS]); + g_hash_table_destroy(priv->whitelist); G_OBJECT_CLASS(hsts_provider_parent_class)->finalize(obj); } @@ -172,6 +189,7 @@ static void parse_hsts_header(HSTSProvider *provider, const char *host, const char *header) { GHashTable *directives = soup_header_parse_semi_param_list(header); + HSTSEntry *entry; int max_age = G_MAXINT; gboolean include_sub_domains = false; GHashTableIter iter; @@ -213,25 +231,16 @@ static void parse_hsts_header(HSTSProvider *provider, if (max_age == 0) { remove_host_entry(provider, host); } else { - add_host_entry(provider, host, get_new_entry(max_age, include_sub_domains)); + entry = g_new(HSTSEntry, 1); + entry->expires_at = soup_date_new_from_now(max_age); + entry->include_sub_domains = include_sub_domains; + + add_host_entry(provider, host, entry); + add_host_entry_to_file(provider, host, entry); } } } -/** - * Create a new hsts entry for given data. - * Returned entry have to be freed if no more used. - */ -static HSTSEntry *get_new_entry(int max_age, gboolean include_sub_domains) -{ - HSTSEntry *entry = g_new(HSTSEntry, 1); - - entry->expires_at = soup_date_new_from_now(max_age); - entry->include_sub_domains = include_sub_domains; - - return entry; -} - static void free_entry(HSTSEntry *entry) { soup_date_free(entry->expires_at); @@ -246,14 +255,21 @@ static void free_entry(HSTSEntry *entry) static void add_host_entry(HSTSProvider *provider, const char *host, HSTSEntry *entry) { - if (g_hostname_is_ip_address(host)) { - return; - } - HSTSProviderPrivate *priv = HSTS_PROVIDER_GET_PRIVATE(provider); g_hash_table_replace(priv->whitelist, g_hostname_to_unicode(host), entry); } +static void add_host_entry_to_file(HSTSProvider *provider, const char *host, + HSTSEntry *entry) +{ + char *date = soup_date_to_string(entry->expires_at, SOUP_DATE_ISO8601_FULL); + + util_file_append( + vb.files[FILES_HSTS], HSTS_FILE_FORMAT, host, date, entry->include_sub_domains ? 'y' : 'n' + ); + g_free(date); +} + /** * Removes stored entry for given host. */ @@ -285,9 +301,13 @@ static void request_queued(SoupSessionFeature *feature, { HSTSProvider *provider = HSTS_PROVIDER(feature); SoupURI *uri = soup_message_get_uri(msg); - if (soup_uri_get_scheme(uri) == SOUP_URI_SCHEME_HTTP - && should_secure_host(provider, soup_uri_get_host(uri)) - ) { + + /* only look for HSTS headers sent over https RFC 6797 7.2*/ + if (soup_uri_get_scheme(uri) == SOUP_URI_SCHEME_HTTPS) { + soup_message_add_header_handler( + msg, "got-headers", HSTS_HEADER_NAME, G_CALLBACK(process_hsts_header), feature + ); + } else if (should_secure_host(provider, soup_uri_get_host(uri))) { soup_uri_set_scheme(uri, SOUP_URI_SCHEME_HTTPS); /* change port if the is explicitly set */ if (soup_uri_get_port(uri) == 80) { @@ -295,13 +315,6 @@ static void request_queued(SoupSessionFeature *feature, } soup_session_requeue_message(session, msg); } - - /* Only look for HSTS headers sent over https */ - if (soup_uri_get_scheme(uri) == SOUP_URI_SCHEME_HTTPS) { - soup_message_add_header_handler( - msg, "got-headers", HSTS_HEADER_NAME, G_CALLBACK(process_hsts_header), feature - ); - } } static void request_started(SoupSessionFeature *feature, @@ -324,3 +337,80 @@ static void request_unqueued(SoupSessionFeature *feature, { g_signal_handlers_disconnect_by_func(msg, process_hsts_header, feature); } + +/** + * Reads the entries save in given file and store them in the whitelist. + */ +static void load_entries(HSTSProvider *provider, const char *file) +{ + char **lines, **parts, *host, *line; + int i, len, partlen; + gboolean include_sub_domains; + SoupDate *date; + HSTSEntry *entry; + + lines = util_get_lines(file); + if (!(len = g_strv_length(lines))) { + return; + } + + for (i = len - 1; i >= 0; i--) { + line = lines[i]; + /* skip empty or commented lines */ + if (!*line || *line == '#') { + continue; + } + + parts = g_strsplit(line, "\t", 3); + partlen = g_strv_length(parts); + if (partlen == 3) { + host = parts[0]; + if (g_hostname_is_ip_address(host)) { + continue; + } + date = soup_date_new_from_string(parts[1]); + if (!date) { + continue; + } + include_sub_domains = (*parts[2] == 'y') ? true : false; + + /* built the new entry to add */ + entry = g_new(HSTSEntry, 1); + entry->expires_at = soup_date_new_from_string(parts[1]); + entry->include_sub_domains = include_sub_domains; + + add_host_entry(provider, host, entry); + } else { + g_warning("could not parse hsts line '%s'", line); + } + g_strfreev(parts); + } + g_strfreev(lines); +} + +/** + * Saves all entries of given provider in given file. + */ +static void save_entries(HSTSProvider *provider, const char *file) +{ + GHashTableIter iter; + char *host, *date; + HSTSEntry *entry; + FILE *f; + HSTSProviderPrivate *priv = HSTS_PROVIDER_GET_PRIVATE(provider); + + if ((f = fopen(file, "w"))) { + flock(fileno(f), LOCK_EX); + + g_hash_table_iter_init(&iter, priv->whitelist); + while (g_hash_table_iter_next (&iter, (gpointer)&host, (gpointer)&entry)) { + date = soup_date_to_string(entry->expires_at, SOUP_DATE_ISO8601_FULL); + fprintf(f, HSTS_FILE_FORMAT, host, date, entry->include_sub_domains ? 'y' : 'n'); + g_free(date); + } + + flock(fileno(f), LOCK_UN); + fclose(f); + } +} +#endif diff --git a/src/hsts.h b/src/hsts.h index 661dc10..9f9f801 100644 --- a/src/hsts.h +++ b/src/hsts.h @@ -17,6 +17,9 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ +#include "config.h" +#ifdef FEATURE_HSTS + #ifndef _HSTS_H #define _HSTS_H @@ -44,3 +47,4 @@ GType hsts_provider_get_type(void); HSTSProvider *hsts_provider_new(void); #endif /* end of include guard: _HSTS_H */ +#endif diff --git a/src/main.c b/src/main.c index 9ea0f8f..044a1b9 100644 --- a/src/main.c +++ b/src/main.c @@ -375,6 +375,7 @@ void vb_quit(void) for (int i = 0; i < FILES_LAST; i++) { g_free(vb.files[i]); + vb.files[i] = NULL; } gtk_main_quit(); @@ -900,6 +901,10 @@ static void init_files(void) vb.files[FILES_QUEUE] = g_build_filename(path, "queue", NULL); util_create_file_if_not_exists(vb.files[FILES_QUEUE]); #endif +#ifdef FEATURE_HSTS + vb.files[FILES_HSTS] = g_build_filename(path, "hsts", NULL); + util_create_file_if_not_exists(vb.files[FILES_HSTS]); +#endif vb.files[FILES_SCRIPT] = g_build_filename(path, "scripts.js", NULL); diff --git a/src/main.h b/src/main.h index 83779ac..44cb715 100644 --- a/src/main.h +++ b/src/main.h @@ -201,7 +201,9 @@ typedef enum { FILES_QUEUE, #endif FILES_USER_STYLE, +#ifdef FEATURE_HSTS FILES_HSTS, +#endif FILES_LAST } VbFile; diff --git a/src/session.c b/src/session.c index b56cda1..2482340 100644 --- a/src/session.c +++ b/src/session.c @@ -21,7 +21,9 @@ #include #include "main.h" #include "session.h" +#ifdef FEATURE_HSTS #include "hsts.h" +#endif #ifdef FEATURE_COOKIE @@ -50,7 +52,9 @@ static void cookiejar_set_property(GObject *self, guint prop_id, const GValue *value, GParamSpec *pspec); #endif +#ifdef FEATURE_HSTS static HSTSProvider *hsts; +#endif extern VbCore vb; @@ -68,16 +72,20 @@ void session_init(void) SOUP_SESSION_FEATURE(cookiejar_new(vb.files[FILES_COOKIE], false)) ); #endif +#ifdef FEATURE_HSTS hsts = hsts_provider_new(); - soup_session_add_feature( - vb.session, - SOUP_SESSION_FEATURE(hsts) - ); + soup_session_add_feature(vb.session, SOUP_SESSION_FEATURE(hsts)); +#endif } void session_cleanup(void) { +#ifdef FEATURE_HSTS + /* remove feature from session to make sure the feature is finalized on + * later call of g_object_unref */ + soup_session_remove_feature(vb.session, SOUP_SESSION_FEATURE(hsts)); g_object_unref(hsts); +#endif } #ifdef FEATURE_COOKIE