Support HSTS as a SoupFeature
Parse the Strict-Transport-Security header and enforce https on any listed domains (and subdomains).
This commit is contained in:
parent
64c9517459
commit
c49fb82e87
4 changed files with 125 additions and 1 deletions
91
katze/midori-hsts.vala
Normal file
91
katze/midori-hsts.vala
Normal file
|
@ -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 ());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1039,6 +1039,9 @@ midori_load_soup_session (gpointer settings)
|
||||||
g_signal_connect (session, "request-queued",
|
g_signal_connect (session, "request-queued",
|
||||||
G_CALLBACK (midori_soup_session_settings_accept_language_cb), settings);
|
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);
|
midori_soup_session_debug (session);
|
||||||
|
|
||||||
g_object_set_data (G_OBJECT (session), "midori-session-initialized", (void*)1);
|
g_object_set_data (G_OBJECT (session), "midori-session-initialized", (void*)1);
|
||||||
|
|
|
@ -1460,7 +1460,7 @@ midori_debug (const gchar* token)
|
||||||
{
|
{
|
||||||
static const gchar* debug_token = NULL;
|
static const gchar* debug_token = NULL;
|
||||||
const gchar* debug = g_getenv ("MIDORI_DEBUG");
|
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 ";
|
const gchar* full_debug_tokens = "adblock:1 adblock:2 startup bookmarks ";
|
||||||
if (debug_token == NULL)
|
if (debug_token == NULL)
|
||||||
{
|
{
|
||||||
|
|
30
tests/hsts.vala
Normal file
30
tests/hsts.vala
Normal file
|
@ -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 ();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue