class DirSourceObject(TorDocumentObject): # [Exactly once, at start] # dir-source dannenberg 0232AF901C31A04EE9848595AF9BB7620D4C5B2E dannenberg.torauth.de 193.23.244.244 80 443 START_ITEM = Item( 'dir-source', parse_func=ItemParsers.split_symbol, parse_args=[ ' ', [ 'nickname', 'fingerprint', 'hostname', 'address', 'dir_port', 'or_port' ] ], ) ITEMS = [ # "contact" SP string NL # [Exactly once.] # contact Andreas Lehner Item('contact'), # "vote-digest" SP digest NL # [Exactly once.] # vote-digest 8E4B75DC6EC0AB037A9D2C2C3EB46BBCDFDA17C4 Item('vote-digest'), ]
class RouterObject(TorDocumentObject): CLASS = Router # "r" SP nickname SP identity SP digest SP publication SP IP SP ORPort SP DirPort NL # [At start, exactly once.] # r seele AAoQ1DAR6kkoo19hBAX5K0QztNw HVuAXf9AUKcwgmwTP4DI6xHhmeI 2019-07-20 13:33:25 67.174.243.193 9001 0 START_ITEM = Item('r', parse_func=_parse_r_line) ITEMS = [ # "a" SP address ":" port NL # [Any number] # ... Item('a', type=ItemType.AnyNumber), # "s" SP Flags NL # [Exactly once.] # s Running Stable V2Dir Valid ItemEnum('s', enum_cls=RouterFlags, out_name='flags'), # "v" SP version NL # [At most once.] # v Tor 0.3.5.8 Item('v', out_name='version', type=ItemType.AtMostOnce), # "pr" SP Entries NL # [At most once.] # pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 ... Item('pr', type=ItemType.AtMostOnce), # "w" SP "Bandwidth=" INT [SP "Measured=" INT] [SP "Unmeasured=1"] NL # [At most once.] # w Bandwidth=5 Item('w', type=ItemType.AtMostOnce), # "p" SP ("accept" / "reject") SP PortList NL # [At most once.] # p reject 1-65535 Item('p', type=ItemType.AtMostOnce), ]
class DirKeyCertificateObject(TorDocumentObject): START_ITEM = ItemInt('dir-key-certificate-version') ITEMS = [ # fingerprint 14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 Item('fingerprint'), # dir-key-published 2019-06-01 00:00:00 ItemDate('dir-key-published'), # dir-key-expires 2019-11-01 00:00:00 ItemDate('dir-key-expires'), ItemMulti('dir-identity-key', 'rsa public key'), ItemMulti('dir-signing-key', 'rsa public key'), ItemMulti('dir-key-crosscert', 'id signature'), ItemMulti('dir-key-certification', 'signature'), ]
class NetworkStatusDiffDocument(TorDocument): DOCUMENT_NAME = 'network_status_diff' # The first line is "network-status-diff-version 1" NL START_ITEM = ItemInt('network-status-diff-version') ITEMS = [ # The second line is "hash" SP FromDigest SP ToDigest NL Item('hash', parse_func=ItemParsers.split_symbol, parse_args=[' ', ['from_digest', 'to_digest']]), # Diff Actions ItemAction(out_name='actions'), ] def __init__(self, raw_string): super().__init__(raw_string)
class NetworkStatusDocument(TorDocument): DOCUMENT_NAME = 'network_status' # "network-status-version" SP version NL # [At start, exactly once.] # network-status-version 3 START_ITEM = ItemInt('network-status-version') ITEMS = [ # "vote-status" SP type NL # [Exactly once.] # vote-status consensus Item('vote-status'), # "consensus-method" SP Integer NL # [At most once for consensuses; does not occur in votes.] # [No extra arguments] # consensus-method 28 ItemInt('consensus-method'), # "valid-after" SP YYYY-MM-DD SP HH:MM:SS NL # [Exactly once.] # valid-after 2019-07-20 21:00:00 ItemDate('valid-after'), # "fresh-until" SP YYYY-MM-DD SP HH:MM:SS NL # [Exactly once.] # fresh-until 2019-07-20 22:00:00 ItemDate('fresh-until'), # "valid-until" SP YYYY-MM-DD SP HH:MM:SS NL # [Exactly once.] # valid-until 2019-07-21 00:00:00 ItemDate('valid-until'), # "voting-delay" SP VoteSeconds SP DistSeconds NL # [Exactly once.] # voting-delay 300 300 Item('voting-delay'), # "client-versions" SP VersionList NL # [At most once.] # client-versions 0.2.9.16,0.2.9.17,0.3.5.7,0.3.5.8,0.4.0.5,0.4.0.6,0.4.1.2-alpha,0.4.1.3-alpha,0.4.1.4-rc Item('client-versions'), # "server-versions" SP VersionList NL # [At most once.] # server-versions 0.2.9.15,0.2.9.16,0.2.9.17,0.3.5.8,0.4.0.5,0.4.0.6,0.4.1.2-alpha,0.4.1.3-alpha,0.4.1.4-rc Item('server-versions'), # "package" SP PackageName SP Version SP URL SP DIGESTS NL # [Any number of times.] # Included in consensuses only for method 19 and later. # "known-flags" SP FlagList NL # [Exactly once.] # known-flags Authority BadExit Exit Fast Guard HSDir NoEdConsensus Running Stable StaleDesc V2Dir Valid Item('known-flags'), # "recommended-client-protocols" SP Entries NL # [At most once.] # recommended-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 ... Item('recommended-client-protocols'), # "recommended-relay-protocols" SP Entries NL # [At most once.] # recommended-relay-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 ... Item('recommended-relay-protocols'), # "required-client-protocols" SP Entries NL # [At most once.] # required-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 ...` Item('required-client-protocols'), # "required-relay-protocols" SP Entries NL # [At most once.] # required-relay-protocols Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=3-4 ... Item('required-relay-protocols'), # "params" SP [Parameters] NL # [At most once] # params CircuitPriorityHalflifeMsec=30000 DoSCircuitCreationEnabled=1 DoSConnectionEnabled=1 ... Item('params'), # "shared-rand-previous-value" SP NumReveals SP Value NL # [At most once] # shared-rand-previous-value 9 ybFb42KVOFmJR/EMtjPJNJiTBDyiI0eefmebenN9EY0= Item('shared-rand-previous-value'), # "shared-rand-current-value" SP NumReveals SP Value NL # [At most once] # shared-rand-current-value 9 p4+CMGa6M7EhDqGNpofcJ2MeQ7f7qdF8QslK+AOnrQg= Item('shared-rand-current-value'), ItemObject(DirSourceObject, out_name='voters'), ItemObject(RouterObject, out_name='routers'), # "directory-signature" [SP Algorithm] SP identity SP signing-key-digest # NL Signature ItemSignature('directory-signature'), ] def __init__(self, raw_string): super().__init__( raw_string, digests_names=['sha1', 'sha3_256'], digest_start='network-status-version', digest_end='directory-signature ', ) def link_consensus(self, consensus): for router in self.routers: router._consensus = consensus @property def is_fresh(self): return self.valid_until > datetime.utcnow() @property def digest_sha1(self): return self.get_digest('sha1') @property def digest_sha3_256(self): return self.get_digest('sha3_256') def find_signature(self, identity): for sign in self.signatures: if sign['identity'] == identity: return sign def apply_diff(self, diff): logger.info('Apply network-status-diff for %s to %s', self.digest_sha3_256.hex(), diff.to_digest.lower()) assert self.digest_sha3_256.hex() == diff.from_digest.lower() lines = self.raw_string.split('\n') cur_line = 1 for action in diff.actions: cur_end = len(lines) cur_line = action.apply(lines, cur_line, cur_end) raw_string_new = '\n'.join(lines) + '\n' assert sha3_256( raw_string_new.encode()).hex() == diff.to_digest.lower() return NetworkStatusDocument(raw_string_new)
class RouterObject(TorDocumentObject): # "r" SP nickname SP identity SP digest SP publication SP IP SP ORPort SP DirPort NL # [At start, exactly once.] # r seele AAoQ1DAR6kkoo19hBAX5K0QztNw HVuAXf9AUKcwgmwTP4DI6xHhmeI 2019-07-20 13:33:25 67.174.243.193 9001 0 START_ITEM = Item('r', parse_func=_parse_r_line) ITEMS = [ # "a" SP address ":" port NL # [Any number] # ... Item('a', type=ItemType.AnyNumber), # "s" SP Flags NL # [Exactly once.] # s Running Stable V2Dir Valid ItemEnum('s', enum_cls=RouterFlags, out_name='flags'), # "v" SP version NL # [At most once.] # v Tor 0.3.5.8 Item('v', out_name='version', type=ItemType.AtMostOnce), # "pr" SP Entries NL # [At most once.] # pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 ... Item('pr', type=ItemType.AtMostOnce), # "w" SP "Bandwidth=" INT [SP "Measured=" INT] [SP "Unmeasured=1"] NL # [At most once.] # w Bandwidth=5 Item('w', type=ItemType.AtMostOnce), # "p" SP ("accept" / "reject") SP PortList NL # [At most once.] # p reject 1-65535 Item('p', type=ItemType.AtMostOnce), ] def __init__(self): self._consensus = None self._service_key = None @cached_property def fingerprint(self): return b64decode(self._fingerprint) @cached_property def descriptor(self): logger.debug('Getting descriptor for %s...', self) return self._consensus.get_descriptor(self._fingerprint) @property def service_key(self): return self._service_key @service_key.setter def service_key(self, value): self._service_key = value @property def descriptor_url_prefix(self): """ Get the URL to the onion router's descriptor (where keys are stored). :return: URL """ return 'http://{}:{}/tor/server/fp'.format(self.ip, self.dir_port) def descriptor_url(self, fingerprint): return '{}/{}'.format(self.descriptor_url_prefix, b16encode(b64decode(fingerprint)).decode()) def get_descriptor_for(self, fingerprint): """ Get another router descriptor through this one. :param fingerprint: :return: Descriptor object """ logger.debug('Getting descriptor for %s from %s', fingerprint, self) url = self.descriptor_url(fingerprint) try: response = http_get(url) except (ConnectionError, socket.timeout, HTTPError, URLError) as e: logger.debug(e) raise FetchDescriptorError("Can't fetch descriptor from %s" % url) descriptor_info = RouterDescriptorParser.parse(response) return Descriptor(**descriptor_info) def __str__(self): """Get router string representation.""" return '{}:{} ({}; {})'.format(self.ip, self.tor_port, self.nickname, self.version)