示例#1
0
    def __init__(self, key, domainmap, domainrules,
                 answerParameters=None, whitelist=None):
        """Create a bridge distributor which uses email.

        :type emailHmac: callable
        :param emailHmac: An hmac function used to order email addresses
            within a ring. See :func:`~bridgedb.crypto.getHMACFunc`.
        :param dict domainmap: A map from lowercase domains that we support
            mail from to their canonical forms. See `EMAIL_DOMAIN_MAP` option
            in `bridgedb.conf`.
        :param domainrules: DOCDOC
        :param answerParameters: DOCDOC
        :type whitelist: dict or ``None``
        :param whitelist: A dictionary that maps whitelisted email addresses
            to GnuPG fingerprints.
        """
        super(EmailDistributor, self).__init__(key)

        self.domainmap = domainmap
        self.domainrules = domainrules
        self.whitelist = whitelist or dict()
        self.answerParameters = answerParameters

        key1 = getHMAC(key, "Map-Addresses-To-Ring")
        key2 = getHMAC(key, "Order-Bridges-In-Ring")

        self.emailHmac = getHMACFunc(key1, hex=False)
        #XXX cache options not implemented
        self.hashring = FilteredBridgeSplitter(key2, max_cached_rings=5)

        self.name = "Email"
示例#2
0
    def __init__(self,
                 key,
                 domainmap,
                 domainrules,
                 answerParameters=None,
                 whitelist=None):
        """Create a bridge distributor which uses email.

        :type emailHmac: callable
        :param emailHmac: An hmac function used to order email addresses
            within a ring. See :func:`~bridgedb.crypto.getHMACFunc`.
        :param dict domainmap: A map from lowercase domains that we support
            mail from to their canonical forms. See `EMAIL_DOMAIN_MAP` option
            in `bridgedb.conf`.
        :param domainrules: DOCDOC
        :param answerParameters: DOCDOC
        :type whitelist: dict or ``None``
        :param whitelist: A dictionary that maps whitelisted email addresses
            to GnuPG fingerprints.
        """
        super(EmailDistributor, self).__init__(key)

        self.domainmap = domainmap
        self.domainrules = domainrules
        self.whitelist = whitelist or dict()
        self.answerParameters = answerParameters

        key1 = getHMAC(key, "Map-Addresses-To-Ring")
        key2 = getHMAC(key, "Order-Bridges-In-Ring")

        self.emailHmac = getHMACFunc(key1, hex=False)
        #XXX cache options not implemented
        self.hashring = FilteredBridgeSplitter(key2, max_cached_rings=5)

        self.name = "Email"
示例#3
0
    def setUp(self):

        self.bridges = copy.deepcopy(util.generateFakeBridges())

        self.fd, self.fname = tempfile.mkstemp(suffix=".sqlite",
                                               dir=os.getcwd())
        bridgedb.Storage.initializeDBLock()
        self.db = bridgedb.Storage.openDatabase(self.fname)
        bridgedb.Storage.setDBFilename(self.fname)

        key = 'fake-hmac-key'
        self.splitter = Bridges.BridgeSplitter(key)
        ringParams = Bridges.BridgeRingParameters(needPorts=[(443, 1)],
                                                  needFlags=[("Stable", 1)])
        self.https_distributor = HTTPSDistributor(4,
                                                  crypto.getHMAC(
                                                      key,
                                                      "HTTPS-IP-Dist-Key"),
                                                  None,
                                                  answerParameters=ringParams)
        self.moat_distributor = MoatDistributor(4,
                                                crypto.getHMAC(
                                                    key, "Moat-Dist-Key"),
                                                None,
                                                answerParameters=ringParams)
        self.unallocated_distributor = Bridges.UnallocatedHolder()

        self.splitter.addRing(self.https_distributor.hashring, "https", p=10)
        self.splitter.addRing(self.moat_distributor.hashring, "moat", p=10)
        self.splitter.addRing(self.unallocated_distributor,
                              "unallocated",
                              p=10)
        self.https_ring = self.splitter.ringsByName.get("https")
        self.moat_ring = self.splitter.ringsByName.get("moat")
        self.unallocated_ring = self.splitter.ringsByName.get("unallocated")
示例#4
0
文件: Dist.py 项目: wfn/bridgedb
    def __init__(self, key, domainmap, domainrules,
                 answerParameters=None):
        """Create a bridge distributor which uses email.

        :type emailHmac: callable
        :param emailHmac: An hmac function used to order email addresses
            within a ring. See :func:`~bridgedb.crypto.getHMACFunc`.
        :param dict domainmap: A map from lowercase domains that we support
            mail from to their canonical forms. See `EMAIL_DOMAIN_MAP` option
            in `bridgedb.conf`.
        :param domainrules: DOCDOC
        :param answerParameters: DOCDOC
        """
        key1 = getHMAC(key, "Map-Addresses-To-Ring")
        self.emailHmac = getHMACFunc(key1, hex=False)

        key2 = getHMAC(key, "Order-Bridges-In-Ring")
        # XXXX clear the store when the period rolls over!
        self.domainmap = domainmap
        self.domainrules = domainrules
        self.answerParameters = answerParameters

        #XXX cache options not implemented
        self.splitter = bridgedb.Bridges.FilteredBridgeSplitter(
            key2, max_cached_rings=5)

        self.setDistributorName('Email')
示例#5
0
    def __init__(self, areaMapper, nClusters, key,
                 ipCategories=None, answerParameters=None):
        """Create a Distributor that decides which bridges to distribute based
        upon the client's IP address and the current time.

        :type areaMapper: callable
        :param areaMapper: A function that maps IP addresses arbitrarily to
            strings, such that addresses which map to identical strings are
            considered to be in the same "area" (for some arbitrary definition
            of "area"). See :func:`bridgedb.Dist.uniformMap` for an example.
        :param integer nClusters: The number of clusters to group IP addresses
            into. Note that if PROXY_LIST_FILES is set in bridgedb.conf, then
            the actual number of clusters is one higher than ``nClusters``,
            because the set of known open proxies constitutes its own
            category.
            DOCDOC What exactly does a cluster *do*?
        :param bytes key: The master HMAC key for this distributor. All added
            bridges are HMACed with this key in order to place them into the
            hashrings.
        :type ipCategories: iterable or None
        :param ipCategories: DOCDOC
        :type answerParameters: :class:`bridgedb.Bridges.BridgeRingParameters`
        :param answerParameters: A mechanism for ensuring that the set of
            bridges that this distributor answers a client with fit certain
            parameters, i.e. that an answer has "at least two obfsproxy
            bridges" or "at least one bridge on port 443", etc.
        """
        self.areaMapper = areaMapper
        self.nClusters = nClusters
        self.answerParameters = answerParameters

        if not ipCategories:
            ipCategories = []
        if not answerParameters:
            answerParameters = []
        self.rings = []

        self.categories = []
        for c in ipCategories:
            self.categories.append(c)

        key2 = getHMAC(key, "Assign-Bridges-To-Rings")
        key3 = getHMAC(key, "Order-Areas-In-Rings")
        self.areaOrderHmac = getHMACFunc(key3, hex=False)
        key4 = getHMAC(key, "Assign-Areas-To-Rings")
        self.areaClusterHmac = getHMACFunc(key4, hex=True)

        # add splitter and cache the default rings
        # plus leave room for dynamic filters
        #
        # XXX Why is the "extra room" hardcoded to be 5? Shouldn't it be some
        #     fraction of the number of clusters/categories? --isis
        ring_cache_size  = self.nClusters + len(ipCategories) + 5
        self.splitter = bridgedb.Bridges.FilteredBridgeSplitter(
            key2, max_cached_rings=ring_cache_size)
        logging.debug("Added splitter %s to IPBasedDistributor."
                      % self.splitter.__class__)

        self.setDistributorName('HTTPS')
示例#6
0
def createBridgeRings(cfg, proxyList, key):
    """Create the bridge distributors defined by the config file

    :type cfg:  :class:`Conf`
    :param cfg: The current configuration, including any in-memory settings
        (i.e. settings whose values were not obtained from the config file,
        but were set via a function somewhere)
    :type proxyList: :class:`~bridgedb.proxy.ProxySet`
    :param proxyList: The container for the IP addresses of any currently
        known open proxies.
    :param bytes key: Hashring master key
    :rtype: tuple
    :returns: A BridgeSplitter hashring, an
        :class:`~bridgedb.https.distributor.HTTPSDistributor` or None, and an
        :class:`~bridgedb.email.distributor.EmailDistributor` or None.
    """
    # Create a BridgeSplitter to assign the bridges to the different
    # distributors.
    hashring = Bridges.BridgeSplitter(crypto.getHMAC(key, "Hashring-Key"))
    logging.debug("Created hashring: %r" % hashring)

    # Create ring parameters.
    ringParams = Bridges.BridgeRingParameters(needPorts=cfg.FORCE_PORTS,
                                              needFlags=cfg.FORCE_FLAGS)

    emailDistributor = ipDistributor = None
    # As appropriate, create an IP-based distributor.
    if cfg.HTTPS_DIST and cfg.HTTPS_SHARE:
        logging.debug("Setting up HTTPS Distributor...")
        ipDistributor = HTTPSDistributor(cfg.N_IP_CLUSTERS,
                                         crypto.getHMAC(
                                             key, "HTTPS-IP-Dist-Key"),
                                         proxyList,
                                         answerParameters=ringParams)
        hashring.addRing(ipDistributor.hashring, "https", cfg.HTTPS_SHARE)

    # As appropriate, create an email-based distributor.
    if cfg.EMAIL_DIST and cfg.EMAIL_SHARE:
        logging.debug("Setting up Email Distributor...")
        emailDistributor = EmailDistributor(
            crypto.getHMAC(key, "Email-Dist-Key"),
            cfg.EMAIL_DOMAIN_MAP.copy(),
            cfg.EMAIL_DOMAIN_RULES.copy(),
            answerParameters=ringParams,
            whitelist=cfg.EMAIL_WHITELIST.copy())
        hashring.addRing(emailDistributor.hashring, "email", cfg.EMAIL_SHARE)

    # As appropriate, tell the hashring to leave some bridges unallocated.
    if cfg.RESERVED_SHARE:
        hashring.addRing(Bridges.UnallocatedHolder(), "unallocated",
                         cfg.RESERVED_SHARE)

    # Add pseudo distributors to hashring
    for pseudoRing in cfg.FILE_BUCKETS.keys():
        hashring.addPseudoRing(pseudoRing)

    return hashring, emailDistributor, ipDistributor
示例#7
0
    def __init__(self,
                 totalSubrings,
                 key,
                 proxies=None,
                 answerParameters=None):
        """Create a Distributor that decides which bridges to distribute based
        upon the client's IP address and the current time.

        :param int totalSubrings: The number of subhashrings to group clients
            into. Note that if ``PROXY_LIST_FILES`` is set in bridgedb.conf,
            then the actual number of clusters is one higher than
            ``totalSubrings``, because the set of all known open proxies is
            given its own subhashring.
        :param bytes key: The master HMAC key for this distributor. All added
            bridges are HMACed with this key in order to place them into the
            hashrings.
        :type proxies: :class:`~bridgedb.proxy.ProxySet`
        :param proxies: A :class:`bridgedb.proxy.ProxySet` containing known
            Tor Exit relays and other known proxies.  These will constitute
            the extra cluster, and any client requesting bridges from one of
            these **proxies** will be distributed bridges from a separate
            subhashring that is specific to Tor/proxy users.
        :type answerParameters:
            :class:`bridgedb.bridgerings.BridgeRingParameters`
        :param answerParameters: A mechanism for ensuring that the set of
            bridges that this distributor answers a client with fit certain
            parameters, i.e. that an answer has "at least two obfsproxy
            bridges" or "at least one bridge on port 443", etc.
        """
        super(HTTPSDistributor, self).__init__(key)
        self.totalSubrings = totalSubrings
        self.answerParameters = answerParameters

        if proxies:
            logging.info("Added known proxies to HTTPS distributor...")
            self.proxies = proxies
            self.totalSubrings += 1
            self.proxySubring = self.totalSubrings
        else:
            logging.warn("No known proxies were added to HTTPS distributor!")
            self.proxies = proxy.ProxySet()
            self.proxySubring = 0

        self.ringCacheSize = self.totalSubrings * 3

        key2 = getHMAC(key, "Assign-Bridges-To-Rings")
        key3 = getHMAC(key, "Order-Areas-In-Rings")
        key4 = getHMAC(key, "Assign-Areas-To-Rings")

        self._clientToPositionHMAC = getHMACFunc(key3, hex=False)
        self._subnetToSubringHMAC = getHMACFunc(key4, hex=True)
        self.hashring = FilteredBridgeSplitter(key2, self.ringCacheSize)
        self.name = 'HTTPS'
        logging.debug("Added %s to %s distributor." %
                      (self.hashring.__class__.__name__, self.name))
