]> spindle.queued.net Git - midori/commitdiff
Support HSTS as a SoupFeature
authorChristian Dywan <christian@twotoasts.de>
Sat, 1 Sep 2012 16:05:32 +0000 (18:05 +0200)
committerChristian Dywan <christian@twotoasts.de>
Sat, 1 Sep 2012 16:13:14 +0000 (18:13 +0200)
Parse the Strict-Transport-Security header and
enforce https on any listed domains (and subdomains).

katze/midori-hsts.vala [new file with mode: 0644]
midori/main.c
midori/midori-app.c
tests/hsts.vala [new file with mode: 0644]

diff --git a/katze/midori-hsts.vala b/katze/midori-hsts.vala
new file mode 100644 (file)
index 0000000..32e8216
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ Copyright (C) 2012 Christian Dywan <christian@twotoasts.de>
+
+ 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<string, Directive> whitelist;
+        bool debug = false;
+
+        public HSTS (owned string new_filename) {
+            filename = new_filename;
+            whitelist = new HashTable<string, Directive> (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 ());
+        }
+
+    }
+}
index 001faacf7a9afdfa91ef574651c4f916ef920bf7..f9d87e04fcd6b7dafd74576a9a5a9c226ed38c8a 100644 (file)
@@ -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);
index 85dc8e121ec9d20f258321b41dfa0cd05836e722..e8256bf66a18679e2d76c06c20868067413670e5 100644 (file)
@@ -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 (file)
index 0000000..a330619
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ Copyright (C) 2012 Christian Dywan <christian@twotoasts.de>
+
+ 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 ();
+}
+