commit 4e29ae8dfc69cb68a25c9cf159e56db4e943565b Author: Ravi Chandra Padmala neenaoffline@gmail.com Date: Tue Aug 7 12:09:25 2012 +0530
stem.descriptor.networkstatus_descriptor => stem.descriptor.networkstatus --- run_tests.py | 4 +- stem/descriptor/__init__.py | 2 +- stem/descriptor/networkstatus.py | 614 +++++++++++++++++++++ stem/descriptor/networkstatus_descriptor.py | 614 --------------------- test/integ/descriptor/networkstatus.py | 228 ++++++++ test/integ/descriptor/networkstatus_descriptor.py | 228 -------- 6 files changed, 845 insertions(+), 845 deletions(-)
diff --git a/run_tests.py b/run_tests.py index 10aa24c..8d115f1 100755 --- a/run_tests.py +++ b/run_tests.py @@ -46,7 +46,7 @@ import test.integ.socket.control_socket import test.integ.descriptor.reader import test.integ.descriptor.server_descriptor import test.integ.descriptor.extrainfo_descriptor -import test.integ.descriptor.networkstatus_descriptor +import test.integ.descriptor.networkstatus import test.integ.response.protocolinfo import test.integ.util.conf import test.integ.util.proc @@ -135,7 +135,7 @@ INTEG_TESTS = ( test.integ.descriptor.reader.TestDescriptorReader, test.integ.descriptor.server_descriptor.TestServerDescriptor, test.integ.descriptor.extrainfo_descriptor.TestExtraInfoDescriptor, - test.integ.descriptor.networkstatus_descriptor.TestNetworkStatusDocument, + test.integ.descriptor.networkstatus.TestNetworkStatusDocument, test.integ.version.TestVersion, test.integ.response.protocolinfo.TestProtocolInfo, test.integ.process.TestProcess, diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py index 5afe214..903a877 100644 --- a/stem/descriptor/__init__.py +++ b/stem/descriptor/__init__.py @@ -17,7 +17,7 @@ __all__ = [ "reader", "extrainfo_descriptor", "server_descriptor", - "networkstatus_descriptor", + "networkstatus", "parse_file", "Descriptor", ] diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py new file mode 100644 index 0000000..8daf6f7 --- /dev/null +++ b/stem/descriptor/networkstatus.py @@ -0,0 +1,614 @@ +""" +Parsing for Tor network status documents. Currently supports parsing v3 network +status documents (both votes and consensus'). + +The network status documents also contain a list of router descriptors, +directory authorities, signatures etc. + +The votes and consensus' can be obtained from any of the following sources... + +* the 'cached-consensus' file in tor's data directory +* tor metrics, at https://metrics.torproject.org/data.html +* directory authorities and mirrors via their DirPort + +**Module Overview:** + +:: + + parse_file - parses a network status file and provides a NetworkStatusDocument + NetworkStatusDocument - Tor v3 network status document + +- MicrodescriptorConsensus - Tor microdescriptor consensus document + RouterDescriptor - Router descriptor; contains information about a Tor relay + +- RouterMicrodescriptor - Router microdescriptor; contains information that doesn't change often + DirectorySignature + DirectoryAuthority +""" + +import re +import base64 +import hashlib +import datetime + +import stem.prereq +import stem.descriptor +import stem.descriptor.extrainfo_descriptor +import stem.version +import stem.exit_policy +import stem.util.log as log +import stem.util.connection +import stem.util.tor_tools + +_bandwidth_weights_regex = re.compile(" ".join(["W%s=\d+" % weight for weight in ["bd", + "be", "bg", "bm", "db", "eb", "ed", "ee", "eg", "em", "gb", "gd", "gg", "gm", "mb", "md", "me", "mg", "mm"]])) + +def parse_file(document_file, validate = True): + """ + Iterates over the router descriptors in a network status document. + + :param file document_file: file with network status document content + :param bool validate: checks the validity of the document's contents if True, skips these checks otherwise + + :returns: iterator for :class:`stem.descriptor.networkstatus_descriptor.RouterDescriptor` instances in the file + + :raises: + * ValueError if the contents is malformed and validate is True + * IOError if the file can't be read + """ + + document = NetworkStatusDocument(document_file.read(), validate) + return document.router_descriptors + +def _strptime(string, validate = True, optional = False): + try: + return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S") + except ValueError, exc: + if validate or not optional: raise exc + +class NetworkStatusDocument(stem.descriptor.Descriptor): + """ + A v3 network status document. + + This could be a v3 consensus or vote document. + + :var bool validated: ***** whether the document is validated + :var str network_status_version: ***** a document format version. For v3 documents this is "3" + :var str vote_status: ***** status of the vote (is either "vote" or "consensus") + :var list consensus_methods: A list of supported consensus generation methods (integers) + :var datetime published: time when the document was published + :var int consensus_method: consensus method used to generate a consensus + :var datetime valid_after: ***** time when the consensus becomes valid + :var datetime fresh_until: ***** time until when the consensus is considered to be fresh + :var datetime valid_until: ***** time until when the consensus is valid + :var int vote_delay: ***** number of seconds allowed for collecting votes from all authorities + :var int dist_delay: number of seconds allowed for collecting signatures from all authorities + :var list client_versions: list of recommended Tor client versions + :var list server_versions: list of recommended Tor server versions + :var list known_flags: ***** list of known router flags + :var list params: dict of parameter(str) => value(int) mappings + :var list router_descriptors: ***** list of RouterDescriptor objects defined in the document + :var list directory_authorities: ***** list of DirectoryAuthority objects that have generated this document + :var dict bandwidth_weights: dict of weight(str) => value(int) mappings + :var list directory_signatures: ***** list of signatures this document has + + ***** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined + """ + + def __init__(self, raw_content, validate = True): + """ + Parse a v3 network status document and provide a new NetworkStatusDocument object. + + :param str raw_content: raw network status document data + :param bool validate: True if the document is to be validated, False otherwise + + :raises: ValueError if the document is invalid + """ + + super(NetworkStatusDocument, self).__init__(raw_content) + + self.router_descriptors = [] + self.directory_authorities = [] + self.directory_signatures = [] + self.validated = validate + + self.network_status_version = None + self.vote_status = None + self.consensus_methods = [] + self.published = None + self.consensus_method = None + self.valid_after = None + self.fresh_until = None + self.valid_until = None + self.vote_delay = None + self.dist_delay = None + self.client_versions = [] + self.server_versions = [] + self.known_flags = [] + self.params = {} + self.bandwidth_weights = {} + + self._parse(raw_content) + + def _generate_router(self, raw_content, vote, validate): + return RouterDescriptor(raw_content, vote, validate) + + def _validate_network_status_version(self): + return self.network_status_version == "3" + + def get_unrecognized_lines(self): + """ + Returns any unrecognized trailing lines. + + :returns: a list of unrecognized trailing lines + """ + + return self._unrecognized_lines + + def _parse(self, raw_content): + # preamble + validate = self.validated + doc_parser = stem.descriptor.DescriptorParser(raw_content, validate) + + read_keyword_line = lambda keyword, optional = False: setattr(self, keyword.replace("-", "_"), doc_parser.read_keyword_line(keyword, optional)) + + map(read_keyword_line, ["network-status-version", "vote-status"]) + if validate and not self._validate_network_status_version(): + raise ValueError("Invalid network-status-version: %s" % self.network_status_version) + + if self.vote_status == "vote": vote = True + elif self.vote_status == "consensus": vote = False + elif validate: raise ValueError("Unrecognized document type specified in vote-status") + + if vote: + read_keyword_line("consensus-methods", True) + self.consensus_methods = [int(method) for method in self.consensus_methods.split(" ")] + self.published = _strptime(doc_parser.read_keyword_line("published", True), validate, True) + else: + self.consensus_method = int(doc_parser.read_keyword_line("consensus-method", True)) + + map(read_keyword_line, ["valid-after", "fresh-until", "valid-until"]) + self.valid_after = _strptime(self.valid_after, validate) + self.fresh_until = _strptime(self.fresh_until, validate) + self.valid_until = _strptime(self.valid_until, validate) + voting_delay = doc_parser.read_keyword_line("voting-delay") + self.vote_delay, self.dist_delay = [int(delay) for delay in voting_delay.split(" ")] + + client_versions = doc_parser.read_keyword_line("client-versions", True) + if client_versions: + self.client_versions = [stem.version.Version(version_string) for version_string in client_versions.split(",")] + server_versions = doc_parser.read_keyword_line("server-versions", True) + if server_versions: + self.server_versions = [stem.version.Version(version_string) for version_string in server_versions.split(",")] + self.known_flags = doc_parser.read_keyword_line("known-flags").split(" ") + read_keyword_line("params", True) + if self.params: + self.params = dict([(param.split("=")[0], int(param.split("=")[1])) for param in self.params.split(" ")]) + + # authority section + while doc_parser.line.startswith("dir-source "): + dirauth_data = doc_parser.read_until(["dir-source", "r"]) + self.directory_authorities.append(DirectoryAuthority(dirauth_data, vote, validate)) + + def _router_desc_generator(raw_content, vote, validate): + parser = stem.descriptor.DescriptorParser(raw_content, validate) + while parser.line != None: + descriptor = parser.read_until("r") + yield self._generate_router(descriptor, vote, validate) + + # router descriptors + if doc_parser.peek_keyword() == "r": + router_descriptors_data = doc_parser.read_until(["bandwidth-weights", "directory-footer", "directory-signature"]) + self.router_descriptors = _router_desc_generator(router_descriptors_data, vote, validate) + elif validate: + raise ValueError("No router descriptors found") + + # footer section + if self.consensus_method > 9 or vote and filter(lambda x: x >= 9, self.consensus_methods): + if doc_parser.line == "directory-footer": + doc_parser.read_line() + elif validate: + raise ValueError("Network status document missing directory-footer") + + if not vote: + read_keyword_line("bandwidth-weights", True) + if _bandwidth_weights_regex.match(self.bandwidth_weights): + self.bandwidth_weights = dict([(weight.split("=")[0], int(weight.split("=")[1])) for weight in self.bandwidth_weights.split(" ")]) + elif validate: + raise ValueError("Invalid bandwidth-weights line") + + while doc_parser.line.startswith("directory-signature "): + signature_data = doc_parser.read_until(["directory-signature"]) + self.directory_signatures.append(DirectorySignature(signature_data)) + + self._unrecognized_lines = doc_parser.remaining() + if validate and self._unrecognized_lines: raise ValueError("Unrecognized trailing data") + +class DirectoryAuthority(stem.descriptor.Descriptor): + """ + Contains directory authority information obtained from v3 network status + documents. + + :var str nickname: directory authority's nickname + :var str identity: uppercase hex fingerprint of the authority's identity key + :var str address: hostname + :var str ip: current IP address + :var int dirport: current directory port + :var int orport: current orport + :var str contact: directory authority's contact information + :var str legacy_dir_key: fingerprint of and obsolete identity key + :var :class:`stem.descriptor.networkstatus_descriptor.KeyCertificate` key_certificate: directory authority's current key certificate + :var str vote_digest: digest of the authority that contributed to the consensus + """ + + def __init__(self, raw_content, vote = True, validate = True): + """ + Parse a directory authority entry in a v3 network status document and + provide a DirectoryAuthority object. + + :param str raw_content: raw directory authority entry information + :param bool validate: True if the document is to be validated, False otherwise + + :raises: ValueError if the raw data is invalid + """ + + super(DirectoryAuthority, self).__init__(raw_content) + self.nickname, self.identity, self.address, self.ip = None, None, None, None + self.dirport, self.orport, self.legacy_dir_key = None, None, None + self.key_certificate, self.contact, self.vote_digest = None, None, None + parser = stem.descriptor.DescriptorParser(raw_content, validate) + + dir_source = parser.read_keyword_line("dir-source") + self.nickname, self.identity, self.address, self.ip, self.dirport, self.orport = dir_source.split(" ") + self.dirport = int(self.dirport) + self.orport = int(self.orport) + + self.contact = parser.read_keyword_line("contact") + if vote: + self.legacy_dir_key = parser.read_keyword_line("legacy-dir-key", True) + self.key_certificate = KeyCertificate("\n".join(parser.remaining()), validate) + else: + self.vote_digest = parser.read_keyword_line("vote-digest", True) + rmng = parser.remaining() + if rmng and validate: + raise ValueError("Unrecognized trailing data in directory authority information") + +class KeyCertificate(stem.descriptor.Descriptor): + """ + Directory key certificate. + + :var str key_certificate_version: ***** version of the key certificate (Should be "3") + :var str ip: IP address on which the directory authority is listening + :var int port: port on which the directory authority is listening + :var str fingerprint: ***** hex encoded fingerprint of the authority's identity key + :var str identity_key: ***** long term authority identity key + :var datetime published: ***** time (in GMT) when this document & the key were last generated + :var str expires: ***** time (in GMT) after which this key becomes invalid + :var str signing_key: ***** directory server's public signing key + :var str crosscert: signature made using certificate's signing key + :var str certification: ***** signature of this key certificate signed with the identity key + + ***** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined + """ + + def __init__(self, raw_content, validate = True): + """ + Parse a key certificate entry and provide a KeyCertificate object. + + :param str raw_content: raw key certificate information + :param bool validate: True if the document is to be validated, False otherwise + + :raises: ValueError if the raw data is invalid + """ + + super(KeyCertificate, self).__init__(raw_content) + self.key_certificate_version, self.ip, self.port = None, None, None + self.fingerprint, self.identity_key, self.published = None, None, None + self.expires, self.signing_key, self.crosscert = None, None, None + self.certification = None + parser = stem.descriptor.DescriptorParser(raw_content, validate) + peek_check_kw = lambda keyword: keyword == parser.peek_keyword() + seen_keywords = set() + + self.key_certificate_version = parser.read_keyword_line("dir-key-certificate-version") + if validate and self.key_certificate_version != "3": raise ValueError("Unrecognized dir-key-certificate-version") + + def _read_keyword_line(keyword): + if validate and keyword in seen_keywords: + raise ValueError("Invalid key certificate: '%s' appears twice" % keyword) + seen_keywords.add(keyword) + return parser.read_keyword_line(keyword) + + while parser.line: + if peek_check_kw("dir-address"): + line = _read_keyword_line("dir-address") + try: + self.ip, self.port = line.rsplit(":", 1) + self.port = int(self.port) + except Exception: + if validate: raise ValueError("Invalid dir-address line: %s" % line) + + elif peek_check_kw("fingerprint"): + self.fingerprint = _read_keyword_line("fingerprint") + + elif peek_check_kw("dir-identity-key"): + _read_keyword_line("dir-identity-key") + self.identity_key = parser.read_block("RSA PUBLIC KEY") + + elif peek_check_kw("dir-key-published"): + self.published = _strptime(_read_keyword_line("dir-key-published")) + + elif peek_check_kw("dir-key-expires"): + self.expires = _strptime(_read_keyword_line("dir-key-expires")) + + elif peek_check_kw("dir-signing-key"): + _read_keyword_line("dir-signing-key") + self.signing_key = parser.read_block("RSA PUBLIC KEY") + + elif peek_check_kw("dir-key-crosscert"): + _read_keyword_line("dir-key-crosscert") + self.crosscert = parser.read_block("ID SIGNATURE") + + elif peek_check_kw("dir-key-certification"): + _read_keyword_line("dir-key-certification") + self.certification = parser.read_block("SIGNATURE") + break + + elif validate: + raise ValueError("Key certificate contains unrecognized lines: %s" % parser.line) + + else: + # ignore unrecognized lines if we aren't validating + self._unrecognized_lines.append(parser.read_line()) + + self._unrecognized_lines = parser.remaining() + if self._unrecognized_lines and validate: + raise ValueError("Unrecognized trailing data in key certificate") + + def get_unrecognized_lines(self): + """ + Returns any unrecognized lines. + + :returns: a list of unrecognized lines + """ + + return self._unrecognized_lines + +class DirectorySignature(stem.descriptor.Descriptor): + """ + Contains directory signature information described in a v3 network status + document. + + :var str identity: signature identity + :var str key_digest: signature key digest + :var str method: method used to generate the signature + :var str signature: the signature data + """ + + def __init__(self, raw_content, validate = True): + """ + Parse a directory signature entry in a v3 network status document and + provide a DirectorySignature object. + + :param str raw_content: raw directory signature entry information + :param bool validate: True if the document is to be validated, False otherwise + + :raises: ValueError if the raw data is invalid + """ + + super(DirectorySignature, self).__init__(raw_content) + self.identity, self.key_digest, self.method, self.signature = None, None, None, None + parser = stem.descriptor.DescriptorParser(raw_content, validate) + + signature_line = parser.read_keyword_line("directory-signature").split(" ") + + if len(signature_line) == 2: + self.identity, self.key_digest = signature_line + if len(signature_line) == 3: # for microdescriptor consensuses + self.method, self.identity, self.key_digest = signature_line + + self.signature = parser.read_block("SIGNATURE") + self._unrecognized_lines = parser.remaining() + if self._unrecognized_lines and validate: + raise ValueError("Unrecognized trailing data in directory signature") + +class RouterDescriptor(stem.descriptor.Descriptor): + """ + Router descriptor object. Parses and stores router information in a router + entry read from a v3 network status document. + + :var str nickname: ***** router's nickname + :var str identity: ***** router's identity + :var str digest: ***** router's digest + :var datetime publication: ***** router's publication + :var str ip: ***** router's IP address + :var int orport: ***** router's ORPort + :var int dirport: ***** router's DirPort + + :var bool is_valid: ***** router is valid + :var bool is_guard: ***** router is suitable for use as an entry guard + :var bool is_named: ***** router is named + :var bool is_unnamed: ***** router is unnamed + :var bool is_running: ***** router is running and currently usable + :var bool is_stable: ***** router is stable, i.e., it's suitable for for long-lived circuits + :var bool is_exit: ***** router is an exit router + :var bool is_fast: ***** router is Fast, i.e., it's usable for high-bandwidth circuits + :var bool is_authority: ***** router is a directory authority + :var bool supports_v2dir: ***** router supports v2dir + :var bool supports_v3dir: ***** router supports v3dir + :var bool is_hsdir: ***** router is a hidden status + :var bool is_badexit: ***** router is marked a bad exit + :var bool is_baddirectory: ***** router is a bad directory + + :var :class:`stem.version.Version`,str version: Version of the Tor protocol this router is running + + :var int bandwidth: router's claimed bandwidth + :var int measured_bandwidth: router's measured bandwidth + + :var :class:`stem.exit_policy.MicrodescriptorExitPolicy` exitpolicy: router's exitpolicy + + :var str microdescriptor_hashes: a list of two-tuples with a list of consensus methods(int) that may produce the digest and a dict with algorithm(str) => digest(str) mappings. algorithm is the hashing algorithm (usually "sha256") that is used to produce digest (the base64 encoding of the hash of the router's microdescriptor with trailing =s omitted). + + ***** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined + """ + + def __init__(self, raw_contents, vote = True, validate = True): + """ + Parse a router descriptor in a v3 network status document and provide a new + RouterDescriptor object. + + :param str raw_content: router descriptor content to be parsed + :param bool validate: whether the router descriptor should be validated + """ + + super(RouterDescriptor, self).__init__(raw_contents) + + self.nickname = None + self.identity = None + self.digest = None + self.publication = None + self.ip = None + self.orport = None + self.dirport = None + + self.is_valid = False + self.is_guard = False + self.is_named = False + self.is_unnamed = False + self.is_running = False + self.is_stable = False + self.is_exit = False + self.is_fast = False + self.is_authority = False + self.supports_v2dir = False + self.supports_v3dir = False + self.is_hsdir = False + self.is_badexit = False + self.is_baddirectory = False + + self.version = None + + self.bandwidth = None + self.measured_bandwidth = None + + self.exit_policy = None + + self.microdescriptor_hashes = [] + + self._parse(raw_contents, vote, validate) + + def _parse(self, raw_content, vote, validate): + """ + :param dict raw_content: iptor contents to be applied + :param bool validate: checks the validity of descriptor content if True + + :raises: ValueError if an error occures in validation + """ + + parser = stem.descriptor.DescriptorParser(raw_content, validate) + seen_keywords = set() + peek_check_kw = lambda keyword: keyword == parser.peek_keyword() + + r = parser.read_keyword_line("r") + # r mauer BD7xbfsCFku3+tgybEZsg8Yjhvw itcuKQ6PuPLJ7m/Oi928WjO2j8g 2012-06-22 13:19:32 80.101.105.103 9001 0 + # "r" SP nickname SP identity SP digest SP publication SP IP SP ORPort SP DirPort NL + seen_keywords.add("r") + if r: + values = r.split(" ") + self.nickname, self.identity, self.digest = values[0], values[1], values[2] + self.publication = _strptime(" ".join((values[3], values[4])), validate) + self.ip, self.orport, self.dirport = values[5], int(values[6]), int(values[7]) + if self.dirport == 0: self.dirport = None + elif validate: raise ValueError("Invalid router descriptor: empty 'r' line" ) + + while parser.line: + if peek_check_kw("s"): + if "s" in seen_keywords: raise ValueError("Invalid router descriptor: 's' line appears twice") + line = parser.read_keyword_line("s") + if not line: continue + seen_keywords.add("s") + # s Named Running Stable Valid + #A series of space-separated status flags, in *lexical order* + flags = line.split(" ") + flag_map = { + "Valid": "is_valid", + "Guard": "is_guard", + "Named": "is_named", + "Unnamed": "is_unnamed", + "Running": "is_running", + "Stable": "is_stable", + "Exit": "is_exit", + "Fast": "is_fast", + "Authority": "is_authority", + "V2Dir": "supports_v2dir", + "V3Dir": "supports_v3dir", + "HSDir": "is_hsdir", + "BadExit": "is_badexit", + "BadDirectory": "is_baddirectory", + } + map(lambda flag: setattr(self, flag_map[flag], True), flags) + + if self.is_unnamed: self.is_named = False + elif self.is_named: self.is_unnamed = False + + elif peek_check_kw("v"): + if "v" in seen_keywords: raise ValueError("Invalid router descriptor: 'v' line appears twice") + line = parser.read_keyword_line("v", True) + seen_keywords.add("v") + # v Tor 0.2.2.35 + if line: + if line.startswith("Tor "): + self.version = stem.version.Version(line[4:]) + else: + self.version = line + elif validate: raise ValueError("Invalid router descriptor: empty 'v' line" ) + + elif peek_check_kw("w"): + if "w" in seen_keywords: raise ValueError("Invalid router descriptor: 'w' line appears twice") + w = parser.read_keyword_line("w", True) + # "w" SP "Bandwidth=" INT [SP "Measured=" INT] NL + seen_keywords.add("w") + if w: + values = w.split(" ") + if len(values) <= 2 and len(values) > 0: + key, value = values[0].split("=") + if key == "Bandwidth": self.bandwidth = int(value) + elif validate: raise ValueError("Router descriptor contains invalid 'w' line: expected Bandwidth, read " + key) + + if len(values) == 2: + key, value = values[1].split("=") + if key == "Measured": self.measured_bandwidth = int(value) + elif validate: raise ValueError("Router descriptor contains invalid 'w' line: expected Measured, read " + key) + elif validate: raise ValueError("Router descriptor contains invalid 'w' line") + elif validate: raise ValueError("Router descriptor contains empty 'w' line") + + elif peek_check_kw("p"): + if "p" in seen_keywords: raise ValueError("Invalid router descriptor: 'p' line appears twice") + p = parser.read_keyword_line("p", True) + seen_keywords.add("p") + # "p" SP ("accept" / "reject") SP PortList NL + if p: + self.exit_policy = stem.exit_policy.MicrodescriptorExitPolicy(p) + #self.exit_policy = p + + elif vote and peek_check_kw("m"): + # microdescriptor hashes + m = parser.read_keyword_line("m", True) + methods, digests = m.split(" ", 1) + method_list = methods.split(",") + digest_dict = [digest.split("=", 1) for digest in digests.split(" ")] + self.microdescriptor_hashes.append((method_list, digest_dict)) + + elif validate: + raise ValueError("Router descriptor contains unrecognized trailing lines: %s" % parser.line) + + else: + self._unrecognized_lines.append(parser.read_line()) # ignore unrecognized lines if we aren't validating + + def get_unrecognized_lines(self): + """ + Returns any unrecognized lines. + + :returns: a list of unrecognized lines + """ + + return self._unrecognized_lines + diff --git a/stem/descriptor/networkstatus_descriptor.py b/stem/descriptor/networkstatus_descriptor.py deleted file mode 100644 index 8daf6f7..0000000 --- a/stem/descriptor/networkstatus_descriptor.py +++ /dev/null @@ -1,614 +0,0 @@ -""" -Parsing for Tor network status documents. Currently supports parsing v3 network -status documents (both votes and consensus'). - -The network status documents also contain a list of router descriptors, -directory authorities, signatures etc. - -The votes and consensus' can be obtained from any of the following sources... - -* the 'cached-consensus' file in tor's data directory -* tor metrics, at https://metrics.torproject.org/data.html -* directory authorities and mirrors via their DirPort - -**Module Overview:** - -:: - - parse_file - parses a network status file and provides a NetworkStatusDocument - NetworkStatusDocument - Tor v3 network status document - +- MicrodescriptorConsensus - Tor microdescriptor consensus document - RouterDescriptor - Router descriptor; contains information about a Tor relay - +- RouterMicrodescriptor - Router microdescriptor; contains information that doesn't change often - DirectorySignature - DirectoryAuthority -""" - -import re -import base64 -import hashlib -import datetime - -import stem.prereq -import stem.descriptor -import stem.descriptor.extrainfo_descriptor -import stem.version -import stem.exit_policy -import stem.util.log as log -import stem.util.connection -import stem.util.tor_tools - -_bandwidth_weights_regex = re.compile(" ".join(["W%s=\d+" % weight for weight in ["bd", - "be", "bg", "bm", "db", "eb", "ed", "ee", "eg", "em", "gb", "gd", "gg", "gm", "mb", "md", "me", "mg", "mm"]])) - -def parse_file(document_file, validate = True): - """ - Iterates over the router descriptors in a network status document. - - :param file document_file: file with network status document content - :param bool validate: checks the validity of the document's contents if True, skips these checks otherwise - - :returns: iterator for :class:`stem.descriptor.networkstatus_descriptor.RouterDescriptor` instances in the file - - :raises: - * ValueError if the contents is malformed and validate is True - * IOError if the file can't be read - """ - - document = NetworkStatusDocument(document_file.read(), validate) - return document.router_descriptors - -def _strptime(string, validate = True, optional = False): - try: - return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S") - except ValueError, exc: - if validate or not optional: raise exc - -class NetworkStatusDocument(stem.descriptor.Descriptor): - """ - A v3 network status document. - - This could be a v3 consensus or vote document. - - :var bool validated: ***** whether the document is validated - :var str network_status_version: ***** a document format version. For v3 documents this is "3" - :var str vote_status: ***** status of the vote (is either "vote" or "consensus") - :var list consensus_methods: A list of supported consensus generation methods (integers) - :var datetime published: time when the document was published - :var int consensus_method: consensus method used to generate a consensus - :var datetime valid_after: ***** time when the consensus becomes valid - :var datetime fresh_until: ***** time until when the consensus is considered to be fresh - :var datetime valid_until: ***** time until when the consensus is valid - :var int vote_delay: ***** number of seconds allowed for collecting votes from all authorities - :var int dist_delay: number of seconds allowed for collecting signatures from all authorities - :var list client_versions: list of recommended Tor client versions - :var list server_versions: list of recommended Tor server versions - :var list known_flags: ***** list of known router flags - :var list params: dict of parameter(str) => value(int) mappings - :var list router_descriptors: ***** list of RouterDescriptor objects defined in the document - :var list directory_authorities: ***** list of DirectoryAuthority objects that have generated this document - :var dict bandwidth_weights: dict of weight(str) => value(int) mappings - :var list directory_signatures: ***** list of signatures this document has - - ***** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined - """ - - def __init__(self, raw_content, validate = True): - """ - Parse a v3 network status document and provide a new NetworkStatusDocument object. - - :param str raw_content: raw network status document data - :param bool validate: True if the document is to be validated, False otherwise - - :raises: ValueError if the document is invalid - """ - - super(NetworkStatusDocument, self).__init__(raw_content) - - self.router_descriptors = [] - self.directory_authorities = [] - self.directory_signatures = [] - self.validated = validate - - self.network_status_version = None - self.vote_status = None - self.consensus_methods = [] - self.published = None - self.consensus_method = None - self.valid_after = None - self.fresh_until = None - self.valid_until = None - self.vote_delay = None - self.dist_delay = None - self.client_versions = [] - self.server_versions = [] - self.known_flags = [] - self.params = {} - self.bandwidth_weights = {} - - self._parse(raw_content) - - def _generate_router(self, raw_content, vote, validate): - return RouterDescriptor(raw_content, vote, validate) - - def _validate_network_status_version(self): - return self.network_status_version == "3" - - def get_unrecognized_lines(self): - """ - Returns any unrecognized trailing lines. - - :returns: a list of unrecognized trailing lines - """ - - return self._unrecognized_lines - - def _parse(self, raw_content): - # preamble - validate = self.validated - doc_parser = stem.descriptor.DescriptorParser(raw_content, validate) - - read_keyword_line = lambda keyword, optional = False: setattr(self, keyword.replace("-", "_"), doc_parser.read_keyword_line(keyword, optional)) - - map(read_keyword_line, ["network-status-version", "vote-status"]) - if validate and not self._validate_network_status_version(): - raise ValueError("Invalid network-status-version: %s" % self.network_status_version) - - if self.vote_status == "vote": vote = True - elif self.vote_status == "consensus": vote = False - elif validate: raise ValueError("Unrecognized document type specified in vote-status") - - if vote: - read_keyword_line("consensus-methods", True) - self.consensus_methods = [int(method) for method in self.consensus_methods.split(" ")] - self.published = _strptime(doc_parser.read_keyword_line("published", True), validate, True) - else: - self.consensus_method = int(doc_parser.read_keyword_line("consensus-method", True)) - - map(read_keyword_line, ["valid-after", "fresh-until", "valid-until"]) - self.valid_after = _strptime(self.valid_after, validate) - self.fresh_until = _strptime(self.fresh_until, validate) - self.valid_until = _strptime(self.valid_until, validate) - voting_delay = doc_parser.read_keyword_line("voting-delay") - self.vote_delay, self.dist_delay = [int(delay) for delay in voting_delay.split(" ")] - - client_versions = doc_parser.read_keyword_line("client-versions", True) - if client_versions: - self.client_versions = [stem.version.Version(version_string) for version_string in client_versions.split(",")] - server_versions = doc_parser.read_keyword_line("server-versions", True) - if server_versions: - self.server_versions = [stem.version.Version(version_string) for version_string in server_versions.split(",")] - self.known_flags = doc_parser.read_keyword_line("known-flags").split(" ") - read_keyword_line("params", True) - if self.params: - self.params = dict([(param.split("=")[0], int(param.split("=")[1])) for param in self.params.split(" ")]) - - # authority section - while doc_parser.line.startswith("dir-source "): - dirauth_data = doc_parser.read_until(["dir-source", "r"]) - self.directory_authorities.append(DirectoryAuthority(dirauth_data, vote, validate)) - - def _router_desc_generator(raw_content, vote, validate): - parser = stem.descriptor.DescriptorParser(raw_content, validate) - while parser.line != None: - descriptor = parser.read_until("r") - yield self._generate_router(descriptor, vote, validate) - - # router descriptors - if doc_parser.peek_keyword() == "r": - router_descriptors_data = doc_parser.read_until(["bandwidth-weights", "directory-footer", "directory-signature"]) - self.router_descriptors = _router_desc_generator(router_descriptors_data, vote, validate) - elif validate: - raise ValueError("No router descriptors found") - - # footer section - if self.consensus_method > 9 or vote and filter(lambda x: x >= 9, self.consensus_methods): - if doc_parser.line == "directory-footer": - doc_parser.read_line() - elif validate: - raise ValueError("Network status document missing directory-footer") - - if not vote: - read_keyword_line("bandwidth-weights", True) - if _bandwidth_weights_regex.match(self.bandwidth_weights): - self.bandwidth_weights = dict([(weight.split("=")[0], int(weight.split("=")[1])) for weight in self.bandwidth_weights.split(" ")]) - elif validate: - raise ValueError("Invalid bandwidth-weights line") - - while doc_parser.line.startswith("directory-signature "): - signature_data = doc_parser.read_until(["directory-signature"]) - self.directory_signatures.append(DirectorySignature(signature_data)) - - self._unrecognized_lines = doc_parser.remaining() - if validate and self._unrecognized_lines: raise ValueError("Unrecognized trailing data") - -class DirectoryAuthority(stem.descriptor.Descriptor): - """ - Contains directory authority information obtained from v3 network status - documents. - - :var str nickname: directory authority's nickname - :var str identity: uppercase hex fingerprint of the authority's identity key - :var str address: hostname - :var str ip: current IP address - :var int dirport: current directory port - :var int orport: current orport - :var str contact: directory authority's contact information - :var str legacy_dir_key: fingerprint of and obsolete identity key - :var :class:`stem.descriptor.networkstatus_descriptor.KeyCertificate` key_certificate: directory authority's current key certificate - :var str vote_digest: digest of the authority that contributed to the consensus - """ - - def __init__(self, raw_content, vote = True, validate = True): - """ - Parse a directory authority entry in a v3 network status document and - provide a DirectoryAuthority object. - - :param str raw_content: raw directory authority entry information - :param bool validate: True if the document is to be validated, False otherwise - - :raises: ValueError if the raw data is invalid - """ - - super(DirectoryAuthority, self).__init__(raw_content) - self.nickname, self.identity, self.address, self.ip = None, None, None, None - self.dirport, self.orport, self.legacy_dir_key = None, None, None - self.key_certificate, self.contact, self.vote_digest = None, None, None - parser = stem.descriptor.DescriptorParser(raw_content, validate) - - dir_source = parser.read_keyword_line("dir-source") - self.nickname, self.identity, self.address, self.ip, self.dirport, self.orport = dir_source.split(" ") - self.dirport = int(self.dirport) - self.orport = int(self.orport) - - self.contact = parser.read_keyword_line("contact") - if vote: - self.legacy_dir_key = parser.read_keyword_line("legacy-dir-key", True) - self.key_certificate = KeyCertificate("\n".join(parser.remaining()), validate) - else: - self.vote_digest = parser.read_keyword_line("vote-digest", True) - rmng = parser.remaining() - if rmng and validate: - raise ValueError("Unrecognized trailing data in directory authority information") - -class KeyCertificate(stem.descriptor.Descriptor): - """ - Directory key certificate. - - :var str key_certificate_version: ***** version of the key certificate (Should be "3") - :var str ip: IP address on which the directory authority is listening - :var int port: port on which the directory authority is listening - :var str fingerprint: ***** hex encoded fingerprint of the authority's identity key - :var str identity_key: ***** long term authority identity key - :var datetime published: ***** time (in GMT) when this document & the key were last generated - :var str expires: ***** time (in GMT) after which this key becomes invalid - :var str signing_key: ***** directory server's public signing key - :var str crosscert: signature made using certificate's signing key - :var str certification: ***** signature of this key certificate signed with the identity key - - ***** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined - """ - - def __init__(self, raw_content, validate = True): - """ - Parse a key certificate entry and provide a KeyCertificate object. - - :param str raw_content: raw key certificate information - :param bool validate: True if the document is to be validated, False otherwise - - :raises: ValueError if the raw data is invalid - """ - - super(KeyCertificate, self).__init__(raw_content) - self.key_certificate_version, self.ip, self.port = None, None, None - self.fingerprint, self.identity_key, self.published = None, None, None - self.expires, self.signing_key, self.crosscert = None, None, None - self.certification = None - parser = stem.descriptor.DescriptorParser(raw_content, validate) - peek_check_kw = lambda keyword: keyword == parser.peek_keyword() - seen_keywords = set() - - self.key_certificate_version = parser.read_keyword_line("dir-key-certificate-version") - if validate and self.key_certificate_version != "3": raise ValueError("Unrecognized dir-key-certificate-version") - - def _read_keyword_line(keyword): - if validate and keyword in seen_keywords: - raise ValueError("Invalid key certificate: '%s' appears twice" % keyword) - seen_keywords.add(keyword) - return parser.read_keyword_line(keyword) - - while parser.line: - if peek_check_kw("dir-address"): - line = _read_keyword_line("dir-address") - try: - self.ip, self.port = line.rsplit(":", 1) - self.port = int(self.port) - except Exception: - if validate: raise ValueError("Invalid dir-address line: %s" % line) - - elif peek_check_kw("fingerprint"): - self.fingerprint = _read_keyword_line("fingerprint") - - elif peek_check_kw("dir-identity-key"): - _read_keyword_line("dir-identity-key") - self.identity_key = parser.read_block("RSA PUBLIC KEY") - - elif peek_check_kw("dir-key-published"): - self.published = _strptime(_read_keyword_line("dir-key-published")) - - elif peek_check_kw("dir-key-expires"): - self.expires = _strptime(_read_keyword_line("dir-key-expires")) - - elif peek_check_kw("dir-signing-key"): - _read_keyword_line("dir-signing-key") - self.signing_key = parser.read_block("RSA PUBLIC KEY") - - elif peek_check_kw("dir-key-crosscert"): - _read_keyword_line("dir-key-crosscert") - self.crosscert = parser.read_block("ID SIGNATURE") - - elif peek_check_kw("dir-key-certification"): - _read_keyword_line("dir-key-certification") - self.certification = parser.read_block("SIGNATURE") - break - - elif validate: - raise ValueError("Key certificate contains unrecognized lines: %s" % parser.line) - - else: - # ignore unrecognized lines if we aren't validating - self._unrecognized_lines.append(parser.read_line()) - - self._unrecognized_lines = parser.remaining() - if self._unrecognized_lines and validate: - raise ValueError("Unrecognized trailing data in key certificate") - - def get_unrecognized_lines(self): - """ - Returns any unrecognized lines. - - :returns: a list of unrecognized lines - """ - - return self._unrecognized_lines - -class DirectorySignature(stem.descriptor.Descriptor): - """ - Contains directory signature information described in a v3 network status - document. - - :var str identity: signature identity - :var str key_digest: signature key digest - :var str method: method used to generate the signature - :var str signature: the signature data - """ - - def __init__(self, raw_content, validate = True): - """ - Parse a directory signature entry in a v3 network status document and - provide a DirectorySignature object. - - :param str raw_content: raw directory signature entry information - :param bool validate: True if the document is to be validated, False otherwise - - :raises: ValueError if the raw data is invalid - """ - - super(DirectorySignature, self).__init__(raw_content) - self.identity, self.key_digest, self.method, self.signature = None, None, None, None - parser = stem.descriptor.DescriptorParser(raw_content, validate) - - signature_line = parser.read_keyword_line("directory-signature").split(" ") - - if len(signature_line) == 2: - self.identity, self.key_digest = signature_line - if len(signature_line) == 3: # for microdescriptor consensuses - self.method, self.identity, self.key_digest = signature_line - - self.signature = parser.read_block("SIGNATURE") - self._unrecognized_lines = parser.remaining() - if self._unrecognized_lines and validate: - raise ValueError("Unrecognized trailing data in directory signature") - -class RouterDescriptor(stem.descriptor.Descriptor): - """ - Router descriptor object. Parses and stores router information in a router - entry read from a v3 network status document. - - :var str nickname: ***** router's nickname - :var str identity: ***** router's identity - :var str digest: ***** router's digest - :var datetime publication: ***** router's publication - :var str ip: ***** router's IP address - :var int orport: ***** router's ORPort - :var int dirport: ***** router's DirPort - - :var bool is_valid: ***** router is valid - :var bool is_guard: ***** router is suitable for use as an entry guard - :var bool is_named: ***** router is named - :var bool is_unnamed: ***** router is unnamed - :var bool is_running: ***** router is running and currently usable - :var bool is_stable: ***** router is stable, i.e., it's suitable for for long-lived circuits - :var bool is_exit: ***** router is an exit router - :var bool is_fast: ***** router is Fast, i.e., it's usable for high-bandwidth circuits - :var bool is_authority: ***** router is a directory authority - :var bool supports_v2dir: ***** router supports v2dir - :var bool supports_v3dir: ***** router supports v3dir - :var bool is_hsdir: ***** router is a hidden status - :var bool is_badexit: ***** router is marked a bad exit - :var bool is_baddirectory: ***** router is a bad directory - - :var :class:`stem.version.Version`,str version: Version of the Tor protocol this router is running - - :var int bandwidth: router's claimed bandwidth - :var int measured_bandwidth: router's measured bandwidth - - :var :class:`stem.exit_policy.MicrodescriptorExitPolicy` exitpolicy: router's exitpolicy - - :var str microdescriptor_hashes: a list of two-tuples with a list of consensus methods(int) that may produce the digest and a dict with algorithm(str) => digest(str) mappings. algorithm is the hashing algorithm (usually "sha256") that is used to produce digest (the base64 encoding of the hash of the router's microdescriptor with trailing =s omitted). - - ***** attribute is either required when we're parsed with validation or has a default value, others are left as None if undefined - """ - - def __init__(self, raw_contents, vote = True, validate = True): - """ - Parse a router descriptor in a v3 network status document and provide a new - RouterDescriptor object. - - :param str raw_content: router descriptor content to be parsed - :param bool validate: whether the router descriptor should be validated - """ - - super(RouterDescriptor, self).__init__(raw_contents) - - self.nickname = None - self.identity = None - self.digest = None - self.publication = None - self.ip = None - self.orport = None - self.dirport = None - - self.is_valid = False - self.is_guard = False - self.is_named = False - self.is_unnamed = False - self.is_running = False - self.is_stable = False - self.is_exit = False - self.is_fast = False - self.is_authority = False - self.supports_v2dir = False - self.supports_v3dir = False - self.is_hsdir = False - self.is_badexit = False - self.is_baddirectory = False - - self.version = None - - self.bandwidth = None - self.measured_bandwidth = None - - self.exit_policy = None - - self.microdescriptor_hashes = [] - - self._parse(raw_contents, vote, validate) - - def _parse(self, raw_content, vote, validate): - """ - :param dict raw_content: iptor contents to be applied - :param bool validate: checks the validity of descriptor content if True - - :raises: ValueError if an error occures in validation - """ - - parser = stem.descriptor.DescriptorParser(raw_content, validate) - seen_keywords = set() - peek_check_kw = lambda keyword: keyword == parser.peek_keyword() - - r = parser.read_keyword_line("r") - # r mauer BD7xbfsCFku3+tgybEZsg8Yjhvw itcuKQ6PuPLJ7m/Oi928WjO2j8g 2012-06-22 13:19:32 80.101.105.103 9001 0 - # "r" SP nickname SP identity SP digest SP publication SP IP SP ORPort SP DirPort NL - seen_keywords.add("r") - if r: - values = r.split(" ") - self.nickname, self.identity, self.digest = values[0], values[1], values[2] - self.publication = _strptime(" ".join((values[3], values[4])), validate) - self.ip, self.orport, self.dirport = values[5], int(values[6]), int(values[7]) - if self.dirport == 0: self.dirport = None - elif validate: raise ValueError("Invalid router descriptor: empty 'r' line" ) - - while parser.line: - if peek_check_kw("s"): - if "s" in seen_keywords: raise ValueError("Invalid router descriptor: 's' line appears twice") - line = parser.read_keyword_line("s") - if not line: continue - seen_keywords.add("s") - # s Named Running Stable Valid - #A series of space-separated status flags, in *lexical order* - flags = line.split(" ") - flag_map = { - "Valid": "is_valid", - "Guard": "is_guard", - "Named": "is_named", - "Unnamed": "is_unnamed", - "Running": "is_running", - "Stable": "is_stable", - "Exit": "is_exit", - "Fast": "is_fast", - "Authority": "is_authority", - "V2Dir": "supports_v2dir", - "V3Dir": "supports_v3dir", - "HSDir": "is_hsdir", - "BadExit": "is_badexit", - "BadDirectory": "is_baddirectory", - } - map(lambda flag: setattr(self, flag_map[flag], True), flags) - - if self.is_unnamed: self.is_named = False - elif self.is_named: self.is_unnamed = False - - elif peek_check_kw("v"): - if "v" in seen_keywords: raise ValueError("Invalid router descriptor: 'v' line appears twice") - line = parser.read_keyword_line("v", True) - seen_keywords.add("v") - # v Tor 0.2.2.35 - if line: - if line.startswith("Tor "): - self.version = stem.version.Version(line[4:]) - else: - self.version = line - elif validate: raise ValueError("Invalid router descriptor: empty 'v' line" ) - - elif peek_check_kw("w"): - if "w" in seen_keywords: raise ValueError("Invalid router descriptor: 'w' line appears twice") - w = parser.read_keyword_line("w", True) - # "w" SP "Bandwidth=" INT [SP "Measured=" INT] NL - seen_keywords.add("w") - if w: - values = w.split(" ") - if len(values) <= 2 and len(values) > 0: - key, value = values[0].split("=") - if key == "Bandwidth": self.bandwidth = int(value) - elif validate: raise ValueError("Router descriptor contains invalid 'w' line: expected Bandwidth, read " + key) - - if len(values) == 2: - key, value = values[1].split("=") - if key == "Measured": self.measured_bandwidth = int(value) - elif validate: raise ValueError("Router descriptor contains invalid 'w' line: expected Measured, read " + key) - elif validate: raise ValueError("Router descriptor contains invalid 'w' line") - elif validate: raise ValueError("Router descriptor contains empty 'w' line") - - elif peek_check_kw("p"): - if "p" in seen_keywords: raise ValueError("Invalid router descriptor: 'p' line appears twice") - p = parser.read_keyword_line("p", True) - seen_keywords.add("p") - # "p" SP ("accept" / "reject") SP PortList NL - if p: - self.exit_policy = stem.exit_policy.MicrodescriptorExitPolicy(p) - #self.exit_policy = p - - elif vote and peek_check_kw("m"): - # microdescriptor hashes - m = parser.read_keyword_line("m", True) - methods, digests = m.split(" ", 1) - method_list = methods.split(",") - digest_dict = [digest.split("=", 1) for digest in digests.split(" ")] - self.microdescriptor_hashes.append((method_list, digest_dict)) - - elif validate: - raise ValueError("Router descriptor contains unrecognized trailing lines: %s" % parser.line) - - else: - self._unrecognized_lines.append(parser.read_line()) # ignore unrecognized lines if we aren't validating - - def get_unrecognized_lines(self): - """ - Returns any unrecognized lines. - - :returns: a list of unrecognized lines - """ - - return self._unrecognized_lines - diff --git a/test/integ/descriptor/networkstatus.py b/test/integ/descriptor/networkstatus.py new file mode 100644 index 0000000..e65d960 --- /dev/null +++ b/test/integ/descriptor/networkstatus.py @@ -0,0 +1,228 @@ +""" +Integration tests for stem.descriptor.server_descriptor. +""" + +from __future__ import with_statement + +import os +import resource +import datetime +import unittest + +import stem.exit_policy +import stem.version +import stem.descriptor.networkstatus +import test.integ.descriptor + +def _strptime(string): + return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S") + +class TestNetworkStatusDocument(unittest.TestCase): + def test_metrics_consensus(self): + """ + Checks if consensus documents from Metrics are parsed properly. + """ + + descriptor_path = test.integ.descriptor.get_resource("metrics_consensus") + + with file(descriptor_path) as descriptor_file: + desc = stem.descriptor.parse_file(descriptor_path, descriptor_file) + + router = next(desc) + self.assertEquals("sumkledi", router.nickname) + self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router.identity) + self.assertEquals("8mCr8Sl7RF4ENU4jb0FZFA/3do8", router.digest) + self.assertEquals(_strptime("2012-07-12 04:01:55"), router.publication) + self.assertEquals("178.218.213.229", router.ip) + self.assertEquals(80, router.orport) + self.assertEquals(None, router.dirport) + + def test_cached_consensus(self): + """ + Parses the cached-consensus file in our data directory. + """ + + # lengthy test and uneffected by targets, so only run once + if test.runner.only_run_once(self, "test_cached_consensus"): return + + descriptor_path = test.runner.get_runner().get_test_dir("cached-consensus") + + if not os.path.exists(descriptor_path): + test.runner.skip(self, "(no cached-consensus)") + + if stem.util.system.is_windows(): + # might hog memory and hang the system + # and we aren't checking for memory usage in windows, so, skip. + test.runner.skip(self, "(unavailable on windows)") + + count = 0 + with open(descriptor_path) as descriptor_file: + for desc in stem.descriptor.networkstatus.parse_file(descriptor_file): + if resource.getrusage(resource.RUSAGE_SELF).ru_maxrss > 100000: + # if we're using > 100 MB we should fail + self.fail() + assert desc.nickname # check that the router has a nickname + count += 1 + + assert count > 100 # sanity check - assuming atleast 100 relays in the Tor network + + def test_consensus(self): + """ + Checks that consensus documents are properly parsed. + """ + + descriptor_path = test.integ.descriptor.get_resource("consensus") + + descriptor_file = file(descriptor_path) + desc = stem.descriptor.networkstatus.NetworkStatusDocument(descriptor_file.read()) + descriptor_file.close() + + self.assertEquals(True, desc.validated) + self.assertEquals("3", desc.network_status_version) + self.assertEquals("consensus", desc.vote_status) + self.assertEquals([], desc.consensus_methods) + self.assertEquals(None, desc.published) + self.assertEquals(12, desc.consensus_method) + self.assertEquals(_strptime("2012-07-12 10:00:00"), desc.valid_after) + self.assertEquals(_strptime("2012-07-12 11:00:00"), desc.fresh_until) + self.assertEquals(_strptime("2012-07-12 13:00:00"), desc.valid_until) + self.assertEquals(300, desc.vote_delay) + self.assertEquals(300, desc.dist_delay) + expected_client_versions = [stem.version.Version(version_string) for version_string in ["0.2.2.35", + "0.2.2.36", "0.2.2.37", "0.2.3.10-alpha", "0.2.3.11-alpha", "0.2.3.12-alpha", + "0.2.3.13-alpha", "0.2.3.14-alpha", "0.2.3.15-alpha", "0.2.3.16-alpha", "0.2.3.17-beta", + "0.2.3.18-rc", "0.2.3.19-rc"]] + expected_server_versions = [stem.version.Version(version_string) for version_string in ["0.2.2.35", + "0.2.2.36", "0.2.2.37", "0.2.3.10-alpha", "0.2.3.11-alpha", "0.2.3.12-alpha", + "0.2.3.13-alpha", "0.2.3.14-alpha", "0.2.3.15-alpha", "0.2.3.16-alpha", "0.2.3.17-beta", + "0.2.3.18-rc", "0.2.3.19-rc"]] + self.assertEquals(expected_client_versions, desc.client_versions) + self.assertEquals(expected_server_versions, desc.server_versions) + known_flags = ["Authority", "BadExit", "Exit", "Fast", "Guard", "HSDir", "Named", "Running", "Stable", "Unnamed", "V2Dir", "Valid"] + self.assertEquals(known_flags, desc.known_flags) + expected_params = {"CircuitPriorityHalflifeMsec": 30000, "bwauthpid": 1} + self.assertEquals(expected_params, desc.params) + router1 = next(desc.router_descriptors) + self.assertEquals("sumkledi", router1.nickname) + self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router1.identity) + self.assertEquals("8mCr8Sl7RF4ENU4jb0FZFA/3do8", router1.digest) + self.assertEquals(_strptime("2012-07-12 04:01:55"), router1.publication) + self.assertEquals("178.218.213.229", router1.ip) + self.assertEquals(80, router1.orport) + self.assertEquals(None, router1.dirport) + + self.assertEquals(8, len(desc.directory_authorities)) + self.assertEquals("tor26", desc.directory_authorities[0].nickname) + self.assertEquals("14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4", desc.directory_authorities[0].identity) + self.assertEquals("86.59.21.38", desc.directory_authorities[0].address) + self.assertEquals("86.59.21.38", desc.directory_authorities[0].ip) + self.assertEquals(80, desc.directory_authorities[0].dirport) + self.assertEquals(443, desc.directory_authorities[0].orport) + self.assertEquals("Peter Palfrader", desc.directory_authorities[0].contact) + self.assertEquals(None, desc.directory_authorities[0].legacy_dir_key) + self.assertEquals(None, desc.directory_authorities[0].key_certificate) + self.assertEquals("0B6D1E9A300B895AA2D0B427F92917B6995C3C1C", desc.directory_authorities[0].vote_digest) + expected_bandwidth_weights = { + "Wbd": 3335, "Wbe": 0, "Wbg": 3536, "Wbm": 10000, "Wdb": 10000, "Web": 10000, + "Wed": 3329, "Wee": 10000, "Weg": 3329, "Wem": 10000, "Wgb": 10000, "Wgd": 3335, + "Wgg": 6464, "Wgm": 6464, "Wmb": 10000, "Wmd": 3335, "Wme": 0, "Wmg": 3536, "Wmm": 10000 + } + self.assertEquals(expected_bandwidth_weights, desc.bandwidth_weights) + + expected_signature = """HFXB4497LzESysYJ/4jJY83E5vLjhv+igIxD9LU6lf6ftkGeF+lNmIAIEKaMts8H +mfWcW0b+jsrXcJoCxV5IrwCDF3u1aC3diwZY6yiG186pwWbOwE41188XI2DeYPwE +I/TJmV928na7RLZe2mGHCAW3VQOvV+QkCfj05VZ8CsY=""" + self.assertEquals(8, len(desc.directory_signatures)) + self.assertEquals("14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4", desc.directory_signatures[0].identity) + self.assertEquals("BF112F1C6D5543CFD0A32215ACABD4197B5279AD", desc.directory_signatures[0].key_digest) + self.assertEquals(expected_signature, desc.directory_signatures[0].signature) + + def test_vote(self): + """ + Checks that vote documents are properly parsed. + """ + + descriptor_path = test.integ.descriptor.get_resource("vote") + + descriptor_file = file(descriptor_path) + desc = stem.descriptor.networkstatus.NetworkStatusDocument(descriptor_file.read()) + descriptor_file.close() + + self.assertEquals(True, desc.validated) + self.assertEquals("3", desc.network_status_version) + self.assertEquals("vote", desc.vote_status) + self.assertEquals(range(1, 13), desc.consensus_methods) + self.assertEquals(_strptime("2012-07-11 23:50:01"), desc.published) + self.assertEquals(None, desc.consensus_method) + self.assertEquals(_strptime("2012-07-12 00:00:00"), desc.valid_after) + self.assertEquals(_strptime("2012-07-12 01:00:00"), desc.fresh_until) + self.assertEquals(_strptime("2012-07-12 03:00:00"), desc.valid_until) + self.assertEquals(300, desc.vote_delay) + self.assertEquals(300, desc.dist_delay) + self.assertEquals([], desc.client_versions) + self.assertEquals([], desc.server_versions) + known_flags = ["Authority", "BadExit", "Exit", "Fast", "Guard", "HSDir", "Running", "Stable", "V2Dir", "Valid"] + self.assertEquals(known_flags, desc.known_flags) + expected_params = {"CircuitPriorityHalflifeMsec": 30000, "bwauthpid": 1} + self.assertEquals(expected_params, desc.params) + router1 = next(desc.router_descriptors) + self.assertEquals("sumkledi", router1.nickname) + self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router1.identity) + self.assertEquals("B5n4BiALAF8B5AqafxohyYiuj7E", router1.digest) + self.assertEquals(_strptime("2012-07-11 04:22:53"), router1.publication) + self.assertEquals("178.218.213.229", router1.ip) + self.assertEquals(80, router1.orport) + self.assertEquals(None, router1.dirport) + + self.assertEquals(1, len(desc.directory_authorities)) + self.assertEquals("turtles", desc.directory_authorities[0].nickname) + self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_authorities[0].identity) + self.assertEquals("76.73.17.194", desc.directory_authorities[0].address) + self.assertEquals("76.73.17.194", desc.directory_authorities[0].ip) + self.assertEquals(9030, desc.directory_authorities[0].dirport) + self.assertEquals(9090, desc.directory_authorities[0].orport) + self.assertEquals("Mike Perry <email>", desc.directory_authorities[0].contact) + self.assertEquals(None, desc.directory_authorities[0].legacy_dir_key) + + expected_identity_key = """MIIBigKCAYEA6uSmsoxj2MiJ3qyZq0qYXlRoG8o82SNqg+22m+t1c7MlQOZWPJYn +XeMcBCt8xrTeIt2ZI+Q/Kt2QJSeD9WZRevTKk/kn5Tg2+xXPogalUU47y5tUohGz ++Q8+CxtRSXpDxBHL2P8rLHvGrI69wbNHGoQkce/7gJy9vw5Ie2qzbyXk1NG6V8Fb +pr6A885vHo6TbhUnolz2Wqt/kN+UorjLkN2H3fV+iGcQFv42SyHYGDLa0WwL3PJJ +r/veu36S3VaHBrfhutfioi+d3d4Ya0bKwiWi5Lm2CHuuRTgMpHLU9vlci8Hunuxq +HsULe2oMsr4VEic7sW5SPC5Obpx6hStHdNv1GxoSEm3/vIuPM8pINpU5ZYAyH9yO +Ef22ZHeiVMMKmpV9TtFyiFqvlI6GpQn3mNbsQqF1y3XCA3Q4vlRAkpgJVUSvTxFP +2bNDobOyVCpCM/rwxU1+RCNY5MFJ/+oktUY+0ydvTen3gFdZdgNqCYjKPLfBNm9m +RGL7jZunMUNvAgMBAAE=""" + expected_signing_key = """MIGJAoGBAJ5itcJRYNEM3Qf1OVWLRkwjqf84oXPc2ZusaJ5zOe7TVvBMra9GNyc0 +NM9y6zVkHCAePAjr4KbW/8P1olA6FUE2LV9bozaU1jFf6K8B2OELKs5FUEW+n+ic +GM0x6MhngyXonWOcKt5Gj+mAu5lrno9tpNbPkz2Utr/Pi0nsDhWlAgMBAAE=""" + expected_key_crosscert = """RHYImGTwg36wmEdAn7qaRg2sAfql7ZCtPIL/O3lU5OIdXXp0tNn/K00Bamqohjk+ +Tz4FKsKXGDlbGv67PQcZPOK6NF0GRkNh4pk89prrDO4XwtEn7rkHHdBH6/qQ7IRG +GdDZHtZ1a69oFZvPWD3hUaB50xeIe7GoKdKIfdNNJ+8=""" + expected_key_certification = """fasWOGyUZ3iMCYpDfJ+0JcMiTH25sXPWzvlHorEOyOMbaMqRYpZU4GHzt1jLgdl6 +AAoR6KdamsLg5VE8xzst48a4UFuzHFlklZ5O8om2rcvDd5DhSnWWYZnYJecqB+bo +dNisPmaIVSAWb29U8BpNRj4GMC9KAgGYUj8aE/KtutAeEekFfFEHTfWZ2fFp4j3m +9rY8FWraqyiF+Emq1T8pAAgMQ+79R3oZxq0TXS42Z4Anhms735ccauKhI3pDKjbl +tD5vAzIHOyjAOXj7a6jY/GrnaBNuJ4qe/4Hf9UmzK/jKKwG95BPJtPTT4LoFwEB0 +KG2OUeQUNoCck4nDpsZwFqPlrWCHcHfTV2iDYFV1HQWDTtZz/qf+GtB8NXsq+I1w +brADmvReM2BD6p/13h0QURCI5hq7ZYlIKcKrBa0jn1d9cduULl7vgKsRCJDls/ID +emBZ6pUxMpBmV0v+PrA3v9w4DlE7GHAq61FF/zju2kpqj6MInbEvI/E+e438sWsL""" + self.assertEquals("3", desc.directory_authorities[0].key_certificate.key_certificate_version) + self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_authorities[0].key_certificate.fingerprint) + self.assertEquals(_strptime("2011-11-28 21:51:04"), desc.directory_authorities[0].key_certificate.published) + self.assertEquals(_strptime("2012-11-28 21:51:04"), desc.directory_authorities[0].key_certificate.expires) + self.assertEquals(expected_identity_key, desc.directory_authorities[0].key_certificate.identity_key) + self.assertEquals(expected_signing_key, desc.directory_authorities[0].key_certificate.signing_key) + self.assertEquals(expected_key_crosscert, desc.directory_authorities[0].key_certificate.crosscert) + self.assertEquals(expected_key_certification, desc.directory_authorities[0].key_certificate.certification) + self.assertEquals(None, desc.directory_authorities[0].vote_digest) + self.assertEquals({}, desc.bandwidth_weights) + + expected_signature = """fskXN84wB3mXfo+yKGSt0AcDaaPuU3NwMR3ROxWgLN0KjAaVi2eV9PkPCsQkcgw3 +JZ/1HL9sHyZfo6bwaC6YSM9PNiiY6L7rnGpS7UkHiFI+M96VCMorvjm5YPs3FioJ +DnN5aFtYKiTc19qIC7Nmo+afPdDEf0MlJvEOP5EWl3w=""" + self.assertEquals(1, len(desc.directory_signatures)) + self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_signatures[0].identity) + self.assertEquals("D5C30C15BB3F1DA27669C2D88439939E8F418FCF", desc.directory_signatures[0].key_digest) + self.assertEquals(expected_signature, desc.directory_signatures[0].signature) + diff --git a/test/integ/descriptor/networkstatus_descriptor.py b/test/integ/descriptor/networkstatus_descriptor.py deleted file mode 100644 index 46019ea..0000000 --- a/test/integ/descriptor/networkstatus_descriptor.py +++ /dev/null @@ -1,228 +0,0 @@ -""" -Integration tests for stem.descriptor.server_descriptor. -""" - -from __future__ import with_statement - -import os -import resource -import datetime -import unittest - -import stem.exit_policy -import stem.version -import stem.descriptor.networkstatus_descriptor -import test.integ.descriptor - -def _strptime(string): - return datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S") - -class TestNetworkStatusDocument(unittest.TestCase): - def test_metrics_consensus(self): - """ - Checks if consensus documents from Metrics are parsed properly. - """ - - descriptor_path = test.integ.descriptor.get_resource("metrics_consensus") - - with file(descriptor_path) as descriptor_file: - desc = stem.descriptor.parse_file(descriptor_path, descriptor_file) - - router = next(desc) - self.assertEquals("sumkledi", router.nickname) - self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router.identity) - self.assertEquals("8mCr8Sl7RF4ENU4jb0FZFA/3do8", router.digest) - self.assertEquals(_strptime("2012-07-12 04:01:55"), router.publication) - self.assertEquals("178.218.213.229", router.ip) - self.assertEquals(80, router.orport) - self.assertEquals(None, router.dirport) - - def test_cached_consensus(self): - """ - Parses the cached-consensus file in our data directory. - """ - - # lengthy test and uneffected by targets, so only run once - if test.runner.only_run_once(self, "test_cached_consensus"): return - - descriptor_path = test.runner.get_runner().get_test_dir("cached-consensus") - - if not os.path.exists(descriptor_path): - test.runner.skip(self, "(no cached-consensus)") - - if stem.util.system.is_windows(): - # might hog memory and hang the system - # and we aren't checking for memory usage in windows, so, skip. - test.runner.skip(self, "(unavailable on windows)") - - count = 0 - with open(descriptor_path) as descriptor_file: - for desc in stem.descriptor.networkstatus_descriptor.parse_file(descriptor_file): - if resource.getrusage(resource.RUSAGE_SELF).ru_maxrss > 100000: - # if we're using > 100 MB we should fail - self.fail() - assert desc.nickname # check that the router has a nickname - count += 1 - - assert count > 100 # sanity check - assuming atleast 100 relays in the Tor network - - def test_consensus(self): - """ - Checks that consensus documents are properly parsed. - """ - - descriptor_path = test.integ.descriptor.get_resource("consensus") - - descriptor_file = file(descriptor_path) - desc = stem.descriptor.networkstatus_descriptor.NetworkStatusDocument(descriptor_file.read()) - descriptor_file.close() - - self.assertEquals(True, desc.validated) - self.assertEquals("3", desc.network_status_version) - self.assertEquals("consensus", desc.vote_status) - self.assertEquals([], desc.consensus_methods) - self.assertEquals(None, desc.published) - self.assertEquals(12, desc.consensus_method) - self.assertEquals(_strptime("2012-07-12 10:00:00"), desc.valid_after) - self.assertEquals(_strptime("2012-07-12 11:00:00"), desc.fresh_until) - self.assertEquals(_strptime("2012-07-12 13:00:00"), desc.valid_until) - self.assertEquals(300, desc.vote_delay) - self.assertEquals(300, desc.dist_delay) - expected_client_versions = [stem.version.Version(version_string) for version_string in ["0.2.2.35", - "0.2.2.36", "0.2.2.37", "0.2.3.10-alpha", "0.2.3.11-alpha", "0.2.3.12-alpha", - "0.2.3.13-alpha", "0.2.3.14-alpha", "0.2.3.15-alpha", "0.2.3.16-alpha", "0.2.3.17-beta", - "0.2.3.18-rc", "0.2.3.19-rc"]] - expected_server_versions = [stem.version.Version(version_string) for version_string in ["0.2.2.35", - "0.2.2.36", "0.2.2.37", "0.2.3.10-alpha", "0.2.3.11-alpha", "0.2.3.12-alpha", - "0.2.3.13-alpha", "0.2.3.14-alpha", "0.2.3.15-alpha", "0.2.3.16-alpha", "0.2.3.17-beta", - "0.2.3.18-rc", "0.2.3.19-rc"]] - self.assertEquals(expected_client_versions, desc.client_versions) - self.assertEquals(expected_server_versions, desc.server_versions) - known_flags = ["Authority", "BadExit", "Exit", "Fast", "Guard", "HSDir", "Named", "Running", "Stable", "Unnamed", "V2Dir", "Valid"] - self.assertEquals(known_flags, desc.known_flags) - expected_params = {"CircuitPriorityHalflifeMsec": 30000, "bwauthpid": 1} - self.assertEquals(expected_params, desc.params) - router1 = next(desc.router_descriptors) - self.assertEquals("sumkledi", router1.nickname) - self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router1.identity) - self.assertEquals("8mCr8Sl7RF4ENU4jb0FZFA/3do8", router1.digest) - self.assertEquals(_strptime("2012-07-12 04:01:55"), router1.publication) - self.assertEquals("178.218.213.229", router1.ip) - self.assertEquals(80, router1.orport) - self.assertEquals(None, router1.dirport) - - self.assertEquals(8, len(desc.directory_authorities)) - self.assertEquals("tor26", desc.directory_authorities[0].nickname) - self.assertEquals("14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4", desc.directory_authorities[0].identity) - self.assertEquals("86.59.21.38", desc.directory_authorities[0].address) - self.assertEquals("86.59.21.38", desc.directory_authorities[0].ip) - self.assertEquals(80, desc.directory_authorities[0].dirport) - self.assertEquals(443, desc.directory_authorities[0].orport) - self.assertEquals("Peter Palfrader", desc.directory_authorities[0].contact) - self.assertEquals(None, desc.directory_authorities[0].legacy_dir_key) - self.assertEquals(None, desc.directory_authorities[0].key_certificate) - self.assertEquals("0B6D1E9A300B895AA2D0B427F92917B6995C3C1C", desc.directory_authorities[0].vote_digest) - expected_bandwidth_weights = { - "Wbd": 3335, "Wbe": 0, "Wbg": 3536, "Wbm": 10000, "Wdb": 10000, "Web": 10000, - "Wed": 3329, "Wee": 10000, "Weg": 3329, "Wem": 10000, "Wgb": 10000, "Wgd": 3335, - "Wgg": 6464, "Wgm": 6464, "Wmb": 10000, "Wmd": 3335, "Wme": 0, "Wmg": 3536, "Wmm": 10000 - } - self.assertEquals(expected_bandwidth_weights, desc.bandwidth_weights) - - expected_signature = """HFXB4497LzESysYJ/4jJY83E5vLjhv+igIxD9LU6lf6ftkGeF+lNmIAIEKaMts8H -mfWcW0b+jsrXcJoCxV5IrwCDF3u1aC3diwZY6yiG186pwWbOwE41188XI2DeYPwE -I/TJmV928na7RLZe2mGHCAW3VQOvV+QkCfj05VZ8CsY=""" - self.assertEquals(8, len(desc.directory_signatures)) - self.assertEquals("14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4", desc.directory_signatures[0].identity) - self.assertEquals("BF112F1C6D5543CFD0A32215ACABD4197B5279AD", desc.directory_signatures[0].key_digest) - self.assertEquals(expected_signature, desc.directory_signatures[0].signature) - - def test_vote(self): - """ - Checks that vote documents are properly parsed. - """ - - descriptor_path = test.integ.descriptor.get_resource("vote") - - descriptor_file = file(descriptor_path) - desc = stem.descriptor.networkstatus_descriptor.NetworkStatusDocument(descriptor_file.read()) - descriptor_file.close() - - self.assertEquals(True, desc.validated) - self.assertEquals("3", desc.network_status_version) - self.assertEquals("vote", desc.vote_status) - self.assertEquals(range(1, 13), desc.consensus_methods) - self.assertEquals(_strptime("2012-07-11 23:50:01"), desc.published) - self.assertEquals(None, desc.consensus_method) - self.assertEquals(_strptime("2012-07-12 00:00:00"), desc.valid_after) - self.assertEquals(_strptime("2012-07-12 01:00:00"), desc.fresh_until) - self.assertEquals(_strptime("2012-07-12 03:00:00"), desc.valid_until) - self.assertEquals(300, desc.vote_delay) - self.assertEquals(300, desc.dist_delay) - self.assertEquals([], desc.client_versions) - self.assertEquals([], desc.server_versions) - known_flags = ["Authority", "BadExit", "Exit", "Fast", "Guard", "HSDir", "Running", "Stable", "V2Dir", "Valid"] - self.assertEquals(known_flags, desc.known_flags) - expected_params = {"CircuitPriorityHalflifeMsec": 30000, "bwauthpid": 1} - self.assertEquals(expected_params, desc.params) - router1 = next(desc.router_descriptors) - self.assertEquals("sumkledi", router1.nickname) - self.assertEquals("ABPSI4nNUNC3hKPkBhyzHozozrU", router1.identity) - self.assertEquals("B5n4BiALAF8B5AqafxohyYiuj7E", router1.digest) - self.assertEquals(_strptime("2012-07-11 04:22:53"), router1.publication) - self.assertEquals("178.218.213.229", router1.ip) - self.assertEquals(80, router1.orport) - self.assertEquals(None, router1.dirport) - - self.assertEquals(1, len(desc.directory_authorities)) - self.assertEquals("turtles", desc.directory_authorities[0].nickname) - self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_authorities[0].identity) - self.assertEquals("76.73.17.194", desc.directory_authorities[0].address) - self.assertEquals("76.73.17.194", desc.directory_authorities[0].ip) - self.assertEquals(9030, desc.directory_authorities[0].dirport) - self.assertEquals(9090, desc.directory_authorities[0].orport) - self.assertEquals("Mike Perry <email>", desc.directory_authorities[0].contact) - self.assertEquals(None, desc.directory_authorities[0].legacy_dir_key) - - expected_identity_key = """MIIBigKCAYEA6uSmsoxj2MiJ3qyZq0qYXlRoG8o82SNqg+22m+t1c7MlQOZWPJYn -XeMcBCt8xrTeIt2ZI+Q/Kt2QJSeD9WZRevTKk/kn5Tg2+xXPogalUU47y5tUohGz -+Q8+CxtRSXpDxBHL2P8rLHvGrI69wbNHGoQkce/7gJy9vw5Ie2qzbyXk1NG6V8Fb -pr6A885vHo6TbhUnolz2Wqt/kN+UorjLkN2H3fV+iGcQFv42SyHYGDLa0WwL3PJJ -r/veu36S3VaHBrfhutfioi+d3d4Ya0bKwiWi5Lm2CHuuRTgMpHLU9vlci8Hunuxq -HsULe2oMsr4VEic7sW5SPC5Obpx6hStHdNv1GxoSEm3/vIuPM8pINpU5ZYAyH9yO -Ef22ZHeiVMMKmpV9TtFyiFqvlI6GpQn3mNbsQqF1y3XCA3Q4vlRAkpgJVUSvTxFP -2bNDobOyVCpCM/rwxU1+RCNY5MFJ/+oktUY+0ydvTen3gFdZdgNqCYjKPLfBNm9m -RGL7jZunMUNvAgMBAAE=""" - expected_signing_key = """MIGJAoGBAJ5itcJRYNEM3Qf1OVWLRkwjqf84oXPc2ZusaJ5zOe7TVvBMra9GNyc0 -NM9y6zVkHCAePAjr4KbW/8P1olA6FUE2LV9bozaU1jFf6K8B2OELKs5FUEW+n+ic -GM0x6MhngyXonWOcKt5Gj+mAu5lrno9tpNbPkz2Utr/Pi0nsDhWlAgMBAAE=""" - expected_key_crosscert = """RHYImGTwg36wmEdAn7qaRg2sAfql7ZCtPIL/O3lU5OIdXXp0tNn/K00Bamqohjk+ -Tz4FKsKXGDlbGv67PQcZPOK6NF0GRkNh4pk89prrDO4XwtEn7rkHHdBH6/qQ7IRG -GdDZHtZ1a69oFZvPWD3hUaB50xeIe7GoKdKIfdNNJ+8=""" - expected_key_certification = """fasWOGyUZ3iMCYpDfJ+0JcMiTH25sXPWzvlHorEOyOMbaMqRYpZU4GHzt1jLgdl6 -AAoR6KdamsLg5VE8xzst48a4UFuzHFlklZ5O8om2rcvDd5DhSnWWYZnYJecqB+bo -dNisPmaIVSAWb29U8BpNRj4GMC9KAgGYUj8aE/KtutAeEekFfFEHTfWZ2fFp4j3m -9rY8FWraqyiF+Emq1T8pAAgMQ+79R3oZxq0TXS42Z4Anhms735ccauKhI3pDKjbl -tD5vAzIHOyjAOXj7a6jY/GrnaBNuJ4qe/4Hf9UmzK/jKKwG95BPJtPTT4LoFwEB0 -KG2OUeQUNoCck4nDpsZwFqPlrWCHcHfTV2iDYFV1HQWDTtZz/qf+GtB8NXsq+I1w -brADmvReM2BD6p/13h0QURCI5hq7ZYlIKcKrBa0jn1d9cduULl7vgKsRCJDls/ID -emBZ6pUxMpBmV0v+PrA3v9w4DlE7GHAq61FF/zju2kpqj6MInbEvI/E+e438sWsL""" - self.assertEquals("3", desc.directory_authorities[0].key_certificate.key_certificate_version) - self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_authorities[0].key_certificate.fingerprint) - self.assertEquals(_strptime("2011-11-28 21:51:04"), desc.directory_authorities[0].key_certificate.published) - self.assertEquals(_strptime("2012-11-28 21:51:04"), desc.directory_authorities[0].key_certificate.expires) - self.assertEquals(expected_identity_key, desc.directory_authorities[0].key_certificate.identity_key) - self.assertEquals(expected_signing_key, desc.directory_authorities[0].key_certificate.signing_key) - self.assertEquals(expected_key_crosscert, desc.directory_authorities[0].key_certificate.crosscert) - self.assertEquals(expected_key_certification, desc.directory_authorities[0].key_certificate.certification) - self.assertEquals(None, desc.directory_authorities[0].vote_digest) - self.assertEquals({}, desc.bandwidth_weights) - - expected_signature = """fskXN84wB3mXfo+yKGSt0AcDaaPuU3NwMR3ROxWgLN0KjAaVi2eV9PkPCsQkcgw3 -JZ/1HL9sHyZfo6bwaC6YSM9PNiiY6L7rnGpS7UkHiFI+M96VCMorvjm5YPs3FioJ -DnN5aFtYKiTc19qIC7Nmo+afPdDEf0MlJvEOP5EWl3w=""" - self.assertEquals(1, len(desc.directory_signatures)) - self.assertEquals("27B6B5996C426270A5C95488AA5BCEB6BCC86956", desc.directory_signatures[0].identity) - self.assertEquals("D5C30C15BB3F1DA27669C2D88439939E8F418FCF", desc.directory_signatures[0].key_digest) - self.assertEquals(expected_signature, desc.directory_signatures[0].signature) -
tor-commits@lists.torproject.org