示例#8
0
    def __init__(self, totalSubrings, key, proxies=None, answerParameters=None):
        """Create a Distributor that decides which bridges to distribute based
        upon the client's IP address and the current time.

        :param int totalSubrings: The number of subhashrings to group clients
            into. Note that if ``PROXY_LIST_FILES`` is set in bridgedb.conf,
            then the actual number of clusters is one higher than
            ``totalSubrings``, because the set of all known open proxies is
            given its own subhashring.
        :param bytes key: The master HMAC key for this distributor. All added
            bridges are HMACed with this key in order to place them into the
            hashrings.
        :type proxies: :class:`~bridgedb.proxy.ProxySet`
        :param proxies: A :class:`bridgedb.proxy.ProxySet` containing known
            Tor Exit relays and other known proxies.  These will constitute
            the extra cluster, and any client requesting bridges from one of
            these **proxies** will be distributed bridges from a separate
            subhashring that is specific to Tor/proxy users.
        :type answerParameters: :class:`bridgedb.Bridges.BridgeRingParameters`
        :param answerParameters: A mechanism for ensuring that the set of
            bridges that this distributor answers a client with fit certain
            parameters, i.e. that an answer has "at least two obfsproxy
            bridges" or "at least one bridge on port 443", etc.
        """
        super(HTTPSDistributor, self).__init__(key)
        self.totalSubrings = totalSubrings
        self.answerParameters = answerParameters

        if proxies:
            logging.info("Added known proxies to HTTPS distributor...")
            self.proxies = proxies
            self.totalSubrings += 1
            self.proxySubring = self.totalSubrings
        else:
            logging.warn("No known proxies were added to HTTPS distributor!")
            self.proxies = proxy.ProxySet()
            self.proxySubring = 0

        self.ringCacheSize = self.totalSubrings * 3

        key2 = getHMAC(key, "Assign-Bridges-To-Rings")
        key3 = getHMAC(key, "Order-Areas-In-Rings")
        key4 = getHMAC(key, "Assign-Areas-To-Rings")

        self._clientToPositionHMAC = getHMACFunc(key3, hex=False)
        self._subnetToSubringHMAC = getHMACFunc(key4, hex=True)
        self.hashring = FilteredBridgeSplitter(key2, self.ringCacheSize)
        self.name = 'HTTPS'
        logging.debug("Added %s to %s distributor." %
                      (self.hashring.__class__.__name__, self.name))
示例#9
0
文件: captcha.py 项目: wfn/bridgedb
    def check(cls, challenge, solution, secretKey, hmacKey):
        """Check a client's CAPTCHA **solution** against the **challenge**.

        :param str challenge: The contents of the
            ``'captcha_challenge_field'`` HTTP form field.
        :param str solution: The client's proposed solution to the CAPTCHA
            that they were presented with.
        :param str secretkey: A PKCS#1 OAEP-padded, private RSA key, used for
            verifying the client's solution to the CAPTCHA.
        :param bytes hmacKey: A private key for generating HMACs.
        :rtype: bool
        :returns: True if the CAPTCHA solution was correct.
        """
        validHMAC = False

        if not solution:
            return validHMAC

        logging.debug("Checking CAPTCHA solution %r against challenge %r"
                      % (solution, challenge))
        try:
            decoded = urlsafe_b64decode(challenge)
            hmac, original = decoded.split(';', 1)
            verified = crypto.getHMAC(hmacKey, original)
            validHMAC = verified == hmac
        except Exception as error:
            logging.exception(error)
        finally:
            if validHMAC:
                decrypted = secretKey.decrypt(original)
                if solution == decrypted:
                    return True
            return False
示例#10
0
    def getCaptchaImage(self, request):
        """Get a random CAPTCHA image from our **captchaDir**.

        Creates a :class:`~bridgedb.captcha.GimpCaptcha`, and calls its
        :meth:`~bridgedb.captcha.GimpCaptcha.get` method to return a random
        CAPTCHA and challenge string.

        :type request: :api:`twisted.web.http.Request`
        :param request: A client's initial request for some other resource
            which is protected by this one (i.e. protected by a CAPTCHA).
        :returns: A 2-tuple of ``(image, challenge)``, where::
            - ``image`` is a string holding a binary, JPEG-encoded image.
            - ``challenge`` is a unique string associated with the request.
        """
        # Create a new HMAC key, specific to requests from this client:
        clientIP = self.getClientIP(request)
        clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)
        capt = captcha.GimpCaptcha(self.publicKey, self.secretKey,
                                   clientHMACKey, self.captchaDir)
        try:
            capt.get()
        except captcha.GimpCaptchaError as error:
            logging.error(error)
        except Exception as error:  # pragma: no cover
            logging.error("Unhandled error while retrieving Gimp captcha!")
            logging.exception(error)

        return (capt.image, capt.challenge)
示例#11
0
    def checkSolution(self, request):
        """Process a solved CAPTCHA via :meth:`bridgedb.captcha.GimpCaptcha.check`.

        :type request: :api:`twisted.web.http.Request`
        :param request: A ``Request`` object, including POST arguments which
            should include two key/value pairs: one key being
            ``'captcha_challenge_field'``, and the other,
            ``'captcha_response_field'``. These POST arguments should be
            obtained from :meth:`render_GET`.
        :rtupe: bool
        :returns: True, if the CAPTCHA solution was valid; False otherwise.
        """
        valid = False
        challenge, solution = self.extractClientSolution(request)
        clientIP = self.getClientIP(request)
        clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)

        try:
            valid = captcha.GimpCaptcha.check(challenge, solution,
                                              self.secretKey, clientHMACKey)
        except captcha.CaptchaExpired as error:
            logging.warn(error)
            valid = False

        logging.debug("%sorrect captcha from %r: %r."
                      % ("C" if valid else "Inc", clientIP, solution))
        return valid
示例#12
0
    def checkSolution(self, request):
        """Process a solved CAPTCHA by sending rehashing the solution together with
        the client's IP address, and checking that the result matches the challenge.

        The client's IP address is not sent to the ReCaptcha server; instead,
        a completely random IP is generated and sent instead.

        :type request: :api:`twisted.web.http.Request`
        :param request: A ``Request`` object, including POST arguments which
            should include two key/value pairs: one key being
            ``'captcha_challenge_field'``, and the other,
            ``'captcha_response_field'``. These POST arguments should be
            obtained from :meth:`render_GET`.
        :rtupe: bool
        :returns: True, if the CAPTCHA solution was valid; False otherwise.
        """
        challenge, solution = self.extractClientSolution(request)
        clientIP = self.getClientIP(request)
        clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)
        valid = captcha.GimpCaptcha.check(challenge, solution,
                                          self.secretKey, clientHMACKey)
        logging.debug("%sorrect captcha from %r: %r." % (
            "C" if valid else "Inc", Util.logSafely(clientIP), solution))

        return valid
示例#13
0
    def getCaptchaImage(self, request):
        """Get a random CAPTCHA image from our **captchaDir**.

        Creates a :class:`~bridgedb.captcha.GimpCaptcha`, and calls its
        :meth:`~bridgedb.captcha.GimpCaptcha.get` method to return a random
        CAPTCHA and challenge string.

        :type request: :api:`twisted.web.http.Request`
        :param request: A client's initial request for some other resource
            which is protected by this one (i.e. protected by a CAPTCHA).
        :returns: A 2-tuple of ``(image, challenge)``, where::
            - ``image`` is a string holding a binary, JPEG-encoded image.
            - ``challenge`` is a unique string associated with the request.
        """
        # Create a new HMAC key, specific to requests from this client:
        clientIP = self.getClientIP(request)
        clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)
        capt = captcha.GimpCaptcha(self.publicKey, self.secretKey,
                                   clientHMACKey, self.captchaDir)
        try:
            capt.get()
        except captcha.GimpCaptchaError as error:
            logging.error(error)
        except Exception as error:  # pragma: no cover
            logging.error("Unhandled error while retrieving Gimp captcha!")
            logging.exception(error)

        return (capt.image, capt.challenge)
示例#14
0
    def checkSolution(self, request):
        """Process a solved CAPTCHA via :meth:`bridgedb.captcha.GimpCaptcha.check`.

        :type request: :api:`twisted.web.http.Request`
        :param request: A ``Request`` object, including POST arguments which
            should include two key/value pairs: one key being
            ``'captcha_challenge_field'``, and the other,
            ``'captcha_response_field'``. These POST arguments should be
            obtained from :meth:`render_GET`.
        :rtupe: bool
        :returns: True, if the CAPTCHA solution was valid; False otherwise.
        """
        valid = False
        challenge, solution = self.extractClientSolution(request)
        clientIP = self.getClientIP(request)
        clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)

        try:
            valid = captcha.GimpCaptcha.check(challenge, solution,
                                              self.secretKey, clientHMACKey)
        except captcha.CaptchaExpired as error:
            logging.warn(error)
            valid = False

        logging.debug("%sorrect captcha from %r: %r."
                      % ("C" if valid else "Inc", clientIP, solution))
        return valid
示例#15
0
 def prepopulateRings(self):
     # populate all rings (for dumping assignments and testing)
     for filterFn in [filterBridgesByIP4, filterBridgesByIP6]:
         ruleset = frozenset([filterFn])
         key1 = getHMAC(self.splitter.key, "Order-Bridges-In-Ring")
         ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
         self.splitter.addRing(ring, ruleset,
                               filterBridgesByRules([filterFn]),
                               populate_from=self.splitter.bridges)
示例#16
0
 def test_createChallenge_hmacValid(self):
     """The HMAC in createChallenge() return value should be valid."""
     c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey, self.cacheDir)
     challenge = c.createChallenge("ShouldHaveAValidHMAC")
     decoded = urlsafe_b64decode(challenge)
     hmac = decoded[:20]
     orig = decoded[20:]
     correctHMAC = crypto.getHMAC(self.hmacKey, orig)
     self.assertEquals(hmac, correctHMAC)
示例#17
0
 def test_createChallenge_hmacValid(self):
     """The HMAC in createChallenge() return value should be valid."""
     c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
                             self.cacheDir)
     challenge = c.createChallenge('ShouldHaveAValidHMAC')
     decoded = urlsafe_b64decode(challenge)
     hmac = decoded[:20]
     orig = decoded[20:]
     correctHMAC = crypto.getHMAC(self.hmacKey, orig)
     self.assertEquals(hmac, correctHMAC)
示例#18
0
    def prepopulateRings(self):
        logging.info("Prepopulating %s distributor hashrings..." % self.name)
        # populate all rings (for dumping assignments and testing)
        for filterFn in [None, filterBridgesByIP4, filterBridgesByIP6]:
            n = self.nClusters
            for category in self.categories:
                g = filterAssignBridgesToRing(self.splitter.hmac,
                                              self.nClusters +
                                              len(self.categories),
                                              n)
                bridgeFilterRules = [g]
                if filterFn:
                    bridgeFilterRules.append(filterFn)
                ruleset = frozenset(bridgeFilterRules)
                key1 = getHMAC(self.splitter.key,
                               "Order-Bridges-In-Ring-%d" % n)
                n += 1
                ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
                ring.setName('{0} Ring'.format(self.name))
                self.splitter.addRing(ring,
                                      ruleset,
                                      filterBridgesByRules(bridgeFilterRules),
                                      populate_from=self.splitter.bridges)


            # populate all ip clusters
            for clusterNum in xrange(self.nClusters):
                g = filterAssignBridgesToRing(self.splitter.hmac,
                                              self.nClusters +
                                              len(self.categories),
                                              clusterNum)
                bridgeFilterRules = [g]
                if filterFn:
                    bridgeFilterRules.append(filterFn)
                ruleset = frozenset(bridgeFilterRules)
                key1 = getHMAC(self.splitter.key,
                               "Order-Bridges-In-Ring-%d" % clusterNum)
                ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
                self.splitter.addRing(ring,
                                      ruleset,
                                      filterBridgesByRules(bridgeFilterRules),
                                      populate_from=self.splitter.bridges)
示例#19
0
    def check(cls, challenge, solution, secretKey, hmacKey):
        """Check a client's CAPTCHA **solution** against the **challenge**.

        :param str challenge: The contents of the
            ``'captcha_challenge_field'`` HTTP form field.
        :param str solution: The client's proposed solution to the CAPTCHA
            that they were presented with.
        :param str secretKey: A PKCS#1 OAEP-padded, private RSA key, used for
            verifying the client's solution to the CAPTCHA.
        :param bytes hmacKey: A private key for generating HMACs.
        :raises CaptchaExpired: if the **solution** was for a stale CAPTCHA.
        :rtype: bool
        :returns: ``True`` if the CAPTCHA solution was correct and not
            stale. ``False`` otherwise.
        """
        hmacIsValid = False

        if not solution:
            return hmacIsValid

        logging.debug("Checking CAPTCHA solution %r against challenge %r"
                      % (solution, challenge))
        try:
            decoded = urlsafe_b64decode(challenge)
            hmacFromBlob = decoded[:20]
            encBlob = decoded[20:]
            hmacNew = crypto.getHMAC(hmacKey, encBlob)
            hmacIsValid = hmacNew == hmacFromBlob
        except Exception:
            return False
        finally:
            if hmacIsValid:
                try:
                    answerBlob = secretKey.decrypt(encBlob)

                    timestamp = answerBlob[:12].lstrip('0')
                    then = cls.sched.nextIntervalStarts(int(timestamp))
                    now = int(time.time())
                    answer = answerBlob[12:]
                except Exception as error:
                    logging.warn(error.message)
                else:
                    # If the beginning of the 'next' interval (the interval
                    # after the one when the CAPTCHA timestamp was created)
                    # has already passed, then the CAPTCHA is stale.
                    if now >= then:
                        exp = schedule.fromUnixSeconds(then).isoformat(sep=' ')
                        raise CaptchaExpired("Solution %r was for a CAPTCHA "
                                             "which already expired at %s."
                                             % (solution, exp))
                    if solution.lower() == answer.lower():
                        return True
            return False
