From dc606eac7d452827b67a0c054fc518aba95dbbab Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Fri, 6 Jun 2014 15:54:22 -0700 Subject: [PATCH 1/4] Some tweaks to the config format --- README.md | 21 +++++++++++---------- config.json | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 0cc4f5f05..31495a8f5 100644 --- a/README.md +++ b/README.md @@ -49,22 +49,23 @@ The basic file format will be JSON with comments (http://blog.getify.com/json-co { // Canonical URL https://eff.org/starttls-everywhere/config -- redirects to latest version - "timestamp": 1401093333 + "timestamp": "2014-06-06T14:30:16+00:00", + // "timestamp": 1401414363, : also acceptable "author": "Electronic Frontier Foundation https://eff.org", - "expires": 1401414363, // epoch seconds - "address-domains": { + "expires": "2014-06-06T14:30:16+00:00", + "acceptable-mxs": { "gmail.com": { - "accept-mx-domains": ["google.com", "gmail.com"] + "accept-mx-domains": ["*.google.com", "*.gmail.com"] } "yahoo.com": { - "accept-mx-domains": ["yahoodns.net"] + "accept-mx-domains": ["*.yahoodns.net"] } "eff.org": { - "accept-mx-domains": ["eff.org"] + "accept-mx-domains": ["*.eff.org"] } } - "mx-domains": { - "eff.org": { + "security-policies": { + "*.eff.org": { "require-tls": true, "min-tls-version": "TLSv1.1", "enforce-mode": "enforce" @@ -73,13 +74,13 @@ The basic file format will be JSON with comments (http://blog.getify.com/json-co "sha1/YlrkMlC6C4SJRZSVyRvnvoJ+8eM=" ] } - "google.com": { + "*.google.com": { "require-valid-certificate": true, "min-tls-version": "TLSv1.1", "enforce-mode": "log-only", "error-notification": "https://google.com/post/reports/here" }, - "yahoodns.net": { + "*.yahoodns.net": { "require-valid-certificate": true, } } diff --git a/config.json b/config.json index d660587f1..8d6a73d63 100644 --- a/config.json +++ b/config.json @@ -1,17 +1,17 @@ { - // Canonical URL https://eff.org/starttls-everywhere/config -- redirects to - // latest version - "timestamp": 1401093333 + "comment": "Canonical URL https://eff.org/starttls-everywhere/config -- redirects to latest version", + "timestamp": 1401093333, "author": "Electronic Frontier Foundation https://eff.org", - "expires": 1404677353, // epoch seconds - "address-domains": { - "valid-example-recipient.com": { - "accept-mx-domains": [ "valid-example-recipient.com" ] - } - } - "mx-domains": { - "valid-example-recipient.com": { + "expires": 1404677353, "comment 2:": "epoch seconds", + "tls-policies": { + "*.valid-example-recipient.com": { "min-tls-version": "TLSv1.1" } } + "acceptable-mxs": { + "valid-example-recipient.com": { + "accept-mx-domains": [ "*.valid-example-recipient.com" ] + } + }, + } From fcd1a982010ff6fdba113ae208aad4b93750f83e Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Fri, 6 Jun 2014 15:54:33 -0700 Subject: [PATCH 2/4] Break ground on a config parser --- config-parser.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 config-parser.py diff --git a/config-parser.py b/config-parser.py new file mode 100755 index 000000000..69a09a745 --- /dev/null +++ b/config-parser.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +import sys +import json +from datetime import datetime + +def parse_timestamp(ts): + try: + int(ts) + dt = datetime.fromtimestamp(ts) + return dt + except: + raise ValueError, "Invalid timestamp integer: " + `ts` + + +class Config: + def __init__(self, cfg_file_name = "config.json"): + f = open(cfg_file_name) + cfg = json.loads(f.read()) + for atr, val in cfg.items(): + #print atr,val + # Parse and verify each attribute of the structure + if atr.startswith("comment"): + continue + if atr == "author": + if type(val) not in [str, unicode]: + raise TypeError, "Author must be a string: " + `val` + elif atr == "timestamp": + self.timestamp = parse_timestamp(val) + elif atr == "expires": + self.expires = parse_timestamp(val) + elif atr == "address-domains": + if type(val) != dict: + raise TypeError, "address-domains " + `val` + else: + sys.stderr.write("Uknown attribute: " + `atr` + "\n") + +if __name__ == "__main__": + c = Config() From 839c5230483cff26b31ed689ff0408f1b7ad7b01 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Sun, 8 Jun 2014 06:22:22 -0700 Subject: [PATCH 3/4] Fix typos --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 8d6a73d63..1a9034545 100644 --- a/config.json +++ b/config.json @@ -7,11 +7,11 @@ "*.valid-example-recipient.com": { "min-tls-version": "TLSv1.1" } - } + }, "acceptable-mxs": { "valid-example-recipient.com": { "accept-mx-domains": [ "*.valid-example-recipient.com" ] } - }, + } } From c033905b162650e41995bec30c9e79bf66642945 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Sun, 8 Jun 2014 06:22:32 -0700 Subject: [PATCH 4/4] Now validating most of the config json --- config-parser.py | 58 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/config-parser.py b/config-parser.py index 69a09a745..dbc244b25 100755 --- a/config-parser.py +++ b/config-parser.py @@ -3,6 +3,8 @@ import sys import json from datetime import datetime +import string + def parse_timestamp(ts): try: @@ -12,14 +14,30 @@ def parse_timestamp(ts): except: raise ValueError, "Invalid timestamp integer: " + `ts` +legal = string.letters + string.digits + ".-" +known_tlds =["com","org","net","biz","info",] # xxx make me from an ICANN list +def looks_like_a_domain(s): + "Return true if string looks like a domain, as best we can tell..." + global known_tlds + try: + domain = s.lower() + assert domain[0].islower() + assert all([c in legal for c in domain]) + tld = s.split(".")[-1] + if tld not in known_tlds: + # XXX perform DNS query to determine that this TLD exists + pass + return True + except: + return False class Config: def __init__(self, cfg_file_name = "config.json"): f = open(cfg_file_name) - cfg = json.loads(f.read()) - for atr, val in cfg.items(): + self.cfg = json.loads(f.read()) + for atr, val in self.cfg.items(): #print atr,val - # Parse and verify each attribute of the structure + # Verify each attribute of the structure if atr.startswith("comment"): continue if atr == "author": @@ -29,11 +47,39 @@ class Config: self.timestamp = parse_timestamp(val) elif atr == "expires": self.expires = parse_timestamp(val) - elif atr == "address-domains": - if type(val) != dict: - raise TypeError, "address-domains " + `val` + elif atr == "tls-policies": + self.tls_policies = {} + for domain,policies in self.check_tls_policy_domains(val): + if type(policies) != dict: + raise TypeError, domain + "'s policies should be a dict: " + `policies` + self.tls_policies[domain] = {} # being here enforces TLS at all + for policy, value in policies.items(): + if policy == "min-tls-version": + reasonable = ["TLS", "TLSv1.1", "TLSv1.2", "TLSv1.3"] + if not value in reasonable: + raise ValueError, "Not a valid TLS version string: " + `value` + self.tls_policies[domain]["min-tls-version"] = str(value) + elif atr == "acceptable-mxs": + pass else: sys.stderr.write("Uknown attribute: " + `atr` + "\n") + print self.tls_policies + + def check_tls_policy_domains(self, val): + if type(val) != dict: + raise TypeError, "tls-policies should be a dict" + `val` + for domain, policies in val.items(): + try: + assert type(domain) == unicode + d = str(domain) # convert from unicode + except: + raise TypeError, "tls-policy domain not a string" + `domain` + if not d.startswith("*."): + raise ValueError, "tls-policy domains must start with *.; try *."+d + d = d.partition("*.")[2] + if not looks_like_a_domain(d): + raise ValueError, "tls-policy for something that a domain? " + d + yield (d, policies) if __name__ == "__main__": c = Config()