[tor-commits] [stem/master] Rename stem.descriptor.hidden_service_descriptor module
atagar at torproject.org
atagar at torproject.org
Sun Aug 25 00:20:44 UTC 2019
commit f307434824f74550b836927f3c02a4bac53c6c7c
Author: Damian Johnson <atagar at torproject.org>
Date: Tue Aug 20 15:23:58 2019 -0700
Rename stem.descriptor.hidden_service_descriptor module
Dropping the redundant '_descriptor' suffix from this module name. The old name
still works as an alias.
---
docs/api.rst | 2 +-
docs/api/descriptor/hidden_service.rst | 5 +
docs/api/descriptor/hidden_service_descriptor.rst | 5 -
docs/change_log.rst | 7 +-
docs/contents.rst | 2 +-
docs/tutorials/mirror_mirror_on_the_wall.rst | 2 +-
docs/tutorials/over_the_river.rst | 2 +-
stem/control.py | 2 +-
stem/descriptor/__init__.py | 12 +-
stem/descriptor/hidden_service.py | 441 ++++++++++++++++++++
stem/descriptor/hidden_service_descriptor.py | 446 +--------------------
stem/response/events.py | 4 +-
test/settings.cfg | 10 +-
...den_service_descriptor.py => hidden_service.py} | 4 +-
14 files changed, 474 insertions(+), 470 deletions(-)
diff --git a/docs/api.rst b/docs/api.rst
index a8ba7e24..cbbf0dd0 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -35,7 +35,7 @@ remotely like Tor does.
* `stem.descriptor.microdescriptor <api/descriptor/microdescriptor.html>`_ - Minimalistic counterpart for server descriptors.
* `stem.descriptor.networkstatus <api/descriptor/networkstatus.html>`_ - Network status documents which make up the Tor consensus.
* `stem.descriptor.router_status_entry <api/descriptor/router_status_entry.html>`_ - Relay entries within a network status document.
- * `stem.descriptor.hidden_service_descriptor <api/descriptor/hidden_service_descriptor.html>`_ - Descriptors generated for hidden services.
+ * `stem.descriptor.hidden_service <api/descriptor/hidden_service.html>`_ - Descriptors generated for hidden services.
* `stem.descriptor.bandwidth_file <api/descriptor/bandwidth_file.html>`_ - Bandwidth authority metrics.
* `stem.descriptor.tordnsel <api/descriptor/tordnsel.html>`_ - `TorDNSEL <https://www.torproject.org/projects/tordnsel.html.en>`_ exit lists.
* `stem.descriptor.certificate <api/descriptor/certificate.html>`_ - `Ed25519 certificates <https://gitweb.torproject.org/torspec.git/tree/cert-spec.txt>`_.
diff --git a/docs/api/descriptor/hidden_service.rst b/docs/api/descriptor/hidden_service.rst
new file mode 100644
index 00000000..21b9bd7b
--- /dev/null
+++ b/docs/api/descriptor/hidden_service.rst
@@ -0,0 +1,5 @@
+Hidden Service Descriptor
+=========================
+
+.. automodule:: stem.descriptor.hidden_service
+
diff --git a/docs/api/descriptor/hidden_service_descriptor.rst b/docs/api/descriptor/hidden_service_descriptor.rst
deleted file mode 100644
index 145203e6..00000000
--- a/docs/api/descriptor/hidden_service_descriptor.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Hidden Service Descriptor
-=========================
-
-.. automodule:: stem.descriptor.hidden_service_descriptor
-
diff --git a/docs/change_log.rst b/docs/change_log.rst
index c244d7d2..d9c6bfa3 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -52,16 +52,16 @@ The following are only available within Stem's `git repository
* Controller events could fail to be delivered in a timely fashion (:trac:`27173`)
* Adjusted :func:`~stem.control.Controller.get_microdescriptors` fallback to also use '.new' cache files (:trac:`28508`)
* ExitPolicies could raise TypeError when read concurrently (:trac:`29899`)
- * **STALE_DESC** :data:`~stem.Flag` (:spec:`d14164d8`)
+ * **STALE_DESC** :data:`~stem.Flag` (:spec:`d14164d`)
* **DORMANT** and **ACTIVE** :data:`~stem.Signal` (:spec:`4421149`)
* **QUERY_RATE_LIMITED** :data:`~stem.HSDescReason` (:spec:`bd80679`)
- * **EXTOR** and **HTTPTUNNEL** :data:`~stem.Listener`
+ * **EXTOR** and **HTTPTUNNEL** :data:`~stem.control.Listener`
* **Descriptors**
* Added the `stem.descriptor.collector <api/descriptor/collector.html>`_ module (:trac:`17979`)
* `Bandwidth file support <api/descriptor/bandwidth_file.html>`_ (:trac:`29056`)
- * `stem.descriptor.remote <api/descriptor/remote.html>`_ now raise :class:`stem.DownloadFailed`
+ * `stem.descriptor.remote <api/descriptor/remote.html>`_ methods now raise :class:`stem.DownloadFailed`
* Check Ed25519 validity though the cryptography module rather than PyNaCl (:trac:`22022`)
* Download compressed descriptors by default (:trac:`29186`)
* Added :class:`~stem.descriptor.Compression` class
@@ -78,6 +78,7 @@ The following are only available within Stem's `git repository
* Replaced the **digest** attribute of :class:`~stem.descriptor.microdescriptor.Microdescriptor` with a method by the same name (:trac:`28398`)
* Default the **version_flavor** attribute of :class:`~stem.descriptor.networkstatus.NetworkStatusDocumentV3` to 'ns' (:spec:`d97f8d9`)
* DescriptorDownloader crashed if **use_mirrors** is set (:trac:`28393`)
+ * Renamed stem.descriptor.hidden_service_descriptor to stem.descriptor.hidden_service
* Don't download from Serge, a bridge authority that frequently timeout
* Updated dizum authority's address (:trac:`31406`)
diff --git a/docs/contents.rst b/docs/contents.rst
index 267979e0..98e80a5f 100644
--- a/docs/contents.rst
+++ b/docs/contents.rst
@@ -50,7 +50,7 @@ Contents
api/descriptor/microdescriptor
api/descriptor/networkstatus
api/descriptor/router_status_entry
- api/descriptor/hidden_service_descriptor
+ api/descriptor/hidden_service
api/descriptor/tordnsel
api/descriptor/export
diff --git a/docs/tutorials/mirror_mirror_on_the_wall.rst b/docs/tutorials/mirror_mirror_on_the_wall.rst
index 04cc86de..699625e4 100644
--- a/docs/tutorials/mirror_mirror_on_the_wall.rst
+++ b/docs/tutorials/mirror_mirror_on_the_wall.rst
@@ -34,7 +34,7 @@ Descriptor Type
`Microdescriptor <../api/descriptor/microdescriptor.html>`_ Minimalistic document that just includes the information necessary for Tor clients to work.
`Network Status Document <../api/descriptor/networkstatus.html>`_ Though Tor relays are decentralized, the directories that track the overall network are not. These central points are called **directory authorities**, and every hour they publish a document called a **consensus** (aka, network status document). The consensus in turn is made up of **router status entries**.
`Router Status Entry <../api/descriptor/router_status_entry.html>`_ Relay information provided by the directory authorities including flags, heuristics used for relay selection, etc.
-`Hidden Service Descriptor <../api/descriptor/hidden_service_descriptor.html>`_ Information pertaining to a `Hidden Service <https://www.torproject.org/docs/hidden-services.html.en>`_. These can only be `queried through the tor process <over_the_river.html#hidden-service-descriptors>`_.
+`Hidden Service Descriptor <../api/descriptor/hidden_service.html>`_ Information pertaining to a `Hidden Service <https://www.torproject.org/docs/hidden-services.html.en>`_. These can only be `queried through the tor process <over_the_river.html#hidden-service-descriptors>`_.
================================================================================ ===========
.. _where-do-descriptors-come-from:
diff --git a/docs/tutorials/over_the_river.rst b/docs/tutorials/over_the_river.rst
index dac78827..ff8c7feb 100644
--- a/docs/tutorials/over_the_river.rst
+++ b/docs/tutorials/over_the_river.rst
@@ -171,7 +171,7 @@ its :func:`~stem.control.Controller.get_hidden_service_descriptor` method...
A hidden service's introduction points are a base64 encoded field that's
possibly encrypted. These can be decoded (and decrypted if necessary) with the
descriptor's
-:func:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor.introduction_points`
+:func:`~stem.descriptor.hidden_service.HiddenServiceDescriptor.introduction_points`
method.
.. literalinclude:: /_static/example/introduction_points.py
diff --git a/stem/control.py b/stem/control.py
index d8423ffa..81cb1682 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -2136,7 +2136,7 @@ class Controller(BaseController):
:param list servers: requrest the descriptor from these specific servers
:param float timeout: seconds to wait when **await_result** is **True**
- :returns: :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor`
+ :returns: :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptor`
for the given service if **await_result** is **True**, or **None** otherwise
:raises:
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index c099ca86..fd96c042 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -118,7 +118,7 @@ __all__ = [
'collector',
'export',
'extrainfo_descriptor',
- 'hidden_service_descriptor',
+ 'hidden_service',
'microdescriptor',
'networkstatus',
'reader',
@@ -329,7 +329,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`
+ hidden-service-descriptor 1.0 :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptor`
========================================= =====
If you're using **python 3** then beware that the open() function defaults to
@@ -536,10 +536,10 @@ 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 == stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor.TYPE_ANNOTATION_NAME and major_version == 1:
- document_type = stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor
+ elif descriptor_type == stem.descriptor.hidden_service.HiddenServiceDescriptor.TYPE_ANNOTATION_NAME and major_version == 1:
+ document_type = stem.descriptor.hidden_service.HiddenServiceDescriptor
- for desc in stem.descriptor.hidden_service_descriptor._parse_file(descriptor_file, validate = validate, **kwargs):
+ for desc in stem.descriptor.hidden_service._parse_file(descriptor_file, validate = validate, **kwargs):
yield desc
elif descriptor_type == stem.descriptor.bandwidth_file.BandwidthFile.TYPE_ANNOTATION_NAME and major_version == 1:
document_type = stem.descriptor.bandwidth_file.BandwidthFile
@@ -1521,7 +1521,7 @@ def _descriptor_components(raw_contents, validate, extra_keywords = (), non_asci
import stem.descriptor.bandwidth_file
import stem.descriptor.extrainfo_descriptor
-import stem.descriptor.hidden_service_descriptor
+import stem.descriptor.hidden_service
import stem.descriptor.microdescriptor
import stem.descriptor.networkstatus
import stem.descriptor.server_descriptor
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
new file mode 100644
index 00000000..665d8664
--- /dev/null
+++ b/stem/descriptor/hidden_service.py
@@ -0,0 +1,441 @@
+# Copyright 2015-2019, 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.
+
+These are only available through the Controller's
+:func:`~stem.control.get_hidden_service_descriptor` method.
+
+**Module Overview:**
+
+::
+
+ HiddenServiceDescriptor - Tor hidden service descriptor.
+
+.. versionadded:: 1.4.0
+"""
+
+import base64
+import binascii
+import collections
+import hashlib
+import io
+
+import stem.prereq
+import stem.util.connection
+import stem.util.str_tools
+
+from stem.descriptor import (
+ PGP_BLOCK_END,
+ Descriptor,
+ _descriptor_content,
+ _descriptor_components,
+ _read_until_keywords,
+ _bytes_for_block,
+ _value,
+ _parse_simple_line,
+ _parse_timestamp_line,
+ _parse_key_block,
+ _random_date,
+ _random_crypto_blob,
+)
+
+if stem.prereq._is_lru_cache_available():
+ from functools import lru_cache
+else:
+ from stem.util.lru_cache import lru_cache
+
+REQUIRED_FIELDS = (
+ 'rendezvous-service-descriptor',
+ 'version',
+ 'permanent-key',
+ 'secret-id-part',
+ 'publication-time',
+ 'protocol-versions',
+ 'signature',
+)
+
+INTRODUCTION_POINTS_ATTR = {
+ 'identifier': None,
+ 'address': None,
+ 'port': None,
+ 'onion_key': None,
+ 'service_key': None,
+ 'intro_authentication': [],
+}
+
+# introduction-point fields that can only appear once
+
+SINGLE_INTRODUCTION_POINT_FIELDS = [
+ 'introduction-point',
+ 'ip-address',
+ 'onion-port',
+ 'onion-key',
+ 'service-key',
+]
+
+BASIC_AUTH = 1
+STEALTH_AUTH = 2
+
+
+class IntroductionPoints(collections.namedtuple('IntroductionPoints', INTRODUCTION_POINTS_ATTR.keys())):
+ """
+ :var str identifier: hash of this introduction point's identity key
+ :var str address: address of this introduction point
+ :var int port: port where this introduction point is listening
+ :var str onion_key: public key for communicating with this introduction point
+ :var str service_key: public key for communicating with this hidden service
+ :var list intro_authentication: tuples of the form (auth_type, auth_data) for
+ establishing a connection
+ """
+
+
+class DecryptionFailure(Exception):
+ """
+ Failure to decrypt the hidden service descriptor's introduction-points.
+ """
+
+
+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.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
+
+
+def _parse_version_line(descriptor, entries):
+ value = _value('version', entries)
+
+ if value.isdigit():
+ descriptor.version = int(value)
+ else:
+ raise ValueError('version line must have a positive integer value: %s' % value)
+
+
+def _parse_protocol_versions_line(descriptor, entries):
+ value = _value('protocol-versions', entries)
+
+ try:
+ versions = [int(entry) for entry in value.split(',')]
+ except ValueError:
+ raise ValueError('protocol-versions line has non-numeric versoins: protocol-versions %s' % value)
+
+ for v in versions:
+ if v <= 0:
+ raise ValueError('protocol-versions must be positive integers: %s' % value)
+
+ descriptor.protocol_versions = versions
+
+
+def _parse_introduction_points_line(descriptor, entries):
+ _, block_type, block_contents = entries['introduction-points'][0]
+
+ if not block_contents or block_type != 'MESSAGE':
+ raise ValueError("'introduction-points' should be followed by a MESSAGE block, but was a %s" % block_type)
+
+ descriptor.introduction_points_encoded = block_contents
+ descriptor.introduction_points_auth = [] # field was never implemented in tor (#15190)
+
+ try:
+ descriptor.introduction_points_content = _bytes_for_block(block_contents)
+ except TypeError:
+ raise ValueError("'introduction-points' isn't base64 encoded content:\n%s" % block_contents)
+
+
+_parse_rendezvous_service_descriptor_line = _parse_simple_line('rendezvous-service-descriptor', 'descriptor_id')
+_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_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 int 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: **\\*** list of **int** versions that are supported when establishing a connection
+ :var str introduction_points_encoded: raw introduction points blob
+ :var list introduction_points_auth: **\\*** tuples of the form
+ (auth_method, auth_data) for our introduction_points_content
+ (**deprecated**, always **[]**)
+ :var bytes introduction_points_content: decoded introduction-points content
+ without authentication data, if using 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
+
+ .. versionchanged:: 1.6.0
+ Moved from the deprecated `pycrypto
+ <https://www.dlitz.net/software/pycrypto/>`_ module to `cryptography
+ <https://pypi.org/project/cryptography/>`_ for validating signatures.
+
+ .. versionchanged:: 1.6.0
+ Added the **skip_crypto_validation** constructor argument.
+ """
+
+ TYPE_ANNOTATION_NAME = 'hidden-service-descriptor'
+
+ 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_encoded': (None, _parse_introduction_points_line),
+ 'introduction_points_auth': ([], _parse_introduction_points_line),
+ 'introduction_points_content': (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,
+ }
+
+ @classmethod
+ def content(cls, attr = None, exclude = (), sign = False):
+ if sign:
+ raise NotImplementedError('Signing of %s not implemented' % cls.__name__)
+
+ return _descriptor_content(attr, exclude, (
+ ('rendezvous-service-descriptor', 'y3olqqblqw2gbh6phimfuiroechjjafa'),
+ ('version', '2'),
+ ('permanent-key', _random_crypto_blob('RSA PUBLIC KEY')),
+ ('secret-id-part', 'e24kgecavwsznj7gpbktqsiwgvngsf4e'),
+ ('publication-time', _random_date()),
+ ('protocol-versions', '2,3'),
+ ('introduction-points', '\n-----BEGIN MESSAGE-----\n-----END MESSAGE-----'),
+ ), (
+ ('signature', _random_crypto_blob('SIGNATURE')),
+ ))
+
+ @classmethod
+ def create(cls, attr = None, exclude = (), validate = True, sign = False):
+ return cls(cls.content(attr, exclude, sign), validate = validate, skip_crypto_validation = not sign)
+
+ def __init__(self, raw_contents, validate = False, skip_crypto_validation = False):
+ super(HiddenServiceDescriptor, self).__init__(raw_contents, lazy_load = not validate)
+ entries = _descriptor_components(raw_contents, validate, non_ascii_fields = ('introduction-points'))
+
+ 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)
+
+ if not skip_crypto_validation and stem.prereq.is_crypto_available():
+ signed_digest = self._digest_for_signature(self.permanent_key, self.signature)
+ digest_content = self._content_range('rendezvous-service-descriptor ', '\nsignature\n')
+ content_digest = hashlib.sha1(digest_content).hexdigest().upper()
+
+ if signed_digest != content_digest:
+ raise ValueError('Decrypted digest does not match local digest (calculated: %s, local: %s)' % (signed_digest, content_digest))
+ else:
+ self._entries = entries
+
+ @lru_cache()
+ def introduction_points(self, authentication_cookie = None):
+ """
+ Provided this service's introduction points.
+
+ :returns: **list** of :class:`~stem.descriptor.hidden_service.IntroductionPoints`
+
+ :raises:
+ * **ValueError** if the our introduction-points is malformed
+ * **DecryptionFailure** if unable to decrypt this field
+ """
+
+ content = self.introduction_points_content
+
+ if not content:
+ return []
+ elif authentication_cookie:
+ if not stem.prereq.is_crypto_available():
+ raise DecryptionFailure('Decrypting introduction-points requires the cryptography module')
+
+ try:
+ missing_padding = len(authentication_cookie) % 4
+ authentication_cookie = base64.b64decode(stem.util.str_tools._to_bytes(authentication_cookie) + b'=' * missing_padding)
+ except TypeError as exc:
+ raise DecryptionFailure('authentication_cookie must be a base64 encoded string (%s)' % exc)
+
+ authentication_type = int(binascii.hexlify(content[0:1]), 16)
+
+ if authentication_type == BASIC_AUTH:
+ content = HiddenServiceDescriptor._decrypt_basic_auth(content, authentication_cookie)
+ elif authentication_type == STEALTH_AUTH:
+ content = HiddenServiceDescriptor._decrypt_stealth_auth(content, authentication_cookie)
+ else:
+ raise DecryptionFailure("Unrecognized authentication type '%s', currently we only support basic auth (%s) and stealth auth (%s)" % (authentication_type, BASIC_AUTH, STEALTH_AUTH))
+
+ if not content.startswith(b'introduction-point '):
+ raise DecryptionFailure('Unable to decrypt the introduction-points, maybe this is the wrong key?')
+ elif not content.startswith(b'introduction-point '):
+ raise DecryptionFailure('introduction-points content is encrypted, you need to provide its authentication_cookie')
+
+ return HiddenServiceDescriptor._parse_introduction_points(content)
+
+ @staticmethod
+ def _decrypt_basic_auth(content, authentication_cookie):
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+ from cryptography.hazmat.backends import default_backend
+
+ try:
+ client_blocks = int(binascii.hexlify(content[1:2]), 16)
+ except ValueError:
+ raise DecryptionFailure("When using basic auth the content should start with a number of blocks but wasn't a hex digit: %s" % binascii.hexlify(content[1:2]))
+
+ # parse the client id and encrypted session keys
+
+ client_entries_length = client_blocks * 16 * 20
+ client_entries = content[2:2 + client_entries_length]
+ client_keys = [(client_entries[i:i + 4], client_entries[i + 4:i + 20]) for i in range(0, client_entries_length, 4 + 16)]
+
+ iv = content[2 + client_entries_length:2 + client_entries_length + 16]
+ encrypted = content[2 + client_entries_length + 16:]
+
+ client_id = hashlib.sha1(authentication_cookie + iv).digest()[:4]
+
+ for entry_id, encrypted_session_key in client_keys:
+ if entry_id != client_id:
+ continue # not the session key for this client
+
+ # try decrypting the session key
+
+ cipher = Cipher(algorithms.AES(authentication_cookie), modes.CTR(b'\x00' * len(iv)), default_backend())
+ decryptor = cipher.decryptor()
+ session_key = decryptor.update(encrypted_session_key) + decryptor.finalize()
+
+ # attempt to decrypt the intro points with the session key
+
+ cipher = Cipher(algorithms.AES(session_key), modes.CTR(iv), default_backend())
+ decryptor = cipher.decryptor()
+ decrypted = decryptor.update(encrypted) + decryptor.finalize()
+
+ # check if the decryption looks correct
+
+ if decrypted.startswith(b'introduction-point '):
+ return decrypted
+
+ return content # nope, unable to decrypt the content
+
+ @staticmethod
+ def _decrypt_stealth_auth(content, authentication_cookie):
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+ from cryptography.hazmat.backends import default_backend
+
+ # byte 1 = authentication type, 2-17 = input vector, 18 on = encrypted content
+ iv, encrypted = content[1:17], content[17:]
+ cipher = Cipher(algorithms.AES(authentication_cookie), modes.CTR(iv), default_backend())
+ decryptor = cipher.decryptor()
+
+ return decryptor.update(encrypted) + decryptor.finalize()
+
+ @staticmethod
+ def _parse_introduction_points(content):
+ """
+ Provides the parsed list of IntroductionPoints for the unencrypted content.
+ """
+
+ introduction_points = []
+ content_io = io.BytesIO(content)
+
+ while True:
+ content = b''.join(_read_until_keywords('introduction-point', content_io, ignore_first = True))
+
+ if not content:
+ break # reached the end
+
+ attr = dict(INTRODUCTION_POINTS_ATTR)
+ entries = _descriptor_components(content, False)
+
+ for keyword, values in list(entries.items()):
+ value, block_type, block_contents = values[0]
+
+ if keyword in SINGLE_INTRODUCTION_POINT_FIELDS and len(values) > 1:
+ raise ValueError("'%s' can only appear once in an introduction-point block, but appeared %i times" % (keyword, len(values)))
+
+ if keyword == 'introduction-point':
+ attr['identifier'] = value
+ elif keyword == 'ip-address':
+ if not stem.util.connection.is_valid_ipv4_address(value):
+ raise ValueError("'%s' is an invalid IPv4 address" % value)
+
+ attr['address'] = value
+ elif keyword == 'onion-port':
+ if not stem.util.connection.is_valid_port(value):
+ raise ValueError("'%s' is an invalid port" % value)
+
+ attr['port'] = int(value)
+ elif keyword == 'onion-key':
+ attr['onion_key'] = block_contents
+ elif keyword == 'service-key':
+ attr['service_key'] = block_contents
+ elif keyword == 'intro-authentication':
+ auth_entries = []
+
+ for auth_value, _, _ in values:
+ if ' ' not in auth_value:
+ raise ValueError("We expected 'intro-authentication [auth_type] [auth_data]', but had '%s'" % auth_value)
+
+ auth_type, auth_data = auth_value.split(' ')[:2]
+ auth_entries.append((auth_type, auth_data))
+
+ introduction_points.append(IntroductionPoints(**attr))
+
+ return introduction_points
diff --git a/stem/descriptor/hidden_service_descriptor.py b/stem/descriptor/hidden_service_descriptor.py
index 99d6414e..d77d88aa 100644
--- a/stem/descriptor/hidden_service_descriptor.py
+++ b/stem/descriptor/hidden_service_descriptor.py
@@ -1,444 +1,4 @@
-# Copyright 2015-2019, Damian Johnson and The Tor Project
-# See LICENSE for licensing information
+# TODO: This module (hidden_service_descriptor) is a temporary alias for
+# hidden_service. This alias will be removed in Stem 2.x.
-"""
-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.
-
-These are only available through the Controller's
-:func:`~stem.control.get_hidden_service_descriptor` method.
-
-**Module Overview:**
-
-::
-
- HiddenServiceDescriptor - Tor hidden service descriptor.
-
-.. versionadded:: 1.4.0
-"""
-
-# TODO: In stem 2.x rename this module to 'hidden_service' (ie, drop the
-# redundant '_descriptor' suffix).
-
-import base64
-import binascii
-import collections
-import hashlib
-import io
-
-import stem.prereq
-import stem.util.connection
-import stem.util.str_tools
-
-from stem.descriptor import (
- PGP_BLOCK_END,
- Descriptor,
- _descriptor_content,
- _descriptor_components,
- _read_until_keywords,
- _bytes_for_block,
- _value,
- _parse_simple_line,
- _parse_timestamp_line,
- _parse_key_block,
- _random_date,
- _random_crypto_blob,
-)
-
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
-REQUIRED_FIELDS = (
- 'rendezvous-service-descriptor',
- 'version',
- 'permanent-key',
- 'secret-id-part',
- 'publication-time',
- 'protocol-versions',
- 'signature',
-)
-
-INTRODUCTION_POINTS_ATTR = {
- 'identifier': None,
- 'address': None,
- 'port': None,
- 'onion_key': None,
- 'service_key': None,
- 'intro_authentication': [],
-}
-
-# introduction-point fields that can only appear once
-
-SINGLE_INTRODUCTION_POINT_FIELDS = [
- 'introduction-point',
- 'ip-address',
- 'onion-port',
- 'onion-key',
- 'service-key',
-]
-
-BASIC_AUTH = 1
-STEALTH_AUTH = 2
-
-
-class IntroductionPoints(collections.namedtuple('IntroductionPoints', INTRODUCTION_POINTS_ATTR.keys())):
- """
- :var str identifier: hash of this introduction point's identity key
- :var str address: address of this introduction point
- :var int port: port where this introduction point is listening
- :var str onion_key: public key for communicating with this introduction point
- :var str service_key: public key for communicating with this hidden service
- :var list intro_authentication: tuples of the form (auth_type, auth_data) for
- establishing a connection
- """
-
-
-class DecryptionFailure(Exception):
- """
- Failure to decrypt the hidden service descriptor's introduction-points.
- """
-
-
-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
-
-
-def _parse_version_line(descriptor, entries):
- value = _value('version', entries)
-
- if value.isdigit():
- descriptor.version = int(value)
- else:
- raise ValueError('version line must have a positive integer value: %s' % value)
-
-
-def _parse_protocol_versions_line(descriptor, entries):
- value = _value('protocol-versions', entries)
-
- try:
- versions = [int(entry) for entry in value.split(',')]
- except ValueError:
- raise ValueError('protocol-versions line has non-numeric versoins: protocol-versions %s' % value)
-
- for v in versions:
- if v <= 0:
- raise ValueError('protocol-versions must be positive integers: %s' % value)
-
- descriptor.protocol_versions = versions
-
-
-def _parse_introduction_points_line(descriptor, entries):
- _, block_type, block_contents = entries['introduction-points'][0]
-
- if not block_contents or block_type != 'MESSAGE':
- raise ValueError("'introduction-points' should be followed by a MESSAGE block, but was a %s" % block_type)
-
- descriptor.introduction_points_encoded = block_contents
- descriptor.introduction_points_auth = [] # field was never implemented in tor (#15190)
-
- try:
- descriptor.introduction_points_content = _bytes_for_block(block_contents)
- except TypeError:
- raise ValueError("'introduction-points' isn't base64 encoded content:\n%s" % block_contents)
-
-
-_parse_rendezvous_service_descriptor_line = _parse_simple_line('rendezvous-service-descriptor', 'descriptor_id')
-_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_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 int 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: **\\*** list of **int** versions that are supported when establishing a connection
- :var str introduction_points_encoded: raw introduction points blob
- :var list introduction_points_auth: **\\*** tuples of the form
- (auth_method, auth_data) for our introduction_points_content
- (**deprecated**, always **[]**)
- :var bytes introduction_points_content: decoded introduction-points content
- without authentication data, if using 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
-
- .. versionchanged:: 1.6.0
- Moved from the deprecated `pycrypto
- <https://www.dlitz.net/software/pycrypto/>`_ module to `cryptography
- <https://pypi.org/project/cryptography/>`_ for validating signatures.
-
- .. versionchanged:: 1.6.0
- Added the **skip_crypto_validation** constructor argument.
- """
-
- TYPE_ANNOTATION_NAME = 'hidden-service-descriptor'
-
- 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_encoded': (None, _parse_introduction_points_line),
- 'introduction_points_auth': ([], _parse_introduction_points_line),
- 'introduction_points_content': (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,
- }
-
- @classmethod
- def content(cls, attr = None, exclude = (), sign = False):
- if sign:
- raise NotImplementedError('Signing of %s not implemented' % cls.__name__)
-
- return _descriptor_content(attr, exclude, (
- ('rendezvous-service-descriptor', 'y3olqqblqw2gbh6phimfuiroechjjafa'),
- ('version', '2'),
- ('permanent-key', _random_crypto_blob('RSA PUBLIC KEY')),
- ('secret-id-part', 'e24kgecavwsznj7gpbktqsiwgvngsf4e'),
- ('publication-time', _random_date()),
- ('protocol-versions', '2,3'),
- ('introduction-points', '\n-----BEGIN MESSAGE-----\n-----END MESSAGE-----'),
- ), (
- ('signature', _random_crypto_blob('SIGNATURE')),
- ))
-
- @classmethod
- def create(cls, attr = None, exclude = (), validate = True, sign = False):
- return cls(cls.content(attr, exclude, sign), validate = validate, skip_crypto_validation = not sign)
-
- def __init__(self, raw_contents, validate = False, skip_crypto_validation = False):
- super(HiddenServiceDescriptor, self).__init__(raw_contents, lazy_load = not validate)
- entries = _descriptor_components(raw_contents, validate, non_ascii_fields = ('introduction-points'))
-
- 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)
-
- if not skip_crypto_validation and stem.prereq.is_crypto_available():
- signed_digest = self._digest_for_signature(self.permanent_key, self.signature)
- digest_content = self._content_range('rendezvous-service-descriptor ', '\nsignature\n')
- content_digest = hashlib.sha1(digest_content).hexdigest().upper()
-
- if signed_digest != content_digest:
- raise ValueError('Decrypted digest does not match local digest (calculated: %s, local: %s)' % (signed_digest, content_digest))
- else:
- self._entries = entries
-
- @lru_cache()
- def introduction_points(self, authentication_cookie = None):
- """
- Provided this service's introduction points.
-
- :returns: **list** of :class:`~stem.descriptor.hidden_service_descriptor.IntroductionPoints`
-
- :raises:
- * **ValueError** if the our introduction-points is malformed
- * **DecryptionFailure** if unable to decrypt this field
- """
-
- content = self.introduction_points_content
-
- if not content:
- return []
- elif authentication_cookie:
- if not stem.prereq.is_crypto_available():
- raise DecryptionFailure('Decrypting introduction-points requires the cryptography module')
-
- try:
- missing_padding = len(authentication_cookie) % 4
- authentication_cookie = base64.b64decode(stem.util.str_tools._to_bytes(authentication_cookie) + b'=' * missing_padding)
- except TypeError as exc:
- raise DecryptionFailure('authentication_cookie must be a base64 encoded string (%s)' % exc)
-
- authentication_type = int(binascii.hexlify(content[0:1]), 16)
-
- if authentication_type == BASIC_AUTH:
- content = HiddenServiceDescriptor._decrypt_basic_auth(content, authentication_cookie)
- elif authentication_type == STEALTH_AUTH:
- content = HiddenServiceDescriptor._decrypt_stealth_auth(content, authentication_cookie)
- else:
- raise DecryptionFailure("Unrecognized authentication type '%s', currently we only support basic auth (%s) and stealth auth (%s)" % (authentication_type, BASIC_AUTH, STEALTH_AUTH))
-
- if not content.startswith(b'introduction-point '):
- raise DecryptionFailure('Unable to decrypt the introduction-points, maybe this is the wrong key?')
- elif not content.startswith(b'introduction-point '):
- raise DecryptionFailure('introduction-points content is encrypted, you need to provide its authentication_cookie')
-
- return HiddenServiceDescriptor._parse_introduction_points(content)
-
- @staticmethod
- def _decrypt_basic_auth(content, authentication_cookie):
- from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
- from cryptography.hazmat.backends import default_backend
-
- try:
- client_blocks = int(binascii.hexlify(content[1:2]), 16)
- except ValueError:
- raise DecryptionFailure("When using basic auth the content should start with a number of blocks but wasn't a hex digit: %s" % binascii.hexlify(content[1:2]))
-
- # parse the client id and encrypted session keys
-
- client_entries_length = client_blocks * 16 * 20
- client_entries = content[2:2 + client_entries_length]
- client_keys = [(client_entries[i:i + 4], client_entries[i + 4:i + 20]) for i in range(0, client_entries_length, 4 + 16)]
-
- iv = content[2 + client_entries_length:2 + client_entries_length + 16]
- encrypted = content[2 + client_entries_length + 16:]
-
- client_id = hashlib.sha1(authentication_cookie + iv).digest()[:4]
-
- for entry_id, encrypted_session_key in client_keys:
- if entry_id != client_id:
- continue # not the session key for this client
-
- # try decrypting the session key
-
- cipher = Cipher(algorithms.AES(authentication_cookie), modes.CTR(b'\x00' * len(iv)), default_backend())
- decryptor = cipher.decryptor()
- session_key = decryptor.update(encrypted_session_key) + decryptor.finalize()
-
- # attempt to decrypt the intro points with the session key
-
- cipher = Cipher(algorithms.AES(session_key), modes.CTR(iv), default_backend())
- decryptor = cipher.decryptor()
- decrypted = decryptor.update(encrypted) + decryptor.finalize()
-
- # check if the decryption looks correct
-
- if decrypted.startswith(b'introduction-point '):
- return decrypted
-
- return content # nope, unable to decrypt the content
-
- @staticmethod
- def _decrypt_stealth_auth(content, authentication_cookie):
- from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
- from cryptography.hazmat.backends import default_backend
-
- # byte 1 = authentication type, 2-17 = input vector, 18 on = encrypted content
- iv, encrypted = content[1:17], content[17:]
- cipher = Cipher(algorithms.AES(authentication_cookie), modes.CTR(iv), default_backend())
- decryptor = cipher.decryptor()
-
- return decryptor.update(encrypted) + decryptor.finalize()
-
- @staticmethod
- def _parse_introduction_points(content):
- """
- Provides the parsed list of IntroductionPoints for the unencrypted content.
- """
-
- introduction_points = []
- content_io = io.BytesIO(content)
-
- while True:
- content = b''.join(_read_until_keywords('introduction-point', content_io, ignore_first = True))
-
- if not content:
- break # reached the end
-
- attr = dict(INTRODUCTION_POINTS_ATTR)
- entries = _descriptor_components(content, False)
-
- for keyword, values in list(entries.items()):
- value, block_type, block_contents = values[0]
-
- if keyword in SINGLE_INTRODUCTION_POINT_FIELDS and len(values) > 1:
- raise ValueError("'%s' can only appear once in an introduction-point block, but appeared %i times" % (keyword, len(values)))
-
- if keyword == 'introduction-point':
- attr['identifier'] = value
- elif keyword == 'ip-address':
- if not stem.util.connection.is_valid_ipv4_address(value):
- raise ValueError("'%s' is an invalid IPv4 address" % value)
-
- attr['address'] = value
- elif keyword == 'onion-port':
- if not stem.util.connection.is_valid_port(value):
- raise ValueError("'%s' is an invalid port" % value)
-
- attr['port'] = int(value)
- elif keyword == 'onion-key':
- attr['onion_key'] = block_contents
- elif keyword == 'service-key':
- attr['service_key'] = block_contents
- elif keyword == 'intro-authentication':
- auth_entries = []
-
- for auth_value, _, _ in values:
- if ' ' not in auth_value:
- raise ValueError("We expected 'intro-authentication [auth_type] [auth_data]', but had '%s'" % auth_value)
-
- auth_type, auth_data = auth_value.split(' ')[:2]
- auth_entries.append((auth_type, auth_data))
-
- introduction_points.append(IntroductionPoints(**attr))
-
- return introduction_points
+from stem.descriptor.hidden_service import *
diff --git a/stem/response/events.py b/stem/response/events.py
index a9f563c6..7a5f749b 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -701,7 +701,7 @@ class HSDescContentEvent(Event):
:var str directory: hidden service directory servicing the request
:var str directory_fingerprint: hidden service directory's finterprint
:var str directory_nickname: hidden service directory's nickname if it was provided
- :var stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor descriptor: descriptor that was retrieved
+ :var stem.descriptor.hidden_service.HiddenServiceDescriptor descriptor: descriptor that was retrieved
"""
_VERSION_ADDED = stem.version.Requirement.EVENT_HS_DESC_CONTENT
@@ -726,7 +726,7 @@ class HSDescContentEvent(Event):
self.descriptor = None
if desc_content:
- self.descriptor = list(stem.descriptor.hidden_service_descriptor._parse_file(io.BytesIO(desc_content)))[0]
+ self.descriptor = list(stem.descriptor.hidden_service._parse_file(io.BytesIO(desc_content)))[0]
class LogEvent(Event):
diff --git a/test/settings.cfg b/test/settings.cfg
index 1bdb1a0a..3308c104 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -172,7 +172,7 @@ pycodestyle.ignore E722
pycodestyle.ignore stem/__init__.py => E402: import stem.util.connection
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.bandwidth_file
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.extrainfo_descriptor
-pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.hidden_service_descriptor
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.hidden_service
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.microdescriptor
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.networkstatus
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.server_descriptor
@@ -184,9 +184,7 @@ pycodestyle.ignore test/unit/util/connection.py => W291: _tor tor 158
# issue.
pyflakes.ignore run_tests.py => 'unittest' imported but unused
-pyflakes.ignore stem/client/datatype.py => redefinition of unused 'pop' from *
pyflakes.ignore stem/control.py => undefined name 'controller'
-pyflakes.ignore stem/interpreter/__init__.py => undefined name 'raw_input'
pyflakes.ignore stem/manual.py => undefined name 'unichr'
pyflakes.ignore stem/prereq.py => 'int_to_bytes' imported but unused
pyflakes.ignore stem/prereq.py => 'int_from_bytes' imported but unused
@@ -210,6 +208,10 @@ pyflakes.ignore stem/prereq.py => 'cryptography.hazmat.primitives.ciphers.modes'
pyflakes.ignore stem/prereq.py => 'cryptography.hazmat.primitives.ciphers.Cipher' imported but unused
pyflakes.ignore stem/prereq.py => 'cryptography.hazmat.primitives.ciphers.algorithms' imported but unused
pyflakes.ignore stem/prereq.py => 'lzma' imported but unused
+pyflakes.ignore stem/client/datatype.py => redefinition of unused 'pop' from *
+pyflakes.ignore stem/descriptor/hidden_service_descriptor.py => 'stem.descriptor.hidden_service.*' imported but unused
+pyflakes.ignore stem/descriptor/hidden_service_descriptor.py => 'from stem.descriptor.hidden_service import *' used; unable to detect undefined names
+pyflakes.ignore stem/interpreter/__init__.py => undefined name 'raw_input'
pyflakes.ignore stem/response/events.py => undefined name 'long'
pyflakes.ignore stem/util/__init__.py => undefined name 'long'
pyflakes.ignore stem/util/__init__.py => undefined name 'unicode'
@@ -256,7 +258,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.descriptor.hidden_service.TestHiddenServiceDescriptor
|test.unit.descriptor.certificate.TestEd25519Certificate
|test.unit.descriptor.bandwidth_file.TestBandwidthFile
|test.unit.exit_policy.rule.TestExitPolicyRule
diff --git a/test/unit/descriptor/hidden_service_descriptor.py b/test/unit/descriptor/hidden_service.py
similarity index 99%
rename from test/unit/descriptor/hidden_service_descriptor.py
rename to test/unit/descriptor/hidden_service.py
index e9ba012b..c49741d3 100644
--- a/test/unit/descriptor/hidden_service_descriptor.py
+++ b/test/unit/descriptor/hidden_service.py
@@ -1,5 +1,5 @@
"""
-Unit tests for stem.descriptor.hidden_service_descriptor.
+Unit tests for stem.descriptor.hidden_service.
"""
import datetime
@@ -10,7 +10,7 @@ import stem.descriptor
import stem.prereq
import test.require
-from stem.descriptor.hidden_service_descriptor import (
+from stem.descriptor.hidden_service import (
REQUIRED_FIELDS,
DecryptionFailure,
HiddenServiceDescriptor,
More information about the tor-commits
mailing list