commit 4d122b1a417fe318f0bb3d169688d25f3e749754 Author: Damian Johnson atagar@torproject.org Date: Sun Jul 21 17:10:26 2013 -0700
Implementing a get_key_certificates() method
Method for fetching key certificates for the authorities. This included a little work so parse_file() could return multiple certificates when they're concatenated together. --- stem/descriptor/__init__.py | 3 ++- stem/descriptor/networkstatus.py | 31 +++++++++++++++++++++++++++++++ stem/descriptor/remote.py | 36 ++++++++++++++++++++++++++++++++++++ test/integ/descriptor/remote.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-)
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py index e3b5a8b..14b29d1 100644 --- a/stem/descriptor/__init__.py +++ b/stem/descriptor/__init__.py @@ -237,7 +237,8 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto for desc in stem.descriptor.networkstatus._parse_file(descriptor_file, document_type, validate = validate, document_handler = document_handler): yield desc elif descriptor_type == "dir-key-certificate-3" and major_version == 1: - yield stem.descriptor.networkstatus.KeyCertificate(descriptor_file.read(), validate = validate) + for desc in stem.descriptor.networkstatus._parse_file_key_certs(descriptor_file, validate = validate): + yield desc elif descriptor_type in ("network-status-consensus-3", "network-status-vote-3") and major_version == 1: document_type = stem.descriptor.networkstatus.NetworkStatusDocumentV3
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py index baf7f0a..ec21304 100644 --- a/stem/descriptor/networkstatus.py +++ b/stem/descriptor/networkstatus.py @@ -55,6 +55,7 @@ import stem.util.tor_tools import stem.version
from stem.descriptor import ( + PGP_BLOCK_END, Descriptor, DocumentHandler, _get_descriptor_components, @@ -220,6 +221,36 @@ def _parse_file(document_file, document_type = None, validate = True, is_microde raise ValueError("Unrecognized document_handler: %s" % document_handler)
+def _parse_file_key_certs(certificate_file, validate = True): + """ + Parses a file containing one or more authority key certificates. + + :param file certificate_file: file with key certificates + :param bool validate: checks the validity of the certificate's contents if + **True**, skips these checks otherwise + + :returns: iterator for :class:`stem.descriptor.networkstatus.KeyCertificate` + instance in the file + + :raises: + * **ValueError** if the key certificate content is invalid and validate is + **True** + * **IOError** if the file can't be read + """ + + while True: + keycert_content = _read_until_keywords("dir-key-certification", certificate_file) + + # we've reached the 'router-signature', now include the pgp style block + block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] + keycert_content += _read_until_keywords(block_end_prefix, certificate_file, True) + + if keycert_content: + yield stem.descriptor.networkstatus.KeyCertificate(bytes.join(b"", keycert_content), validate = validate) + else: + break # done parsing file + + class NetworkStatusDocument(Descriptor): """ Common parent for network status documents. diff --git a/stem/descriptor/remote.py b/stem/descriptor/remote.py index a009078..0aabd74 100644 --- a/stem/descriptor/remote.py +++ b/stem/descriptor/remote.py @@ -52,6 +52,7 @@ itself... |- get_extrainfo_descriptors - provides present :class:`~stem.descriptor.extrainfo_descriptor.ExtraInfoDescriptor` |- get_microdescriptors - provides present :class:`~stem.descriptor.microdescriptor.Microdescriptor` |- get_consensus - provides present :class:`~stem.descriptor.router_status_entry.RouterStatusEntryV3` + |- get_key_certificates - provides present :class:`~stem.descriptor.networkstatus.KeyCertificate` +- query - request an arbitrary descriptor resource
.. data:: MAX_DESCRIPTOR_BATCH_SIZE @@ -118,6 +119,8 @@ def _guess_descriptor_type(resource): return 'microdescriptor 1.0' elif resource.startswith('/tor/status-vote/'): return 'network-status-consensus-3 1.0' + elif resource.startswith('/tor/keys/'): + return 'dir-key-certificate-3 1.0' else: raise ValueError("Unable to determine the descriptor type for '%s'" % resource)
@@ -517,6 +520,39 @@ class DescriptorDownloader(object):
return self.query(resource + '.z', document_handler = document_handler, **query_args)
+ def get_key_certificates(self, authority_v3idents = None, **query_args): + """ + Provides the key certificates for authorities with the given fingerprints. + If no fingerprints are provided then this returns all present key + certificates. + + :param str authority_v3idents: fingerprint or list of fingerprints of the + authority keys, see `'v3ident' in tor's config.c + https://gitweb.torproject.org/tor.git/blob/f631b73:/src/or/config.c#l816`_ + for the values. + :param query_args: additional arguments for the + :class:`~stem.descriptor.remote.Query` constructor + + :returns: :class:`~stem.descriptor.remote.Query` for the key certificates + + :raises: **ValueError** if we request more than 96 key certificates by + their identity fingerprints (this is due to a limit on the url length by + squid proxies). + """ + + resource = '/tor/keys/all.z' + + if isinstance(authority_v3idents, str): + authority_v3idents = [authority_v3idents] + + if authority_v3idents: + if len(authority_v3idents) > MAX_DESCRIPTOR_BATCH_SIZE: + raise ValueError("Unable to request more than %i key certificates at a time by their identity fingerprints" % MAX_DESCRIPTOR_BATCH_SIZE) + + resource = '/tor/keys/fp/%s.z' % '+'.join(authority_v3idents) + + return self.query(resource, **query_args) + def query(self, resource, **query_args): """ Issues a request for the given resource. diff --git a/test/integ/descriptor/remote.py b/test/integ/descriptor/remote.py index b3c549d..d11a4ae 100644 --- a/test/integ/descriptor/remote.py +++ b/test/integ/descriptor/remote.py @@ -6,6 +6,7 @@ import unittest
import stem.descriptor.extrainfo_descriptor import stem.descriptor.microdescriptor +import stem.descriptor.networkstatus import stem.descriptor.remote import stem.descriptor.router_status_entry import stem.descriptor.server_descriptor @@ -178,3 +179,32 @@ class TestDescriptorDownloader(unittest.TestCase): consensus = list(consensus_query) self.assertTrue(len(consensus) > 50) self.assertTrue(isinstance(consensus[0], stem.descriptor.router_status_entry.RouterStatusEntryV3)) + + def test_get_key_certificates(self): + """ + Exercises the downloader's get_key_certificates() method. + """ + + if test.runner.require_online(self): + return + elif test.runner.only_run_once(self, "test_get_key_certificates"): + return + + downloader = stem.descriptor.remote.DescriptorDownloader() + + single_query = downloader.get_key_certificates('D586D18309DED4CD6D57C18FDB97EFA96D330566') + + multiple_query = downloader.get_key_certificates([ + 'D586D18309DED4CD6D57C18FDB97EFA96D330566', + '14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4', + ]) + + single_query.run() + multiple_query.run() + + single_query_results = list(single_query) + self.assertEqual(1, len(single_query_results)) + self.assertEqual('D586D18309DED4CD6D57C18FDB97EFA96D330566', single_query_results[0].fingerprint) + self.assertTrue(isinstance(single_query_results[0], stem.descriptor.networkstatus.KeyCertificate)) + + self.assertEqual(2, len(list(multiple_query)))
tor-commits@lists.torproject.org