示例#20
0
 def test_createChallenge_decryptedAnswerMatches(self):
     """The HMAC in createChallenge() return value should be valid."""
     c = captcha.GimpCaptcha(self.sekrit, self.publik, self.hmacKey,
                             self.cacheDir)
     answer = 'ThisAnswerShouldDecryptToThis'
     challenge = c.createChallenge(answer)
     decoded = urlsafe_b64decode(challenge)
     hmac, orig = decoded.split(';', 1)
     correctHMAC = crypto.getHMAC(self.hmacKey, orig)
     self.assertEqual(hmac, correctHMAC)
     decrypted = self.sekrit.decrypt(orig)
     self.assertEqual(answer, decrypted)
示例#21
0
    def test_createChallenge_decryptedAnswerMatches(self):
        """The HMAC in createChallenge() return value should be valid."""
        c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey, self.cacheDir)
        challenge = c.createChallenge("ThisAnswerShouldDecryptToThis")
        decoded = urlsafe_b64decode(challenge)
        hmac = decoded[:20]
        orig = decoded[20:]
        correctHMAC = crypto.getHMAC(self.hmacKey, orig)
        self.assertEqual(hmac, correctHMAC)

        decrypted = self.sekrit.decrypt(orig)
        timestamp = int(decrypted[:12].lstrip("0"))
        # The timestamp should be within 30 seconds of right now.
        self.assertApproximates(timestamp, int(time.time()), 30)
        self.assertEqual("ThisAnswerShouldDecryptToThis", decrypted[12:])
示例#22
0
    def test_createChallenge_decryptedAnswerMatches(self):
        """The HMAC in createChallenge() return value should be valid."""
        c = captcha.GimpCaptcha(self.publik, self.sekrit, self.hmacKey,
                                self.cacheDir)
        challenge = c.createChallenge('ThisAnswerShouldDecryptToThis')
        decoded = urlsafe_b64decode(challenge)
        hmac = decoded[:20]
        orig = decoded[20:]
        correctHMAC = crypto.getHMAC(self.hmacKey, orig)
        self.assertEqual(hmac, correctHMAC)

        decrypted = self.sekrit.decrypt(orig)
        timestamp = int(decrypted[:12].lstrip('0'))
        # The timestamp should be within 30 seconds of right now.
        self.assertApproximates(timestamp, int(time.time()), 30)
        self.assertEqual('ThisAnswerShouldDecryptToThis', decrypted[12:])
示例#23
0
    def prepopulateRings(self):
        """Prepopulate this distributor's hashrings and subhashrings with
        bridges.
        """
        logging.info("Prepopulating %s distributor hashrings..." % self.name)

        for filterFn in [byIPv4, byIPv6]:
            ruleset = frozenset([filterFn])
            key = getHMAC(self.key, "Order-Bridges-In-Ring")
            ring = BridgeRing(key, self.answerParameters)
            self.hashring.addRing(ring,
                                  ruleset,
                                  byFilters([filterFn]),
                                  populate_from=self.hashring.bridges)

        # Since prepopulateRings is called every half hour when the bridge
        # descriptors are re-parsed, we should clean the database then.
        self.cleanDatabase()
示例#24
0
    def prepopulateRings(self):
        """Prepopulate this distributor's hashrings and subhashrings with
        bridges.
        """
        logging.info("Prepopulating %s distributor hashrings..." % self.name)

        for filterFn in [byIPv4, byIPv6]:
            ruleset = frozenset([filterFn])
            key = getHMAC(self.key, "Order-Bridges-In-Ring")
            ring = BridgeRing(key, self.answerParameters)
            self.hashring.addRing(ring, ruleset, byFilters([filterFn]),
                                  populate_from=self.hashring.bridges)

        # Since prepopulateRings is called every half hour when the bridge
        # descriptors are re-parsed, we should clean the database then.
        self.cleanDatabase()

        logging.info("Bridges allotted for %s distribution: %d"
                     % (self.name, len(self.hashring)))
示例#25
0
文件: captcha.py 项目: wfn/bridgedb
    def createChallenge(self, answer):
        """Encrypt the CAPTCHA **answer** and HMAC the encrypted data.

        Take a string containing the answer to a CAPTCHA and encrypts it to
        :attr:`publicKey`. The resulting encrypted blob is then HMACed with a
        client-specific :attr:`hmacKey`. These two strings are then joined
        together in the form:

                HMAC ";" ENCRYPTED_ANSWER

        and lastly base64-encoded (in a URL safe manner).

        :param str answer: The answer to a CAPTCHA.
        :rtype: str
        :returns: An HMAC of, as well as a string containing the URL-safe,
            base64-encoded encrypted **answer**.
        """
        encrypted = self.publicKey.encrypt(answer)
        hmac = crypto.getHMAC(self.hmacKey, encrypted)
        challenge = hmac + ';' + encrypted
        encoded = urlsafe_b64encode(challenge)
        return encoded
示例#26
0
    def checkSolution(self, challenge, solution, clientIP):
        """Process a solved CAPTCHA via
        :meth:`bridgedb.captcha.GimpCaptcha.check`.

        :param str challenge: A base64-encoded, encrypted challenge.
        :param str solution: The client's solution to the captcha
        :param str clientIP: The client's IP address.
        :rtupe: bool
        :returns: True, if the CAPTCHA solution was valid; False otherwise.
        """
        valid = False
        clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)

        try:
            valid = captcha.GimpCaptcha.check(challenge, solution,
                                              self.secretKey, clientHMACKey)
        except Exception as impossible:
            logging.error(impossible)
            raise impossible
        finally:
            logging.debug("%sorrect captcha from %r: %r." %
                          ("C" if valid else "Inc", clientIP, solution))

        return valid
示例#27
0
    def checkSolution(self, challenge, solution, clientIP):
        """Process a solved CAPTCHA via
        :meth:`bridgedb.captcha.GimpCaptcha.check`.

        :param str challenge: A base64-encoded, encrypted challenge.
        :param str solution: The client's solution to the captcha
        :param str clientIP: The client's IP address.
        :rtupe: bool
        :returns: True, if the CAPTCHA solution was valid; False otherwise.
        """
        valid = False
        clientHMACKey = crypto.getHMAC(self.hmacKey, clientIP)

        try:
            valid = captcha.GimpCaptcha.check(challenge, solution,
                                              self.secretKey, clientHMACKey)
        except Exception as impossible:
            logging.error(impossible)
            raise impossible
        finally:
            logging.debug("%sorrect captcha from %r: %r." %
                          ("C" if valid else "Inc", clientIP, solution))

        return valid
示例#28
0
    def getBridges(self, bridgeRequest, interval, clock=None):
        """Return a list of bridges to give to a user.

        .. hint:: All checks on the email address (which should be stored in
            the ``bridgeRequest.client`` attribute), such as checks for
            whitelisting and canonicalization of domain name, are done in
            :meth:`bridgedb.distributors.email.autoresponder.getMailTo` and
            :meth:`bridgedb.distributors.email.autoresponder.SMTPAutoresponder.runChecks`.

        :type bridgeRequest:
            :class:`~bridgedb.distributors.email.request.EmailBridgeRequest`
        :param bridgeRequest: A
            :class:`~bridgedb.bridgerequest.BridgeRequestBase` with the
            :data:`~bridgedb.bridgerequest.BridgeRequestBase.client` attribute
            set to a string containing the client's full, canonicalized email
            address.
        :type interval: str
        :param interval: The time period when we got this request. This can be
            any string, so long as it changes with every period.
        :type clock: :api:`twisted.internet.task.Clock`
        :param clock: If given, use the clock to ask what time it is, rather
            than :api:`time.time`. This should likely only be used for
            testing.
        :rtype: :any:`list` or ``None``
        :returns: A list of :class:`~bridgedb.bridges.Bridges` for the
            ``bridgeRequest.client``, if allowed.  Otherwise, returns ``None``.
        """
        if (not bridgeRequest.client) or (bridgeRequest.client == 'default'):
            raise addr.BadEmail(
                ("%s distributor can't get bridges for invalid email address: "
                 "%s") % (self.name, bridgeRequest.client), bridgeRequest.client)

        logging.info("Attempting to get bridges for %s..." % bridgeRequest.client)

        now = time.time()

        if clock:
            now = clock.seconds()

        with bridgedb.Storage.getDB() as db:
            wasWarned = db.getWarnedEmail(bridgeRequest.client)
            lastSaw = db.getEmailTime(bridgeRequest.client)
            if lastSaw is not None:
                if bridgeRequest.client in self.whitelist:
                    logging.info(
                        "Whitelisted address %s was last seen %d seconds ago."
                        % (bridgeRequest.client, now - lastSaw))
                elif (lastSaw + self.emailRateMax) >= now:
                    wait = (lastSaw + self.emailRateMax) - now
                    logging.info("Client %s must wait another %d seconds."
                                 % (bridgeRequest.client, wait))
                    if wasWarned:
                        raise IgnoreEmail(
                            "Client %s was warned." % bridgeRequest.client,
                            bridgeRequest.client)
                    else:
                        logging.info("Sending duplicate request warning.")
                        db.setWarnedEmail(bridgeRequest.client, True, now)
                        db.commit()
                        raise TooSoonEmail("Must wait %d seconds" % wait,
                                           bridgeRequest.client)
            # warning period is over
            elif wasWarned:
                db.setWarnedEmail(bridgeRequest.client, False)

            pos = self.emailHmac("<%s>%s" % (interval, bridgeRequest.client))

            ring = None
            filtres = frozenset(bridgeRequest.filters)
            if filtres in self.hashring.filterRings:
                logging.debug("Cache hit %s" % filtres)
                _, ring = self.hashring.filterRings[filtres]
            else:
                logging.debug("Cache miss %s" % filtres)
                key = getHMAC(self.key, "Order-Bridges-In-Ring")
                ring = BridgeRing(key, self.answerParameters)
                self.hashring.addRing(ring, filtres, byFilters(filtres),
                                      populate_from=self.hashring.bridges)

            returnNum = self.bridgesPerResponse(ring)
            result = ring.getBridges(pos, returnNum, filterBySubnet=False)

            db.setEmailTime(bridgeRequest.client, now)
            db.commit()

        return result
示例#29
0
def createBridgeRings(cfg, proxyList, key):
    """Create the bridge distributors defined by the config file

    :type cfg:  :class:`Conf`
    :param cfg: The current configuration, including any in-memory settings
        (i.e. settings whose values were not obtained from the config file,
        but were set via a function somewhere)
    :type proxyList: :class:`~bridgedb.proxy.ProxySet`
    :param proxyList: The container for the IP addresses of any currently
        known open proxies.
    :param bytes key: Hashring master key
    :rtype: tuple
    :returns: A :class:`~bridgedb.Bridges.BridgeSplitter` hashring, an
        :class:`~bridgedb.distributors.https.distributor.HTTPSDistributor` or None, and an
        :class:`~bridgedb.distributors.email.distributor.EmailDistributor` or None, and an
        :class:`~bridgedb.distributors.moat.distributor.MoatDistributor` or None.
    """
    # Create a BridgeSplitter to assign the bridges to the different
    # distributors.
    hashring = Bridges.BridgeSplitter(crypto.getHMAC(key, "Hashring-Key"))
    logging.debug("Created hashring: %r" % hashring)

    # Create ring parameters.
    ringParams = Bridges.BridgeRingParameters(needPorts=cfg.FORCE_PORTS,
                                              needFlags=cfg.FORCE_FLAGS)

    emailDistributor = ipDistributor = moatDistributor = None

    # As appropriate, create a Moat distributor.
    if cfg.MOAT_DIST and cfg.MOAT_SHARE:
        logging.debug("Setting up Moat Distributor...")
        moatDistributor = MoatDistributor(
            cfg.MOAT_N_IP_CLUSTERS,
            crypto.getHMAC(key, "Moat-Dist-Key"),
            proxyList,
            answerParameters=ringParams)
        hashring.addRing(moatDistributor.hashring, "moat", cfg.MOAT_SHARE)

    # As appropriate, create an IP-based distributor.
    if cfg.HTTPS_DIST and cfg.HTTPS_SHARE:
        logging.debug("Setting up HTTPS Distributor...")
        ipDistributor = HTTPSDistributor(
            cfg.N_IP_CLUSTERS,
            crypto.getHMAC(key, "HTTPS-IP-Dist-Key"),
            proxyList,
            answerParameters=ringParams)
        hashring.addRing(ipDistributor.hashring, "https", cfg.HTTPS_SHARE)

    # As appropriate, create an email-based distributor.
    if cfg.EMAIL_DIST and cfg.EMAIL_SHARE:
        logging.debug("Setting up Email Distributor...")
        emailDistributor = EmailDistributor(
            crypto.getHMAC(key, "Email-Dist-Key"),
            cfg.EMAIL_DOMAIN_MAP.copy(),
            cfg.EMAIL_DOMAIN_RULES.copy(),
            answerParameters=ringParams,
            whitelist=cfg.EMAIL_WHITELIST.copy())
        hashring.addRing(emailDistributor.hashring, "email", cfg.EMAIL_SHARE)

    # As appropriate, tell the hashring to leave some bridges unallocated.
    if cfg.RESERVED_SHARE:
        hashring.addRing(Bridges.UnallocatedHolder(),
                         "unallocated",
                         cfg.RESERVED_SHARE)

    return hashring, emailDistributor, ipDistributor, moatDistributor
