From e99abfacfd40c4f7c1f404c7225d5d3ad4792ef8 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 12 Jun 2014 05:22:30 -0700 Subject: [PATCH 1/6] Include the demo example with the real stuff, temporarily --- starttls-everywhere.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/starttls-everywhere.json b/starttls-everywhere.json index d0e656186..4b47ea0c3 100644 --- a/starttls-everywhere.json +++ b/starttls-everywhere.json @@ -1,5 +1,9 @@ { "tls-policies": { + "*.valid-example-recipient.com": { + "min-tls-version": "TLSv1.1", + "force-tls" : true + }, "*.mx.aol.com": { "min-tls-version": "TLSv1", "require-tls": true @@ -50,6 +54,9 @@ } }, "acceptable-mxs": { + "valid-example-recipient.com": { + "accept-mx-domains": [ "*.valid-example-recipient.com" ] + }, "wp.pl": { "accept-mx-domains": [ "*.wp.pl" From 3d7b53daf19c4f47e34e1fb74b57367462968142 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 12 Jun 2014 05:24:05 -0700 Subject: [PATCH 2/6] Tune verbosity; reload postfix conf if we can --- MTAConfigGenerator.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/MTAConfigGenerator.py b/MTAConfigGenerator.py index 3b69ba37a..d78fa55e5 100755 --- a/MTAConfigGenerator.py +++ b/MTAConfigGenerator.py @@ -1,14 +1,16 @@ #!/usr/bin/env python import string -import os.path +import os, os.path POSTFIX_DIR = "/etc/postfix" DEFAULT_POLICY_FILE = os.path.join(POSTFIX_DIR, "starttls_everywhere_policy") POLICY_CF_ENTRY="texthash:" + DEFAULT_POLICY_FILE def parse_line(line_data): - "return the and right hand sides of stripped, non-comment postfix config line" + """ + return the (line number, left hand side, right hand side) + of a stripped postfix config line""" # lines are like: # smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache num,line = line_data @@ -32,6 +34,7 @@ class PostfixConfigGenerator(MTAConfigGenerator): self.postfix_cf_file = self.find_postfix_cf() self.wrangle_existing_config() self.set_domainwise_tls_policies() + os.system("sudo service postfix reload") def ensure_cf_var(self, var, ideal, also_acceptable): """ @@ -105,7 +108,7 @@ class PostfixConfigGenerator(MTAConfigGenerator): self.new_cf += line self.new_cf += sep + new_cf_lines - print self.new_cf + #print self.new_cf f = open(self.fn, "w") f.write(self.new_cf) f.close() From 02abaf57bd0cd67cabb8e1cdaefe1dabbb269776 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Thu, 12 Jun 2014 05:24:51 -0700 Subject: [PATCH 3/6] Remove stray conf entry from README --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 2f0a410a1..7a08c3ecf 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,6 @@ The basic file format will be JSON with comments (http://blog.getify.com/json-co "eff.org": { "accept-mx-domains": ["*.eff.org"] } - "*.yahoodns.net": { - "require-valid-certificate": true, - } } } From 9da4f93ae587156d4f9be5829a32a4201e5f1588 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Mon, 16 Jun 2014 02:46:46 -0700 Subject: [PATCH 4/6] Changes for live demo --- Vagrantfile | 1 + starttls-everywhere.json | 1 - vagrant-bootstrap.sh | 6 +++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 3ce93c917..b7153a7b8 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -16,6 +16,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| valid.vm.hostname = "valid-example-recipient.com" end config.vm.synced_folder "vagrant-shared", "/vagrant" + config.vm.synced_folder "vagrant-shared/starttls-everywhere", "/vagrant/starttls-everywhere" config.vm.provision :shell, path: "vagrant-bootstrap.sh" config.vm.provider "virtualbox" do |vb| diff --git a/starttls-everywhere.json b/starttls-everywhere.json index 4b47ea0c3..df3ff41ab 100644 --- a/starttls-everywhere.json +++ b/starttls-everywhere.json @@ -1,7 +1,6 @@ { "tls-policies": { "*.valid-example-recipient.com": { - "min-tls-version": "TLSv1.1", "force-tls" : true }, "*.mx.aol.com": { diff --git a/vagrant-bootstrap.sh b/vagrant-bootstrap.sh index 13593a0cc..82622edea 100755 --- a/vagrant-bootstrap.sh +++ b/vagrant-bootstrap.sh @@ -23,9 +23,9 @@ if [ "`hostname`" = "sender" ]; then (while sleep 10; do echo -e 'Subject: hi\n\nhi' | sendmail vagrant@valid-example-recipient.com done) & - ln -sf "/vagrant/postfix-config-sender-tls_policy.cf" /etc/postfix/tls_policy + #ln -sf "/vagrant/postfix-config-sender-tls_policy.cf" /etc/postfix/tls_policy fi -ln -sf "/vagrant/postfix-config-`hostname`.cf" /etc/postfix/main.cf -ln -sf "/vagrant/certificates" /etc/certificates +#ln -sf "/vagrant/postfix-config-`hostname`.cf" /etc/postfix/main.cf +#ln -sf "/vagrant/certificates" /etc/certificates postfix reload From 2eba47a716956bdcfc99c466b617ecc2e5b9c32a Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Mon, 16 Jun 2014 14:23:33 -0700 Subject: [PATCH 5/6] Verify more of the policy language --- ConfigParser.py | 28 ++++++++++++++++++++++++---- MTAConfigGenerator.py | 2 +- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/ConfigParser.py b/ConfigParser.py index 206132293..541ff3f30 100755 --- a/ConfigParser.py +++ b/ConfigParser.py @@ -35,6 +35,8 @@ class Config: def __init__(self, cfg_file_name = "config.json"): f = open(cfg_file_name) self.cfg = json.loads(f.read()) + self.tls_policies = {} + self.mx_map = {} for atr, val in self.cfg.items(): # Verify each attribute of the structure if atr.startswith("comment"): @@ -47,21 +49,39 @@ class Config: elif atr == "expires": self.expires = parse_timestamp(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": + for policy, v in policies.items(): + value = lower(str(v)) + if policy == "require-tls": + if value in ("true", "1", "yes"): + self.tls_policies[domain]["required"] = True + elif value in ("false", "0", "no"): + self.tls_policies[domain]["required"] = False + else: + raise ValueError, "Unknown require-tls value " + `value` + elif policy == "min-tls-version": reasonable = ["TLS", "TLSv1", "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 policy == "enforce-mode": + if value == "enforce": + self.tls_policies[domain]["enforce"] = True + elif value == "log-only": + self.tls_policies[domain]["enforce"] = False + else: + raise ValueError, "Not a known enoforcement policy " + `value` elif atr == "acceptable-mxs": - pass + for domain, mxball in acceptable_mx: + pass else: sys.stderr.write("Uknown attribute: " + `atr` + "\n") + # XXX is it ever permissible to have a domain with an acceptable-mx + # that does not point to a TLS security policy? If not, check/warn/fail + # here print self.tls_policies def check_tls_policy_domains(self, val): diff --git a/MTAConfigGenerator.py b/MTAConfigGenerator.py index 875624e57..1d322c8e9 100755 --- a/MTAConfigGenerator.py +++ b/MTAConfigGenerator.py @@ -78,7 +78,7 @@ class PostfixConfigGenerator(MTAConfigGenerator): # Check we're currently accepting inbound STARTTLS sensibly self.ensure_cf_var("smtpd_use_tls", "yes", []) # Ideally we use it opportunistically in the outbound direction - self.ensure_cf_var("smtp_tls_security_level", "may", ["encrypt"]) + self.ensure_cf_var("smtp_tls_security_level", "may", ["encrypt","dane"]) # Maximum verbosity lets us collect failure information self.ensure_cf_var("smtp_tls_loglevel", "1", []) # Inject a reference to our per-domain policy map From 3df343495e2ac81d5ceb501b43240a68cbbca54c Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 18 Jun 2014 10:58:53 -0700 Subject: [PATCH 6/6] Various fixups --- ConfigParser.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ConfigParser.py b/ConfigParser.py index 85b704d93..4fb69b6b2 100755 --- a/ConfigParser.py +++ b/ConfigParser.py @@ -54,7 +54,7 @@ class Config: raise TypeError, domain + "'s policies should be a dict: " + `policies` self.tls_policies[domain] = {} # being here enforces TLS at all for policy, v in policies.items(): - value = lower(str(v)) + value = str(v).lower() if policy == "require-tls": if value in ("true", "1", "yes"): self.tls_policies[domain]["required"] = True @@ -64,6 +64,7 @@ class Config: raise ValueError, "Unknown require-tls value " + `value` elif policy == "min-tls-version": reasonable = ["TLS", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"] + reasonable = map(string.lower, reasonable) if not value in reasonable: raise ValueError, "Not a valid TLS version string: " + `value` self.tls_policies[domain]["min-tls-version"] = str(value) @@ -76,7 +77,7 @@ class Config: raise ValueError, "Not a known enoforcement policy " + `value` elif atr == "acceptable-mxs": self.acceptable_mxs = val - for domain, mxball in selg.acceptable_mxs: + for domain, mxball in self.acceptable_mxs.items(): pass else: sys.stderr.write("Unknown attribute: " + `atr` + "\n")