[tor-commits] [stem/master] Implementing a get_key_certificates() method

atagar at torproject.org atagar at torproject.org
Mon Jul 22 03:10:17 UTC 2013


commit 4d122b1a417fe318f0bb3d169688d25f3e749754
Author: Damian Johnson <atagar at 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)))





More information about the tor-commits mailing list