示例#30
0
def addWebServer(cfg, dist, sched):
    """Set up a web server.

    :param cfg: A configuration object from :mod:`bridgedb.Main`. Currently,
         we use these options::
             HTTPS_N_BRIDGES_PER_ANSWER
             HTTP_UNENCRYPTED_PORT
             HTTP_UNENCRYPTED_BIND_IP
             HTTP_USE_IP_FROM_FORWARDED_HEADER
             HTTPS_PORT
             HTTPS_BIND_IP
             HTTPS_USE_IP_FROM_FORWARDED_HEADER
             RECAPTCHA_ENABLED
             RECAPTCHA_PUB_KEY
             RECAPTCHA_PRIV_KEY
             RECAPTCHA_REMOTEIP
             GIMP_CAPTCHA_ENABLED
             GIMP_CAPTCHA_DIR
    :type dist: :class:`bridgedb.Dist.IPBasedDistributor`
    :param dist: A bridge distributor.
    :type sched: :class:`bridgedb.Time.IntervalSchedule`
    :param sched: DOCDOC
    """
    httpdist = resource.Resource()
    httpdist.putChild('', WebRoot())
    httpdist.putChild('robots.txt',
                      static.File(os.path.join(template_root, 'robots.txt')))
    httpdist.putChild('assets',
                      static.File(os.path.join(template_root, 'assets/')))
    httpdist.putChild('options', WebResourceOptions())

    bridgesResource = WebResourceBridges(
        dist, sched, cfg.HTTPS_N_BRIDGES_PER_ANSWER,
        cfg.HTTP_USE_IP_FROM_FORWARDED_HEADER,
        includeFingerprints=cfg.HTTPS_INCLUDE_FINGERPRINTS)

    if cfg.RECAPTCHA_ENABLED:
        protected = ReCaptchaProtectedResource(
                recaptchaPrivKey=cfg.RECAPTCHA_PRIV_KEY,
                recaptchaPubKey=cfg.RECAPTCHA_PUB_KEY,
                remoteip=cfg.RECAPTCHA_REMOTEIP,
                useForwardedHeader=cfg.HTTP_USE_IP_FROM_FORWARDED_HEADER,
                protectedResource=bridgesResource)
        httpdist.putChild('bridges', protected)

    elif cfg.GIMP_CAPTCHA_ENABLED:
        # Get the HMAC secret key for CAPTCHA challenges and create a new key
        # from it for use on the server:
        captchaKey = crypto.getKey(cfg.GIMP_CAPTCHA_HMAC_KEYFILE)
        hmacKey = crypto.getHMAC(captchaKey, "Captcha-Key")

        # Load or create our encryption keys:
        secretKey, publicKey = crypto.getRSAKey(cfg.GIMP_CAPTCHA_RSA_KEYFILE)

        protected = GimpCaptchaProtectedResource(
            secretKey=secretKey,
            publicKey=publicKey,
            hmacKey=hmacKey,
            captchaDir=cfg.GIMP_CAPTCHA_DIR,
            useForwardedHeader=cfg.HTTP_USE_IP_FROM_FORWARDED_HEADER,
            protectedResource=bridgesResource)
        httpdist.putChild('bridges', protected)
    else:
        httpdist.putChild('bridges', bridgesResource)

    site = server.Site(httpdist)

    if cfg.HTTP_UNENCRYPTED_PORT:
        ip = cfg.HTTP_UNENCRYPTED_BIND_IP or ""
        try:
            reactor.listenTCP(cfg.HTTP_UNENCRYPTED_PORT, site, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)

    if cfg.HTTPS_PORT:
        from twisted.internet.ssl import DefaultOpenSSLContextFactory
        #from OpenSSL.SSL import SSLv3_METHOD
        ip = cfg.HTTPS_BIND_IP or ""
        factory = DefaultOpenSSLContextFactory(cfg.HTTPS_KEY_FILE,
                                               cfg.HTTPS_CERT_FILE)
        try:
            reactor.listenSSL(cfg.HTTPS_PORT, site, factory, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)

    return site
示例#31
0
    def getBridgesForIP(self, ip, epoch, N=1, countryCode=None,
                        bridgeFilterRules=None):
        """Return a list of bridges to give to a user.

        :param str ip: The user's IP address, as a dotted quad.
        :param str epoch: The time period when we got this request.  This can
                          be any string, so long as it changes with every
                          period.
        :param int N: The number of bridges to try to give back. (default: 1)
        :param str countryCode: DOCDOC (default: None)
        :param list bridgeFilterRules: A list of callables used filter the
                                       bridges returned in the response to the
                                       client. See :mod:`~bridgedb.Filters`.
        :rtype: list
        :return: A list of :class:`~bridgedb.Bridges.Bridge`s to include in
                 the response. See
                 :meth:`bridgedb.HTTPServer.WebResource.getBridgeRequestAnswer`
                 for an example of how this is used.
        """
        logging.info("Attempting to return %d bridges to client %s..."
                     % (N, ip))

        if not bridgeFilterRules:
            bridgeFilterRules=[]

        if not len(self.splitter):
            logging.warn("Bailing! Splitter has zero bridges!")
            return []

        logging.debug("Bridges in splitter:\t%d" % len(self.splitter))
        logging.debug("Client request epoch:\t%s" % epoch)
        logging.debug("Active bridge filters:\t%s"
                      % ' '.join([x.func_name for x in bridgeFilterRules]))

        area = self.areaMapper(ip)
        logging.debug("IP mapped to area:\t%s"
                      % logSafely("{0}.0/24".format(area)))

        key1 = ''
        pos = 0
        n = self.nClusters

        # only one of ip categories or area clustering is active
        # try to match the request to an ip category
        for category in self.categories:
            # IP Categories
            if category.contains(ip):
                g = filterAssignBridgesToRing(self.splitter.hmac,
                                              self.nClusters +
                                              len(self.categories),
                                              n)
                bridgeFilterRules.append(g)
                logging.info("category<%s>%s", epoch, logSafely(area))
                pos = self.areaOrderHmac("category<%s>%s" % (epoch, area))
                key1 = getHMAC(self.splitter.key,
                               "Order-Bridges-In-Ring-%d" % n)
                break
            n += 1

        # if no category matches, use area clustering
        else:
            # IP clustering
            h = int( self.areaClusterHmac(area)[:8], 16)
            # length of numClusters
            clusterNum = h % self.nClusters

            g = filterAssignBridgesToRing(self.splitter.hmac,
                                          self.nClusters +
                                          len(self.categories),
                                          clusterNum)
            bridgeFilterRules.append(g)
            pos = self.areaOrderHmac("<%s>%s" % (epoch, area))
            key1 = getHMAC(self.splitter.key,
                           "Order-Bridges-In-Ring-%d" % clusterNum)

        # try to find a cached copy
        ruleset = frozenset(bridgeFilterRules)

        # See if we have a cached copy of the ring,
        # otherwise, add a new ring and populate it
        if ruleset in self.splitter.filterRings.keys():
            logging.debug("Cache hit %s" % ruleset)
            _,ring = self.splitter.filterRings[ruleset]

        # else create the ring and populate it
        else:
            logging.debug("Cache miss %s" % ruleset)
            ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
            self.splitter.addRing(ring,
                                  ruleset,
                                  filterBridgesByRules(bridgeFilterRules),
                                  populate_from=self.splitter.bridges)

        # get an appropriate number of bridges
        numBridgesToReturn = getNumBridgesPerAnswer(ring,
                                                    max_bridges_per_answer=N)
        answer = ring.getBridges(pos, numBridgesToReturn)
        return answer
示例#32
0
    def getBridgesForIP(self, ip, epoch, N=1, countryCode=None,
                        bridgeFilterRules=None):
        """Return a list of bridges to give to a user.

        :param str ip: The user's IP address, as a dotted quad.
        :param str epoch: The time period when we got this request.  This can
                          be any string, so long as it changes with every
                          period.
        :param int N: The number of bridges to try to give back. (default: 1)
        :param str countryCode: The two-letter geoip country code of the
            client's IP address. If given, it is assumed that any bridges
            distributed to that client should not be blocked in that
            country. (default: None)
        :param list bridgeFilterRules: A list of callables used filter the
                                       bridges returned in the response to the
                                       client. See :mod:`~bridgedb.Filters`.
        :rtype: list
        :return: A list of :class:`~bridgedb.Bridges.Bridge`s to include in
                 the response. See
                 :meth:`bridgedb.HTTPServer.WebResource.getBridgeRequestAnswer`
                 for an example of how this is used.
        """
        logging.info("Attempting to return %d bridges to client %s..."
                     % (N, ip))

        if not bridgeFilterRules:
            bridgeFilterRules=[]

        if not len(self.splitter):
            logging.warn("Bailing! Splitter has zero bridges!")
            return []

        logging.debug("Bridges in splitter:\t%d" % len(self.splitter))
        logging.debug("Client request epoch:\t%s" % epoch)
        logging.debug("Active bridge filters:\t%s"
                      % ' '.join([x.func_name for x in bridgeFilterRules]))

        area = self.areaMapper(ip)
        logging.debug("IP mapped to area:\t%s" % area)

        key1 = ''
        pos = 0
        n = self.nClusters

        # only one of ip categories or area clustering is active
        # try to match the request to an ip category
        for category in self.categories:
            # IP Categories
            if ip in category:
                # The tag is a tag applied to a proxy IP address when it is
                # added to the bridgedb.proxy.ProxySet. For Tor Exit relays,
                # the default is 'exit_relay'. For other proxies loaded from
                # the PROXY_LIST_FILES config option, the default tag is the
                # full filename that the IP address originally came from.
                tag = category.getTag(ip)
                logging.info("Client was from known proxy (tag: %s): %s" % (tag, ip))
                g = filterAssignBridgesToRing(self.splitter.hmac,
                                              self.nClusters +
                                              len(self.categories),
                                              n)
                bridgeFilterRules.append(g)
                # Cluster Tor/proxy users into four groups.  This means that
                # no matter how many different Tor Exits or proxies a client
                # uses, the most they can ever get is four different sets of
                # bridge lines (per period).
                group = (int(ipaddr.IPAddress(ip)) % 4) + 1
                logging.debug(("Assigning client hashring position based on: "
                               "known-proxy<%s>%s") % (epoch, group))
                pos = self.areaOrderHmac("known-proxy<%s>%s" % (epoch, group))
                key1 = getHMAC(self.splitter.key,
                               "Order-Bridges-In-Ring-%d" % n)
                break
            n += 1

        # if no category matches, use area clustering
        else:
            # IP clustering
            h = int( self.areaClusterHmac(area)[:8], 16)
            # length of numClusters
            clusterNum = h % self.nClusters

            g = filterAssignBridgesToRing(self.splitter.hmac,
                                          self.nClusters +
                                          len(self.categories),
                                          clusterNum)
            bridgeFilterRules.append(g)
            pos = self.areaOrderHmac("<%s>%s" % (epoch, area))
            key1 = getHMAC(self.splitter.key,
                           "Order-Bridges-In-Ring-%d" % clusterNum)

        # try to find a cached copy
        ruleset = frozenset(bridgeFilterRules)

        # See if we have a cached copy of the ring,
        # otherwise, add a new ring and populate it
        if ruleset in self.splitter.filterRings.keys():
            logging.debug("Cache hit %s" % ruleset)
            _, ring = self.splitter.filterRings[ruleset]

        # else create the ring and populate it
        else:
            logging.debug("Cache miss %s" % ruleset)
            ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
            self.splitter.addRing(ring,
                                  ruleset,
                                  filterBridgesByRules(bridgeFilterRules),
                                  populate_from=self.splitter.bridges)

        # get an appropriate number of bridges
        numBridgesToReturn = getNumBridgesPerAnswer(ring,
                                                    max_bridges_per_answer=N)
        answer = ring.getBridges(pos, numBridgesToReturn)
        return answer
