Re-flow indentation of bugfix items.
[tor-bridgedb.git] / bridgedb / bridges.py
blobcd0194895ae547e40a386a7277cd8fe2dea3bc45
1 # -*- coding: utf-8 ; test-case-name: bridgedb.test.test_bridges -*-
3 # This file is part of BridgeDB, a Tor bridge distribution system.
5 # :authors: please see the AUTHORS file for attributions
6 # :copyright: (c) 2013-2017, Isis Lovecruft
7 # (c) 2007-2017, The Tor Project, Inc.
8 # :license: see LICENSE for licensing information
10 """Classes for manipulating and storing Bridges and their attributes.
12 .. inheritance-diagram:: PluggableTransportUnavailable MalformedBridgeInfo MalformedPluggableTransport InvalidPluggableTransportIP MissingServerDescriptorDigest ServerDescriptorDigestMismatch ServerDescriptorWithoutNetworkstatus InvalidExtraInfoSignature Flags PluggableTransport Bridge
13 :parts: 1
15 ------------
17 **Glossary Terms**
19 .. glossary::
21 Bridge Line
22 A "Bridge Line" is how BridgeDB refers to lines in a ``torrc``
23 file which should begin with the word ``"Bridge"``, and it is how
24 a client tells their Tor process that they would like to use a
25 particular bridge.
27 ------------
28 """
30 from __future__ import print_function
32 import base64
33 import codecs
34 import hashlib
35 import ipaddr
36 import logging
37 import os
38 import warnings
40 from Crypto.Util import asn1
41 from Crypto.Util.number import bytes_to_long
42 from Crypto.Util.number import long_to_bytes
44 from zope.interface import implementer
45 from zope.interface import Attribute
46 from zope.interface import Interface
48 import bridgedb.Storage
50 from bridgedb import geo
51 from bridgedb import safelog
52 from bridgedb import bridgerequest
53 from bridgedb.crypto import removePKCS1Padding
54 from bridgedb.parse.addr import isIPAddress
55 from bridgedb.parse.addr import isIPv4
56 from bridgedb.parse.addr import isIPv6
57 from bridgedb.parse.addr import isValidIP
58 from bridgedb.parse.addr import PortList
59 from bridgedb.parse.fingerprint import isValidFingerprint
60 from bridgedb.parse.fingerprint import toHex
61 from bridgedb.parse.fingerprint import fromHex
62 from bridgedb.parse.nickname import isValidRouterNickname
63 from bridgedb.util import isascii_noncontrol
66 class PluggableTransportUnavailable(Exception):
67 """Raised when a :class:`Bridge` doesn't have the requested
68 :class:`PluggableTransport`.
69 """
71 class MalformedBridgeInfo(ValueError):
72 """Raised when some information about a bridge appears malformed."""
74 class MalformedPluggableTransport(MalformedBridgeInfo):
75 """Raised when information used to initialise a :class:`PluggableTransport`
76 appears malformed.
77 """
79 class InvalidPluggableTransportIP(MalformedBridgeInfo):
80 """Raised when a :class:`PluggableTransport` has an invalid address."""
82 class MissingServerDescriptorDigest(MalformedBridgeInfo):
83 """Raised when the hash digest for an ``@type bridge-server-descriptor``
84 (which should be in the corresponding ``@type bridge-networkstatus``
85 document), was missing.
86 """
88 class ServerDescriptorDigestMismatch(MalformedBridgeInfo):
89 """Raised when the digest in an ``@type bridge-networkstatus`` document
90 doesn't match the hash digest of the ``@type bridge-server-descriptor``'s
91 contents.
92 """
94 class ServerDescriptorWithoutNetworkstatus(MalformedBridgeInfo):
95 """Raised when we find a ``@type bridge-server-descriptor`` which was not
96 mentioned in the latest ``@type bridge-networkstatus`` document.
97 """
99 class InvalidExtraInfoSignature(MalformedBridgeInfo):
100 """Raised if the signature on an ``@type bridge-extrainfo`` is invalid."""
103 class IBridge(Interface):
104 """I am a (mostly) stub interface whose primary purpose is merely to allow
105 other classes to signify whether or not they can be treated like a
106 :class:`Bridge`.
108 fingerprint = Attribute(
109 ("The lowercased, hexadecimal-encoded, hash digest of this Bridge's "
110 "public identity key."))
111 address = Attribute("This Bridge's primary public IP address.")
112 port = Attribute("The port which this Bridge is listening on.")
115 class Flags(object):
116 """All the flags which a :class:`Bridge` may have."""
118 fast = False
119 guard = False
120 running = False
121 stable = False
122 valid = False
124 def update(self, flags):
125 """Update with **flags** taken from an ``@type networkstatus-bridge``
126 's'-line.
128 From `dir-spec.txt`_:
130 | "s" SP Flags NL
132 | [Exactly once.]
134 | A series of space-separated status flags, in lexical order (as ASCII
135 | byte strings). Currently documented flags are:
137 | [...]
138 | "Fast" if the router is suitable for high-bandwidth circuits.
139 | "Guard" if the router is suitable for use as an entry guard.
140 | [...]
141 | "Stable" if the router is suitable for long-lived circuits.
142 | "Running" if the router is currently usable.
143 | [...]
144 | "Valid" if the router has been 'validated'.
146 .. _dir-spec.txt:
147 https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt?id=7647f6d4d#n1602
149 :param list flags: A list of strings containing each of the flags
150 parsed from the 's'-line.
152 self.fast = 'Fast' in flags
153 self.guard = 'Guard' in flags
154 self.running = 'Running' in flags
155 self.stable = 'Stable' in flags
156 self.valid = 'Valid' in flags
159 @implementer(IBridge)
160 class BridgeAddressBase(object):
161 """A base class for describing one of a :class:`Bridge`'s or a
162 :class:`PluggableTransport`'s location, including its identity key
163 fingerprint and IP address.
165 :type fingerprint: str
166 :ivar fingerprint: The uppercased, hexadecimal fingerprint of the identity
167 key of the parent bridge running this pluggable transport instance,
168 i.e. the main ORPort bridge whose ``@type bridge-server-descriptor``
169 contains a hash digest for a ``@type bridge-extrainfo-document``, the
170 latter of which contains the parameter of this pluggable transport in
171 its ``transport`` line.
173 :type address: ``ipaddr.IPv4Address`` or ``ipaddr.IPv6Address``
174 :ivar address: The IP address of :class:`Bridge` or one of its
175 :class:`PluggableTransport`s.
177 :type country: str
178 :ivar country: The two-letter GeoIP country code of the :ivar:`address`.
180 :type port: int
181 :ivar port: A integer specifying the port which this :class:`Bridge`
182 (or :class:`PluggableTransport`) is listening on.
185 def __init__(self):
186 self._fingerprint = None
187 self._address = None
188 self._country = None
189 self._port = None
191 @property
192 def fingerprint(self):
193 """Get this Bridge's fingerprint.
195 :rtype: str
196 :returns: A 40-character hexadecimal formatted string representation
197 of the SHA-1 hash digest of the public half of this Bridge's
198 identity key.
200 return self._fingerprint
202 @fingerprint.setter
203 def fingerprint(self, value):
204 """Set this Bridge's fingerprint to **value**.
206 .. info: The purported fingerprint will be checked for specification
207 conformity with
208 :func:`~bridgedb.parse.fingerprint.isValidFingerprint`.
210 :param str value: The fingerprint for this Bridge.
212 if value and isValidFingerprint(value):
213 self._fingerprint = value.upper()
215 @fingerprint.deleter
216 def fingerprint(self):
217 """Reset this Bridge's fingerprint."""
218 self._fingerprint = None
220 @property
221 def identity(self):
222 """Get this Bridge's identity digest.
224 :rtype: bytes
225 :returns: The binary-encoded SHA-1 hash digest of the public half of
226 this Bridge's identity key, if available; otherwise, returns
227 ``None``.
229 if self.fingerprint:
230 return fromHex(self.fingerprint)
232 @identity.setter
233 def identity(self, value):
234 """Set this Bridge's identity digest to **value**.
236 .. info: The purported identity digest will be checked for
237 specification conformity with
238 :func:`~bridgedb.parse.fingerprint.isValidFingerprint`.
240 :param str value: The binary-encoded SHA-1 hash digest of the public
241 half of this Bridge's identity key.
243 self.fingerprint = toHex(value).decode('utf-8')
245 @identity.deleter
246 def identity(self):
247 """Reset this Bridge's identity digest."""
248 del(self.fingerprint)
250 @property
251 def address(self):
252 """Get this bridge's address.
254 :rtype: :class:`~ipaddr.IPv4Address` or :class:`~ipaddr.IPv6Address`
255 :returns: The bridge's address.
257 return self._address
259 @address.setter
260 def address(self, value):
261 """Set this Bridge's address.
263 :param value: The main ORPort IP address of this bridge.
265 if value and isValidIP(value): # XXX only conditionally set _address?
266 self._address = isIPAddress(value, compressed=False)
268 @address.deleter
269 def address(self):
270 """Reset this Bridge's address to ``None``."""
271 self._address = None
273 @property
274 def country(self):
275 """Get the two-letter GeoIP country code for the :ivar:`address`.
277 :rtype: str or ``None``
278 :returns: If :ivar:`address` is set, this returns a two-letter country
279 code for the geolocated region that :ivar:`address` is within;
280 otherwise, returns ``None``.
282 if self.address:
283 return geo.getCountryCode(self.address)
285 @property
286 def port(self):
287 """Get the port number which this ``Bridge`` is listening
288 for incoming client connections on.
290 :rtype: int or None
291 :returns: The port (as an int), if it is known and valid; otherwise,
292 returns ``None``.
294 return self._port
296 @port.setter
297 def port(self, value):
298 """Store the port number which this ``Bridge`` is listening
299 for incoming client connections on.
301 :param int value: The transport's port.
303 if isinstance(value, int) and (0 <= value <= 65535):
304 self._port = value
306 @port.deleter
307 def port(self):
308 """Reset this ``Bridge``'s port to ``None``."""
309 self._port = None
313 @implementer(IBridge)
314 class PluggableTransport(BridgeAddressBase):
315 """A single instance of a Pluggable Transport (PT) offered by a
316 :class:`Bridge`.
318 Pluggable transports are described within a bridge's
319 ``@type bridge-extrainfo`` descriptor, see the
320 ``Specifications: Client behavior`` section and the
321 ``TOR_PT_SERVER_TRANSPORT_OPTIONS`` description in pt-spec.txt_ for
322 additional specification.
324 .. _pt-spec.txt:
325 https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt
327 :vartype fingerprint: str
328 :ivar fingerprint: The uppercased, hexadecimal fingerprint of the identity
329 key of the parent bridge running this pluggable transport instance,
330 i.e. the main ORPort bridge whose ``@type bridge-server-descriptor``
331 contains a hash digest for a ``@type bridge-extrainfo-document``, the
332 latter of which contains the parameter of this pluggable transport in
333 its ``transport`` line.
335 :vartype methodname: str
336 :ivar methodname: The canonical "name" for this pluggable transport,
337 i.e. the one which would be specified in a torrc file. For example,
338 ``"obfs4"``.
340 :vartype address: ``ipaddr.IPv4Address`` or ``ipaddr.IPv6Address``
341 :ivar address: The IP address of the transport. Currently (as of 20 March
342 2014), there are no known, widely-deployed pluggable transports which
343 support IPv6. Ergo, this is very likely going to be an IPv4 address.
345 :vartype port: int
346 :ivar port: A integer specifying the port which this pluggable transport
347 is listening on. (This should likely be whatever port the bridge
348 specified in its ``ServerTransportPlugin`` torrc line, unless the
349 pluggable transport is running in "managed" mode.)
351 :vartype arguments: dict
352 :ivar arguments: Some PTs can take additional arguments, which must be
353 distributed to the client out-of-band. These are present in the
354 ``@type bridge-extrainfo-document``, in the ``transport`` line like
355 so::
357 METHOD SP ADDR ":" PORT SP [K=V[,K=V[,K=V[…]]]]
359 where K is the key in **arguments**, and V is the value. For example,
360 in the case of ``scramblesuit``, for which the client must supply a
361 shared secret to the ``scramblesuit`` instance running on the bridge,
362 the **arguments** would be something like::
364 {'password': 'NEQGQYLUMUQGK5TFOJ4XI2DJNZTS4LRO'}
367 # A list of PT names that are resistant to active probing attacks.
368 probing_resistant_transports = []
370 def __init__(self, fingerprint=None, methodname=None,
371 address=None, port=None, arguments=None):
372 """Create a ``PluggableTransport`` describing a PT running on a bridge.
374 :param str fingerprint: The uppercased, hexadecimal fingerprint of the
375 identity key of the parent bridge running this pluggable transport.
376 :param str methodname: The canonical "name" for this pluggable
377 transport. See :data:`methodname`.
378 :param str address: The IP address of the transport. See
379 :data:`address`.
380 :param int port: A integer specifying the port which this pluggable
381 transport is listening on.
382 :param dict arguments: Any additional arguments which the PT takes,
383 which must be distributed to the client out-of-band. See
384 :data:`arguments`.
386 super(PluggableTransport, self).__init__()
387 self._methodname = None
388 self._blockedIn = {}
390 self.fingerprint = fingerprint
391 self.address = address
392 self.port = port
393 self.methodname = methodname
394 self.arguments = arguments
396 # Because we can intitialise this class with the __init__()
397 # parameters, or use the ``updateFromStemTransport()`` method, we'll
398 # only use the ``_runChecks()`` method now if we were initialised with
399 # parameters:
400 if (fingerprint or address or port or methodname or arguments):
401 self._runChecks()
403 def _parseArgumentsIntoDict(self, argumentList):
404 """Convert a list of Pluggable Transport arguments into a dictionary
405 suitable for :data:`arguments`.
407 :param list argumentList: A list of Pluggable Transport
408 arguments. There might be multiple, comma-separated ``K=V``
409 Pluggable Transport arguments in a single item in the
410 **argumentList**, or each item might be its own ``K=V``; we don't
411 care and we should be able to parse it either way.
412 :rtype: dict
413 :returns: A dictionary of all the ``K=V`` Pluggable Transport
414 arguments.
416 argDict = {}
418 # PT arguments are comma-separated in the extrainfo
419 # descriptors. While there *shouldn't* be anything after them that was
420 # separated by a space (and hence would wind up being in a different
421 # item in `arguments`), if there was we'll join it to the rest of the
422 # PT arguments with a comma so that they are parsed as if they were PT
423 # arguments as well:
424 allArguments = ','.join(argumentList)
426 for arg in allArguments.split(','):
427 if arg: # It might be an empty string
428 try:
429 key, value = arg.split('=')
430 except ValueError:
431 logging.warn(" Couldn't parse K=V from PT arg: %r" % arg)
432 else:
433 logging.debug(" Parsed PT Argument: %s: %s" % (key, value))
434 argDict[key] = value
436 return argDict
438 def _runChecks(self):
439 """Validate that we were initialised with acceptable parameters.
441 We currently check that:
443 1. The :data:`port` is an integer, and that it is between the values
444 of ``0`` and ``65535`` (inclusive).
446 2. The :data:`arguments` is a dictionary.
448 3. The :data:`arguments` do not contain non-ASCII or control
449 characters or double quotes or backslashes, in keys or
450 in values.
452 :raises MalformedPluggableTransport: if any of the above checks fails.
454 if not self.fingerprint:
455 raise MalformedPluggableTransport(
456 ("Cannot create %s without owning Bridge fingerprint!")
457 % self.__class__.__name__)
459 if not self.address:
460 raise InvalidPluggableTransportIP(
461 ("Cannot create PluggableTransport with address '%s'. "
462 "type(address)=%s.") % (self.address, type(self.address)))
464 if not self.port:
465 raise MalformedPluggableTransport(
466 ("Cannot create PluggableTransport without a valid port."))
468 if not isinstance(self.arguments, dict):
469 raise MalformedPluggableTransport(
470 ("Cannot create PluggableTransport with arguments type: %s")
471 % type(self.arguments))
473 for item in self.arguments.items():
474 kv = ''.join(item)
475 if not isascii_noncontrol(kv):
476 raise MalformedPluggableTransport(
477 ("Cannot create PluggableTransport with non-ASCII or "
478 "control characters in arguments: %r=%r") % item)
479 if '"' in kv or '\\' in kv:
480 raise MalformedPluggableTransport(
481 ("Cannot create PluggableTransport with double quotes or "
482 "backslashes in arguments: %r=%r") % item)
484 if not self._checkArguments():
485 raise MalformedPluggableTransport(
486 ("Can't use %s transport with missing arguments. Arguments: "
487 "%s") % (self.methodname, ' '.join(self.arguments.keys())))
489 def _checkArguments(self):
490 """This method is a temporary fix for PTs with missing arguments
491 (see :trac:`13202`).
493 .. todo: This method can be removed after Tor-0.2.4.x is deprecated.
495 # obfs4 requires (iat-mode && (cert || (node-id && public-key))):
496 if self.methodname == 'obfs4':
497 if self.arguments.get('iat-mode'):
498 if (self.arguments.get('cert') or \
499 (self.arguments.get('node-id') and self.arguments.get('public-key'))):
500 return True
501 # scramblesuit requires (password):
502 elif self.methodname == 'scramblesuit':
503 if self.arguments.get('password'):
504 return True
505 else:
506 return True
508 return False
510 @property
511 def methodname(self):
512 """Get this :class:`PluggableTransport`'s methodname.
514 :rtype: str
515 :returns: The (lowercased) methodname of this ``PluggableTransport``,
516 e.g. ``"obfs4"``.
518 return self._methodname
520 @methodname.setter
521 def methodname(self, value):
522 """Set this ``PluggableTransport``'s methodname.
524 .. hint:: The **value** will be automatically lowercased.
526 :param str value: The new methodname.
528 if value:
529 try:
530 self._methodname = value.lower()
531 except (AttributeError, TypeError):
532 raise TypeError("methodname must be a str or unicode")
534 def isProbingResistant(self):
535 """Reveal if this pluggable transport is active probing-resistant.
537 :rtype: bool
538 :returns: ``True`` if this pluggable transport is resistant to active
539 probing attacks, ``False`` otherwise.
542 return self.methodname in PluggableTransport.probing_resistant_transports
545 def getTransportLine(self, includeFingerprint=True, bridgePrefix=False):
546 """Get a Bridge Line for this :class:`PluggableTransport`.
548 .. note:: If **bridgePrefix** is ``False``, this method does not
549 return lines which are prefixed with the word ``'bridge'``, as they
550 would be in a torrc file. Instead, lines returned look like::
552 obfs3 245.102.100.252:23619 59ca743e89b508e16b8c7c6d2290efdfd14eea98
554 This was made configurable to fix Vidalia being a brain-damaged
555 piece of shit (:trac:`5851`). TorLaucher replaced Vidalia soon after,
556 and TorLauncher is intelligent enough to understand
557 :term:`Bridge Line`s regardless of whether or not they are prefixed
558 with the word "Bridge".
560 .. _#5851: https://bugs.torproject.org/5851
562 :param bool includeFingerprints: If ``True``, include the digest of
563 this bridges public identity key in the torrc line.
564 :param bool bridgePrefix: If ``True``, add ``'Bridge '`` to the
565 beginning of each returned line (suitable for pasting directly
566 into a ``torrc`` file).
567 :rtype: str
568 :returns: A configuration line for adding this Pluggable Transport
569 into a ``torrc`` file.
571 sections = []
573 if bridgePrefix:
574 sections.append('Bridge')
576 if self.address.version == 6:
577 # If the address was IPv6, put brackets around it:
578 host = '%s [%s]:%d' % (self.methodname, self.address, self.port)
579 else:
580 host = '%s %s:%d' % (self.methodname, self.address, self.port)
581 sections.append(host)
583 if includeFingerprint:
584 sections.append(self.fingerprint)
586 for key, value in self.arguments.items():
587 sections.append('%s=%s' % (key, value))
589 line = ' '.join(sections)
591 return line
593 def updateFromStemTransport(self, fingerprint, methodname, kitchenSink):
594 """Update this :class:`PluggableTransport` from the data structure
595 which Stem uses.
597 Stem's
598 :class:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`
599 parses extrainfo ``transport`` lines into a dictionary with the
600 following structure::
602 {u'obfs2': (u'34.230.223.87', 37339, []),
603 u'obfs3': (u'34.230.223.87', 37338, []),
604 u'obfs4': (u'34.230.223.87', 37341, [
605 (u'iat-mode=0,'
606 u'node-id=2a79f14120945873482b7823caabe2fcde848722,'
607 u'public-key=0a5b046d07f6f971b7776de682f57c5b9cdc8fa060db7ef59de82e721c8098f4')]),
608 u'scramblesuit': (u'34.230.223.87', 37340, [
609 u'password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'])}
611 This method will initialise this class from the dictionary key
612 (**methodname**) and its tuple of values (**kitchenSink**).
614 :param str fingerprint: The uppercased, hexadecimal fingerprint of the
615 identity key of the parent bridge running this pluggable transport.
616 :param str methodname: The :data:`methodname` of this Pluggable
617 Transport.
618 :param tuple kitchenSink: Everything else that was on the
619 ``transport`` line in the bridge's extrainfo descriptor, which
620 Stem puts into the 3-tuples shown in the example above.
622 self.fingerprint = str(fingerprint)
623 self.methodname = str(methodname)
624 self.address = kitchenSink[0]
626 port = kitchenSink[1]
627 if port == 'anyport': # IDK. Stem, WTF?
628 port = 0
630 self.port = int(port)
631 self.arguments = self._parseArgumentsIntoDict(kitchenSink[2])
632 self._runChecks()
635 @implementer(IBridge)
636 class BridgeBase(BridgeAddressBase):
637 """The base class for all bridge implementations."""
639 def __init__(self):
640 super(BridgeBase, self).__init__()
642 self._nickname = None
643 self._orPort = None
644 self.socksPort = 0 # Bridges should always have ``SOCKSPort`` and
645 self.dirPort = 0 # ``DirPort`` set to ``0``
646 self.orAddresses = []
647 self.transports = []
648 self.flags = Flags()
650 @property
651 def nickname(self):
652 """Get this Bridge's nickname.
654 :rtype: str
655 :returns: The Bridge's nickname.
657 return self._nickname
659 @nickname.setter
660 def nickname(self, value):
661 """Set this Bridge's nickname to **value**.
663 .. note:: We don't need to call
664 :func:`bridgedb.parse.nickname.isValidRouterNickname() since Stem
665 will check nickname specification conformity.
667 :param str value: The nickname of this Bridge.
669 self._nickname = value
671 @nickname.deleter
672 def nickname(self):
673 """Reset this Bridge's nickname."""
674 self._nickname = None
676 @property
677 def orPort(self):
678 """Get this bridge's ORPort.
680 :rtype: int
681 :returns: This Bridge's default ORPort.
683 return self.port
685 @orPort.setter
686 def orPort(self, value):
687 """Set this Bridge's ORPort.
689 :param int value: The Bridge's ORPort.
691 self.port = value
693 @orPort.deleter
694 def orPort(self):
695 """Reset this Bridge's ORPort."""
696 del self.port
699 @implementer(IBridge)
700 class BridgeBackwardsCompatibility(BridgeBase):
701 """Backwards compatibility methods for the old Bridge class."""
703 def __init__(self, nickname=None, ip=None, orport=None,
704 fingerprint=None, id_digest=None, or_addresses=None):
705 """Create a :class:`Bridge <bridgedb.bridges.IBridge>` which is
706 backwards compatible with the legacy Bridge class implementation.
708 .. note: For backwards compatibility, **nickname**, **ip**, and
709 **orport** must be the first, second, and third arguments,
710 respectively. The **fingerprint** and **id_digest** were
711 previously kwargs, and are also provided for backwards
712 compatibility. New calls to :meth:`__init__` *should* avoid using
713 these kwargs, and instead use the methods
714 :meth:`updateFromNetworkStatus`,
715 :meth:`updateFromServerDescriptor`, and
716 :meth:`updateFromExtraInfoDescriptor`.
718 super(BridgeBackwardsCompatibility, self).__init__()
720 self.desc_digest = None
721 self.ei_digest = None
722 self.running = False
723 self.stable = False
725 if nickname or ip or orport or fingerprint or id_digest:
726 self._backwardsCompatible(nickname=nickname, address=ip,
727 orPort=orport, fingerprint=fingerprint,
728 idDigest=id_digest,
729 orAddresses=or_addresses)
731 def _backwardsCompatible(self, nickname=None, address=None, orPort=None,
732 fingerprint=None, idDigest=None,
733 orAddresses=None):
734 """Functionality for maintaining backwards compatibility with the older
735 version of this class (see :class:`bridgedb.test.deprecated.Bridge`).
737 self.nickname = nickname
738 self.orPort = orPort
739 if address:
740 self.address = address
742 if idDigest:
743 if not fingerprint:
744 if not len(idDigest) == 20:
745 raise TypeError("Bridge with invalid ID")
746 self.fingerprint = toHex(idDigest).decode('utf-8')
747 elif fingerprint:
748 if not isValidFingerprint(fingerprint):
749 raise TypeError("Bridge with invalid fingerprint (%r)"
750 % fingerprint)
751 self.fingerprint = fingerprint.lower()
752 else:
753 raise TypeError("Bridge with no ID")
755 if orAddresses and isinstance(orAddresses, dict):
756 for ip, portlist in orAddresses.items():
757 validAddress = isIPAddress(ip, compressed=False)
758 if validAddress:
759 # The old code expected a `bridgedb.parse.addr.PortList`:
760 if isinstance(portlist, PortList):
761 for port in portlist.ports:
762 self.orAddresses.append(
763 (validAddress, port, validAddress.version,))
764 elif isinstance(portlist, int):
765 self.orAddresses.append(
766 (validAddress, portlist, validAddress.version,))
767 else:
768 logging.warn("Can't parse port for ORAddress %r: %r"
769 % (ip, portlist))
771 def getID(self):
772 """Get the binary encoded form of this ``Bridge``'s ``fingerprint``.
774 This method is provided for backwards compatibility and should not
775 be relied upon.
777 return self.identity
779 def setDescriptorDigest(self, digest):
780 """Set this ``Bridge``'s server-descriptor digest.
782 This method is provided for backwards compatibility and should not
783 be relied upon.
785 self.desc_digest = digest # old attribute for backwards compat
786 self.descriptorDigest = digest # new attribute
788 def setExtraInfoDigest(self, digest):
789 """Set this ``Bridge``'s extrainfo digest.
791 This method is provided for backwards compatibility and should not
792 be relied upon.
794 self.ei_digest = digest # old attribute for backwards compat
795 self.extrainfoDigest = digest # new attribute
797 def setStatus(self, running=None, stable=None):
798 """Set this ``Bridge``'s "Running" and "Stable" flags.
800 This method is provided for backwards compatibility and should not
801 be relied upon.
803 if running is not None:
804 self.running = bool(running)
805 self.flags.running = bool(running)
806 if stable is not None:
807 self.stable = bool(stable)
808 self.flags.stable = bool(stable)
810 def getConfigLine(self, includeFingerprint=False, addressClass=None,
811 request=None, transport=None):
812 """Get a vanilla bridge line for this ``Bridge``.
814 This method is provided for backwards compatibility and should not
815 be relied upon.
817 The old ``bridgedb.bridges.Bridge.getConfigLine()`` method didn't know
818 about :class:`~bridgedb.bridgerequest.BridgeRequestBase`s, and so this
819 modified version is backwards compatible by creating a
820 :class:`~bridgedb.bridgerequest.BridgeRequestBase` for
821 :meth:`getBridgeLine`. The default parameters are the same as they
822 were in the old ``bridgedb.bridges.Bridge`` class.
824 :param bool includeFingerprint: If ``True``, include the
825 ``fingerprint`` of this :class:`Bridge` in the returned bridge
826 line.
827 :type addressClass: :class:`ipaddr.IPv4Address` or
828 :class:`ipaddr.IPv6Address`.
829 :param addressClass: Type of address to choose.
830 :param str request: A string (somewhat) unique to this request,
831 e.g. email-address or ``HTTPSDistributor.getSubnet(ip)``. In
832 this case, this is not a :class:`~bridgerequest.BridgeRequestBase`
833 (as might be expected) but the equivalent of
834 :data:`bridgerequest.BridgeRequestBase.client`.
835 :param str transport: A pluggable transport method name.
837 ipVersion = 6 if addressClass is ipaddr.IPv6Address else 4
838 bridgeRequest = bridgerequest.BridgeRequestBase(ipVersion)
839 bridgeRequest.client = request if request else bridgeRequest.client
840 bridgeRequest.isValid(True)
842 if transport:
843 bridgeRequest.withPluggableTransportType(transport)
845 bridgeRequest.generateFilters()
846 bridgeLine = self.getBridgeLine(bridgeRequest, includeFingerprint)
847 return bridgeLine
849 # Bridge Stability (`#5482 <https://bugs.torproject.org>`_) properties.
850 @property
851 def familiar(self):
852 """A bridge is "familiar" if 1/8 of all active bridges have appeared
853 more recently than it, or if it has been around for a Weighted Time of
854 eight days.
856 with bridgedb.Storage.getDB() as db: # pragma: no cover
857 return db.getBridgeHistory(self.fingerprint).familiar
859 @property
860 def wfu(self):
861 """Weighted Fractional Uptime"""
862 with bridgedb.Storage.getDB() as db: # pragma: no cover
863 return db.getBridgeHistory(self.fingerprint).weightedFractionalUptime
865 @property
866 def weightedTime(self):
867 """Weighted Time"""
868 with bridgedb.Storage.getDB() as db: # pragma: no cover
869 return db.getBridgeHistory(self.fingerprint).weightedTime
871 @property
872 def wmtbac(self):
873 """Weighted Mean Time Between Address Change"""
874 with bridgedb.Storage.getDB() as db: # pragma: no cover
875 return db.getBridgeHistory(self.fingerprint).wmtbac
877 @property
878 def tosa(self):
879 """The Time On Same Address (TOSA)"""
880 with bridgedb.Storage.getDB() as db: # pragma: no cover
881 return db.getBridgeHistory(self.fingerprint).tosa
883 @property
884 def weightedUptime(self):
885 """Weighted Uptime"""
886 with bridgedb.Storage.getDB() as db: # pragma: no cover
887 return db.getBridgeHistory(self.fingerprint).weightedUptime
890 @implementer(IBridge)
891 class Bridge(BridgeBackwardsCompatibility):
892 """A single bridge, and all the information we have for it.
894 :vartype fingerprint: :any:`str` or ``None``
895 :ivar fingerprint: This ``Bridge``'s fingerprint, in lowercased
896 hexadecimal format.
898 :vartype nickname: :any:`str` or ``None``
899 :ivar nickname: This ``Bridge``'s router nickname.
901 :vartype socksPort: int
902 :ivar socksPort: This ``Bridge``'s SOCKSPort. Should always be ``0``.
904 :vartype dirPort: int
905 :ivar dirPort: This ``Bridge``'s DirPort. Should always be ``0``.
907 :vartype orAddresses: list
908 :ivar orAddresses: A list of 3-tuples in the form::
910 (ADDRESS, PORT, IP_VERSION)
912 where:
913 * ADDRESS is an :class:`ipaddr.IPAddress`,
914 * PORT is an ``int``,
915 * IP_VERSION is either ``4`` or ``6``.
917 :vartype transports: list
918 :ivar transports: A list of :class:`PluggableTransport`, one for each
919 transport that this :class:`Bridge` currently supports.
921 :vartype flags: :class:`~bridgedb.bridges.Flags`
922 :ivar flags: All flags assigned by the BridgeAuthority to this
923 :class:`Bridge`.
925 :vartype hibernating: bool
926 :ivar hibernating: ``True`` if this :class:`Bridge` is hibernating and not
927 currently serving clients (e.g. if the Bridge hit its configured
928 ``RelayBandwidthLimit``); ``False`` otherwise.
930 :vartype distribution_request: str
931 :ivar distribution_request: If the bridge specified a
932 "bridgedb-distribution-request" line in its ``@type
933 bridge-server-descriptor``, the requested distribution method will be
934 stored here. If the line was absent, this will be set to ``"any"``.
936 :vartype _blockedIn: dict
937 :ivar _blockedIn: A dictionary of ``ADDRESS:PORT`` pairs to lists of
938 lowercased, two-letter country codes (e.g. ``"us"``, ``"gb"``,
939 ``"cn"``, etc.) which that ``ADDRESS:PORT`` pair is blocked in.
941 :vartype contact: :any:`str` or ``None``
942 :ivar contact: The contact information for the this Bridge's operator.
944 :vartype family: :any:`set` or ``None``
945 :ivar family: The fingerprints of other Bridges related to this one.
947 :vartype platform: :any:`str` or ``None``
948 :ivar platform: The ``platform`` line taken from the
949 ``@type bridge-server-descriptor``, e.g.
950 ``'Tor 0.2.5.4-alpha on Linux'``.
952 :vartype software: :class:`stem.version.Version` or ``None``
953 :ivar software: The OR version portion of the ``platform`` line.
955 :vartype os: :any:`str` or ``None``
956 :ivar os: The OS portion of the ``platform`` line.
958 #: (:any:`bool`) If ``True``, check that the signature of the bridge's
959 #: ``@type bridge-server-descriptor`` is valid and that the signature was
960 #: created with the ``signing-key`` contained in that descriptor.
961 _checkServerDescriptorSignature = True
963 def __init__(self, *args, **kwargs):
964 """Create and store information for a new ``Bridge``.
966 .. note: For backwards compatibility, **nickname**, **ip**, and
967 **orport** must be the first, second, and third arguments,
968 respectively. The **fingerprint** and **id_digest** were
969 previously kwargs, and are also provided for backwards
970 compatibility. New calls to :meth:`__init__` *should* avoid using
971 these kwargs, and instead use the methods
972 :meth:`updateFromNetworkStatus`,
973 :meth:`updateFromServerDescriptor`, and
974 :meth:`updateFromExtraInfoDescriptor`.
976 super(Bridge, self).__init__(*args, **kwargs)
978 self.socksPort = 0 # Bridges should always have ``SOCKSPort`` and
979 self.dirPort = 0 # ``DirPort`` set to ``0``
980 self.orAddresses = []
981 self.transports = []
982 self.flags = Flags()
983 self.hibernating = False
984 self._blockedIn = {}
985 self.distribution_request = "any"
987 self.bandwidth = None
988 self.bandwidthAverage = None
989 self.bandwidthBurst = None
990 self.bandwidthObserved = None
992 self.contact = None
993 self.family = None
994 self.platform = None
995 self.software = None
996 self.os = None
997 self.uptime = None
998 self.bridgeIPs = None
1000 self.onionKey = None
1001 self.ntorOnionKey = None
1002 self.signingKey = None
1004 self.descriptors = {'networkstatus': None,
1005 'server': None,
1006 'extrainfo': None}
1008 #: The hash digest of this bridge's ``@type bridge-server-descriptor``,
1009 #: as signed (but not including the signature). This is found in the
1010 #: 'r'-line of this bridge's ``@type bride-networkstatus`` document,
1011 #: however it is stored here re-encoded from base64 into hexadecimal,
1012 #: and converted to uppercase.
1013 self.descriptorDigest = None
1014 self.extrainfoDigest = None
1016 def __str__(self):
1017 """Return a pretty string representation that identifies this Bridge.
1019 .. warning:: With safelogging disabled, the returned string contains
1020 the bridge's fingerprint, which should be handled with care.
1022 If safelogging is enabled, the returned string will have the SHA-1
1023 hash of the bridge's fingerprint (a.k.a. a hashed fingerprint).
1025 Hashed fingerprints will be prefixed with ``'$$'``, and the real
1026 fingerprints are prefixed with ``'$'``.
1028 :rtype: str
1029 :returns: A string in the form:
1030 :data:`nickname```.$``:data:`fingerprint`.
1032 nickname = self.nickname if self.nickname else 'Unnamed'
1033 prefix = '$'
1034 separator = "~"
1035 fingerprint = self.fingerprint
1037 if safelog.safe_logging:
1038 prefix = '$$'
1039 if fingerprint:
1040 fingerprint = hashlib.sha1(fingerprint.encode('utf-8')).hexdigest().upper()
1042 if not fingerprint:
1043 fingerprint = '0' * 40
1045 return prefix + fingerprint + separator + nickname
1047 def hasProbingResistantPT(self):
1048 # We want to know if this bridge runs any active probing-resistant PTs
1049 # because if so, we should *only* hand out its active probing-resistant
1050 # PTs. Otherwise, a non-resistant PT would get this bridge scanned and
1051 # blocked: <https://bugs.torproject.org/28655>
1052 return any([t.isProbingResistant() for t in self.transports])
1054 def _checkServerDescriptor(self, descriptor):
1055 # If we're parsing the server-descriptor, require a networkstatus
1056 # document:
1057 if not self.descriptors['networkstatus']:
1058 raise ServerDescriptorWithoutNetworkstatus(
1059 ("We received a server-descriptor for bridge '%s' which has "
1060 "no corresponding networkstatus document.") %
1061 descriptor.fingerprint)
1063 ns = self.descriptors['networkstatus']
1065 # We must have the digest of the server-descriptor from the
1066 # networkstatus document:
1067 if not self.descriptorDigest:
1068 raise MissingServerDescriptorDigest(
1069 ("The server-descriptor digest was missing from networkstatus "
1070 "document for bridge '%s'.") % descriptor.fingerprint)
1072 digested = descriptor.digest()
1073 # The digested server-descriptor must match the digest reported by the
1074 # BridgeAuthority in the bridge's networkstatus document:
1075 if not self.descriptorDigest == digested:
1076 raise ServerDescriptorDigestMismatch(
1077 ("The server-descriptor digest for bridge '%s' doesn't match "
1078 "the digest reported by the BridgeAuthority in the "
1079 "networkstatus document: \n"
1080 "Digest reported in networkstatus: %s\n"
1081 "Actual descriptor digest: %s\n") %
1082 (descriptor.fingerprint, self.descriptorDigest, digested))
1084 def _constructBridgeLine(self, addrport, includeFingerprint=True,
1085 bridgePrefix=False):
1086 """Construct a :term:`Bridge Line` from an (address, port) tuple.
1088 :param tuple addrport: A 3-tuple of ``(address, port, ipversion)``
1089 where ``address`` is a string, ``port`` is an integer, and
1090 ``ipversion`` is a integer (``4`` or ``6``).
1091 :param bool includeFingerprint: If ``True``, include the
1092 ``fingerprint`` of this :class:`Bridge` in the returned bridge
1093 line.
1094 :param bool bridgePrefix: if ``True``, prefix the :term:`Bridge Line`
1095 with ``'Bridge '``.
1096 :rtype: string
1097 :returns: A bridge line suitable for adding into a ``torrc`` file or
1098 Tor Launcher.
1100 if not addrport:
1101 return
1103 address, port, version = addrport
1105 if not address or not port:
1106 return
1108 bridgeLine = []
1110 if bridgePrefix:
1111 bridgeLine.append('Bridge')
1113 if version == 4:
1114 bridgeLine.append("%s:%d" % (str(address), port))
1115 elif version == 6:
1116 bridgeLine.append("[%s]:%d" % (str(address), port))
1118 if includeFingerprint:
1119 bridgeLine.append("%s" % self.fingerprint)
1121 return ' '.join(bridgeLine)
1123 @classmethod
1124 def _getBlockKey(cls, address, port):
1125 """Format an **address**:**port** pair appropriately for use as a key
1126 in the :data:`_blockedIn` dictionary.
1128 :param address: An IP address of this :class:`Bridge` or one of its
1129 :data:`transports`.
1130 :param port: A port.
1131 :rtype: str
1132 :returns: A string in the form ``"ADDRESS:PORT"`` for IPv4 addresses,
1133 and ``"[ADDRESS]:PORT`` for IPv6.
1135 if isIPv6(str(address)):
1136 key = "[%s]:%s" % (address, port)
1137 else:
1138 key = "%s:%s" % (address, port)
1140 return key
1142 def _getTransportForRequest(self, bridgeRequest):
1143 """If a transport was requested, return the correlated
1144 :term:`Bridge Line` based upon the client identifier in the
1145 **bridgeRequest**.
1147 .. warning:: If this bridge doesn't have any of the requested
1148 pluggable transport type (optionally, not blocked in whichever
1149 countries the user doesn't want their bridges to be blocked in),
1150 then this method returns ``None``. This should only happen
1151 rarely, because the bridges are filtered into the client's
1152 hashring based on the **bridgeRequest** options.
1154 :type bridgeRequest: :class:`bridgedb.bridgerequest.BridgeRequestBase`
1155 :param bridgeRequest: A ``BridgeRequest`` which stores all of the
1156 client-specified options for which type of bridge they want to
1157 receive.
1158 :raises PluggableTransportUnavailable: if this bridge doesn't have any
1159 of the requested pluggable transport type. This shouldn't happen
1160 because the bridges are filtered into the client's hashring based
1161 on the **bridgeRequest** options, however, this is useful in the
1162 unlikely event that it does happen, so that the calling function
1163 can fetch an additional bridge from the hashring as recompense for
1164 what would've otherwise been a missing :term:`Bridge Line`.
1165 :rtype: str or ``None``
1166 :returns: If no transports were requested, return ``None``, otherwise
1167 return a :term:`Bridge Line` for the requested pluggable transport
1168 type.
1170 desired = bridgeRequest.justOnePTType()
1171 ipVersion = bridgeRequest.ipVersion
1173 logging.info("Bridge %s answering request for %s transport..." %
1174 (self, desired))
1176 # Filter all this Bridge's ``transports`` according to whether or not
1177 # their ``methodname`` matches the requested transport:
1178 transports = filter(lambda pt: pt.methodname == desired, self.transports)
1179 # Filter again for whichever of IPv4 or IPv6 was requested:
1180 transports = list(filter(lambda pt: pt.address.version == ipVersion, transports))
1182 if not transports:
1183 raise PluggableTransportUnavailable(
1184 ("Client requested transport %s, but bridge %s doesn't "
1185 "have any of that transport!") % (desired, self))
1187 unblocked = []
1188 for pt in transports:
1189 if not sum([self.transportIsBlockedIn(cc, pt.methodname)
1190 for cc in bridgeRequest.notBlockedIn]):
1191 unblocked.append(pt)
1193 if unblocked:
1194 position = bridgeRequest.getHashringPlacement('Order-Or-Addresses')
1195 return transports[position % len(unblocked)]
1196 else:
1197 logging.warn(("Client requested transport %s%s, but bridge %s "
1198 "doesn't have any of that transport!") %
1199 (desired, " not blocked in %s" %
1200 " ".join(bridgeRequest.notBlockedIn)
1201 if bridgeRequest.notBlockedIn else "", self))
1203 def _getVanillaForRequest(self, bridgeRequest):
1204 """If vanilla bridges were requested, return the assigned
1205 :term:`Bridge Line` based upon the client identifier in the
1206 **bridgeRequest**.
1208 :type bridgeRequest: :class:`bridgedb.bridgerequest.BridgeRequestBase`
1209 :param bridgeRequest: A ``BridgeRequest`` which stores all of the
1210 client-specified options for which type of bridge they want to
1211 receive.
1212 :rtype: str or ``None``
1213 :returns: If no transports were requested, return ``None``, otherwise
1214 return a :term:`Bridge Line` for the requested pluggable transport
1215 type.
1217 logging.info(
1218 "Bridge %s answering request for IPv%s vanilla address..." %
1219 (self, bridgeRequest.ipVersion))
1221 addresses = []
1223 for address, port, version in self.allVanillaAddresses:
1224 # Filter ``allVanillaAddresses`` by whether IPv4 or IPv6 was requested:
1225 if version == bridgeRequest.ipVersion:
1226 # Determine if the address is blocked in any of the country
1227 # codes. Because :meth:`addressIsBlockedIn` returns a bool,
1228 # we get a list like: ``[True, False, False, True]``, and
1229 # because bools are ints, they may be summed. What we care
1230 # about is that there are no ``True``s, for any country code,
1231 # so we check that the sum is zero (meaning the list was full
1232 # of ``False``s).
1234 # XXX Do we want to add a method for this construct?
1235 if not sum([self.addressIsBlockedIn(cc, address, port)
1236 for cc in bridgeRequest.notBlockedIn]):
1237 addresses.append((address, port, version))
1239 if addresses:
1240 # Use the client's unique data to HMAC them into their position in
1241 # the hashring of filtered bridges addresses:
1242 position = bridgeRequest.getHashringPlacement('Order-Or-Addresses')
1243 vanilla = addresses[position % len(addresses)]
1244 logging.info("Got vanilla bridge for client.")
1246 return vanilla
1248 def _updateORAddresses(self, orAddresses):
1249 """Update this :class:`Bridge`'s :data:`orAddresses` attribute from a
1250 3-tuple (i.e. as Stem creates when parsing descriptors).
1252 :param tuple orAddresses: A 3-tuple of: an IP address, a port number,
1253 and a boolean (``False`` if IPv4, ``True`` if IPv6).
1254 :raises FutureWarning: if any IPv4 addresses are found. As of
1255 tor-0.2.5, only IPv6 addresses should be found in a descriptor's
1256 `ORAddress` line.
1258 for (address, port, ipVersion) in orAddresses:
1259 version = 6
1260 if not ipVersion: # `False` means IPv4; `True` means IPv6.
1261 # See https://bugs.torproject.org/9380#comment:27
1262 warnings.warn(FutureWarning((
1263 "Got IPv4 address in 'a'/'or-address' line! Descriptor "
1264 "format may have changed!")))
1265 version = 4
1267 validatedAddress = isIPAddress(address, compressed=False)
1268 if validatedAddress:
1269 self.orAddresses.append( (validatedAddress, port, version,) )
1271 @property
1272 def allVanillaAddresses(self):
1273 """Get all valid, non-PT address:port pairs for this bridge.
1275 :rtype: list
1276 :returns: All of this bridge's ORAddresses, as well as its ORPort IP
1277 address and port.
1279 # Force deep-copying of the orAddresses. Otherwise, the later use of
1280 # ``addresses.append()`` is both non-reentrant and non-idempotent, as
1281 # it would change the value of ``Bridge.orAddresses``, as well as
1282 # append a (possibly updated, if ``Bridge.address`` or
1283 # ``Bridge.orPort`` changed!) new copy of the bridge's primary
1284 # ORAddress each time this property is called.
1285 addresses = self.orAddresses[:]
1287 # Add the default ORPort address. It will always be IPv4, otherwise
1288 # Stem should have raised a ValueError during parsing. But for
1289 # testability, check which type it is:
1290 version = 4
1291 if isIPv6(self.address):
1292 version = 6
1294 addresses.append((self.address, self.orPort, version))
1296 return addresses
1298 def assertOK(self):
1299 """Perform some additional validation on this bridge's info.
1301 We require that:
1303 1. Any IP addresses contained in :data:`orAddresses` are valid,
1304 according to :func:`~bridgedb.parse.addr.isValidIP`.
1306 2. Any ports in :data:`orAddresses` are between ``1`` and ``65535``
1307 (inclusive).
1309 3. All IP version numbers given in :data:`orAddresses` are either
1310 ``4`` or ``6``.
1312 .. todo:: This should probably be reimplemented as a property that
1313 automatically sanitises the values for each ORAddress, as is done
1314 for :data:`bridgedb.bridges.BridgeAddressBase.address` and
1315 :data:`bridgedb.bridges.BridgeBase.orPort`.
1317 :raises MalformedBridgeInfo: if something was found to be malformed or
1318 invalid.
1320 malformed = []
1322 for (address, port, version) in self.orAddresses:
1323 if not isValidIP(address):
1324 malformed.append("Invalid ORAddress address: '%s'" % address)
1325 if not (0 <= port <= 65535):
1326 malformed.append("Invalid ORAddress port: '%d'" % port)
1327 if not version in (4, 6):
1328 malformed.append("Invalid ORAddress IP version: %r" % version)
1330 if malformed:
1331 raise MalformedBridgeInfo('\n'.join(malformed))
1333 def getBridgeLine(self, bridgeRequest, includeFingerprint=True,
1334 bridgePrefix=False):
1335 """Return a valid :term:`Bridge Line` for a client to give to Tor
1336 Launcher or paste directly into their ``torrc``.
1338 This is a helper method to call either :meth:`_getTransportForRequest`
1339 or :meth:`_getVanillaForRequest` depending on whether or not a
1340 :class:`PluggableTransport` was requested in the
1341 :class:`bridgeRequest <bridgedb bridgerequest.BridgeRequestBase>`, and
1342 then construct the :term:`Bridge Line` accordingly.
1344 :type bridgeRequest: :class:`bridgedb.bridgerequest.BridgeRequestBase`
1345 :param bridgeRequest: A ``BridgeRequest`` which stores all of the
1346 client-specified options for which type of bridge they want to
1347 receive.
1348 :param bool includeFingerprint: If ``True``, include the
1349 ``fingerprint`` of this :class:`Bridge` in the returned bridge
1350 line.
1351 :param bool bridgePrefix: if ``True``, prefix the :term:`Bridge Line`
1352 with ``'Bridge '``.
1354 if not bridgeRequest.isValid():
1355 logging.info("Bridge request was not valid. Dropping request.")
1356 return # XXX raise error perhaps?
1358 bridgeLine = None
1360 if bridgeRequest.transports:
1361 pt = self._getTransportForRequest(bridgeRequest)
1362 if pt:
1363 bridgeLine = pt.getTransportLine(includeFingerprint,
1364 bridgePrefix)
1365 else:
1366 addrport = self._getVanillaForRequest(bridgeRequest)
1367 bridgeLine = self._constructBridgeLine(addrport,
1368 includeFingerprint,
1369 bridgePrefix)
1370 return bridgeLine
1372 def _addBlockByKey(self, key, countryCode):
1373 """Create or append to the list of blocked countries for a **key**.
1375 :param str key: The key to lookup in the :data:`Bridge._blockedIn`
1376 dictionary. This should be in the form returned by
1377 :meth:`_getBlockKey`.
1378 :param str countryCode: A two-character country code specifier.
1380 if key in self._blockedIn:
1381 self._blockedIn[key].append(countryCode.lower())
1382 else:
1383 self._blockedIn[key] = [countryCode.lower(),]
1385 def addressIsBlockedIn(self, countryCode, address, port):
1386 """Determine if a specific (address, port) tuple is blocked in
1387 **countryCode**.
1389 :param str countryCode: A two-character country code specifier.
1390 :param str address: An IP address (presumedly one used by this
1391 bridge).
1392 :param int port: A port.
1393 :rtype: bool
1394 :returns: ``True`` if the **address**:**port** pair is blocked in
1395 **countryCode**, ``False`` otherwise.
1397 key = self._getBlockKey(address, port)
1399 try:
1400 if countryCode.lower() in self._blockedIn[key]:
1401 logging.info("Vanilla address %s of bridge %s blocked in %s."
1402 % (key, self, countryCode.lower()))
1403 return True
1404 except KeyError:
1405 return False # That address:port pair isn't blocked anywhere
1407 return False
1409 def transportIsBlockedIn(self, countryCode, methodname):
1410 """Determine if any of a specific type of pluggable transport which
1411 this bridge might be running is blocked in a specific country.
1413 :param str countryCode: A two-character country code specifier.
1414 :param str methodname: The type of pluggable transport to check,
1415 e.g. ``'obfs4'``.
1416 :rtype: bool
1417 :returns: ``True`` if any address:port pair which this bridge is
1418 running a :class:`PluggableTransport` on is blocked in
1419 **countryCode**, ``False`` otherwise.
1421 for pt in self.transports:
1422 if pt.methodname == methodname.lower():
1423 if self.addressIsBlockedIn(countryCode, pt.address, pt.port):
1424 logging.info("Transport %s of bridge %s is blocked in %s."
1425 % (pt.methodname, self, countryCode))
1426 return True
1427 return False
1429 def isBlockedIn(self, countryCode):
1430 """Determine, according to our stored bridge reachability reports, if
1431 any of the address:port pairs used by this :class:`Bridge` or it's
1432 :data:`transports` are blocked in **countryCode**.
1434 :param str countryCode: A two-character country code specifier.
1435 :rtype: bool
1436 :returns: ``True`` if at least one address:port pair used by this
1437 bridge is blocked in **countryCode**; ``False`` otherwise.
1439 # Check all supported pluggable tranport types:
1440 for methodname in self.supportedTransportTypes:
1441 if self.transportIsBlockedIn(countryCode.lower(), methodname):
1442 return True
1444 for address, port, version in self.allVanillaAddresses:
1445 if self.addressIsBlockedIn(countryCode.lower(), address, port):
1446 return True
1448 return False
1450 def setBlockedIn(self, countryCode, address=None, port=None, methodname=None):
1451 """Mark this :class:`Bridge` as being blocked in **countryCode**.
1453 By default, if called with no parameters other than a **countryCode**,
1454 we'll mark all this :class:`Bridge`'s :data:`allVanillaAddresses` and
1455 :data:`transports` as being blocked.
1457 Otherwise, we'll filter on any and all parameters given.
1459 If only a **methodname** is given, then we assume that all
1460 :data:`transports` with that **methodname** are blocked in
1461 **countryCode**. If the methodname is ``"vanilla"``, then we assume
1462 each address in data:`allVanillaAddresses` is blocked.
1464 :param str countryCode: A two-character country code specifier.
1465 :param address: An IP address of this Bridge or one of its
1466 :data:`transports`.
1467 :param port: A specific port that is blocked, if available. If the
1468 **port** is ``None``, then any address this :class:`Bridge` or its
1469 :class:`PluggableTransport`s has that matches the given **address**
1470 will be marked as block, regardless of its port. This parameter
1471 is ignored unless an **address** is given.
1472 :param str methodname: A :data:`PluggableTransport.methodname` to
1473 match. Any remaining :class:`PluggableTransport`s from
1474 :data:`transports` which matched the other parameters and now also
1475 match this **methodname** will be marked as being blocked in
1476 **countryCode**.
1478 vanillas = self.allVanillaAddresses
1479 transports = self.transports
1481 if methodname:
1482 # Don't process the vanilla if we weren't told to do so:
1483 if not (methodname == 'vanilla') and not (address or port):
1484 vanillas = []
1486 transports = filter(lambda pt: methodname == pt.methodname, transports)
1488 if address:
1489 vanillas = filter(lambda ip: str(address) == str(ip[0]), vanillas)
1490 transports = filter(lambda pt: str(address) == str(pt.address), transports)
1492 if port:
1493 vanillas = filter(lambda ip: int(port) == int(ip[1]), vanillas)
1494 transports = filter(lambda pt: int(port) == int(pt.port), transports)
1496 for addr, port, _ in vanillas:
1497 key = self._getBlockKey(addr, port)
1498 logging.info("Vanilla address %s for bridge %s is now blocked in %s."
1499 % (key, self, countryCode))
1500 self._addBlockByKey(key, countryCode)
1502 for transport in transports:
1503 key = self._getBlockKey(transport.address, transport.port)
1504 logging.info("Transport %s %s for bridge %s is now blocked in %s."
1505 % (transport.methodname, key, self, countryCode))
1506 self._addBlockByKey(key, countryCode)
1507 transport._blockedIn[key] = self._blockedIn[key]
1509 def getDescriptorLastPublished(self):
1510 """Get the timestamp for when this bridge's last known server
1511 descriptor was published.
1513 :rtype: :type:`datetime.datetime` or ``None``
1514 :returns: A datetime object representing the timestamp of when the
1515 last known ``@type bridge-server-descriptor`` was published, or
1516 ``None`` if we have never seen a server descriptor for this
1517 bridge.
1519 return getattr(self.descriptors['server'], 'published', None)
1521 def getExtrainfoLastPublished(self):
1522 """Get the timestamp for when this bridge's last known extrainfo
1523 descriptor was published.
1525 :rtype: :type:`datetime.datetime` or ``None``
1526 :returns: A datetime object representing the timestamp of when the
1527 last known ``@type bridge-extrainfo`` descriptor was published, or
1528 ``None`` if we have never seen an extrainfo descriptor for this
1529 bridge.
1531 return getattr(self.descriptors['extrainfo'], 'published', None)
1533 def getNetworkstatusLastPublished(self):
1534 """Get the timestamp for when this bridge's last known networkstatus
1535 descriptor was published.
1537 :rtype: :type:`datetime.datetime` or ``None``
1538 :returns: A datetime object representing the timestamp of when the
1539 last known ``@type networkstatus-bridge`` document was published,
1540 or ``None`` if we have never seen a networkstatus document for
1541 this bridge.
1543 return getattr(self.descriptors['networkstatus'], 'published', None)
1545 @property
1546 def supportedTransportTypes(self):
1547 """A deduplicated list of all the :data:`PluggableTranport.methodname`s
1548 which this bridge supports.
1550 return list(set([pt.methodname for pt in self.transports]))
1552 def updateFromNetworkStatus(self, descriptor, ignoreNetworkstatus=False):
1553 """Update this bridge's attributes from a parsed networkstatus
1554 document.
1556 :type descriptor:
1557 :class:`stem.descriptors.router_status_entry.RouterStatusEntry`
1558 :param descriptor: The networkstatus document for this bridge.
1559 :param bool ignoreNetworkstatus: If ``True``, then ignore most of the
1560 information in the networkstatus document.
1562 self.descriptors['networkstatus'] = descriptor
1564 # These fields are *only* found in the networkstatus document:
1565 self.flags.update(descriptor.flags)
1566 self.descriptorDigest = descriptor.digest
1568 if not ignoreNetworkstatus:
1569 self.bandwidth = descriptor.bandwidth
1571 # These fields are also found in the server-descriptor. We will prefer
1572 # to use the information taken later from the server-descriptor
1573 # because it is signed by the bridge. However, for now, we harvest all
1574 # the info we can:
1575 self.fingerprint = descriptor.fingerprint
1577 if not ignoreNetworkstatus:
1578 self.nickname = descriptor.nickname
1579 self.address = descriptor.address
1580 self.orPort = descriptor.or_port
1581 self._updateORAddresses(descriptor.or_addresses)
1583 def updateFromServerDescriptor(self, descriptor, ignoreNetworkstatus=False):
1584 """Update this bridge's info from an ``@type bridge-server-descriptor``.
1586 .. note::
1587 If :func:`~bridgedb.parse.descriptor.parseServerDescriptorFile` is
1588 called with ``validate=True``, then Stem will handle checking that
1589 the ``signing-key`` hashes to the ``fingerprint``. Stem will also
1590 check that the ``router-signature`` on the descriptor is valid,
1591 was created with the ``signing-key``, and is a signature of the
1592 correct digest of the descriptor document (it recalculates the
1593 digest for the descriptor to ensure that the signed one and the
1594 actual digest match).
1596 :type descriptor:
1597 :class:`stem.descriptor.server_descriptor.RelayDescriptor`
1598 :param descriptor: The bridge's server descriptor to gather data from.
1599 :raises MalformedBridgeInfo: If this Bridge has no corresponding
1600 networkstatus entry, or its **descriptor** digest didn't match the
1601 expected digest (from the networkstatus entry).
1603 if ignoreNetworkstatus:
1604 try:
1605 self._checkServerDescriptor(descriptor)
1606 except (ServerDescriptorWithoutNetworkstatus,
1607 MissingServerDescriptorDigest,
1608 ServerDescriptorDigestMismatch) as ignored:
1609 logging.warn(ignored)
1610 else:
1611 self._checkServerDescriptor(descriptor)
1613 self.descriptors['server'] = descriptor
1615 # Replace the values which we harvested from the networkstatus
1616 # descriptor, because that one isn't signed with the bridge's identity
1617 # key.
1618 self.fingerprint = descriptor.fingerprint
1619 self.address = descriptor.address
1620 self.nickname = descriptor.nickname
1621 self.orPort = descriptor.or_port
1622 self._updateORAddresses(descriptor.or_addresses)
1623 self.hibernating = descriptor.hibernating
1625 if descriptor.bridge_distribution:
1626 self.distribution_request = descriptor.bridge_distribution
1628 self.onionKey = descriptor.onion_key
1629 self.ntorOnionKey = descriptor.ntor_onion_key
1630 self.signingKey = descriptor.signing_key
1632 self.bandwidthAverage = descriptor.average_bandwidth
1633 self.bandwidthBurst = descriptor.burst_bandwidth
1634 self.bandwidthObserved = descriptor.observed_bandwidth
1636 self.contact = descriptor.contact
1637 self.family = descriptor.family
1638 self.platform = descriptor.platform
1639 self.software = descriptor.tor_version
1640 self.os = descriptor.operating_system
1641 self.uptime = descriptor.uptime
1643 self.extrainfoDigest = descriptor.extra_info_digest
1645 def _verifyExtraInfoSignature(self, descriptor):
1646 """Verify the signature on the contents of this :class:`Bridge`'s
1647 ``@type bridge-extrainfo`` descriptor.
1649 :type descriptor:
1650 :class:`stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor`
1651 :param descriptor: An ``@type bridge-extrainfo`` descriptor for this
1652 :class:`Bridge`, parsed with Stem.
1653 :raises InvalidExtraInfoSignature: if the signature was invalid,
1654 missing, malformed, or couldn't be verified successfully.
1655 :returns: ``None`` if the signature was valid and verifiable.
1657 # The blocksize is always 128 bits for a 1024-bit key
1658 BLOCKSIZE = 128
1660 TOR_SIGNING_KEY_HEADER = u'-----BEGIN RSA PUBLIC KEY-----\n'
1661 TOR_SIGNING_KEY_FOOTER = u'-----END RSA PUBLIC KEY-----'
1662 TOR_BEGIN_SIGNATURE = u'-----BEGIN SIGNATURE-----\n'
1663 TOR_END_SIGNATURE = u'-----END SIGNATURE-----\n'
1665 logging.info("Verifying extrainfo signature for %s..." % self)
1667 # Get the bytes of the descriptor signature without the headers:
1668 document, signature = str(descriptor).split(TOR_BEGIN_SIGNATURE)
1669 signature = signature.replace(TOR_END_SIGNATURE, '')
1670 signature = signature.replace('\n', '')
1671 signature = signature.strip()
1673 try:
1674 # Get the ASN.1 sequence:
1675 sequence = asn1.DerSequence()
1677 key = self.signingKey
1678 key = key.strip(TOR_SIGNING_KEY_HEADER)
1679 key = key.strip(TOR_SIGNING_KEY_FOOTER)
1680 key = key.replace('\n', '')
1681 key = base64.b64decode(key)
1683 sequence.decode(key)
1685 modulus = sequence[0]
1686 publicExponent = sequence[1]
1688 # The public exponent of RSA signing-keys should always be 65537,
1689 # but we're not going to turn them down if they want to use a
1690 # potentially dangerous exponent.
1691 if publicExponent != 65537: # pragma: no cover
1692 logging.warn("Odd RSA exponent in signing-key for %s: %s" %
1693 (self, publicExponent))
1695 # Base64 decode the signature:
1696 signatureDecoded = base64.b64decode(signature)
1698 # Convert the signature to a long:
1699 signatureLong = bytes_to_long(signatureDecoded)
1701 # Decrypt the long signature with the modulus and public exponent:
1702 decryptedInt = pow(signatureLong, publicExponent, modulus)
1704 # Then convert it back to a byte array:
1705 decryptedBytes = long_to_bytes(decryptedInt, BLOCKSIZE)
1707 # Remove the PKCS#1 padding from the signature:
1708 unpadded = removePKCS1Padding(decryptedBytes)
1710 # This is the hexadecimal SHA-1 hash digest of the descriptor document
1711 # as it was signed:
1712 signedDigest = codecs.encode(unpadded, 'hex_codec').decode('utf-8')
1713 actualDigest = hashlib.sha1(document.encode('utf-8')).hexdigest()
1715 except Exception as error:
1716 logging.debug("Error verifying extrainfo signature: %s" % error)
1717 raise InvalidExtraInfoSignature(
1718 "Extrainfo signature for %s couldn't be decoded: %s" %
1719 (self, signature))
1720 else:
1721 if signedDigest != actualDigest:
1722 raise InvalidExtraInfoSignature(
1723 ("The extrainfo digest signed by bridge %s didn't match the "
1724 "actual digest.\nSigned digest: %s\nActual digest: %s") %
1725 (self, signedDigest, actualDigest))
1726 else:
1727 logging.info("Extrainfo signature was verified successfully!")
1729 def updateFromExtraInfoDescriptor(self, descriptor, verify=True):
1730 """Update this bridge's information from an extrainfo descriptor.
1732 Stem's
1733 :class:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`
1734 parses extrainfo ``transport`` lines into a dictionary with the
1735 following structure::
1737 {u'obfs2': (u'34.230.223.87', 37339, []),
1738 u'obfs3': (u'34.230.223.87', 37338, []),
1739 u'obfs4': (u'34.230.223.87', 37341, [
1740 (u'iat-mode=0,'
1741 u'node-id=2a79f14120945873482b7823caabe2fcde848722,'
1742 u'public-key=0a5b046d07f6f971b7776de682f57c5b9cdc8fa060db7ef59de82e721c8098f4')]),
1743 u'scramblesuit': (u'34.230.223.87', 37340, [
1744 u'password=ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'])}
1747 .. todo:: The ``transport`` attribute of Stem's
1748 ``BridgeExtraInfoDescriptor`` class is a dictionary that uses the
1749 Pluggable Transport's type as the keys… meaning that if a bridge
1750 were to offer four instances of ``obfs3``, only one of them would
1751 get to us through Stem. This might pose a problem someday.
1753 :type descriptor:
1754 :class:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`
1755 :param descriptor: DOCDOC
1756 :param bool verify: If ``True``, check that the ``router-signature``
1757 on the extrainfo **descriptor** is a valid signature from
1758 :data:`signingkey`.
1760 if verify:
1761 try:
1762 self._verifyExtraInfoSignature(descriptor)
1763 except InvalidExtraInfoSignature as error:
1764 logging.warn(error)
1765 logging.info(("Tossing extrainfo descriptor due to an invalid "
1766 "signature."))
1767 return
1769 self.descriptors['extrainfo'] = descriptor
1770 self.bridgeIPs = descriptor.bridge_ips
1772 oldTransports = self.transports[:]
1774 for methodname, (address, port, args) in descriptor.transport.items():
1775 updated = False
1776 # See if we already know about this transport. If so, update its
1777 # info; otherwise, add a new transport below.
1778 for pt in self.transports:
1779 if pt.methodname == methodname:
1781 logging.info("Found old %s transport for %s... Updating..."
1782 % (methodname, self))
1784 if not (address == str(pt.address)) and (port == pt.port):
1785 logging.info(("Address/port for %s transport for "
1786 "%s changed: old=%s:%s new=%s:%s")
1787 % (methodname, self, pt.address, pt.port,
1788 address, port))
1790 original = pt
1791 try:
1792 pt.updateFromStemTransport(str(self.fingerprint),
1793 methodname,
1794 (address, port, args,))
1795 except MalformedPluggableTransport as error:
1796 logging.info(str(error))
1797 else:
1798 oldTransports.remove(original)
1800 updated = True
1801 break
1803 if updated:
1804 continue
1805 else:
1806 # We didn't update it. It must be a new transport for this
1807 # bridges that we're hearing about for the first time, so add
1808 # it:
1809 logging.info(
1810 "Received new %s pluggable transport for bridge %s."
1811 % (methodname, self))
1812 try:
1813 transport = PluggableTransport()
1814 transport.updateFromStemTransport(str(self.fingerprint),
1815 methodname,
1816 (address, port, args,))
1817 self.transports.append(transport)
1818 except MalformedPluggableTransport as error:
1819 logging.info(str(error))
1821 # These are the pluggable transports which we knew about before, which
1822 # however were not updated in this descriptor, ergo the bridge must
1823 # not have them any more:
1824 for pt in oldTransports:
1825 logging.info("Removing dead transport for bridge %s: %s %s:%s %s" %
1826 (self, pt.methodname, pt.address, pt.port, pt.arguments))
1827 self.transports.remove(pt)