commit ceac2ff7b2c966cfea495850d6905d93e675ec03 Author: Isis Lovecruft isis@torproject.org Date: Tue Mar 31 02:19:27 2015 +0000
Add HTTPS_ROTATION_PERIOD and EMAIL_ROTATION_PERIOD config options.
This implements configuration of the hashring rotation periods for BridgeDB's distributors via the configuration file.
The behaviour for hashring rotation for the EmailBasedDistributor is such that, when `bridgedb.email.autoresponder.createResponseBody()` calls `EmailBasedDistributor.getBridgesForEmail(EMAIL_ADDRESS, EPOCH)` with the `EPOCH` set to the start of the current EMAIL_ROTATION_PERIOD, the client's position in the hashring is determined by the HMAC of the string `"<EPOCH>EMAIL_ADDRESS"`. Therefore, the EMAIL_ROTATION_PERIOD directly effects where the client is placed in the hashring, resulting in different bridges for the user, depending on whether the period has elapsed. With the default setting of `"1 day"`, and taking into account also that the EmailBasedDistributor only responds to a particular user once per three hours, this results in the client being able to ask for (and receive) vanilla bridges (starting from hashring position A) at 9:00, obfs3 bridges (also from position A) at 12:00, obfs4 bridges (from position A again) at 15:00, and so on, and finally different vanilla bridges (from hashring position B) the next morning at 9:00.
For the IPBasedDistributor, the behaviour for hashring rotation is that the client's hashring position is determined by the HMAC of the string `"<EPOCH>AREA"` where `EPOCH` is again the start of the current HTTPS_ROTATION_PERIOD, and the `AREA` is the `/16` subnet which the client's IP address resides within. If the client is using Tor or some other open proxy, then the client's hashring position is determined by `"known-proxy<EPOCH>GROUP"` where `EPOCH` is the same as before, and `GROUP` is a number (currently 1 through 4, inclusive) deterministically derived from the IP address of the Tor Exit relay or open proxy that the client is using. Using `GROUP` causes there to only be 4 sets of bridges available to any and all Tor/proxy users at a given time. Hence additionally using `EPOCH` rotates the set of 4 bridges available. The default setting is `"3 hours"`, causing all Tor/proxy users to have 4 different sets of bridges every three hours, while non-Tor users have a new set of bridges (probably) unique to their IP address available every three hours.
These defaults for HTTPS_ROTATION_PERIOD and EMAIL_ROTATION_PERIOD will likely need to be changed and fiddled with to make the behaviour an optimum balance between user-friendly and resilient to enumeration.
* FIXES #1839: https://bugs.torproject.org/1839. This implement Roger's suggestion to add the rotation periods as configuration file options. --- bridgedb.conf | 32 ++++++++++++++++++++++++++++++++ lib/bridgedb/HTTPServer.py | 15 ++++++++++----- lib/bridgedb/Main.py | 8 ++------ lib/bridgedb/configure.py | 4 ++++ lib/bridgedb/email/server.py | 12 +++++++++--- lib/bridgedb/test/email_helpers.py | 3 +++ lib/bridgedb/test/test_email_server.py | 2 +- 7 files changed, 61 insertions(+), 15 deletions(-)
diff --git a/bridgedb.conf b/bridgedb.conf index c91cc73..2a1c367 100644 --- a/bridgedb.conf +++ b/bridgedb.conf @@ -20,6 +20,11 @@ # # CHANGELOG: # ~~~~~~~~~~ +# Changed in version 0.3.2 - 2015-03-30 +# * CHANGE to using BridgeDB release versions for bridgedb.conf file versions. +# * ADD support for specifying bridge rotation periods via the +# EMAIL_ROTATION_PERIOD and HTTPS_ROTATION_PERIOD settings. +# # Changed in version 0.0.15 - 2015-03-26 # * ADD new SUPPORTED_TRANSPORTS and DEFAULT_TRANSPORT settings. # @@ -295,6 +300,20 @@ HTTPS_INCLUDE_FINGERPRINTS = True # the *last* entry from its X-Forwarded-For header as the client's IP. HTTPS_USE_IP_FROM_FORWARDED_HEADER = False
+# (string or None) The period at which the available bridges rotates to a +# separate set of bridges. This setting can be used in the form +# +# "COUNT PERIOD" where +# COUNT is an integer +# PERIOD is one of "second", "minute", "hour", "day", +# "week", or "month" (or any plural form). +# +# For example, setting HTTPS_ROTATION_PERIOD = "3 days" will result in the set +# of bridges which are available through the web interface (either HTTP or +# HTTPS) getting rotated once every three days. Setting this to None disables +# rotation entirely. +HTTPS_ROTATION_PERIOD = "3 hours" + # (string or None) The IP address to listen on for unencrypted HTTP # connections. Set to ``None`` to disable unencrypted connections to the web # interface. @@ -342,6 +361,19 @@ GIMP_CAPTCHA_RSA_KEYFILE = 'captcha_rsa_key' # True if we are enabling distribution via Email; false otherwise. EMAIL_DIST = True
+# (string or None) The period at which the available bridges rotates to a +# separate set of bridges. This setting can be used in the form +# +# "COUNT PERIOD" where +# COUNT is an integer +# PERIOD is one of "second", "minute", "hour", "day", +# "week", or "month" (or any plural form). +# +# For example, setting EMAIL_ROTATION_PERIOD = "3 days" will result in the set +# of bridges which are available through the email interface getting rotated +# once every three days. Setting this to None disables rotation entirely. +EMAIL_ROTATION_PERIOD = "1 day" + # What email addresses do we use for outgoing email?
# EMAIL_FROM_ADDR goes in the 'From:' header on outgoing emails: diff --git a/lib/bridgedb/HTTPServer.py b/lib/bridgedb/HTTPServer.py index ae86957..2e0398c 100644 --- a/lib/bridgedb/HTTPServer.py +++ b/lib/bridgedb/HTTPServer.py @@ -54,6 +54,8 @@ from bridgedb.parse import headers from bridgedb.parse.addr import isIPAddress from bridgedb.qrcodes import generateQR from bridgedb.safelog import logSafely +from bridgedb.schedule import Unscheduled +from bridgedb.schedule import ScheduledInterval
TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'templates') @@ -819,7 +821,7 @@ class WebRoot(resource.Resource): return rendered
-def addWebServer(cfg, dist, sched): +def addWebServer(cfg, dist): """Set up a web server for HTTP(S)-based bridge distribution.
:type cfg: :class:`bridgedb.persistent.Conf` @@ -835,6 +837,7 @@ def addWebServer(cfg, dist, sched): HTTPS_PORT HTTPS_BIND_IP HTTPS_USE_IP_FROM_FORWARDED_HEADER + HTTPS_ROTATION_PERIOD RECAPTCHA_ENABLED RECAPTCHA_PUB_KEY RECAPTCHA_SEC_KEY @@ -845,10 +848,6 @@ def addWebServer(cfg, dist, sched): GIMP_CAPTCHA_RSA_KEYFILE :type dist: :class:`bridgedb.Dist.IPBasedDistributor` :param dist: A bridge distributor. - :type sched: :class:`bridgedb.schedule.ScheduledInterval` - :param sched: The scheduled interval at which bridge selection, which - are ultimately displayed on the :class:`WebResourceBridges` page, will - be shifted. :raises SystemExit: if the servers cannot be started. :rtype: :api:`twisted.web.server.Site` :returns: A webserver. @@ -887,6 +886,12 @@ def addWebServer(cfg, dist, sched): hmacKey=hmacKey, captchaDir=cfg.GIMP_CAPTCHA_DIR)
+ if cfg.HTTPS_ROTATION_PERIOD: + count, period = cfg.HTTPS_ROTATION_PERIOD.split() + sched = ScheduledInterval(count, period) + else: + sched = Unscheduled() + bridges = WebResourceBridges(dist, sched, numBridges, fwdHeaders, includeFingerprints=fprInclude) if captcha: diff --git a/lib/bridgedb/Main.py b/lib/bridgedb/Main.py index 31a9c66..3b018f9 100644 --- a/lib/bridgedb/Main.py +++ b/lib/bridgedb/Main.py @@ -512,13 +512,9 @@ def run(options, reactor=reactor):
# Configure all servers: if config.HTTPS_DIST and config.HTTPS_SHARE: - #webSchedule = schedule.ScheduledInterval("day", 2) - webSchedule = schedule.Unscheduled() - HTTPServer.addWebServer(config, ipDistributor, webSchedule) + HTTPServer.addWebServer(config, ipDistributor) if config.EMAIL_DIST and config.EMAIL_SHARE: - #emailSchedule = schedule.ScheduledInterval("day", 1) - emailSchedule = schedule.Unscheduled() - addSMTPServer(config, emailDistributor, emailSchedule) + addSMTPServer(config, emailDistributor)
tasks = {}
diff --git a/lib/bridgedb/configure.py b/lib/bridgedb/configure.py index 54dcdff..8a24d35 100644 --- a/lib/bridgedb/configure.py +++ b/lib/bridgedb/configure.py @@ -115,6 +115,10 @@ def loadConfig(configFile=None, configCls=None): else: setattr(config, attr, os.path.abspath(os.path.expanduser(setting)))
+ for attr in ["HTTPS_ROTATION_PERIOD", "EMAIL_ROTATION_PERIOD"]: + setting = getattr(config, attr, None) # Default to None + setattr(config, attr, setting) + for attr in ["FORCE_PORTS", "FORCE_FLAGS", "NO_DISTRIBUTION_COUNTRIES"]: setting = getattr(config, attr, []) # Default to empty lists setattr(config, attr, setting) diff --git a/lib/bridgedb/email/server.py b/lib/bridgedb/email/server.py index cde4637..8034d35 100644 --- a/lib/bridgedb/email/server.py +++ b/lib/bridgedb/email/server.py @@ -68,6 +68,8 @@ from bridgedb.email import request from bridgedb.parse import addr from bridgedb.parse.addr import UnsupportedDomain from bridgedb.parse.addr import canonicalizeEmailDomain +from bridgedb.schedule import ScheduledInterval +from bridgedb.schedule import Unscheduled
class MailServerContext(object): @@ -458,7 +460,7 @@ class SMTPIncomingServerFactory(smtp.SMTPFactory): return p
-def addServer(config, distributor, schedule): +def addServer(config, distributor): """Set up a SMTP server which listens on the configured ``EMAIL_PORT`` for incoming connections, and responds as necessary to requests for bridges.
@@ -467,9 +469,13 @@ def addServer(config, distributor, schedule): :type distributor: :class:`bridgedb.Dist.EmailBasedDistributor` :param dist: A distributor which will handle database interactions, and will decide which bridges to give to who and when. - :type schedule: :class:`bridgedb.schedule.ScheduledInterval` - :param schedule: The schedule. XXX: Is this even used? """ + if config.EMAIL_ROTATION_PERIOD: + count, period = config.EMAIL_ROTATION_PERIOD.split() + schedule = ScheduledInterval(count, period) + else: + schedule = Unscheduled() + context = MailServerContext(config, distributor, schedule) factory = SMTPIncomingServerFactory() factory.setContext(context) diff --git a/lib/bridgedb/test/email_helpers.py b/lib/bridgedb/test/email_helpers.py index 5e4262d..c80e2ea 100644 --- a/lib/bridgedb/test/email_helpers.py +++ b/lib/bridgedb/test/email_helpers.py @@ -24,6 +24,7 @@ from bridgedb.test.test_HTTPServer import DummyBridge
EMAIL_DIST = True +EMAIL_ROTATION_PERIOD = "1 day" EMAIL_INCLUDE_FINGERPRINTS = True EMAIL_GPG_SIGNING_ENABLED = True EMAIL_GPG_HOMEDIR = '.gnupg' @@ -54,6 +55,7 @@ EMAIL_PORT = 5225
TEST_CONFIG_FILE = io.StringIO(unicode("""\ EMAIL_DIST = %s +EMAIL_ROTATION_PERIOD = %s EMAIL_INCLUDE_FINGERPRINTS = %s EMAIL_GPG_SIGNING_ENABLED = %s EMAIL_GPG_HOMEDIR = %s @@ -75,6 +77,7 @@ EMAIL_FROM_ADDR = %s EMAIL_BIND_IP = %s EMAIL_PORT = %s """ % (repr(EMAIL_DIST), + repr(EMAIL_ROTATION_PERIOD), repr(EMAIL_INCLUDE_FINGERPRINTS), repr(EMAIL_GPG_SIGNING_ENABLED), repr(EMAIL_GPG_HOMEDIR), diff --git a/lib/bridgedb/test/test_email_server.py b/lib/bridgedb/test/test_email_server.py index f79f603..e8c9112 100644 --- a/lib/bridgedb/test/test_email_server.py +++ b/lib/bridgedb/test/test_email_server.py @@ -524,7 +524,7 @@ class EmailServerServiceTests(SMTPTestCaseMixin, unittest.TestCase):
def test_addServer(self): """Call :func:`bridgedb.email.server.addServer` to test startup.""" - factory = server.addServer(self.config, self.dist, self.sched) + factory = server.addServer(self.config, self.dist) factory.timeout = None factory.protocol.timeout = None # Or else the reactor gets dirty
tor-commits@lists.torproject.org