示例#33
0
def addWebServer(cfg, dist, sched):
    """Set up a web server for HTTP(S)-based bridge distribution.

    :type cfg: :class:`bridgedb.persistent.Conf`
    :param cfg: A configuration object from
         :mod:`bridgedb.Main`. Currently, we use these options::
             HTTP_UNENCRYPTED_PORT
             HTTP_UNENCRYPTED_BIND_IP
             HTTP_USE_IP_FROM_FORWARDED_HEADER
             HTTPS_N_BRIDGES_PER_ANSWER
             HTTPS_INCLUDE_FINGERPRINTS
             HTTPS_KEY_FILE
             HTTPS_CERT_FILE
             HTTPS_PORT
             HTTPS_BIND_IP
             HTTPS_USE_IP_FROM_FORWARDED_HEADER
             RECAPTCHA_ENABLED
             RECAPTCHA_PUB_KEY
             RECAPTCHA_SEC_KEY
             RECAPTCHA_REMOTEIP
             GIMP_CAPTCHA_ENABLED
             GIMP_CAPTCHA_DIR
             GIMP_CAPTCHA_HMAC_KEYFILE
             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.
    """
    captcha = None
    fwdHeaders = cfg.HTTP_USE_IP_FROM_FORWARDED_HEADER
    numBridges = cfg.HTTPS_N_BRIDGES_PER_ANSWER
    fprInclude = cfg.HTTPS_INCLUDE_FINGERPRINTS

    logging.info("Starting web servers...")

    httpdist = resource.Resource()
    httpdist.putChild('', WebRoot())
    httpdist.putChild('robots.txt',
                      static.File(os.path.join(TEMPLATE_DIR, 'robots.txt')))
    httpdist.putChild('keys',
                      static.File(os.path.join(TEMPLATE_DIR, 'bridgedb.asc')))
    httpdist.putChild('assets',
                      static.File(os.path.join(TEMPLATE_DIR, 'assets/')))
    httpdist.putChild('options', WebResourceOptions())
    httpdist.putChild('howto', WebResourceHowto())

    if cfg.RECAPTCHA_ENABLED:
        publicKey = cfg.RECAPTCHA_PUB_KEY
        secretKey = cfg.RECAPTCHA_SEC_KEY
        captcha = partial(ReCaptchaProtectedResource,
                          remoteIP=cfg.RECAPTCHA_REMOTEIP)
    elif cfg.GIMP_CAPTCHA_ENABLED:
        # Get the master HMAC secret key for CAPTCHA challenges, and then
        # create a new HMAC key from it for use on the server.
        captchaKey = crypto.getKey(cfg.GIMP_CAPTCHA_HMAC_KEYFILE)
        hmacKey = crypto.getHMAC(captchaKey, "Captcha-Key")
        # Load or create our encryption keys:
        secretKey, publicKey = crypto.getRSAKey(cfg.GIMP_CAPTCHA_RSA_KEYFILE)
        captcha = partial(GimpCaptchaProtectedResource,
                          hmacKey=hmacKey,
                          captchaDir=cfg.GIMP_CAPTCHA_DIR)

    bridges = WebResourceBridges(dist, sched, numBridges,
                                 fwdHeaders, includeFingerprints=fprInclude)
    if captcha:
        # Protect the 'bridges' page with a CAPTCHA, if configured to do so:
        protected = captcha(publicKey=publicKey,
                            secretKey=secretKey,
                            useForwardedHeader=fwdHeaders,
                            protectedResource=bridges)
        httpdist.putChild('bridges', protected)
        logging.info("Protecting resources with %s." % captcha.func.__name__)
    else:
        httpdist.putChild('bridges', bridges)

    site = server.Site(httpdist)
    site.displayTracebacks = False

    if cfg.HTTP_UNENCRYPTED_PORT:
        ip = cfg.HTTP_UNENCRYPTED_BIND_IP or ""
        port = cfg.HTTP_UNENCRYPTED_PORT or 80
        try:
            reactor.listenTCP(port, site, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started HTTP server on %s:%d" % (str(ip), int(port)))

    if cfg.HTTPS_PORT:
        ip = cfg.HTTPS_BIND_IP or ""
        port = cfg.HTTPS_PORT or 443
        try:
            from twisted.internet.ssl import DefaultOpenSSLContextFactory
            factory = DefaultOpenSSLContextFactory(cfg.HTTPS_KEY_FILE,
                                                   cfg.HTTPS_CERT_FILE)
            reactor.listenSSL(port, site, factory, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started HTTPS server on %s:%d" % (str(ip), int(port)))

    return site
示例#34
0
def createBridgeRings(cfg, proxyList, key):
    """Create the bridge distributors defined by the config file

    :type cfg:  :class:`Conf`
    :param cfg: The current configuration, including any in-memory
                settings (i.e. settings whose values were not obtained from the
                config file, but were set via a function somewhere)
    :type proxyList: :class:`ProxyCategory`
    :param proxyList: The container for the IP addresses of any currently
                      known open proxies.
    :param bytes key: Splitter master key
    :rtype: tuple
    :returns: A BridgeSplitter splitter, an IPBasedDistributor or None,
              and an EmailBasedDistributor or None.
    """

    # Create a BridgeSplitter to assign the bridges to the different
    # distributors.
    splitter = Bridges.BridgeSplitter(crypto.getHMAC(key, "Splitter-Key"))
    logging.debug("Created splitter: %r" % splitter)

    # Create ring parameters.
    ringParams = Bridges.BridgeRingParameters(needPorts=cfg.FORCE_PORTS,
                                              needFlags=cfg.FORCE_FLAGS)

    emailDistributor = ipDistributor = None
    # As appropriate, create an IP-based distributor.
    if cfg.HTTPS_DIST and cfg.HTTPS_SHARE:
        logging.debug("Setting up HTTPS Distributor...")
        categories = []
        if proxyList.ipset:
            logging.debug("Adding proxyList to HTTPS Distributor categories.")
            categories.append(proxyList)
        logging.debug("HTTPS Distributor categories: '%s'" % categories)

        ipDistributor = Dist.IPBasedDistributor(
            Dist.uniformMap,
            cfg.N_IP_CLUSTERS,
            crypto.getHMAC(key, "HTTPS-IP-Dist-Key"),
            categories,
            answerParameters=ringParams)
        splitter.addRing(ipDistributor, "https", cfg.HTTPS_SHARE)

    # As appropriate, create an email-based distributor.
    if cfg.EMAIL_DIST and cfg.EMAIL_SHARE:
        logging.debug("Setting up Email Distributor...")
        emailDistributor = Dist.EmailBasedDistributor(
            crypto.getHMAC(key, "Email-Dist-Key"),
            cfg.EMAIL_DOMAIN_MAP.copy(),
            cfg.EMAIL_DOMAIN_RULES.copy(),
            answerParameters=ringParams,
            whitelist=cfg.EMAIL_WHITELIST.copy())
        splitter.addRing(emailDistributor, "email", cfg.EMAIL_SHARE)

    # As appropriate, tell the splitter to leave some bridges unallocated.
    if cfg.RESERVED_SHARE:
        splitter.addRing(Bridges.UnallocatedHolder(),
                         "unallocated",
                         cfg.RESERVED_SHARE)

    # Add pseudo distributors to splitter
    for pseudoRing in cfg.FILE_BUCKETS.keys():
        splitter.addPseudoRing(pseudoRing)

    return splitter, emailDistributor, ipDistributor
示例#35
0
def addWebServer(config, distributor):
    """Set up a web server for HTTP(S)-based bridge distribution.

    :type config: :class:`bridgedb.persistent.Conf`
    :param config: A configuration object from
         :mod:`bridgedb.main`. Currently, we use these options::
             HTTP_UNENCRYPTED_PORT
             HTTP_UNENCRYPTED_BIND_IP
             HTTP_USE_IP_FROM_FORWARDED_HEADER
             HTTPS_N_BRIDGES_PER_ANSWER
             HTTPS_INCLUDE_FINGERPRINTS
             HTTPS_KEY_FILE
             HTTPS_CERT_FILE
             HTTPS_PORT
             HTTPS_BIND_IP
             HTTPS_USE_IP_FROM_FORWARDED_HEADER
             HTTPS_ROTATION_PERIOD
             RECAPTCHA_ENABLED
             RECAPTCHA_PUB_KEY
             RECAPTCHA_SEC_KEY
             RECAPTCHA_REMOTEIP
             GIMP_CAPTCHA_ENABLED
             GIMP_CAPTCHA_DIR
             GIMP_CAPTCHA_HMAC_KEYFILE
             GIMP_CAPTCHA_RSA_KEYFILE
             SERVER_PUBLIC_FQDN
             CSP_ENABLED
             CSP_REPORT_ONLY
             CSP_INCLUDE_SELF
    :type distributor: :class:`bridgedb.distributors.https.distributor.HTTPSDistributor`
    :param distributor: A bridge distributor.
    :raises SystemExit: if the servers cannot be started.
    :rtype: :api:`twisted.web.server.Site`
    :returns: A webserver.
    """
    captcha = None
    fwdHeaders = config.HTTP_USE_IP_FROM_FORWARDED_HEADER
    numBridges = config.HTTPS_N_BRIDGES_PER_ANSWER
    fprInclude = config.HTTPS_INCLUDE_FINGERPRINTS

    logging.info("Starting web servers...")

    setFQDN(config.SERVER_PUBLIC_FQDN)

    index   = IndexResource()
    options = OptionsResource()
    howto   = HowtoResource()
    robots  = static.File(os.path.join(TEMPLATE_DIR, 'robots.txt'))
    assets  = static.File(os.path.join(TEMPLATE_DIR, 'assets/'))
    keys    = static.Data(bytes(strings.BRIDGEDB_OPENPGP_KEY), 'text/plain')
    csp     = CSPResource(enabled=config.CSP_ENABLED,
                          includeSelf=config.CSP_INCLUDE_SELF,
                          reportViolations=config.CSP_REPORT_ONLY,
                          useForwardedHeader=fwdHeaders)

    root = CustomErrorHandlingResource()
    root.putChild('', index)
    root.putChild('robots.txt', robots)
    root.putChild('keys', keys)
    root.putChild('assets', assets)
    root.putChild('options', options)
    root.putChild('howto', howto)
    root.putChild('maintenance', maintenance)
    root.putChild('error', resource500)
    root.putChild(CSPResource.reportURI, csp)

    if config.RECAPTCHA_ENABLED:
        publicKey = config.RECAPTCHA_PUB_KEY
        secretKey = config.RECAPTCHA_SEC_KEY
        captcha = partial(ReCaptchaProtectedResource,
                          remoteIP=config.RECAPTCHA_REMOTEIP)
    elif config.GIMP_CAPTCHA_ENABLED:
        # Get the master HMAC secret key for CAPTCHA challenges, and then
        # create a new HMAC key from it for use on the server.
        captchaKey = crypto.getKey(config.GIMP_CAPTCHA_HMAC_KEYFILE)
        hmacKey = crypto.getHMAC(captchaKey, "Captcha-Key")
        # Load or create our encryption keys:
        secretKey, publicKey = crypto.getRSAKey(config.GIMP_CAPTCHA_RSA_KEYFILE)
        captcha = partial(GimpCaptchaProtectedResource,
                          hmacKey=hmacKey,
                          captchaDir=config.GIMP_CAPTCHA_DIR)

    if config.HTTPS_ROTATION_PERIOD:
        count, period = config.HTTPS_ROTATION_PERIOD.split()
        sched = ScheduledInterval(count, period)
    else:
        sched = Unscheduled()

    bridges = BridgesResource(distributor, sched, numBridges, fwdHeaders,
                              includeFingerprints=fprInclude)
    if captcha:
        # Protect the 'bridges' page with a CAPTCHA, if configured to do so:
        protected = captcha(publicKey=publicKey,
                            secretKey=secretKey,
                            useForwardedHeader=fwdHeaders,
                            protectedResource=bridges)
        root.putChild('bridges', protected)
        logging.info("Protecting resources with %s." % captcha.func.__name__)
    else:
        root.putChild('bridges', bridges)

    site = Site(root)
    site.displayTracebacks = False

    if config.HTTP_UNENCRYPTED_PORT:  # pragma: no cover
        ip = config.HTTP_UNENCRYPTED_BIND_IP or ""
        port = config.HTTP_UNENCRYPTED_PORT or 80
        try:
            reactor.listenTCP(port, site, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started HTTP server on %s:%d" % (str(ip), int(port)))

    if config.HTTPS_PORT:  # pragma: no cover
        ip = config.HTTPS_BIND_IP or ""
        port = config.HTTPS_PORT or 443
        try:
            from twisted.internet.ssl import DefaultOpenSSLContextFactory
            factory = DefaultOpenSSLContextFactory(config.HTTPS_KEY_FILE,
                                                   config.HTTPS_CERT_FILE)
            reactor.listenSSL(port, site, factory, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started HTTPS server on %s:%d" % (str(ip), int(port)))

    return site
示例#36
0
    def prepopulateRings(self):
        """Prepopulate this distributor's hashrings and subhashrings with
        bridges.

        The hashring structure for this distributor is influenced by the
        ``N_IP_CLUSTERS`` configuration option, as well as the number of
        ``PROXY_LIST_FILES``.

        Essentially, :data:`totalSubrings` is set to the specified
        ``N_IP_CLUSTERS``.  All of the ``PROXY_LIST_FILES``, plus the list of
        Tor Exit relays (downloaded into memory with :script:`get-tor-exits`),
        are stored in :data:`proxies`, and the latter is added as an
        additional cluster (such that :data:`totalSubrings` becomes
        ``N_IP_CLUSTERS + 1``).  The number of subhashrings which this
        :class:`Distributor` has active in its hashring is then
        :data:`totalSubrings`, where the last cluster is reserved for all
        :data:`proxies`.

        As an example, if BridgeDB was configured with ``N_IP_CLUSTERS=4`` and
        ``PROXY_LIST_FILES=["open-socks-proxies.txt"]``, then the total number
        of subhashrings is five — four for the "clusters", and one for the
        :data:`proxies`. Thus, the resulting hashring-subhashring structure
        would look like:

        +------------------+---------------------------------------------------+-------------+
        |                  |               Directly connecting users           | Tor / known |
        |                  |                                                   | proxy users |
        +------------------+------------+------------+------------+------------+-------------+
        | Clusters         | Cluster-1  | Cluster-2  | Cluster-3  | Cluster-4  | Cluster-5   |
        +==================+============+============+============+============+=============+
        | Subhashrings     |            |            |            |            |             |
        | (total, assigned)| (5,1)      | (5,2)      | (5,3)      | (5,4)      | (5,5)       |
        +------------------+------------+------------+------------+------------+-------------+
        | Filtered         | (5,1)-IPv4 | (5,2)-IPv4 | (5,3)-IPv4 | (5,4)-IPv4 | (5,5)-IPv4  |
        | Subhashrings     |            |            |            |            |             |
        | bBy requested    +------------+------------+------------+------------+-------------+
        | bridge type)     | (5,1)-IPv6 | (5,2)-IPv6 | (5,3)-IPv6 | (5,4)-IPv6 | (5,5)-IPv6  |
        |                  |            |            |            |            |             |
        +------------------+------------+------------+------------+------------+-------------+

        The "filtered subhashrings" are essentially filtered copies of their
        respective subhashring, such that they only contain bridges which
        support IPv4 or IPv6, respectively.  Additionally, the contents of
        ``(5,1)-IPv4`` and ``(5,1)-IPv6`` sets are *not* disjoint.

        Thus, in this example, we end up with **10 total subhashrings**.
        """
        logging.info("Prepopulating %s distributor hashrings..." % self.name)

        for filterFn in [byIPv4, byIPv6]:
            for subring in range(1, self.totalSubrings + 1):
                filters = self._buildHashringFilters([
                    filterFn,
                ], subring)
                key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
                ring = BridgeRing(key1, self.answerParameters)
                # For consistency with previous implementation of this method,
                # only set the "name" for "clusters" which are for this
                # distributor's proxies:
                if subring == self.proxySubring:
                    ring.setName('{0} Proxy Ring'.format(self.name))
                self.hashring.addRing(ring,
                                      filters,
                                      byFilters(filters),
                                      populate_from=self.hashring.bridges)
示例#37
0
    def getBridgesForEmail(self, emailaddress, epoch, N=1, parameters=None,
                           countryCode=None, bridgeFilterRules=None):
        """Return a list of bridges to give to a user.

        :param str emailaddress: The user's email address, as given in a
            :header:`From:` line.
        :param epoch: The time period when we got this request. This can be
            any string, so long as it changes with every period.
        :param int N: The number of bridges to try to give back.
        :param parameters: DOCDOC
        :param countryCode: DOCDOC
        :param bridgeFilterRules: DOCDOC
        """
        if not bridgeFilterRules:
            bridgeFilterRules=[]
        now = time.time()

        # All checks on the email address, such as checks for whitelisting and
        # canonicalization of domain name, are done in
        # :meth:`bridgedb.email.autoresponder.getMailTo` and
        # :meth:`bridgedb.email.autoresponder.SMTPAutoresponder.runChecks`.
        if not emailaddress:
            logging.error(("%s distributor can't get bridges for blank email "
                           "address!") % (self.name, emailaddress))
            return []

        with bridgedb.Storage.getDB() as db:
            wasWarned = db.getWarnedEmail(emailaddress)
            lastSaw = db.getEmailTime(emailaddress)

            logging.info("Attempting to return for %d bridges for %s..."
                         % (N, emailaddress))

            if lastSaw is not None:
                if emailaddress in self.whitelist.keys():
                    logging.info(("Whitelisted email address %s was last seen "
                                  "%d seconds ago.")
                                 % (emailaddress, now - lastSaw))
                elif (lastSaw + MAX_EMAIL_RATE) >= now:
                    wait = (lastSaw + MAX_EMAIL_RATE) - now
                    logging.info("Client %s must wait another %d seconds."
                                 % (emailaddress, wait))
                    if wasWarned:
                        raise IgnoreEmail("Client was warned.", emailaddress)
                    else:
                        logging.info("Sending duplicate request warning.")
                        db.setWarnedEmail(emailaddress, True, now)
                        db.commit()
                        raise TooSoonEmail("Must wait %d seconds" % wait,
                                           emailaddress)

            # warning period is over
            elif wasWarned:
                db.setWarnedEmail(emailaddress, False)

            pos = self.emailHmac("<%s>%s" % (epoch, emailaddress))

            ring = None
            ruleset = frozenset(bridgeFilterRules)
            if ruleset in self.splitter.filterRings.keys():
                logging.debug("Cache hit %s" % ruleset)
                _, ring = self.splitter.filterRings[ruleset]
            else:
                # cache miss, add new ring
                logging.debug("Cache miss %s" % ruleset)

                # add new ring
                key1 = getHMAC(self.splitter.key,
                                                 "Order-Bridges-In-Ring")
                ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters)
                # debug log: cache miss
                self.splitter.addRing(ring, ruleset,
                                      filterBridgesByRules(bridgeFilterRules),
                                      populate_from=self.splitter.bridges)

            numBridgesToReturn = getNumBridgesPerAnswer(ring,
                                                        max_bridges_per_answer=N)
            result = ring.getBridges(pos, numBridgesToReturn)

            db.setEmailTime(emailaddress, now)
            db.commit()

        return result
示例#38
0
def addMoatServer(config, distributor):
    """Set up a web server for moat bridge distribution.

    :type config: :class:`bridgedb.persistent.Conf`
    :param config: A configuration object from
         :mod:`bridgedb.main`. Currently, we use these options::
             GIMP_CAPTCHA_DIR
             SERVER_PUBLIC_FQDN
             SUPPORTED_TRANSPORTS
             MOAT_DIST
             MOAT_DIST_VIA_MEEK_ONLY
             MOAT_TLS_CERT_FILE
             MOAT_TLS_KEY_FILE
             MOAT_SERVER_PUBLIC_ROOT
             MOAT_HTTPS_IP
             MOAT_HTTPS_PORT
             MOAT_HTTP_IP
             MOAT_HTTP_PORT
             MOAT_BRIDGES_PER_ANSWER
             MOAT_TRANSPORT_PREFERENCE_LIST
             MOAT_USE_IP_FROM_FORWARDED_HEADER
             MOAT_SKIP_LOOPBACK_ADDRESSES
             MOAT_ROTATION_PERIOD
             MOAT_GIMP_CAPTCHA_HMAC_KEYFILE
             MOAT_GIMP_CAPTCHA_RSA_KEYFILE
    :type distributor: :class:`bridgedb.distributors.moat.distributor.MoatDistributor`
    :param distributor: A bridge distributor.
    :raises SystemExit: if the servers cannot be started.
    :rtype: :api:`twisted.web.server.Site`
    :returns: A webserver.
    """
    captcha = None
    fwdHeaders = config.MOAT_USE_IP_FROM_FORWARDED_HEADER
    numBridges = config.MOAT_BRIDGES_PER_ANSWER
    skipLoopback = config.MOAT_SKIP_LOOPBACK_ADDRESSES

    logging.info("Starting moat servers...")

    setFQDN(config.SERVER_PUBLIC_FQDN)
    setRoot(config.MOAT_SERVER_PUBLIC_ROOT)
    setSupportedTransports(config.SUPPORTED_TRANSPORTS)
    setPreferredTransports(config.MOAT_TRANSPORT_PREFERENCE_LIST)

    # Get the master HMAC secret key for CAPTCHA challenges, and then
    # create a new HMAC key from it for use on the server.
    captchaKey = crypto.getKey(config.MOAT_GIMP_CAPTCHA_HMAC_KEYFILE)
    hmacKey = crypto.getHMAC(captchaKey, "Moat-Captcha-Key")
    # Load or create our encryption keys:
    secretKey, publicKey = crypto.getRSAKey(
        config.MOAT_GIMP_CAPTCHA_RSA_KEYFILE)
    sched = Unscheduled()

    if config.MOAT_ROTATION_PERIOD:
        count, period = config.MOAT_ROTATION_PERIOD.split()
        sched = ScheduledInterval(count, period)

    sitePublicDir = getRoot()

    meek = CustomErrorHandlingResource()
    moat = CustomErrorHandlingResource()
    fetch = CaptchaFetchResource(hmacKey, publicKey, secretKey,
                                 config.GIMP_CAPTCHA_DIR, fwdHeaders,
                                 skipLoopback)
    check = CaptchaCheckResource(distributor, sched, numBridges, hmacKey,
                                 publicKey, secretKey, fwdHeaders,
                                 skipLoopback)

    moat.putChild(b"fetch", fetch)
    moat.putChild(b"check", check)
    meek.putChild(b"moat", moat)

    root = CustomErrorHandlingResource()
    root.putChild(b"meek", meek)
    root.putChild(b"moat", moat)

    site = Site(root)
    site.displayTracebacks = False

    if config.MOAT_HTTP_PORT:  # pragma: no cover
        ip = config.MOAT_HTTP_IP or ""
        port = config.MOAT_HTTP_PORT or 80
        try:
            reactor.listenTCP(port, site, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started Moat HTTP server on %s:%d" %
                     (str(ip), int(port)))

    if config.MOAT_HTTPS_PORT:  # pragma: no cover
        ip = config.MOAT_HTTPS_IP or ""
        port = config.MOAT_HTTPS_PORT or 443
        try:
            from twisted.internet.ssl import DefaultOpenSSLContextFactory
            factory = DefaultOpenSSLContextFactory(config.MOAT_TLS_KEY_FILE,
                                                   config.MOAT_TLS_CERT_FILE)
            reactor.listenSSL(port, site, factory, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started Moat TLS server on %s:%d" % (str(ip), int(port)))

    return site
示例#39
0
def addWebServer(cfg, dist):
    """Set up a web server for HTTP(S)-based bridge distribution.

    :type cfg: :class:`bridgedb.persistent.Conf`
    :param cfg: A configuration object from
         :mod:`bridgedb.Main`. Currently, we use these options::
             HTTP_UNENCRYPTED_PORT
             HTTP_UNENCRYPTED_BIND_IP
             HTTP_USE_IP_FROM_FORWARDED_HEADER
             HTTPS_N_BRIDGES_PER_ANSWER
             HTTPS_INCLUDE_FINGERPRINTS
             HTTPS_KEY_FILE
             HTTPS_CERT_FILE
             HTTPS_PORT
             HTTPS_BIND_IP
             HTTPS_USE_IP_FROM_FORWARDED_HEADER
             HTTPS_ROTATION_PERIOD
             RECAPTCHA_ENABLED
             RECAPTCHA_PUB_KEY
             RECAPTCHA_SEC_KEY
             RECAPTCHA_REMOTEIP
             GIMP_CAPTCHA_ENABLED
             GIMP_CAPTCHA_DIR
             GIMP_CAPTCHA_HMAC_KEYFILE
             GIMP_CAPTCHA_RSA_KEYFILE
    :type dist: :class:`bridgedb.Dist.IPBasedDistributor`
    :param dist: A bridge distributor.
    :raises SystemExit: if the servers cannot be started.
    :rtype: :api:`twisted.web.server.Site`
    :returns: A webserver.
    """
    captcha = None
    fwdHeaders = cfg.HTTP_USE_IP_FROM_FORWARDED_HEADER
    numBridges = cfg.HTTPS_N_BRIDGES_PER_ANSWER
    fprInclude = cfg.HTTPS_INCLUDE_FINGERPRINTS

    logging.info("Starting web servers...")

    httpdist = resource.Resource()
    httpdist.putChild('', WebRoot())
    httpdist.putChild('robots.txt',
                      static.File(os.path.join(TEMPLATE_DIR, 'robots.txt')))
    httpdist.putChild('keys',
                      static.File(os.path.join(TEMPLATE_DIR, 'bridgedb.asc')))
    httpdist.putChild('assets',
                      static.File(os.path.join(TEMPLATE_DIR, 'assets/')))
    httpdist.putChild('options', WebResourceOptions())
    httpdist.putChild('howto', WebResourceHowto())

    if cfg.RECAPTCHA_ENABLED:
        publicKey = cfg.RECAPTCHA_PUB_KEY
        secretKey = cfg.RECAPTCHA_SEC_KEY
        captcha = partial(ReCaptchaProtectedResource,
                          remoteIP=cfg.RECAPTCHA_REMOTEIP)
    elif cfg.GIMP_CAPTCHA_ENABLED:
        # Get the master HMAC secret key for CAPTCHA challenges, and then
        # create a new HMAC key from it for use on the server.
        captchaKey = crypto.getKey(cfg.GIMP_CAPTCHA_HMAC_KEYFILE)
        hmacKey = crypto.getHMAC(captchaKey, "Captcha-Key")
        # Load or create our encryption keys:
        secretKey, publicKey = crypto.getRSAKey(cfg.GIMP_CAPTCHA_RSA_KEYFILE)
        captcha = partial(GimpCaptchaProtectedResource,
                          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:
        # Protect the 'bridges' page with a CAPTCHA, if configured to do so:
        protected = captcha(publicKey=publicKey,
                            secretKey=secretKey,
                            useForwardedHeader=fwdHeaders,
                            protectedResource=bridges)
        httpdist.putChild('bridges', protected)
        logging.info("Protecting resources with %s." % captcha.func.__name__)
    else:
        httpdist.putChild('bridges', bridges)

    site = server.Site(httpdist)
    site.displayTracebacks = False

    if cfg.HTTP_UNENCRYPTED_PORT:
        ip = cfg.HTTP_UNENCRYPTED_BIND_IP or ""
        port = cfg.HTTP_UNENCRYPTED_PORT or 80
        try:
            reactor.listenTCP(port, site, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started HTTP server on %s:%d" % (str(ip), int(port)))

    if cfg.HTTPS_PORT:
        ip = cfg.HTTPS_BIND_IP or ""
        port = cfg.HTTPS_PORT or 443
        try:
            from twisted.internet.ssl import DefaultOpenSSLContextFactory
            factory = DefaultOpenSSLContextFactory(cfg.HTTPS_KEY_FILE,
                                                   cfg.HTTPS_CERT_FILE)
            reactor.listenSSL(port, site, factory, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started HTTPS server on %s:%d" % (str(ip), int(port)))

    return site
示例#40
0
    def getBridges(self, bridgeRequest, interval):
        """Return a list of bridges to give to a user.

        :type bridgeRequest: :class:`bridgedb.distributors.https.request.HTTPSBridgeRequest`
        :param bridgeRequest: A :class:`~bridgedb.bridgerequest.BridgeRequestBase`
            with the :data:`~bridgedb.bridgerequest.BridgeRequestBase.client`
            attribute set to a string containing the client's IP address.
        :param str interval: The time period when we got this request.  This
            can be any string, so long as it changes with every period.
        :rtype: list
        :return: A list of :class:`~bridgedb.Bridges.Bridge`s to include in
            the response. See
            :meth:`bridgedb.distributors.https.server.WebResourceBridges.getBridgeRequestAnswer`
            for an example of how this is used.
        """
        logging.info("Attempting to get bridges for %s..." % bridgeRequest.client)

        if not len(self.hashring):
            logging.warn("Bailing! Hashring has zero bridges!")
            return []

        usingProxy = False

        # First, check if the client's IP is one of the known :data:`proxies`:
        if bridgeRequest.client in self.proxies:
            # The tag is a tag applied to a proxy IP address when it is added
            # to the bridgedb.proxy.ProxySet. For Tor Exit relays, the default
            # is 'exit_relay'. For other proxies loaded from the
            # PROXY_LIST_FILES config option, the default tag is the full
            # filename that the IP address originally came from.
            usingProxy = True
            tag = self.proxies.getTag(bridgeRequest.client)
            logging.info("Client was from known proxy (tag: %s): %s" %
                         (tag, bridgeRequest.client))

        subnet = self.getSubnet(bridgeRequest.client, usingProxy)
        subring = self.mapSubnetToSubring(subnet, usingProxy)
        position = self.mapClientToHashringPosition(interval, subnet)
        filters = self._buildHashringFilters(bridgeRequest.filters, subring)

        logging.debug("Client request within time interval: %s" % interval)
        logging.debug("Assigned client to subhashring %d/%d" % (subring, self.totalSubrings))
        logging.debug("Assigned client to subhashring position: %s" % position.encode('hex'))
        logging.debug("Total bridges: %d" % len(self.hashring))
        logging.debug("Bridge filters: %s" % ' '.join([x.func_name for x in filters]))

        # Check wheth we have a cached copy of the hashring:
        if filters in self.hashring.filterRings.keys():
            logging.debug("Cache hit %s" % filters)
            _, ring = self.hashring.filterRings[filters]
        # Otherwise, construct a new hashring and populate it:
        else:
            logging.debug("Cache miss %s" % filters)
            key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
            ring = BridgeRing(key1, self.answerParameters)
            self.hashring.addRing(ring, filters, byFilters(filters),
                                  populate_from=self.hashring.bridges)

        # Determine the appropriate number of bridges to give to the client:
        returnNum = self.bridgesPerResponse(ring)
        answer = ring.getBridges(position, returnNum, filterBySubnet=True)

        return answer
示例#41
0
    def createChallenge(self, answer):
        """Encrypt-then-HMAC a timestamp plus the CAPTCHA **answer**.

        A challenge string consists of a URL-safe, base64-encoded string which
        contains an ``HMAC`` concatenated with an ``ENC_BLOB``, in the
        following form::

            CHALLENGE := B64( HMAC | ENC_BLOB )
            ENC_BLOB := RSA_ENC( ANSWER_BLOB )
            ANSWER_BLOB := ( TIMESTAMP | ANSWER )

        where
          * ``B64`` is a URL-safe base64-encode function,
          * ``RSA_ENC`` is the PKCS#1 RSA-OAEP encryption function,
          * and the remaining feilds are specified as follows:

        +-------------+--------------------------------------------+----------+
        | Field       | Description                                | Length   |
        +=============+============================================+==========+
        | HMAC        | An HMAC of the ``ENC_BLOB``, created with  | 20 bytes |
        |             | the client-specific :ivar:`hmacKey`, by    |          |
        |             | applying :func:`~crypto.getHMAC` to the    |          |
        |             | ``ENC_BLOB``.                              |          |
        +-------------+--------------------------------------------+----------+
        | ENC_BLOB    | An encrypted ``ANSWER_BLOB``, created with | varies   |
        |             | a PKCS#1 OAEP-padded RSA :ivar:`publicKey`.|          |
        +-------------+--------------------------------------------+----------+
        | ANSWER_BLOB | Contains the concatenated ``TIMESTAMP``    | varies   |
        |             | and ``ANSWER``.                            |          |
        +-------------+--------------------------------------------+----------+
        | TIMESTAMP   | A Unix Epoch timestamp, in seconds,        | 12 bytes |
        |             | left-padded with "0"s.                     |          |
        +-------------+--------------------------------------------+----------+
        | ANSWER      | A string containing answer to this         | 8 bytes  |
        |             | CAPTCHA :ivar:`image`.                     |          |
        +-------------+--------------------------------------------+----------+

        The steps taken to produce a ``CHALLENGE`` are then:

          1. Create a ``TIMESTAMP``, and pad it on the left with ``0``s to 12
             bytes in length.

          2. Next, take the **answer** to this CAPTCHA :ivar:`image: and
             concatenate the padded ``TIMESTAMP`` and the ``ANSWER``, forming
             an ``ANSWER_BLOB``.

          3. Encrypt the resulting ``ANSWER_BLOB`` to :ivar:`publicKey` to
             create the ``ENC_BLOB``.

          4. Use the client-specific :ivar:`hmacKey` to apply the
             :func:`~crypto.getHMAC` function to the ``ENC_BLOB``, obtaining
             an ``HMAC``.

          5. Create the final ``CHALLENGE`` string by concatenating the
             ``HMAC`` and ``ENC_BLOB``, then base64-encoding the result.

        :param str answer: The answer to a CAPTCHA.
        :rtype: str
        :returns: A challenge string.
        """
        timestamp = str(int(time.time())).zfill(12)
        blob = timestamp + answer
        encBlob = self.publicKey.encrypt(blob)
        hmac = crypto.getHMAC(self.hmacKey, encBlob)
        challenge = urlsafe_b64encode(hmac + encBlob)
        return challenge
示例#42
0
    def getBridges(self, bridgeRequest, interval, clock=None):
        """Return a list of bridges to give to a user.

        .. hint:: All checks on the email address (which should be stored in
            the ``bridgeRequest.client`` attribute), such as checks for
            whitelisting and canonicalization of domain name, are done in
            :meth:`bridgedb.distributors.email.autoresponder.getMailTo` and
            :meth:`bridgedb.distributors.email.autoresponder.SMTPAutoresponder.runChecks`.

        :type bridgeRequest:
            :class:`~bridgedb.distributors.email.request.EmailBridgeRequest`
        :param bridgeRequest: A
            :class:`~bridgedb.bridgerequest.BridgeRequestBase` with the
            :data:`~bridgedb.bridgerequest.BridgeRequestBase.client` attribute
            set to a string containing the client's full, canonicalized email
            address.
        :type interval: str
        :param interval: The time period when we got this request. This can be
            any string, so long as it changes with every period.
        :type clock: :api:`twisted.internet.task.Clock`
        :param clock: If given, use the clock to ask what time it is, rather
            than :api:`time.time`. This should likely only be used for
            testing.
        :rtype: :any:`list` or ``None``
        :returns: A list of :class:`~bridgedb.bridges.Bridge` for the
            ``bridgeRequest.client``, if allowed.  Otherwise, returns ``None``.
        """
        if (not bridgeRequest.client) or (bridgeRequest.client == 'default'):
            raise addr.BadEmail(
                ("%s distributor can't get bridges for invalid email address: "
                 "%s") % (self.name, bridgeRequest.client),
                bridgeRequest.client)

        logging.info("Attempting to get bridges for %s..." %
                     bridgeRequest.client)

        now = time.time()

        if clock:
            now = clock.seconds()

        with bridgedb.Storage.getDB() as db:
            wasWarned = db.getWarnedEmail(bridgeRequest.client)
            lastSaw = db.getEmailTime(bridgeRequest.client)
            if lastSaw is not None:
                if bridgeRequest.client in self.whitelist:
                    logging.info(
                        "Whitelisted address %s was last seen %d seconds ago."
                        % (bridgeRequest.client, now - lastSaw))
                elif (lastSaw + self.emailRateMax) >= now:
                    wait = (lastSaw + self.emailRateMax) - now
                    logging.info("Client %s must wait another %d seconds." %
                                 (bridgeRequest.client, wait))
                    if wasWarned:
                        raise IgnoreEmail(
                            "Client %s was warned." % bridgeRequest.client,
                            bridgeRequest.client)
                    else:
                        logging.info("Sending duplicate request warning.")
                        db.setWarnedEmail(bridgeRequest.client, True, now)
                        db.commit()
                        raise TooSoonEmail("Must wait %d seconds" % wait,
                                           bridgeRequest.client)
            # warning period is over
            elif wasWarned:
                db.setWarnedEmail(bridgeRequest.client, False)

            pos = self.emailHmac("<%s>%s" % (interval, bridgeRequest.client))

            ring = None
            filtres = frozenset(bridgeRequest.filters)
            if filtres in self.hashring.filterRings:
                logging.debug("Cache hit %s" % filtres)
                _, ring = self.hashring.filterRings[filtres]
            else:
                logging.debug("Cache miss %s" % filtres)
                key = getHMAC(self.key, "Order-Bridges-In-Ring")
                ring = BridgeRing(key, self.answerParameters)
                self.hashring.addRing(ring,
                                      filtres,
                                      byFilters(filtres),
                                      populate_from=self.hashring.bridges)

            returnNum = self.bridgesPerResponse(ring)
            result = ring.getBridges(pos, returnNum, filterBySubnet=False)

            db.setEmailTime(bridgeRequest.client, now)
            db.commit()

        return result
示例#43
0
def addWebServer(config, distributor):
    """Set up a web server for HTTP(S)-based bridge distribution.

    :type config: :class:`bridgedb.persistent.Conf`
    :param config: A configuration object from
         :mod:`bridgedb.Main`. Currently, we use these options::
             HTTP_UNENCRYPTED_PORT
             HTTP_UNENCRYPTED_BIND_IP
             HTTP_USE_IP_FROM_FORWARDED_HEADER
             HTTPS_N_BRIDGES_PER_ANSWER
             HTTPS_INCLUDE_FINGERPRINTS
             HTTPS_KEY_FILE
             HTTPS_CERT_FILE
             HTTPS_PORT
             HTTPS_BIND_IP
             HTTPS_USE_IP_FROM_FORWARDED_HEADER
             HTTPS_ROTATION_PERIOD
             RECAPTCHA_ENABLED
             RECAPTCHA_PUB_KEY
             RECAPTCHA_SEC_KEY
             RECAPTCHA_REMOTEIP
             GIMP_CAPTCHA_ENABLED
             GIMP_CAPTCHA_DIR
             GIMP_CAPTCHA_HMAC_KEYFILE
             GIMP_CAPTCHA_RSA_KEYFILE
             SERVER_PUBLIC_FQDN
             CSP_ENABLED
             CSP_REPORT_ONLY
             CSP_INCLUDE_SELF
    :type distributor: :class:`bridgedb.https.distributor.HTTPSDistributor`
    :param distributor: A bridge distributor.
    :raises SystemExit: if the servers cannot be started.
    :rtype: :api:`twisted.web.server.Site`
    :returns: A webserver.
    """
    captcha = None
    fwdHeaders = config.HTTP_USE_IP_FROM_FORWARDED_HEADER
    numBridges = config.HTTPS_N_BRIDGES_PER_ANSWER
    fprInclude = config.HTTPS_INCLUDE_FINGERPRINTS

    logging.info("Starting web servers...")

    setFQDN(config.SERVER_PUBLIC_FQDN)

    index   = IndexResource()
    options = OptionsResource()
    howto   = HowtoResource()
    robots  = static.File(os.path.join(TEMPLATE_DIR, 'robots.txt'))
    assets  = static.File(os.path.join(TEMPLATE_DIR, 'assets/'))
    keys    = static.Data(bytes(strings.BRIDGEDB_OPENPGP_KEY), 'text/plain')
    csp     = CSPResource(enabled=config.CSP_ENABLED,
                          includeSelf=config.CSP_INCLUDE_SELF,
                          reportViolations=config.CSP_REPORT_ONLY,
                          useForwardedHeader=fwdHeaders)

    root = CustomErrorHandlingResource()
    root.putChild('', index)
    root.putChild('robots.txt', robots)
    root.putChild('keys', keys)
    root.putChild('assets', assets)
    root.putChild('options', options)
    root.putChild('howto', howto)
    root.putChild('maintenance', maintenance)
    root.putChild('error', resource500)
    root.putChild(CSPResource.reportURI, csp)

    if config.RECAPTCHA_ENABLED:
        publicKey = config.RECAPTCHA_PUB_KEY
        secretKey = config.RECAPTCHA_SEC_KEY
        captcha = partial(ReCaptchaProtectedResource,
                          remoteIP=config.RECAPTCHA_REMOTEIP)
    elif config.GIMP_CAPTCHA_ENABLED:
        # Get the master HMAC secret key for CAPTCHA challenges, and then
        # create a new HMAC key from it for use on the server.
        captchaKey = crypto.getKey(config.GIMP_CAPTCHA_HMAC_KEYFILE)
        hmacKey = crypto.getHMAC(captchaKey, "Captcha-Key")
        # Load or create our encryption keys:
        secretKey, publicKey = crypto.getRSAKey(config.GIMP_CAPTCHA_RSA_KEYFILE)
        captcha = partial(GimpCaptchaProtectedResource,
                          hmacKey=hmacKey,
                          captchaDir=config.GIMP_CAPTCHA_DIR)

    if config.HTTPS_ROTATION_PERIOD:
        count, period = config.HTTPS_ROTATION_PERIOD.split()
        sched = ScheduledInterval(count, period)
    else:
        sched = Unscheduled()

    bridges = BridgesResource(distributor, sched, numBridges, fwdHeaders,
                              includeFingerprints=fprInclude)
    if captcha:
        # Protect the 'bridges' page with a CAPTCHA, if configured to do so:
        protected = captcha(publicKey=publicKey,
                            secretKey=secretKey,
                            useForwardedHeader=fwdHeaders,
                            protectedResource=bridges)
        root.putChild('bridges', protected)
        logging.info("Protecting resources with %s." % captcha.func.__name__)
    else:
        root.putChild('bridges', bridges)

    site = Site(root)
    site.displayTracebacks = False

    if config.HTTP_UNENCRYPTED_PORT:  # pragma: no cover
        ip = config.HTTP_UNENCRYPTED_BIND_IP or ""
        port = config.HTTP_UNENCRYPTED_PORT or 80
        try:
            reactor.listenTCP(port, site, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started HTTP server on %s:%d" % (str(ip), int(port)))

    if config.HTTPS_PORT:  # pragma: no cover
        ip = config.HTTPS_BIND_IP or ""
        port = config.HTTPS_PORT or 443
        try:
            from twisted.internet.ssl import DefaultOpenSSLContextFactory
            factory = DefaultOpenSSLContextFactory(config.HTTPS_KEY_FILE,
                                                   config.HTTPS_CERT_FILE)
            reactor.listenSSL(port, site, factory, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started HTTPS server on %s:%d" % (str(ip), int(port)))

    return site
示例#44
0
    def prepopulateRings(self):
        """Prepopulate this distributor's hashrings and subhashrings with
        bridges.

        The hashring structure for this distributor is influenced by the
        ``N_IP_CLUSTERS`` configuration option, as well as the number of
        ``PROXY_LIST_FILES``.

        Essentially, :data:`totalSubrings` is set to the specified
        ``N_IP_CLUSTERS``.  All of the ``PROXY_LIST_FILES``, plus the list of
        Tor Exit relays (downloaded into memory with :script:`get-tor-exits`),
        are stored in :data:`proxies`, and the latter is added as an
        additional cluster (such that :data:`totalSubrings` becomes
        ``N_IP_CLUSTERS + 1``).  The number of subhashrings which this
        :class:`Distributor` has active in its hashring is then
        :data:`totalSubrings`, where the last cluster is reserved for all
        :data:`proxies`.

        As an example, if BridgeDB was configured with ``N_IP_CLUSTERS=4`` and
        ``PROXY_LIST_FILES=["open-socks-proxies.txt"]``, then the total number
        of subhashrings is five — four for the "clusters", and one for the
        :data:`proxies`. Thus, the resulting hashring-subhashring structure
        would look like:

        +------------------+---------------------------------------------------+-------------+
        |                  |               Directly connecting users           | Tor / known |
        |                  |                                                   | proxy users |
        +------------------+------------+------------+------------+------------+-------------+
        | Clusters         | Cluster-1  | Cluster-2  | Cluster-3  | Cluster-4  | Cluster-5   |
        +==================+============+============+============+============+=============+
        | Subhashrings     |            |            |            |            |             |
        | (total, assigned)| (5,1)      | (5,2)      | (5,3)      | (5,4)      | (5,5)       |
        +------------------+------------+------------+------------+------------+-------------+
        | Filtered         | (5,1)-IPv4 | (5,2)-IPv4 | (5,3)-IPv4 | (5,4)-IPv4 | (5,5)-IPv4  |
        | Subhashrings     |            |            |            |            |             |
        | bBy requested    +------------+------------+------------+------------+-------------+
        | bridge type)     | (5,1)-IPv6 | (5,2)-IPv6 | (5,3)-IPv6 | (5,4)-IPv6 | (5,5)-IPv6  |
        |                  |            |            |            |            |             |
        +------------------+------------+------------+------------+------------+-------------+

        The "filtered subhashrings" are essentially filtered copies of their
        respective subhashring, such that they only contain bridges which
        support IPv4 or IPv6, respectively.  Additionally, the contents of
        ``(5,1)-IPv4`` and ``(5,1)-IPv6`` sets are *not* disjoint.

        Thus, in this example, we end up with **10 total subhashrings**.
        """
        logging.info("Prepopulating %s distributor hashrings..." % self.name)

        for filterFn in [byIPv4, byIPv6]:
            for subring in range(1, self.totalSubrings + 1):
                filters = self._buildHashringFilters([filterFn,], subring)
                key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
                ring = BridgeRing(key1, self.answerParameters)
                # For consistency with previous implementation of this method,
                # only set the "name" for "clusters" which are for this
                # distributor's proxies:
                if subring == self.proxySubring:
                    ring.setName('{0} Proxy Ring'.format(self.name))
                self.hashring.addRing(ring, filters, byFilters(filters),
                                      populate_from=self.hashring.bridges)

        logging.info("Bridges allotted for %s distribution: %d"
                     % (self.name, len(self.hashring)))

        logging.info("\tNum bridges:\tFilter set:")
        for (ringname, (filterFn, subring)) in self.hashring.filterRings.items():
            filterSet = ' '.join(self.hashring.extractFilterNames(ringname))
            logging.info("\t%2d bridges\t%s" % (len(subring), filterSet))

        logging.info("Total subrings for %s: %d"
                     % (self.name, len(self.hashring.filterRings)))
示例#45
0
def addMoatServer(config, distributor):
    """Set up a web server for moat bridge distribution.

    :type config: :class:`bridgedb.persistent.Conf`
    :param config: A configuration object from
         :mod:`bridgedb.main`. Currently, we use these options::
             GIMP_CAPTCHA_DIR
             SERVER_PUBLIC_FQDN
             SUPPORTED_TRANSPORTS
             MOAT_DIST
             MOAT_DIST_VIA_MEEK_ONLY
             MOAT_TLS_CERT_FILE
             MOAT_TLS_KEY_FILE
             MOAT_SERVER_PUBLIC_ROOT
             MOAT_HTTPS_IP
             MOAT_HTTPS_PORT
             MOAT_HTTP_IP
             MOAT_HTTP_PORT
             MOAT_BRIDGES_PER_ANSWER
             MOAT_TRANSPORT_PREFERENCE_LIST
             MOAT_USE_IP_FROM_FORWARDED_HEADER
             MOAT_SKIP_LOOPBACK_ADDRESSES
             MOAT_ROTATION_PERIOD
             MOAT_GIMP_CAPTCHA_HMAC_KEYFILE
             MOAT_GIMP_CAPTCHA_RSA_KEYFILE
    :type distributor: :class:`bridgedb.distributors.moat.distributor.MoatDistributor`
    :param distributor: A bridge distributor.
    :raises SystemExit: if the servers cannot be started.
    :rtype: :api:`twisted.web.server.Site`
    :returns: A webserver.
    """
    captcha = None
    fwdHeaders = config.MOAT_USE_IP_FROM_FORWARDED_HEADER
    numBridges = config.MOAT_BRIDGES_PER_ANSWER
    skipLoopback = config.MOAT_SKIP_LOOPBACK_ADDRESSES

    logging.info("Starting moat servers...")

    setFQDN(config.SERVER_PUBLIC_FQDN)
    setRoot(config.MOAT_SERVER_PUBLIC_ROOT)
    setSupportedTransports(config.SUPPORTED_TRANSPORTS)
    setPreferredTransports(config.MOAT_TRANSPORT_PREFERENCE_LIST)

    # Get the master HMAC secret key for CAPTCHA challenges, and then
    # create a new HMAC key from it for use on the server.
    captchaKey = crypto.getKey(config.MOAT_GIMP_CAPTCHA_HMAC_KEYFILE)
    hmacKey = crypto.getHMAC(captchaKey, "Moat-Captcha-Key")
    # Load or create our encryption keys:
    secretKey, publicKey = crypto.getRSAKey(config.MOAT_GIMP_CAPTCHA_RSA_KEYFILE)
    sched = Unscheduled()

    if config.MOAT_ROTATION_PERIOD:
        count, period = config.MOAT_ROTATION_PERIOD.split()
        sched = ScheduledInterval(count, period)

    sitePublicDir = getRoot()

    meek = CustomErrorHandlingResource()
    moat = CustomErrorHandlingResource()
    fetch = CaptchaFetchResource(hmacKey, publicKey, secretKey,
                                 config.GIMP_CAPTCHA_DIR,
                                 fwdHeaders, skipLoopback)
    check = CaptchaCheckResource(distributor, sched, numBridges,
                                 hmacKey, publicKey, secretKey,
                                 fwdHeaders, skipLoopback)

    moat.putChild("fetch", fetch)
    moat.putChild("check", check)
    meek.putChild("moat", moat)

    root = CustomErrorHandlingResource()
    root.putChild("meek", meek)
    root.putChild("moat", moat)

    site = Site(root)
    site.displayTracebacks = False

    if config.MOAT_HTTP_PORT:  # pragma: no cover
        ip = config.MOAT_HTTP_IP or ""
        port = config.MOAT_HTTP_PORT or 80
        try:
            reactor.listenTCP(port, site, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started Moat HTTP server on %s:%d" % (str(ip), int(port)))

    if config.MOAT_HTTPS_PORT:  # pragma: no cover
        ip = config.MOAT_HTTPS_IP or ""
        port = config.MOAT_HTTPS_PORT or 443
        try:
            from twisted.internet.ssl import DefaultOpenSSLContextFactory
            factory = DefaultOpenSSLContextFactory(config.MOAT_TLS_KEY_FILE,
                                                   config.MOAT_TLS_CERT_FILE)
            reactor.listenSSL(port, site, factory, interface=ip)
        except CannotListenError as error:
            raise SystemExit(error)
        logging.info("Started Moat TLS server on %s:%d" % (str(ip), int(port)))

    return site
示例#46
0
    def getBridges(self, bridgeRequest, interval):
        """Return a list of bridges to give to a user.

        :type bridgeRequest: :class:`bridgedb.https.request.HTTPSBridgeRequest`
        :param bridgeRequest: A :class:`~bridgedb.bridgerequest.BridgeRequestBase`
            with the :data:`~bridgedb.bridgerequest.BridgeRequestBase.client`
            attribute set to a string containing the client's IP address.
        :param str interval: The time period when we got this request.  This
            can be any string, so long as it changes with every period.
        :rtype: list
        :return: A list of :class:`~bridgedb.Bridges.Bridge`s to include in
            the response. See
            :meth:`bridgedb.https.server.WebResourceBridges.getBridgeRequestAnswer`
            for an example of how this is used.
        """
        logging.info("Attempting to get bridges for %s..." %
                     bridgeRequest.client)

        if not len(self.hashring):
            logging.warn("Bailing! Hashring has zero bridges!")
            return []

        usingProxy = False

        # First, check if the client's IP is one of the known :data:`proxies`:
        if bridgeRequest.client in self.proxies:
            # The tag is a tag applied to a proxy IP address when it is added
            # to the bridgedb.proxy.ProxySet. For Tor Exit relays, the default
            # is 'exit_relay'. For other proxies loaded from the
            # PROXY_LIST_FILES config option, the default tag is the full
            # filename that the IP address originally came from.
            usingProxy = True
            tag = self.proxies.getTag(bridgeRequest.client)
            logging.info("Client was from known proxy (tag: %s): %s" %
                         (tag, bridgeRequest.client))

        subnet = self.getSubnet(bridgeRequest.client, usingProxy)
        subring = self.mapSubnetToSubring(subnet, usingProxy)
        position = self.mapClientToHashringPosition(interval, subnet)
        filters = self._buildHashringFilters(bridgeRequest.filters, subring)

        logging.debug("Client request within time interval: %s" % interval)
        logging.debug("Assigned client to subhashring %d/%d" %
                      (subring, self.totalSubrings))
        logging.debug("Assigned client to subhashring position: %s" %
                      position.encode('hex'))
        logging.debug("Total bridges: %d" % len(self.hashring))
        logging.debug("Bridge filters: %s" %
                      ' '.join([x.func_name for x in filters]))

        # Check wheth we have a cached copy of the hashring:
        if filters in self.hashring.filterRings.keys():
            logging.debug("Cache hit %s" % filters)
            _, ring = self.hashring.filterRings[filters]
        # Otherwise, construct a new hashring and populate it:
        else:
            logging.debug("Cache miss %s" % filters)
            key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring)
            ring = BridgeRing(key1, self.answerParameters)
            self.hashring.addRing(ring,
                                  filters,
                                  byFilters(filters),
                                  populate_from=self.hashring.bridges)

        # Determine the appropriate number of bridges to give to the client:
        returnNum = self.bridgesPerResponse(ring)
        answer = ring.getBridges(position, returnNum)

        return answer