[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