Skip to content

Commit b25084a

Browse files
committed
Add MozillaPreloadHSTSAdapter
Closes #11
1 parent a197194 commit b25084a

File tree

7 files changed

+219
-49
lines changed

7 files changed

+219
-49
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Adapters can be mounted on 'http://', or a narrower mount point.
5252
* HTTPRedirectBlockAdapter - Mount on 'https://' to block HTTPS responses redirecting to HTTP
5353
* HTTPSEverywhereOnlyAdapter - Apply HTTPS Everywhere rules
5454
* ChromePreloadHSTSAdapter - Upgrade to HTTPS for sites on Chrome preload list
55+
* MozillaPreloadHSTSAdapter - Upgrade to HTTPS for sites on Mozilla preload list
5556
* HTTPSEverywhereAdapter - Chrome preload hsts and https everywhere rules combined
5657
* ForceHTTPSAdapter - Just use HTTPS, always, everywhere
5758
* PreferHTTPSAdapter - Check HTTP if there are any redirects, before switching to HTTPS.

https_everywhere/_chrome_preload_hsts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import json
2-
import logging
32
import os.path
43

54
import requests
@@ -15,11 +14,12 @@
1514

1615

1716
def _fetch_preload():
18-
filename = _storage_location("transport_security_state_static.json")
17+
filename = _storage_location(_github_url)
1918
if os.path.exists(filename):
2019
return filename
2120

2221
r = requests.get(_github_url)
22+
r.raise_for_status()
2323

2424
with open(filename, "w") as f:
2525
f.write(r.text)

https_everywhere/_fetch.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ def _storage_location(filename=None, timestamp=None):
2121
except (IOError, OSError):
2222
pass
2323

24-
if timestamp:
25-
filename = "default.rulesets.{}".format(timestamp)
26-
2724
if filename:
25+
if "/" in filename:
26+
filename = os.path.basename(filename)
27+
28+
if timestamp:
29+
filename = "{}.{}".format(filename, timestamp)
30+
2831
return os.path.join(cache_dir, filename)
2932

3033
return cache_dir
@@ -48,7 +51,7 @@ def _get_local_ts():
4851
def _get_local(timestamp=None):
4952
if not timestamp:
5053
timestamp = _get_local_ts() # pragma: no cover
51-
location = _storage_location(timestamp=timestamp)
54+
location = _storage_location("default.rulesets", timestamp)
5255
if os.path.exists(location):
5356
with open(location) as f:
5457
return json.load(f)
@@ -68,7 +71,7 @@ def fetch_update(timestamp=None):
6871
ruleset_url, headers={"Accept-Encoding": "gzip, deflate, br"}, stream=True
6972
)
7073
r.raise_for_status()
71-
location = _storage_location(timestamp=timestamp)
74+
location = _storage_location("default.rulesets", timestamp)
7275
try:
7376
data = gzip.GzipFile(fileobj=r.raw).read()
7477
except Exception:
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import os.path
2+
3+
import requests
4+
5+
from logging_helper import setup_logging
6+
7+
from ._fetch import _storage_location
8+
from ._util import _check_in, _reverse_host
9+
10+
logger = setup_logging()
11+
12+
_hg_url = "https://hg.mozilla.org/releases/mozilla-{version}/raw-file/tip/security/manager/ssl/nsSTSPreloadList.inc"
13+
_VERSIONS = ["beta", "release"]
14+
15+
16+
def _fetch_preload(version="release"):
17+
filename = _storage_location(_hg_url, version)
18+
if os.path.exists(filename):
19+
return filename
20+
21+
r = requests.get(_hg_url.format(version=version))
22+
r.raise_for_status()
23+
24+
with open(filename, "w") as f:
25+
f.write(r.text)
26+
27+
return filename
28+
29+
30+
def _load_preload_data(filename):
31+
with open(filename) as f:
32+
positive = set()
33+
negative = set()
34+
lines = [line.strip() for line in f.readlines()]
35+
start = lines.index("%%")
36+
lines = lines[start + 1 :]
37+
end = lines.index("%%")
38+
lines = lines[:end]
39+
for line in lines:
40+
name, flag = line.split(",")
41+
name = name.strip()
42+
if flag.strip() == "1":
43+
positive.add(name)
44+
else:
45+
negative.add(name)
46+
return positive, negative
47+
48+
49+
def _preload_remove_negative(remove_overlap=False):
50+
filename = _fetch_preload()
51+
domains, negative = _load_preload_data(filename)
52+
53+
for name in negative:
54+
rv = _check_in(domains, name)
55+
if rv:
56+
logger.warning("Removing {} because of negative {}".format(rv, name))
57+
domains.remove(rv)
58+
59+
if remove_overlap:
60+
entries = {}
61+
for name in domains:
62+
reversed_name = _reverse_host(name)
63+
assert reversed_name not in entries
64+
entries[reversed_name] = name
65+
66+
previous = ""
67+
for item in sorted(entries.keys()):
68+
entry = entries[item]
69+
if not previous or previous not in item:
70+
previous = item
71+
continue
72+
73+
domains.remove(entry)
74+
logger.warning(
75+
"Removing {} because of base domain {}".format(entry, entries[previous])
76+
)
77+
78+
return domains

