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
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
30 from __future__
import print_function
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`.
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`
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.
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
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.
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
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.")
116 """All the flags which a :class:`Bridge` may have."""
124 def update(self
, flags
):
125 """Update with **flags** taken from an ``@type networkstatus-bridge``
128 From `dir-spec.txt`_:
134 | A series of space-separated status flags, in lexical order (as ASCII
135 | byte strings). Currently documented flags are:
138 | "Fast" if the router is suitable for high-bandwidth circuits.
139 | "Guard" if the router is suitable for use as an entry guard.
141 | "Stable" if the router is suitable for long-lived circuits.
142 | "Running" if the router is currently usable.
144 | "Valid" if the router has been 'validated'.
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.
178 :ivar country: The two-letter GeoIP country code of the :ivar:`address`.
181 :ivar port: A integer specifying the port which this :class:`Bridge`
182 (or :class:`PluggableTransport`) is listening on.
186 self
._fingerprint
= None
192 def fingerprint(self
):
193 """Get this Bridge's fingerprint.
196 :returns: A 40-character hexadecimal formatted string representation
197 of the SHA-1 hash digest of the public half of this Bridge's
200 return self
._fingerprint
203 def fingerprint(self
, value
):
204 """Set this Bridge's fingerprint to **value**.
206 .. info: The purported fingerprint will be checked for specification
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()
216 def fingerprint(self
):
217 """Reset this Bridge's fingerprint."""
218 self
._fingerprint
= None
222 """Get this Bridge's identity digest.
225 :returns: The binary-encoded SHA-1 hash digest of the public half of
226 this Bridge's identity key, if available; otherwise, returns
230 return fromHex(self
.fingerprint
)
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')
247 """Reset this Bridge's identity digest."""
248 del(self
.fingerprint
)
252 """Get this bridge's address.
254 :rtype: :class:`~ipaddr.IPv4Address` or :class:`~ipaddr.IPv6Address`
255 :returns: The bridge's address.
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)
270 """Reset this Bridge's address to ``None``."""
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``.
283 return geo
.getCountryCode(self
.address
)
287 """Get the port number which this ``Bridge`` is listening
288 for incoming client connections on.
291 :returns: The port (as an int), if it is known and valid; otherwise,
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):
308 """Reset this ``Bridge``'s port to ``None``."""
313 @implementer(IBridge
)
314 class PluggableTransport(BridgeAddressBase
):
315 """A single instance of a Pluggable Transport (PT) offered by a
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.
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,
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.
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
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
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
386 super(PluggableTransport
, self
).__init
__()
387 self
._methodname
= None
390 self
.fingerprint
= fingerprint
391 self
.address
= address
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
400 if (fingerprint
or address
or port
or methodname
or arguments
):
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.
413 :returns: A dictionary of all the ``K=V`` Pluggable Transport
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
424 allArguments
= ','.join(argumentList
)
426 for arg
in allArguments
.split(','):
427 if arg
: # It might be an empty string
429 key
, value
= arg
.split('=')
431 logging
.warn(" Couldn't parse K=V from PT arg: %r" % arg
)
433 logging
.debug(" Parsed PT Argument: %s: %s" % (key
, value
))
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
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
__)
460 raise InvalidPluggableTransportIP(
461 ("Cannot create PluggableTransport with address '%s'. "
462 "type(address)=%s.") % (self
.address
, type(self
.address
)))
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():
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
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'))):
501 # scramblesuit requires (password):
502 elif self
.methodname
== 'scramblesuit':
503 if self
.arguments
.get('password'):
511 def methodname(self
):
512 """Get this :class:`PluggableTransport`'s methodname.
515 :returns: The (lowercased) methodname of this ``PluggableTransport``,
518 return self
._methodname
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.
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.
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).
568 :returns: A configuration line for adding this Pluggable Transport
569 into a ``torrc`` file.
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
)
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
)
593 def updateFromStemTransport(self
, fingerprint
, methodname
, kitchenSink
):
594 """Update this :class:`PluggableTransport` from the data structure
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, [
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
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?
630 self
.port
= int(port
)
631 self
.arguments
= self
._parseArgumentsIntoDict
(kitchenSink
[2])
635 @implementer(IBridge
)
636 class BridgeBase(BridgeAddressBase
):
637 """The base class for all bridge implementations."""
640 super(BridgeBase
, self
).__init
__()
642 self
._nickname
= None
644 self
.socksPort
= 0 # Bridges should always have ``SOCKSPort`` and
645 self
.dirPort
= 0 # ``DirPort`` set to ``0``
646 self
.orAddresses
= []
652 """Get this Bridge's nickname.
655 :returns: The Bridge's nickname.
657 return self
._nickname
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
673 """Reset this Bridge's nickname."""
674 self
._nickname
= None
678 """Get this bridge's ORPort.
681 :returns: This Bridge's default ORPort.
686 def orPort(self
, value
):
687 """Set this Bridge's ORPort.
689 :param int value: The Bridge's ORPort.
695 """Reset this Bridge's ORPort."""
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
725 if nickname
or ip
or orport
or fingerprint
or id_digest
:
726 self
._backwardsCompatible
(nickname
=nickname
, address
=ip
,
727 orPort
=orport
, fingerprint
=fingerprint
,
729 orAddresses
=or_addresses
)
731 def _backwardsCompatible(self
, nickname
=None, address
=None, orPort
=None,
732 fingerprint
=None, idDigest
=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
740 self
.address
= address
744 if not len(idDigest
) == 20:
745 raise TypeError("Bridge with invalid ID")
746 self
.fingerprint
= toHex(idDigest
).decode('utf-8')
748 if not isValidFingerprint(fingerprint
):
749 raise TypeError("Bridge with invalid fingerprint (%r)"
751 self
.fingerprint
= fingerprint
.lower()
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)
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
,))
768 logging
.warn("Can't parse port for ORAddress %r: %r"
772 """Get the binary encoded form of this ``Bridge``'s ``fingerprint``.
774 This method is provided for backwards compatibility and should not
779 def setDescriptorDigest(self
, digest
):
780 """Set this ``Bridge``'s server-descriptor digest.
782 This method is provided for backwards compatibility and should not
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
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
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
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
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)
843 bridgeRequest
.withPluggableTransportType(transport
)
845 bridgeRequest
.generateFilters()
846 bridgeLine
= self
.getBridgeLine(bridgeRequest
, includeFingerprint
)
849 # Bridge Stability (`#5482 <https://bugs.torproject.org>`_) properties.
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
856 with bridgedb
.Storage
.getDB() as db
: # pragma: no cover
857 return db
.getBridgeHistory(self
.fingerprint
).familiar
861 """Weighted Fractional Uptime"""
862 with bridgedb
.Storage
.getDB() as db
: # pragma: no cover
863 return db
.getBridgeHistory(self
.fingerprint
).weightedFractionalUptime
866 def weightedTime(self
):
868 with bridgedb
.Storage
.getDB() as db
: # pragma: no cover
869 return db
.getBridgeHistory(self
.fingerprint
).weightedTime
873 """Weighted Mean Time Between Address Change"""
874 with bridgedb
.Storage
.getDB() as db
: # pragma: no cover
875 return db
.getBridgeHistory(self
.fingerprint
).wmtbac
879 """The Time On Same Address (TOSA)"""
880 with bridgedb
.Storage
.getDB() as db
: # pragma: no cover
881 return db
.getBridgeHistory(self
.fingerprint
).tosa
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
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)
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
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
= []
983 self
.hibernating
= False
985 self
.distribution_request
= "any"
987 self
.bandwidth
= None
988 self
.bandwidthAverage
= None
989 self
.bandwidthBurst
= None
990 self
.bandwidthObserved
= None
998 self
.bridgeIPs
= None
1000 self
.onionKey
= None
1001 self
.ntorOnionKey
= None
1002 self
.signingKey
= None
1004 self
.descriptors
= {'networkstatus': 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
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 ``'$'``.
1029 :returns: A string in the form:
1030 :data:`nickname```.$``:data:`fingerprint`.
1032 nickname
= self
.nickname
if self
.nickname
else 'Unnamed'
1035 fingerprint
= self
.fingerprint
1037 if safelog
.safe_logging
:
1040 fingerprint
= hashlib
.sha1(fingerprint
.encode('utf-8')).hexdigest().upper()
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
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
1094 :param bool bridgePrefix: if ``True``, prefix the :term:`Bridge Line`
1097 :returns: A bridge line suitable for adding into a ``torrc`` file or
1103 address
, port
, version
= addrport
1105 if not address
or not port
:
1111 bridgeLine
.append('Bridge')
1114 bridgeLine
.append("%s:%d" % (str(address
), port
))
1116 bridgeLine
.append("[%s]:%d" % (str(address
), port
))
1118 if includeFingerprint
:
1119 bridgeLine
.append("%s" % self
.fingerprint
)
1121 return ' '.join(bridgeLine
)
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
1130 :param port: A port.
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
)
1138 key
= "%s:%s" % (address
, port
)
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
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
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
1170 desired
= bridgeRequest
.justOnePTType()
1171 ipVersion
= bridgeRequest
.ipVersion
1173 logging
.info("Bridge %s answering request for %s transport..." %
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
))
1183 raise PluggableTransportUnavailable(
1184 ("Client requested transport %s, but bridge %s doesn't "
1185 "have any of that transport!") % (desired
, self
))
1188 for pt
in transports
:
1189 if not sum([self
.transportIsBlockedIn(cc
, pt
.methodname
)
1190 for cc
in bridgeRequest
.notBlockedIn
]):
1191 unblocked
.append(pt
)
1194 position
= bridgeRequest
.getHashringPlacement('Order-Or-Addresses')
1195 return transports
[position
% len(unblocked
)]
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
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
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
1218 "Bridge %s answering request for IPv%s vanilla address..." %
1219 (self
, bridgeRequest
.ipVersion
))
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
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
))
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.")
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
1258 for (address
, port
, ipVersion
) in orAddresses
:
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!")))
1267 validatedAddress
= isIPAddress(address
, compressed
=False)
1268 if validatedAddress
:
1269 self
.orAddresses
.append( (validatedAddress
, port
, version
,) )
1272 def allVanillaAddresses(self
):
1273 """Get all valid, non-PT address:port pairs for this bridge.
1276 :returns: All of this bridge's ORAddresses, as well as its ORPort IP
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:
1291 if isIPv6(self
.address
):
1294 addresses
.append((self
.address
, self
.orPort
, version
))
1299 """Perform some additional validation on this bridge's info.
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``
1309 3. All IP version numbers given in :data:`orAddresses` are either
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
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
)
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
1348 :param bool includeFingerprint: If ``True``, include the
1349 ``fingerprint`` of this :class:`Bridge` in the returned bridge
1351 :param bool bridgePrefix: if ``True``, prefix the :term:`Bridge Line`
1354 if not bridgeRequest
.isValid():
1355 logging
.info("Bridge request was not valid. Dropping request.")
1356 return # XXX raise error perhaps?
1360 if bridgeRequest
.transports
:
1361 pt
= self
._getTransportForRequest
(bridgeRequest
)
1363 bridgeLine
= pt
.getTransportLine(includeFingerprint
,
1366 addrport
= self
._getVanillaForRequest
(bridgeRequest
)
1367 bridgeLine
= self
._constructBridgeLine
(addrport
,
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())
1383 self
._blockedIn
[key
] = [countryCode
.lower(),]
1385 def addressIsBlockedIn(self
, countryCode
, address
, port
):
1386 """Determine if a specific (address, port) tuple is blocked in
1389 :param str countryCode: A two-character country code specifier.
1390 :param str address: An IP address (presumedly one used by this
1392 :param int port: A port.
1394 :returns: ``True`` if the **address**:**port** pair is blocked in
1395 **countryCode**, ``False`` otherwise.
1397 key
= self
._getBlockKey
(address
, port
)
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()))
1405 return False # That address:port pair isn't blocked anywhere
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,
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
))
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.
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
):
1444 for address
, port
, version
in self
.allVanillaAddresses
:
1445 if self
.addressIsBlockedIn(countryCode
.lower(), address
, port
):
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
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
1478 vanillas
= self
.allVanillaAddresses
1479 transports
= self
.transports
1482 # Don't process the vanilla if we weren't told to do so:
1483 if not (methodname
== 'vanilla') and not (address
or port
):
1486 transports
= filter(lambda pt
: methodname
== pt
.methodname
, transports
)
1489 vanillas
= filter(lambda ip
: str(address
) == str(ip
[0]), vanillas
)
1490 transports
= filter(lambda pt
: str(address
) == str(pt
.address
), transports
)
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
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
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
1543 return getattr(self
.descriptors
['networkstatus'], 'published', None)
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
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
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``.
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).
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
:
1605 self
._checkServerDescriptor
(descriptor
)
1606 except (ServerDescriptorWithoutNetworkstatus
,
1607 MissingServerDescriptorDigest
,
1608 ServerDescriptorDigestMismatch
) as ignored
:
1609 logging
.warn(ignored
)
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
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.
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
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()
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
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" %
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
))
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.
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, [
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.
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
1762 self
._verifyExtraInfoSignature
(descriptor
)
1763 except InvalidExtraInfoSignature
as error
:
1765 logging
.info(("Tossing extrainfo descriptor due to an invalid "
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():
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
,
1792 pt
.updateFromStemTransport(str(self
.fingerprint
),
1794 (address
, port
, args
,))
1795 except MalformedPluggableTransport
as error
:
1796 logging
.info(str(error
))
1798 oldTransports
.remove(original
)
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
1810 "Received new %s pluggable transport for bridge %s."
1811 % (methodname
, self
))
1813 transport
= PluggableTransport()
1814 transport
.updateFromStemTransport(str(self
.fingerprint
),
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
)