[tor-commits] [stem/master] Perform decryption prereq checks upfront

atagar at torproject.org atagar at torproject.org
Sun Oct 6 02:07:34 UTC 2019


commit 46686fe160d082a488b3f4d1edefdd7e7c973f00
Author: Damian Johnson <atagar at torproject.org>
Date:   Tue Oct 1 17:29:30 2019 -0700

    Perform decryption prereq checks upfront
    
    Simplifying our prerequirement checks by doing them upfront, and moving sha3
    checking into our prereq module.
---
 stem/descriptor/hidden_service.py         | 11 +++++++++
 stem/descriptor/hsv3_crypto.py            | 40 +------------------------------
 stem/prereq.py                            | 38 +++++++++++++++++++++++------
 stem/util/tor_tools.py                    | 24 +++++++++++++++----
 test/settings.cfg                         |  2 +-
 test/unit/descriptor/hidden_service_v3.py |  3 +--
 6 files changed, 65 insertions(+), 53 deletions(-)

diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index 075625c6..38c7a34b 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -36,6 +36,7 @@ import stem.descriptor.hsv3_crypto
 import stem.prereq
 import stem.util.connection
 import stem.util.str_tools
+import stem.util.tor_tools
 
 from stem.descriptor import (
   PGP_BLOCK_END,
@@ -555,6 +556,16 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
   # and parses the internal descriptor content.
 
   def _decrypt(self, onion_address, outer_layer = False):
+    if onion_address.endswith('.onion'):
+      onion_address = onion_address[:-6]
+
+    if not stem.prereq.is_crypto_available(ed25519 = True):
+      raise ImportError('Hidden service descriptor decryption requires cryptography version 2.6')
+    elif not stem.prereq._is_sha3_available():
+      raise ImportError('Hidden service descriptor decryption requires python 3.6+ or the pysha3 module (https://pypi.org/project/pysha3/)')
+    elif not stem.util.tor_tools.is_valid_hidden_service_address(onion_address, version = 3):
+      raise ValueError("'%s.onion' isn't a valid hidden service v3 address" % onion_address)
+
     cert_lines = self.signing_cert.split('\n')
     desc_signing_cert = stem.descriptor.certificate.Ed25519Certificate.parse(''.join(cert_lines[1:-1]))
 
diff --git a/stem/descriptor/hsv3_crypto.py b/stem/descriptor/hsv3_crypto.py
index f768659f..7b651418 100644
--- a/stem/descriptor/hsv3_crypto.py
+++ b/stem/descriptor/hsv3_crypto.py
@@ -3,24 +3,6 @@ import binascii
 import hashlib
 import struct
 
-import stem.prereq
-
-# SHA3 requires Python 3.6+ *or* the pysha3 module...
-#
-#   https://github.com/tiran/pysha3
-#
-# If pysha3 is present then importing sha3 will monkey patch the methods we
-# want onto hashlib.
-
-if not hasattr(hashlib, 'sha3_256') or not hasattr(hashlib, 'shake_256'):
-  try:
-    import sha3
-  except ImportError:
-    pass
-
-SHA3_AVAILABLE = hasattr(hashlib, 'sha3_256') and hasattr(hashlib, 'shake_256')
-SHA3_ERROR_MSG = '%s requires python 3.6+ or the pysha3 module (https://pypi.org/project/pysha3/)'
-
 """
 Onion addresses
 
@@ -36,7 +18,7 @@ Onion addresses
 CHECKSUM_CONSTANT = b'.onion checksum'
 
 
-def decode_address(onion_address_str):
+def decode_address(onion_address):
   """
   Parse onion_address_str and return the pubkey.
 
@@ -48,19 +30,8 @@ def decode_address(onion_address_str):
   :raises: ValueError
   """
 
-  if not stem.prereq.is_crypto_available(ed25519 = True):
-    raise ImportError('Onion address decoding requires cryptography version 2.6')
-  elif not SHA3_AVAILABLE:
-    raise ImportError(SHA3_ERROR_MSG % 'Onion address decoding')
-
   from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
 
-  if (len(onion_address_str) != 56 + len('.onion')):
-    raise ValueError('Wrong address length')
-
-  # drop the '.onion'
-  onion_address = onion_address_str[:56]
-
   # base32 decode the addr (convert to uppercase since that's what python expects)
   onion_address = base64.b32decode(onion_address.upper())
 
@@ -101,9 +72,6 @@ Both keys are in bytes
 
 
 def get_subcredential(public_identity_key, blinded_key):
-  if not SHA3_AVAILABLE:
-    raise ImportError(SHA3_ERROR_MSG % 'Hidden service subcredentials')
-
   cred_bytes_constant = 'credential'.encode()
   subcred_bytes_constant = 'subcredential'.encode()
 
@@ -156,9 +124,6 @@ def _ciphertext_mac_is_valid(key, salt, ciphertext, mac):
   XXX spec:   H(mac_key_len | mac_key | salt_len | salt | encrypted)
   """
 
-  if not SHA3_AVAILABLE:
-    raise ImportError(SHA3_ERROR_MSG % 'Hidden service validation')
-
   # Construct our own MAC first
   key_len = struct.pack('>Q', len(key))
   salt_len = struct.pack('>Q', len(salt))
@@ -171,9 +136,6 @@ def _ciphertext_mac_is_valid(key, salt, ciphertext, mac):
 
 
 def _decrypt_descriptor_layer(ciphertext_blob_b64, revision_counter, public_identity_key, subcredential, secret_data, string_constant):
-  if not SHA3_AVAILABLE:
-    raise ImportError(SHA3_ERROR_MSG % 'Hidden service descriptor decryption')
-
   from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
   from cryptography.hazmat.backends import default_backend
 
diff --git a/stem/prereq.py b/stem/prereq.py
index bf86ddef..e8218e7f 100644
--- a/stem/prereq.py
+++ b/stem/prereq.py
@@ -2,13 +2,12 @@
 # See LICENSE for licensing information
 
 """
-Checks for stem dependencies. We require python 2.6 or greater (including the
-3.x series), but note we'll be bumping our requirements to python 2.7 in stem
-2.0. Other requirements for complete functionality are...
+Checks for stem dependencies.
 
-* cryptography module
-
-  * validating descriptor signature integrity
+Aside from Python itself Stem only has soft dependencies, which is to say
+module unavailability only impacts features that require it. For example,
+descriptor signature validation requires 'cryptography'. If unavailable
+stem will still read descriptors - just without signature checks.
 
 ::
 
@@ -22,6 +21,7 @@ Checks for stem dependencies. We require python 2.6 or greater (including the
 """
 
 import functools
+import hashlib
 import inspect
 import platform
 import sys
@@ -52,6 +52,9 @@ def _is_python_26():
   Checks if we're running python 2.6. This isn't for users as it'll be removed
   in stem 2.0 (when python 2.6 support goes away).
 
+  .. deprecated:: 1.8.0
+     Stem 2.x will remove this method along with Python 2.x support.
+
   :returns: **True** if we're running python 2.6, **False** otherwise
   """
 
@@ -65,7 +68,7 @@ def is_python_27():
   Checks if we're running python 2.7 or above (including the 3.x series).
 
   .. deprecated:: 1.5.0
-     Function lacks much utility and will be eventually removed.
+     Stem 2.x will remove this method along with Python 2.x support.
 
   :returns: **True** if we meet this requirement and **False** otherwise
   """
@@ -79,6 +82,9 @@ def is_python_3():
   """
   Checks if we're in the 3.0 - 3.x range.
 
+  .. deprecated:: 1.8.0
+     Stem 2.x will remove this method along with Python 2.x support.
+
   :returns: **True** if we meet this requirement and **False** otherwise
   """
 
@@ -267,3 +273,21 @@ def _is_crypto_ed25519_supported():
   else:
     log.log_once('stem.prereq._is_crypto_ed25519_supported', log.INFO, ED25519_UNSUPPORTED)
     return False
+
+
+def _is_sha3_available():
+  """
+  Check if hashlib has sha3 support. This requires Python 3.6+ *or* the `pysha3
+  module <https://github.com/tiran/pysha3>`_.
+  """
+
+  # If pysha3 is present then importing sha3 will monkey patch the methods we
+  # want onto hashlib.
+
+  if not hasattr(hashlib, 'sha3_256') or not hasattr(hashlib, 'shake_256'):
+    try:
+      import sha3
+    except ImportError:
+      pass
+
+  return hasattr(hashlib, 'sha3_256') and hasattr(hashlib, 'shake_256')
diff --git a/stem/util/tor_tools.py b/stem/util/tor_tools.py
index 2aed5130..b703fa50 100644
--- a/stem/util/tor_tools.py
+++ b/stem/util/tor_tools.py
@@ -128,14 +128,17 @@ def is_valid_connection_id(entry):
   return is_valid_circuit_id(entry)
 
 
-def is_valid_hidden_service_address(entry):
+def is_valid_hidden_service_address(entry, version = None):
   """
   Checks if a string is a valid format for being a hidden service address (not
   including the '.onion' suffix).
 
   .. versionchanged:: 1.8.0
-     Responds with **True** if a version 3 hidden service address, rather than
-     just version 2 addresses.
+     Added the **version** argument, and responds with **True** if a version 3
+     hidden service address rather than just version 2 addresses.
+
+  :param int,list version: versions to check for, if unspecified either v2 or v3
+    hidden service address will provide **True**
 
   :returns: **True** if the string could be a hidden service address, **False**
     otherwise
@@ -144,8 +147,21 @@ def is_valid_hidden_service_address(entry):
   if isinstance(entry, bytes):
     entry = stem.util.str_tools._to_unicode(entry)
 
+  if version is None:
+    version = (2, 3)
+  elif isinstance(version, int):
+    version = [version]
+  elif not isinstance(version, (list, tuple)):
+    raise ValueError('Hidden service version must be an integer or list, not a %s' % type(version).__name__)
+
   try:
-    return bool(HS_V2_ADDRESS_PATTERN.match(entry)) or bool(HS_V3_ADDRESS_PATTERN.match(entry))
+    if 2 in version and bool(HS_V2_ADDRESS_PATTERN.match(entry)):
+      return True
+
+    if 3 in version and bool(HS_V3_ADDRESS_PATTERN.match(entry)):
+      return True
+
+    return False
   except TypeError:
     return False
 
diff --git a/test/settings.cfg b/test/settings.cfg
index 5d755bad..eca719df 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -197,6 +197,7 @@ pyflakes.ignore stem/prereq.py => 'unittest' imported but unused
 pyflakes.ignore stem/prereq.py => 'unittest.mock' imported but unused
 pyflakes.ignore stem/prereq.py => 'long_to_bytes' imported but unused
 pyflakes.ignore stem/prereq.py => 'encoding' imported but unused
+pyflakes.ignore stem/prereq.py => 'sha3' imported but unused
 pyflakes.ignore stem/prereq.py => 'signing' imported but unused
 pyflakes.ignore stem/prereq.py => 'sqlite3' imported but unused
 pyflakes.ignore stem/prereq.py => 'cryptography.utils.int_to_bytes' imported but unused
@@ -212,7 +213,6 @@ 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/descriptor/hsv3_crypto.py => 'sha3' imported but unused
 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'
diff --git a/test/unit/descriptor/hidden_service_v3.py b/test/unit/descriptor/hidden_service_v3.py
index fc4b57dc..60cc06b8 100644
--- a/test/unit/descriptor/hidden_service_v3.py
+++ b/test/unit/descriptor/hidden_service_v3.py
@@ -6,7 +6,6 @@ import functools
 import unittest
 
 import stem.descriptor
-import stem.descriptor.hsv3_crypto
 import stem.prereq
 
 from stem.descriptor.hidden_service import (
@@ -52,7 +51,7 @@ class TestHiddenServiceDescriptorV3(unittest.TestCase):
     self.assertTrue('eaH8VdaTKS' in desc.superencrypted)
     self.assertEqual('aglChCQF+lbzKgyxJJTpYGVShV/GMDRJ4+cRGCp+a2y/yX/tLSh7hzqI7rVZrUoGj74Xr1CLMYO3fXYCS+DPDQ', desc.signature)
 
-    if stem.prereq.is_crypto_available(ed25519 = True) and stem.descriptor.hsv3_crypto.SHA3_AVAILABLE:
+    if stem.prereq.is_crypto_available(ed25519 = True) and stem.prereq._is_sha3_available():
       with open(get_resource('hidden_service_v3_outer_layer'), 'rb') as outer_layer_file:
         self.assertEqual(outer_layer_file.read(), desc._decrypt(HS_ADDRESS, outer_layer = True))
 





More information about the tor-commits mailing list