[tor-commits] [stem/master] Initial hidden service descriptor parser

atagar at torproject.org atagar at torproject.org
Sun Mar 1 05:16:35 UTC 2015


commit 380074b53311962690b92670bd640de697b8d9e2
Author: Damian Johnson <atagar at torproject.org>
Date:   Mon Feb 23 15:07:20 2015 -0800

    Initial hidden service descriptor parser
    
    Quick initial parser for hidden service descriptors...
    
      https://trac.torproject.org/projects/tor/ticket/15004
    
    Things left to do are...
    
      * Issue tickets for some of these 'TODO' notes.
      * Support the introduction-points field. It's a bit special.
      * Unit tests for malformed content.
      * Validate that the signature matches the descriptor content.
---
 stem/descriptor/__init__.py                        |    7 +
 stem/descriptor/hidden_service_descriptor.py       |  157 ++++++++++++++++++++
 test/settings.cfg                                  |    1 +
 .../unit/descriptor/data/hidden_service_duckduckgo |   60 ++++++++
 test/unit/descriptor/data/hidden_service_facebook  |   60 ++++++++
 test/unit/descriptor/hidden_service_descriptor.py  |  114 ++++++++++++++
 6 files changed, 399 insertions(+)

diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index abcdab2..87f7f73 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -122,6 +122,7 @@ def parse_file(descriptor_file, descriptor_type = None, validate = False, docume
   torperf 1.0                               **unsupported**
   bridge-pool-assignment 1.0                **unsupported**
   tordnsel 1.0                              :class:`~stem.descriptor.tordnsel.TorDNSEL`
+  hidden-service-descriptor 1.0             :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor`
   ========================================= =====
 
   If you're using **python 3** then beware that the open() function defaults to
@@ -307,6 +308,11 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto
 
     for desc in stem.descriptor.tordnsel._parse_file(descriptor_file, validate = validate, **kwargs):
       yield desc
+  elif descriptor_type == 'hidden-service-descriptor' and major_version == 1:
+    document_type = stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor
+
+    for desc in stem.descriptor.hidden_service_descriptor._parse_file(descriptor_file, validate = validate, **kwargs):
+      yield desc
   else:
     raise TypeError("Unrecognized metrics descriptor format. type: '%s', version: '%i.%i'" % (descriptor_type, major_version, minor_version))
 
@@ -720,3 +726,4 @@ import stem.descriptor.extrainfo_descriptor
 import stem.descriptor.networkstatus
 import stem.descriptor.microdescriptor
 import stem.descriptor.tordnsel
+import stem.descriptor.hidden_service_descriptor
diff --git a/stem/descriptor/hidden_service_descriptor.py b/stem/descriptor/hidden_service_descriptor.py
new file mode 100644
index 0000000..c915f0e
--- /dev/null
+++ b/stem/descriptor/hidden_service_descriptor.py
@@ -0,0 +1,157 @@
+# Copyright 2015, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
+"""
+Parsing for Tor hidden service descriptors as described in Tor's `rend-spec
+<https://gitweb.torproject.org/torspec.git/tree/rend-spec.txt>`_.
+
+Unlike other descriptor types these describe a hidden service rather than a
+relay. They're created by the service, and can only be fetched via relays with
+the HSDir flag.
+
+**Module Overview:**
+
+::
+
+  HiddenServiceDescriptor - Tor hidden service descriptor.
+"""
+
+# TODO: Add a description for how to retrieve them when tor supports that (#14847).
+
+# TODO: We should add a '@type hidden-service-descriptor 1.0' annotation to
+# CollecTor...
+#
+# https://collector.torproject.org/formats.html
+
+from stem.descriptor import (
+  PGP_BLOCK_END,
+  Descriptor,
+  _get_descriptor_components,
+  _read_until_keywords,
+  _value,
+  _parse_simple_line,
+  _parse_timestamp_line,
+  _parse_key_block,
+)
+
+REQUIRED_FIELDS = (
+  'rendezvous-service-descriptor',
+  'version',
+  'permanent-key',
+  'secret-id-part',
+  'publication-time',
+  'protocol-versions',
+  'signature',
+)
+
+
+def _parse_file(descriptor_file, validate = False, **kwargs):
+  """
+  Iterates over the hidden service descriptors in a file.
+
+  :param file descriptor_file: file with descriptor content
+  :param bool validate: checks the validity of the descriptor's content if
+    **True**, skips these checks otherwise
+  :param dict kwargs: additional arguments for the descriptor constructor
+
+  :returns: iterator for :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor`
+    instances in the file
+
+  :raises:
+    * **ValueError** if the contents is malformed and validate is **True**
+    * **IOError** if the file can't be read
+  """
+
+  while True:
+    descriptor_content = _read_until_keywords('signature', descriptor_file)
+
+    # we've reached the 'signature', now include the pgp style block
+    block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0]
+    descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True)
+
+    if descriptor_content:
+      if descriptor_content[0].startswith(b'@type'):
+        descriptor_content = descriptor_content[1:]
+
+      yield HiddenServiceDescriptor(bytes.join(b'', descriptor_content), validate, **kwargs)
+    else:
+      break  # done parsing file
+
+
+# TODO: For the 'version' and 'protocol-versions' lines should they be ints
+# instead? Presently the spec says it's a 'version-number' which is an
+# undefined type.
+
+def _parse_protocol_versions_line(descriptor, entries):
+  value = _value('protocol-versions', entries)
+  descriptor.protocol_versions = value.split(',')
+
+_parse_rendezvous_service_descriptor_line = _parse_simple_line('rendezvous-service-descriptor', 'descriptor_id')
+_parse_version_line = _parse_simple_line('version', 'version')
+_parse_permanent_key_line = _parse_key_block('permanent-key', 'permanent_key', 'RSA PUBLIC KEY')
+_parse_secret_id_part_line = _parse_simple_line('secret-id-part', 'secret_id_part')
+_parse_publication_time_line = _parse_timestamp_line('publication-time', 'published')
+_parse_introduction_points_line = _parse_key_block('introduction-points', 'introduction_points_blob', 'MESSAGE')
+_parse_signature_line = _parse_key_block('signature', 'signature', 'SIGNATURE')
+
+
+class HiddenServiceDescriptor(Descriptor):
+  """
+  Hidden service descriptor.
+
+  :var str descriptor_id: **\*** identifier for this descriptor, this is a base32 hash of several fields
+  :var str version: **\*** hidden service descriptor version
+  :var str permanent_key: **\*** long term key of the hidden service
+  :var str secret_id_part: **\*** hash of the time period, cookie, and replica
+    values so our descriptor_id can be validated
+  :var datetime published: **\*** time in UTC when this descriptor was made
+  :var list protocol_versions: **\*** versions that are supported when establishing a connection
+  :var str introduction_points_blob: **\*** raw introduction points blob, if
+    the hidden service uses cookie authentication this is encrypted
+  :var str signature: signature of the descriptor content
+
+  **\*** attribute is either required when we're parsed with validation or has
+  a default value, others are left as **None** if undefined
+  """
+
+  ATTRIBUTES = {
+    'descriptor_id': (None, _parse_rendezvous_service_descriptor_line),
+    'version': (None, _parse_version_line),
+    'permanent_key': (None, _parse_permanent_key_line),
+    'secret_id_part': (None, _parse_secret_id_part_line),
+    'published': (None, _parse_publication_time_line),
+    'protocol_versions': ([], _parse_protocol_versions_line),
+    'introduction_points_blob': (None, _parse_introduction_points_line),
+    'signature': (None, _parse_signature_line),
+  }
+
+  PARSER_FOR_LINE = {
+    'rendezvous-service-descriptor': _parse_rendezvous_service_descriptor_line,
+    'version': _parse_version_line,
+    'permanent-key': _parse_permanent_key_line,
+    'secret-id-part': _parse_secret_id_part_line,
+    'publication-time': _parse_publication_time_line,
+    'protocol-versions': _parse_protocol_versions_line,
+    'introduction-points': _parse_introduction_points_line,
+    'signature': _parse_signature_line,
+  }
+
+  def __init__(self, raw_contents, validate = False):
+    super(HiddenServiceDescriptor, self).__init__(raw_contents, lazy_load = not validate)
+    entries = _get_descriptor_components(raw_contents, validate)
+
+    if validate:
+      for keyword in REQUIRED_FIELDS:
+        if keyword not in entries:
+          raise ValueError("Hidden service descriptor must have a '%s' entry" % keyword)
+        elif keyword in entries and len(entries[keyword]) > 1:
+          raise ValueError("The '%s' entry can only appear once in a hidden service descriptor" % keyword)
+
+      if 'rendezvous-service-descriptor' != list(entries.keys())[0]:
+        raise ValueError("Hidden service descriptor must start with a 'rendezvous-service-descriptor' entry")
+      elif 'signature' != list(entries.keys())[-1]:
+        raise ValueError("Hidden service descriptor must end with a 'signature' entry")
+
+      self._parse(entries, validate)
+    else:
+      self._entries = entries
diff --git a/test/settings.cfg b/test/settings.cfg
index c0c8342..21f5eb2 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -172,6 +172,7 @@ test.unit_tests
 |test.unit.descriptor.networkstatus.document_v2.TestNetworkStatusDocument
 |test.unit.descriptor.networkstatus.document_v3.TestNetworkStatusDocument
 |test.unit.descriptor.networkstatus.bridge_document.TestBridgeNetworkStatusDocument
+|test.unit.descriptor.hidden_service_descriptor.TestHiddenServiceDescriptor
 |test.unit.exit_policy.rule.TestExitPolicyRule
 |test.unit.exit_policy.policy.TestExitPolicy
 |test.unit.version.TestVersion
diff --git a/test/unit/descriptor/data/hidden_service_duckduckgo b/test/unit/descriptor/data/hidden_service_duckduckgo
new file mode 100644
index 0000000..f3f4868
--- /dev/null
+++ b/test/unit/descriptor/data/hidden_service_duckduckgo
@@ -0,0 +1,60 @@
+ at type hidden-service-descriptor 1.0
+rendezvous-service-descriptor y3olqqblqw2gbh6phimfuiroechjjafa
+version 2
+permanent-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBAJ/SzzgrXPxTlFrKVhXh3buCWv2QfcNgncUpDpKouLn3AtPH5Ocys0jE
+aZSKdvaiQ62md2gOwj4x61cFNdi05tdQjS+2thHKEm/KsB9BGLSLBNJYY356bupg
+I5gQozM65ENelfxYlysBjJ52xSDBd8C4f/p9umdzaaaCmzXG/nhzAgMBAAE=
+-----END RSA PUBLIC KEY-----
+secret-id-part e24kgecavwsznj7gpbktqsiwgvngsf4e
+publication-time 2015-02-23 20:00:00
+protocol-versions 2,3
+introduction-points
+-----BEGIN MESSAGE-----
+aW50cm9kdWN0aW9uLXBvaW50IGl3a2k3N3h0YnZwNnF2ZWRmcndkem5jeHMzY2th
+eWV1CmlwLWFkZHJlc3MgMTc4LjYyLjIyMi4xMjkKb25pb24tcG9ydCA0NDMKb25p
+b24ta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFL
+OTRCRVlJSFo0S2RFa2V5UGhiTENwUlc1RVNnKzJXUFFock00eXVLWUd1cTh3Rldn
+dW1aWVI5CmsvV0EvL0ZZWE1CejBiQitja3Vacy9ZdTluSytITHpwR2FwVjBjbHN0
+NEdVTWNCSW5VQ3pDY3BqSlRRc1FEZ20KMy9ZM2NxaDBXNTVnT0NGaG9tUTQvMVdP
+WWc3WUNqazRYWUhKRTIwT2RHMkxsNXpvdEs2ZkFnTUJBQUU9Ci0tLS0tRU5EIFJT
+QSBQVUJMSUMgS0VZLS0tLS0Kc2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVC
+TElDIEtFWS0tLS0tCk1JR0pBb0dCQUpYbUpiOGxTeWRNTXFDZ0NnZmd2bEIyRTVy
+cGQ1N2t6L0FxZzcvZDFIS2MzK2w1UW9Vdkh5dXkKWnNBbHlrYThFdTUzNGhsNDFv
+cUVLcEFLWWNNbjFUTTB2cEpFR05WT2MrMDVCSW54STloOWYwTWcwMVBEMHRZdQpH
+Y0xIWWdCemNyZkVtS3dNdE04V0VtY01KZDduMnVmZmFBdko4NDZXdWJiZVY3TVcx
+WWVoQWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQppbnRyb2R1
+Y3Rpb24tcG9pbnQgZW00Z2prNmVpaXVhbGhtbHlpaWZyemM3bGJ0cnNiaXAKaXAt
+YWRkcmVzcyA0Ni40LjE3NC41Mgpvbmlvbi1wb3J0IDQ0Mwpvbmlvbi1rZXkKLS0t
+LS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dCQUxCbWhkRjV3SHhI
+cnBMU21qQVpvdHR4MjIwKzk5NUZkTU9PdFpOalJ3MURCU3ByVVpacXR4V2EKUDhU
+S3BIS3p3R0pLQ1ZZSUlqN2xvaGJ2OVQ5dXJtbGZURTA1VVJHZW5ab2lmT0ZOejNZ
+d01KVFhTY1FFQkoxMAo5aVdOTERUc2tMekRLQ0FiR2hibi9NS3dPZllHQmhOVGxq
+ZHlUbU5ZNUVDUmJSempldjl2QWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBL
+RVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQVUJMSUMgS0VZLS0t
+LS0KTUlHSkFvR0JBTXhNSG9BbXJiVU1zeGlJQ3AzaVRQWWdobjBZdWVLSHgyMTl3
+dThPL1E1MVF5Y1ZWTHBYMjdkMQpoSlhrUEIzM1hRQlhzQlM3U3hzU3NTQ1EzR0V1
+clFKN0d1QkxwWUlSL3Zxc2FrRS9sOHdjMkNKQzVXVWh5RkZrCisxVFdJVUk1dHhu
+WEx5V0NSY0tEVXJqcWRvc0RhRG9zZ0hGZzIzTW54K3hYY2FRL2ZyQi9BZ01CQUFF
+PQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJvZHVjdGlvbi1wb2lu
+dCBqcWhmbDM2NHgzdXBlNmxxbnhpem9sZXdsZnJzdzJ6eQppcC1hZGRyZXNzIDYy
+LjIxMC44Mi4xNjkKb25pb24tcG9ydCA0NDMKb25pb24ta2V5Ci0tLS0tQkVHSU4g
+UlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFQVWtxeGdmWWR3MFBtL2c2TWJo
+bVZzR0tsdWppZm1raGRmb0VldXpnbyt3bkVzR3Z3VWVienJ6CmZaSlJ0MGNhWEZo
+bkNHZ1FEMklnbWFyVWFVdlAyNGZYby80bVl6TGNQZUk3Z1puZXVBUUpZdm05OFl2
+OXZPSGwKTmFNL1d2RGtDc0ozR1ZOSjFIM3dMUFFSSTN2N0tiTnVjOXRDT1lsL3Iw
+OU9oVmFXa3phakFnTUJBQUU9Ci0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K
+c2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pB
+b0dCQUxieDhMZXFSb1Avcjl3OWhqd0Q0MVlVbTdQbzY5N3hSdHl0RjBNY3lMQ1M3
+R1JpVVluamk3S1kKZmVwWGR2Ti9KbDVxUUtISUJiNjAya3VPVGwwcE44UStZZUZV
+U0lJRGNtUEJMcEJEaEgzUHZyUU1jR1ZhaU9XSAo4dzBITVpDeGd3QWNDQzUxdzVW
+d2l1bXhFSk5CVmNac094MG16TjFDbG95KzkwcTBsRlhMQWdNQkFBRT0KLS0tLS1F
+TkQgUlNBIFBVQkxJQyBLRVktLS0tLQoK
+-----END MESSAGE-----
+signature
+-----BEGIN SIGNATURE-----
+VKMmsDIUUFOrpqvcQroIZjDZTKxqNs88a4M9Te8cR/ZvS7H2nffv6iQs0tom5X4D
+4Dy4iZiy+pwYxdHfaOxmdpgMCRvgPb34MExWr5YemH0QuGtnlp5Wxr8GYaAQVuZX
+cZjQLW0juUYCbgIGdxVEBnlEt2rgBSM9+1oR7EAfV1U=
+-----END SIGNATURE-----
diff --git a/test/unit/descriptor/data/hidden_service_facebook b/test/unit/descriptor/data/hidden_service_facebook
new file mode 100644
index 0000000..705c776
--- /dev/null
+++ b/test/unit/descriptor/data/hidden_service_facebook
@@ -0,0 +1,60 @@
+ at type hidden-service-descriptor 1.0
+rendezvous-service-descriptor utjk4arxqg6s6zzo7n6cjnq6ot34udhr
+version 2
+permanent-key
+-----BEGIN RSA PUBLIC KEY-----
+MIGKAoGBAL7zXRnwSycHlKSqK+B8PFvy7RhkQ+OytYtxBwOLzhD82oY7zbpjSHY4
+BZ+hsnceXVjB+f1mXGjvLY6pnYxuufV4wsMsk7a58aJOqUvZFFI2vXhJtnLICxoZ
+AEHWzajz4ULagahB1Vi62loQE84OEcuFBekTvnHca1ZTxwp16aZtAgQvoiLZ
+-----END RSA PUBLIC KEY-----
+secret-id-part 6355jaerje3bqozopwq2qmpf4iviizdn
+publication-time 2014-10-31 23:00:00
+protocol-versions 2,3
+introduction-points
+-----BEGIN MESSAGE-----
+aW50cm9kdWN0aW9uLXBvaW50IHJ6Y3V3am5jbDdpYXpkbm41NXV5cmEybWVlamxz
+eXVoCmlwLWFkZHJlc3MgMTkzLjExLjExNC40Nwpvbmlvbi1wb3J0IDkwMDQKb25p
+b24ta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFM
+dndQc012TkhqMTY3YlJyLzlLbi9iVGtPUTN0VC9qOFdSOWV5NTlZU2NBVEQ4TXVz
+OEV6ZWpNCjc0RjdhTGR1VjZuRk15djlFYmhEbVNIZDZRMmhwNkYwb2FGODB0MHMv
+bThXYmVWTUF1aTRvVWRSU1ZRb0drY20KTDJXTlViNy84UWYwb2hFKzZ1K1pTL0FO
+U2NEc0FDT2hwNkliMWxHWjhaZGxWZFJzeWxtckFnTUJBQUU9Ci0tLS0tRU5EIFJT
+QSBQVUJMSUMgS0VZLS0tLS0Kc2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVC
+TElDIEtFWS0tLS0tCk1JR0pBb0dCQU1YVUJRMVd0cXJSZHRkaTdhODkvWU9EQUJ6
+d2U2L0JSS3dEcGFBUENyMC9CZlppVnFadXFFMzcKYmlxbW1pODBPVm5uQzd6eis3
+cDZYQ1QycitEemxtSmFCOGdzTjZCZlU2ek45Wklwd3ptcm1XTy94SGNoZ1BkOQpK
+SUNrSUxHWEJlQnorNmtnSTZiZDcrbFZEYmxJYXNRNHkrUjhMWWxTeCtRMk9mK1gy
+eDFwQWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQppbnRyb2R1
+Y3Rpb24tcG9pbnQgcXE0YXZmdnZhcHljMnhyNmNpYmpuaXFoNTdpY2FkMnEKaXAt
+YWRkcmVzcyAxODguNDAuODMuMjA4Cm9uaW9uLXBvcnQgNDQ0Cm9uaW9uLWtleQot
+LS0tLUJFR0lOIFJTQSBQVUJMSUMgS0VZLS0tLS0KTUlHSkFvR0JBTWZySUJ1TlVF
+MGZxa3ZySDlEZzJkNkM5SFUvNWR2TGxQL0NGVGFFYlVhMURvUHhyQm5iQ1BEcQp5
+bHhwNGs3M04yVDM3aXJrN3FacmVZQ3B6aUVPQ3paRU4xdGlkM0t1c0tFczgzL0FQ
+aDFreTVRRmpkRmpBU0NxCmovRDl0VXNYYkVaa1FEN203SUlmWU40SG52KzROM0dV
+WjVqNkhFMWJvYmxsbURyNkd2U0xBZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElD
+IEtFWS0tLS0tCnNlcnZpY2Uta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVkt
+LS0tLQpNSUdKQW9HQkFNRXMyc2tRLzJsVjRFUGpRTDFrZk0zZjExUzRNZUdPcDFC
+aWgrcm1ESGhic09yWDRoZk1GV1NuCjY1dTlZUE9zY1hldUlHbFp4YmFyUUY2Nzkz
+bGMrbk9KbjVyQ016a0pYNWt0OHdobkZoVlFXN0ZoejUxRmdMNFkKNUFKKzdkaW9l
+WTNKdjVNVE1rMUF4Qk1hbkJsR2YxazZrU05kZWxtMCs2bWtZVmVOcVJzckFnTUJB
+QUU9Ci0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0KaW50cm9kdWN0aW9uLXBv
+aW50IGI1ZHNpY29oZ2Fqb3Vhc3VxZ3lyNWtwNXIzdzdpaWYyCmlwLWFkZHJlc3Mg
+MTg1LjUuNTMuNzIKb25pb24tcG9ydCA5MDAxCm9uaW9uLWtleQotLS0tLUJFR0lO
+IFJTQSBQVUJMSUMgS0VZLS0tLS0KTUlHSkFvR0JBTWVUc0VqS1ZMeGsyL3o4bnRG
+T2xWU09ZTWFjWTZBeFJTTXdQdkxoT2FsajVkNkRraitnSVA5agpsWDFoekRpZ0VP
+b2FBV3FwWjRjRENLa3p6UGdzWEc3ZXVnSmFxTGQydUliZmh4WEdISTJDNWtWQWZZ
+VnVyL1N3CnBXNFRRbmhKUytvbG1BWG84b1hvT2JNWGt1YURUdmlLSXZLa3lQWCs0
+dGJ4a1RxWWt5ZURBZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0t
+CnNlcnZpY2Uta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdK
+QW9HQkFPM05SSGMwTld5MFo5aFQrZEUxZ3hKRnFsb0dhdmdLYzZJZ0E1UU5ORDNO
+RHJJakdBYmNsRVh4Ck1tY1BBR2hhc1VlNnJ2aFN0VlRkZEVvN1V0SElCM3F2YXNL
+d0NPem82Z2dLSHZtVVRFOWRNTk9LSEcyLy9xTi8KVkducGdJUkRoeDRwbE9DOXV6
+NGFKU0RZdlhiSXVtZmtxdGJzb1BacUZMYUFqdGFWR015VkFnTUJBQUU9Ci0tLS0t
+RU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0KCg==
+-----END MESSAGE-----
+signature
+-----BEGIN SIGNATURE-----
+tFCNLEOFeqeDZOruKt4SBGvJ2Y2lLo29XUzd09FlIalnwb71TKcKSWi1JVB0vDqF
+ftGlP4M+nZGh6YGxdEriV0ikFbcW+F4XOpWR5NEPV5cCoQAMd1a8mqivGjM/lQwe
+41p+e4XCex2Brsl3o+pSyHy/U+p+xdQkmNPqrHpXVK8=
+-----END SIGNATURE-----
diff --git a/test/unit/descriptor/hidden_service_descriptor.py b/test/unit/descriptor/hidden_service_descriptor.py
new file mode 100644
index 0000000..579c549
--- /dev/null
+++ b/test/unit/descriptor/hidden_service_descriptor.py
@@ -0,0 +1,114 @@
+"""
+Unit tests for stem.descriptor.hidden_service_descriptor.
+"""
+
+import datetime
+import unittest
+
+import stem.descriptor
+
+from test.unit.descriptor import get_resource
+
+EXPECTED_DDG_PERMANENT_KEY = """\
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBAJ/SzzgrXPxTlFrKVhXh3buCWv2QfcNgncUpDpKouLn3AtPH5Ocys0jE
+aZSKdvaiQ62md2gOwj4x61cFNdi05tdQjS+2thHKEm/KsB9BGLSLBNJYY356bupg
+I5gQozM65ENelfxYlysBjJ52xSDBd8C4f/p9umdzaaaCmzXG/nhzAgMBAAE=
+-----END RSA PUBLIC KEY-----\
+"""
+
+EXPECTED_DDG_INTRODUCTION_POINTS_BLOB = """\
+-----BEGIN MESSAGE-----
+aW50cm9kdWN0aW9uLXBvaW50IGl3a2k3N3h0YnZwNnF2ZWRmcndkem5jeHMzY2th
+eWV1CmlwLWFkZHJlc3MgMTc4LjYyLjIyMi4xMjkKb25pb24tcG9ydCA0NDMKb25p
+b24ta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFL
+OTRCRVlJSFo0S2RFa2V5UGhiTENwUlc1RVNnKzJXUFFock00eXVLWUd1cTh3Rldn
+dW1aWVI5CmsvV0EvL0ZZWE1CejBiQitja3Vacy9ZdTluSytITHpwR2FwVjBjbHN0
+NEdVTWNCSW5VQ3pDY3BqSlRRc1FEZ20KMy9ZM2NxaDBXNTVnT0NGaG9tUTQvMVdP
+WWc3WUNqazRYWUhKRTIwT2RHMkxsNXpvdEs2ZkFnTUJBQUU9Ci0tLS0tRU5EIFJT
+QSBQVUJMSUMgS0VZLS0tLS0Kc2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVC
+TElDIEtFWS0tLS0tCk1JR0pBb0dCQUpYbUpiOGxTeWRNTXFDZ0NnZmd2bEIyRTVy
+cGQ1N2t6L0FxZzcvZDFIS2MzK2w1UW9Vdkh5dXkKWnNBbHlrYThFdTUzNGhsNDFv
+cUVLcEFLWWNNbjFUTTB2cEpFR05WT2MrMDVCSW54STloOWYwTWcwMVBEMHRZdQpH
+Y0xIWWdCemNyZkVtS3dNdE04V0VtY01KZDduMnVmZmFBdko4NDZXdWJiZVY3TVcx
+WWVoQWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQppbnRyb2R1
+Y3Rpb24tcG9pbnQgZW00Z2prNmVpaXVhbGhtbHlpaWZyemM3bGJ0cnNiaXAKaXAt
+YWRkcmVzcyA0Ni40LjE3NC41Mgpvbmlvbi1wb3J0IDQ0Mwpvbmlvbi1rZXkKLS0t
+LS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dCQUxCbWhkRjV3SHhI
+cnBMU21qQVpvdHR4MjIwKzk5NUZkTU9PdFpOalJ3MURCU3ByVVpacXR4V2EKUDhU
+S3BIS3p3R0pLQ1ZZSUlqN2xvaGJ2OVQ5dXJtbGZURTA1VVJHZW5ab2lmT0ZOejNZ
+d01KVFhTY1FFQkoxMAo5aVdOTERUc2tMekRLQ0FiR2hibi9NS3dPZllHQmhOVGxq
+ZHlUbU5ZNUVDUmJSempldjl2QWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBL
+RVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQVUJMSUMgS0VZLS0t
+LS0KTUlHSkFvR0JBTXhNSG9BbXJiVU1zeGlJQ3AzaVRQWWdobjBZdWVLSHgyMTl3
+dThPL1E1MVF5Y1ZWTHBYMjdkMQpoSlhrUEIzM1hRQlhzQlM3U3hzU3NTQ1EzR0V1
+clFKN0d1QkxwWUlSL3Zxc2FrRS9sOHdjMkNKQzVXVWh5RkZrCisxVFdJVUk1dHhu
+WEx5V0NSY0tEVXJqcWRvc0RhRG9zZ0hGZzIzTW54K3hYY2FRL2ZyQi9BZ01CQUFF
+PQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJvZHVjdGlvbi1wb2lu
+dCBqcWhmbDM2NHgzdXBlNmxxbnhpem9sZXdsZnJzdzJ6eQppcC1hZGRyZXNzIDYy
+LjIxMC44Mi4xNjkKb25pb24tcG9ydCA0NDMKb25pb24ta2V5Ci0tLS0tQkVHSU4g
+UlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFQVWtxeGdmWWR3MFBtL2c2TWJo
+bVZzR0tsdWppZm1raGRmb0VldXpnbyt3bkVzR3Z3VWVienJ6CmZaSlJ0MGNhWEZo
+bkNHZ1FEMklnbWFyVWFVdlAyNGZYby80bVl6TGNQZUk3Z1puZXVBUUpZdm05OFl2
+OXZPSGwKTmFNL1d2RGtDc0ozR1ZOSjFIM3dMUFFSSTN2N0tiTnVjOXRDT1lsL3Iw
+OU9oVmFXa3phakFnTUJBQUU9Ci0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K
+c2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pB
+b0dCQUxieDhMZXFSb1Avcjl3OWhqd0Q0MVlVbTdQbzY5N3hSdHl0RjBNY3lMQ1M3
+R1JpVVluamk3S1kKZmVwWGR2Ti9KbDVxUUtISUJiNjAya3VPVGwwcE44UStZZUZV
+U0lJRGNtUEJMcEJEaEgzUHZyUU1jR1ZhaU9XSAo4dzBITVpDeGd3QWNDQzUxdzVW
+d2l1bXhFSk5CVmNac094MG16TjFDbG95KzkwcTBsRlhMQWdNQkFBRT0KLS0tLS1F
+TkQgUlNBIFBVQkxJQyBLRVktLS0tLQoK
+-----END MESSAGE-----\
+"""
+
+EXPECTED_DDG_SIGNATURE = """\
+-----BEGIN SIGNATURE-----
+VKMmsDIUUFOrpqvcQroIZjDZTKxqNs88a4M9Te8cR/ZvS7H2nffv6iQs0tom5X4D
+4Dy4iZiy+pwYxdHfaOxmdpgMCRvgPb34MExWr5YemH0QuGtnlp5Wxr8GYaAQVuZX
+cZjQLW0juUYCbgIGdxVEBnlEt2rgBSM9+1oR7EAfV1U=
+-----END SIGNATURE-----\
+"""
+
+
+class TestHiddenServiceDescriptor(unittest.TestCase):
+  def test_for_duckduckgo_with_validation(self):
+    """
+    Parse duckduckgo's descriptor.
+    """
+
+    descriptor_file = open(get_resource('hidden_service_duckduckgo'), 'rb')
+    desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
+    self._assert_matches_duckduckgo(desc)
+
+  def test_for_duckduckgo_without_validation(self):
+    """
+    Parse duckduckgo's descriptor
+    """
+
+    descriptor_file = open(get_resource('hidden_service_duckduckgo'), 'rb')
+    desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = False))
+    self._assert_matches_duckduckgo(desc)
+
+  def test_for_facebook(self):
+    """
+    Parse facebook's descriptor.
+    """
+
+    descriptor_file = open(get_resource('hidden_service_facebook'), 'rb')
+
+    desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
+    self.assertEqual('utjk4arxqg6s6zzo7n6cjnq6ot34udhr', desc.descriptor_id)
+    self.assertEqual('2', desc.version)
+    self.assertEqual('6355jaerje3bqozopwq2qmpf4iviizdn', desc.secret_id_part)
+    self.assertEqual(datetime.datetime(2014, 10, 31, 23, 0, 0), desc.published)
+    self.assertEqual(['2', '3'], desc.protocol_versions)
+
+  def _assert_matches_duckduckgo(self, desc):
+    self.assertEqual('y3olqqblqw2gbh6phimfuiroechjjafa', desc.descriptor_id)
+    self.assertEqual('2', desc.version)
+    self.assertEqual(EXPECTED_DDG_PERMANENT_KEY, desc.permanent_key)
+    self.assertEqual('e24kgecavwsznj7gpbktqsiwgvngsf4e', desc.secret_id_part)
+    self.assertEqual(datetime.datetime(2015, 2, 23, 20, 0, 0), desc.published)
+    self.assertEqual(['2', '3'], desc.protocol_versions)
+    self.assertEqual(EXPECTED_DDG_INTRODUCTION_POINTS_BLOB, desc.introduction_points_blob)
+    self.assertEqual(EXPECTED_DDG_SIGNATURE, desc.signature)





More information about the tor-commits mailing list