hstsPreloadChromium
authorGeorgios Kontaxis <redacted>
Tue, 10 Nov 2015 16:34:13 +0000 (11:34 -0500)
committerGeorgios Kontaxis <redacted>
Sun, 6 Dec 2015 03:02:17 +0000 (22:02 -0500)
Makefile [new file with mode: 0644]
README.md
get_list.sh [new file with mode: 0644]
hstsPreloadChromium.py [new file with mode: 0755]
makedb.py [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..537424a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+.PHONY: all clean
+
+all: db.sqlite3
+
+chromium_hsts_list.dat:
+       bash get_list.sh
+
+db.sqlite3: chromium_hsts_list.dat
+       python makedb.py
+
+clean:
+       rm -i chromium_hsts_list.dat db.sqlite3
index e1600f649ad61488745f979e9207228940ccf9d1..f54f9db009c210dffea06ad3f1fad4e0413dfae6 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1 +1,14 @@
 # hstsPreloadChromium
+
+```
+usage: hstsPreloadChromium.py [-h] [--verbose] E [E ...]
+
+Look up entries in the Chromium HSTS preload list.
+
+positional arguments:
+  E              Entry to look up.
+
+optional arguments:
+  -h, --help     show this help message and exit
+  --verbose, -v  Output information on the lookup process.
+```
diff --git a/get_list.sh b/get_list.sh
new file mode 100644 (file)
index 0000000..e0aa7a8
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# https://blog.mozilla.org/security/2012/11/01/preloading-hsts/
+URL="https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.json?format=TEXT"
+
+curl -L "${URL}" | base64 --decode |
+       egrep -v "^([ ]*\/\/|$)" > "chromium_hsts_list.dat";
diff --git a/hstsPreloadChromium.py b/hstsPreloadChromium.py
new file mode 100755 (executable)
index 0000000..b87ca70
--- /dev/null
@@ -0,0 +1,113 @@
+#!/usr/bin/python -u
+
+# kontaxis 2015-11-03
+
+# Chromium maintains an HSTS preload list so that URLs for domains
+# supporting HSTS are always automatically rewritten from HTTP to HTTPS
+# (if necessary). This eliminates the vulnerability window the first time
+# someone visits such domain over HTTP->HTTPS. Mozilla Firefox uses the
+# same preload list to implement the same functionality.
+#
+# This program will take a hostname as input and print it in its output
+# if the hostname is found on the HSTS preload list. Otherwise it will
+# print nothing.
+
+# References:
+# - https://blog.mozilla.org/security/2012/11/01/preloading-hsts/
+
+from __future__ import print_function
+
+import argparse
+import os
+import sys
+import sqlite3
+
+class hstsPreloadChromium:
+       verbose = False
+
+       _dbConnCursor = None
+
+       def __init__(self, dbPath):
+               conn = sqlite3.connect(dbpath)
+               conn.text_factory = str
+               self._dbConnCursor = conn.cursor()
+
+       def lookup(self, entries):
+               hits = []
+
+               for hostname in entries:
+                       self.verbose and print("hsts '%s' : " % hostname, end="")
+
+                       self._dbConnCursor.execute('SELECT domain from hsts where domain=?',
+                               (hostname,))
+                       match = self._dbConnCursor.fetchone()
+                       if match:
+                               hits.append(hostname)
+                               self.verbose and print("TRUE")
+                               continue
+
+                       # Lookup was a miss.
+                       self.verbose and print("FALSE")
+
+                       # Look for ever shorter wildcards.
+                       labels = hostname.strip(".").split(".")
+
+                       for i in range(1, len(labels)):
+                               hsts_wild = ".".join(["*"] + labels[i:len(labels)])
+
+                               self.verbose and print("hsts '%s' : " % hsts_wild, end="")
+
+                               self._dbConnCursor.execute('SELECT domain from hsts where domain=?',
+                                       (hsts_wild,))
+                               match = self._dbConnCursor.fetchone()
+                               if match:
+                                       hits.append(hostname)
+                                       self.verbose and print("TRUE")
+                                       break
+
+                               # Wildcard lookup was a miss.
+                               self.verbose and print("FALSE")
+
+               return hits
+
+
+if __name__ == "__main__":
+
+       # Parse arguments.
+       parser = argparse.ArgumentParser(
+               description="Look up entries in the Chromium HSTS preload list.")
+
+       parser.add_argument("--verbose", "-v",
+               action="store_const", const=True, default=False,
+               help = "Output information on the lookup process.")
+
+       parser.add_argument("entries", metavar="E", nargs="+",
+               help="Entry to look up.")
+
+       args = parser.parse_args()
+
+       # Make sure the SQLite3 database file exists in the same directory.
+       dirname = os.path.dirname(sys.argv[0])
+       dbpath  = os.path.join(dirname, "db.sqlite3")
+
+       if not os.path.exists(dbpath):
+               print("ERROR. Path '%s' is unavailable." % dbpath, file=sys.stderr)
+               sys.exit(-1)
+
+       if not os.path.isfile(dbpath):
+               print("ERROR. Path '%s' is not a file."  % dbpath, file=sys.stderr)
+               sys.exit(-1)
+
+       hsts = hstsPreloadChromium(dbpath)
+       hsts.verbose = args.verbose
+
+       hits = hsts.lookup(args.entries)
+       for hit in hits:
+               print("%s" % hit)
+
+       # Success
+       if hits:
+               sys.exit(0)
+
+       # Failure
+       sys.exit(1)
diff --git a/makedb.py b/makedb.py
new file mode 100644 (file)
index 0000000..5978966
--- /dev/null
+++ b/makedb.py
@@ -0,0 +1,58 @@
+#!/usr/bin/python -u
+
+# kontaxis 2015-11-03
+
+# References:
+# - https://blog.mozilla.org/security/2012/11/01/preloading-hsts/
+
+from __future__ import print_function
+
+import json
+import os
+import sqlite3
+import sys
+import time
+
+dirname = os.path.dirname(sys.argv[0])
+
+# Populate hsts records array
+hsts = []
+
+f = file(os.path.join(dirname, "chromium_hsts_list.dat"), "r")
+j = json.loads(f.read())
+f.close()
+
+for entry in j["entries"]:
+       if not "mode" in entry or entry["mode"] != "force-https":
+               continue
+       # We expect a name.
+       if not "name" in entry:
+               continue
+       hsts.append((entry["name"],))
+       if not "include_subdomains" in entry or entry["include_subdomains"] != True:
+               continue
+       hsts.append(("*.%s" % entry["name"],))
+
+# Make it happen
+conn = sqlite3.connect("db.sqlite3")
+conn.text_factory = str
+c = conn.cursor()
+
+# Create schema.
+c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?",
+       ("last_generated",))
+match = c.fetchone()
+if not match:
+       c.execute("CREATE TABLE last_generated (epoch integer);")
+       c.execute("CREATE TABLE hsts (domain text);")
+       c.execute("CREATE INDEX hsts_domain on hsts (domain);")
+
+c.execute('DELETE FROM last_generated');
+c.execute('INSERT INTO last_generated VALUES(?)',
+       (str(int(time.time())),))
+
+c.execute('DELETE FROM hsts');
+c.executemany('INSERT INTO hsts VALUES (?)', hsts)
+
+conn.commit()
+conn.close()
git clone https://git.99rst.org/PROJECT