diff --git a/katze/midori-hsts.vala b/katze/midori-hsts.vala new file mode 100644 index 00000000..32e8216b --- /dev/null +++ b/katze/midori-hsts.vala @@ -0,0 +1,91 @@ +/* + Copyright (C) 2012 Christian Dywan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See the file COPYING for the full license text. +*/ + +namespace Midori { + public class HSTS : GLib.Object, Soup.SessionFeature { + public class Directive { + public Soup.Date? expires = null; + public bool sub_domains = false; + + public Directive (bool include_sub_domains) { + expires = new Soup.Date.from_now (int.MAX); + sub_domains = include_sub_domains; + } + + public Directive.from_header (string header) { + var param_list = Soup.header_parse_param_list (header); + string? max_age = param_list.lookup ("max-age"); + if (max_age != null) + expires = new Soup.Date.from_now (max_age.to_int ()); + if (param_list.lookup_extended ("includeSubDomains", null, null)) + sub_domains = true; + Soup.header_free_param_list (param_list); + } + + public bool is_valid () { + return expires != null && !expires.is_past (); + } + } + + public string? filename { get; set; default = null; } + HashTable whitelist; + bool debug = false; + + public HSTS (owned string new_filename) { + filename = new_filename; + whitelist = new HashTable (str_hash, str_equal); + if (strcmp (Environment.get_variable ("MIDORI_DEBUG"), "hsts") == 0) + debug = true; + } + + /* No sub-features */ + public bool add_feature (Type type) { return false; } + public bool remove_feature (Type type) { return false; } + public bool has_feature (Type type) { return false; } + + public void attach (Soup.Session session) { session.request_queued.connect (queued); } + public void detach (Soup.Session session) { /* FIXME disconnect */ } + + /* Never called but required by the interface */ + public void request_started (Soup.Session session, Soup.Message msg, Soup.Socket socket) { } + public void request_queued (Soup.Session session, Soup.Message message) { } + public void request_unqueued (Soup.Session session, Soup.Message msg) { } + + void queued (Soup.Session session, Soup.Message message) { + Directive? directive = whitelist.lookup (message.uri.host); + if (directive != null && directive.is_valid ()) { + message.uri.set_scheme ("https"); + session.requeue_message (message); + if (debug) + stdout.printf ("HTPS: Enforce %s\n", message.uri.to_string (false)); + } + else if (message.uri.scheme == "http") + message.finished.connect (strict_transport_security_handled); + } + + void strict_transport_security_handled (Soup.Message message) { + if (message == null || message.uri == null) + return; + + unowned string? hsts = message.response_headers.get_one ("Strict-Transport-Security"); + if (hsts == null) + return; + + var directive = new Directive.from_header (hsts); + if (directive.is_valid ()) + whitelist.insert (message.uri.host, directive); + if (debug) + stdout.printf ("HTPS: '%s' sets '%s' valid? %s\n", + message.uri.to_string (false), hsts, directive.is_valid ().to_string ()); + } + + } +} diff --git a/midori/main.c b/midori/main.c index 001faacf..f9d87e04 100644 --- a/midori/main.c +++ b/midori/main.c @@ -1039,6 +1039,9 @@ midori_load_soup_session (gpointer settings) g_signal_connect (session, "request-queued", G_CALLBACK (midori_soup_session_settings_accept_language_cb), settings); + soup_session_add_feature (session, + SOUP_SESSION_FEATURE (midori_hsts_new (build_config_filename ("hsts")))); + midori_soup_session_debug (session); g_object_set_data (G_OBJECT (session), "midori-session-initialized", (void*)1); diff --git a/midori/midori-app.c b/midori/midori-app.c index 85dc8e12..e8256bf6 100644 --- a/midori/midori-app.c +++ b/midori/midori-app.c @@ -1460,7 +1460,7 @@ midori_debug (const gchar* token) { static const gchar* debug_token = NULL; const gchar* debug = g_getenv ("MIDORI_DEBUG"); - const gchar* debug_tokens = "soup soup:1 soup:2 soup:3 cookies paths "; + const gchar* debug_tokens = "soup soup:1 soup:2 soup:3 cookies paths hsts "; const gchar* full_debug_tokens = "adblock:1 adblock:2 startup bookmarks "; if (debug_token == NULL) { diff --git a/tests/hsts.vala b/tests/hsts.vala new file mode 100644 index 00000000..a3306191 --- /dev/null +++ b/tests/hsts.vala @@ -0,0 +1,30 @@ +/* + Copyright (C) 2012 Christian Dywan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See the file COPYING for the full license text. +*/ +static void http_hsts () { + Midori.HSTS.Directive d; + d = new Midori.HSTS.Directive.from_header ("max-age=31536000"); + assert (d.is_valid () && !d.sub_domains); + d = new Midori.HSTS.Directive.from_header ("max-age=15768000 ; includeSubDomains"); + assert (d.is_valid () && d.sub_domains); + + /* Invalid */ + d = new Midori.HSTS.Directive.from_header (""); + assert (!d.is_valid () && !d.sub_domains); + d = new Midori.HSTS.Directive.from_header ("includeSubDomains"); + assert (!d.is_valid () && d.sub_domains); +} + +void main (string[] args) { + Test.init (ref args); + Test.add_func ("/http/hsts", http_hsts); + Test.run (); +} +