commit 46686fe160d082a488b3f4d1edefdd7e7c973f00 Author: Damian Johnson atagar@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))