https_everywhere/adapter.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from ._rules import https_url_rewrite, _get_rulesets
1313
from ._chrome_preload_hsts import _preload_including_subdomains
14+
from ._mozilla_preload_hsts import _preload_remove_negative
1415
from ._util import _check_in
1516

1617
PY2 = str != "".__class__
@@ -146,11 +147,11 @@ def get_redirect(self, url):
146147
return super(HTTPSEverywhereOnlyAdapter, self).get_redirect(url)
147148

148149

149-
class ChromePreloadHSTSAdapter(RedirectAdapter):
150+
class PreloadHSTSAdapter(RedirectAdapter):
150151
def __init__(self, *args, **kwargs):
151-
super(ChromePreloadHSTSAdapter, self).__init__(*args, **kwargs)
152+
super(PreloadHSTSAdapter, self).__init__(*args, **kwargs)
152153
# prime cache
153-
self._domains = _preload_including_subdomains()
154+
self._domains = self._get_preload()
154155

155156
def get_redirect(self, url):
156157
if url.startswith("http://"):
@@ -159,7 +160,15 @@ def get_redirect(self, url):
159160
new_url = "https:" + url[5:]
160161
return new_url
161162

162-
return super(ChromePreloadHSTSAdapter, self).get_redirect(url)
163+
return super(PreloadHSTSAdapter, self).get_redirect(url)
164+
165+
166+
class ChromePreloadHSTSAdapter(PreloadHSTSAdapter):
167+
_get_preload = _preload_including_subdomains
168+
169+
170+
class MozillaPreloadHSTSAdapter(PreloadHSTSAdapter):
171+
_get_preload = _preload_remove_negative
163172

164173

165174
class HTTPSEverywhereAdapter(ChromePreloadHSTSAdapter, HTTPSEverywhereOnlyAdapter):

tests/test_adapter.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
HTTPRedirectBlockAdapter,
1010
HTTPSEverywhereOnlyAdapter,
1111
ChromePreloadHSTSAdapter,
12+
MozillaPreloadHSTSAdapter,
1213
HTTPSEverywhereAdapter,
1314
ForceHTTPSAdapter,
1415
PreferHTTPSAdapter,
@@ -261,6 +262,12 @@ def test_python_org_packages(self):
261262
self.assertEqual(r.history[1].reason, "Moved Permanently")
262263

263264

265+
class TestMozillaPreloadAdapter(TestChromePreloadAdapter):
266+
cls = MozillaPreloadHSTSAdapter
267+
268+
test_medbank_mt = TestEverywhereOnlyAdapter.test_medbank_mt
269+
270+
264271
class TestEverywhereAdapter(TestChromePreloadAdapter):
265272

266273
cls = HTTPSEverywhereAdapter

0 commit comments

Comments
 (0)