commit e0eab2e3bce1dd404c7435b920485f2781146c90
Author: Patrick O'Doherty <p(a)trickod.com>
Date: Sat Mar 11 19:02:45 2017 -0800
Fix Python 3 compatibility issues & broken unit test.
Fix a number of exceptions caused by mixing str and bytes types, which
while OK in Python 2 cause runtime exceptions in Python 3.
Fix broken truncated extension parsing test. Not quite sure how this
test ever failed, as python's slices (in both 2 and 3) always truncate
instead of raising exceptions for out of bounds indices.
---
stem/descriptor/certificate.py | 57 +++++++++++++++++-----------
stem/descriptor/hidden_service_descriptor.py | 2 +-
stem/descriptor/server_descriptor.py | 5 ++-
test/unit/descriptor/certificate.py | 15 ++++----
test/unit/manual.py | 3 ++
5 files changed, 49 insertions(+), 33 deletions(-)
diff --git a/stem/descriptor/certificate.py b/stem/descriptor/certificate.py
index 8904b58..09cf1cf 100644
--- a/stem/descriptor/certificate.py
+++ b/stem/descriptor/certificate.py
@@ -22,6 +22,7 @@ contain one SignedWithEd25519KeyCertificateExtension.
"""
import base64
+import binascii
import hashlib
import time
@@ -37,11 +38,14 @@ except ImportError:
SIGNATURE_LENGTH = 64
STANDARD_ATTRIBUTES_LENGTH = 40
CERTIFICATE_FLAGS_LENGTH = 4
-ED25519_ROUTER_SIGNATURE_PREFIX = 'Tor router descriptor signature v1'
+ED25519_ROUTER_SIGNATURE_PREFIX = b'Tor router descriptor signature v1'
def _bytes_to_long(b):
- return long(b.encode('hex'), 16)
+ if stem.prereq.is_python_3():
+ return int(binascii.hexlify(stem.util.str_tools._to_bytes(b)), 16)
+ else:
+ return long(binascii.hexlify(b), 16)
def _parse_long_offset(offset, length):
@@ -59,21 +63,22 @@ def _parse_offset(offset, length):
def _parse_certificate(raw_contents, master_key_bytes, validate = False):
- version, cert_type = raw_contents[0:2]
+ version = raw_contents[0:1]
+ cert_type = raw_contents[1:2]
- if version == '\x01':
- if cert_type == '\x04':
+ if version == b'\x01':
+ if cert_type == b'\x04':
return Ed25519KeyCertificate(raw_contents, master_key_bytes, validate = validate)
- elif cert_type == '\x05':
+ elif cert_type == b'\x05':
# TLS link certificated signed with ed25519 signing key
pass
- elif cert_type == '\x06':
+ elif cert_type == b'\x06':
# Ed25519 authentication signed with ed25519 signing key
pass
else:
- raise ValueError("Unknown Certificate type %s" % cert_type.encode('hex'))
+ raise ValueError("Unknown Certificate type %s" % binascii.hexlify(cert_type))
else:
- raise ValueError("Unknown Certificate version %s" % version.encode('hex'))
+ raise ValueError("Unknown Certificate version %s" % binascii.hexlify(version))
def _parse_extensions(raw_contents):
@@ -83,18 +88,19 @@ def _parse_extensions(raw_contents):
extensions = []
extension_bytes = raw_contents[STANDARD_ATTRIBUTES_LENGTH:-SIGNATURE_LENGTH]
+
while len(extension_bytes) > 0:
- try:
- ext_length = _bytes_to_long(extension_bytes[0:2])
- ext_type, ext_flags = extension_bytes[2:CERTIFICATE_FLAGS_LENGTH]
- ext_data = extension_bytes[CERTIFICATE_FLAGS_LENGTH:(CERTIFICATE_FLAGS_LENGTH + ext_length)]
- except:
+ ext_length = _bytes_to_long(extension_bytes[0:2])
+ ext_type = extension_bytes[2:3]
+ ext_flags = extension_bytes[3:CERTIFICATE_FLAGS_LENGTH]
+ ext_data = extension_bytes[CERTIFICATE_FLAGS_LENGTH:(CERTIFICATE_FLAGS_LENGTH + ext_length)]
+ if len(ext_type) == 0 or len(ext_flags) == 0 or len(ext_data) == 0:
raise ValueError('Certificate contained truncated extension')
if ext_type == SignedWithEd25519KeyCertificateExtension.TYPE:
extension = SignedWithEd25519KeyCertificateExtension(ext_type, ext_flags, ext_data)
else:
- raise ValueError('Invalid certificate extension type: %s' % ext_type.encode('hex'))
+ raise ValueError('Invalid certificate extension type: %s' % binascii.hexlify(ext_type))
extensions.append(extension)
extension_bytes = extension_bytes[CERTIFICATE_FLAGS_LENGTH + ext_length:]
@@ -127,19 +133,23 @@ class Certificate(object):
def __init__(self, raw_contents, identity_key, validate = False):
self.certificate_bytes = raw_contents
- self.identity_key = identity_key
+
+ if type(identity_key) == bytes:
+ self.identity_key = stem.util.str_tools._to_unicode(identity_key)
+ else:
+ self.identity_key = identity_key
self.__set_certificate_entries(raw_contents)
def __set_certificate_entries(self, raw_contents):
entries = OrderedDict()
- for key, func in Certificate.ATTRIBUTES.iteritems():
+ for key, func in Certificate.ATTRIBUTES.items():
try:
entries[key] = func(raw_contents)
except IndexError:
raise ValueError('Unable to get bytes for %s from certificate' % key)
- for key, value in entries.iteritems():
+ for key, value in entries.items():
setattr(self, key, value)
@@ -167,7 +177,7 @@ class Ed25519KeyCertificate(Certificate):
signature_bytes = base64.b64decode(stem.util.str_tools._to_bytes(signature) + b'=' * missing_padding)
verify_key = nacl.signing.VerifyKey(self.certified_key)
- signed_part = descriptor[:descriptor.index('router-sig-ed25519 ') + len('router-sig-ed25519 ')]
+ signed_part = descriptor[:descriptor.index(b'router-sig-ed25519 ') + len('router-sig-ed25519 ')]
descriptor_with_prefix = ED25519_ROUTER_SIGNATURE_PREFIX + signed_part
descriptor_sha256_digest = hashlib.sha256(descriptor_with_prefix).digest()
@@ -181,10 +191,11 @@ class Ed25519KeyCertificate(Certificate):
raise ValueError('Certificate validation requires the pynacl module')
import nacl.signing
+ import nacl.encoding
from nacl.exceptions import BadSignatureError
if self.identity_key:
- verify_key = nacl.signing.VerifyKey(base64.b64decode(self.identity_key + '='))
+ verify_key = nacl.signing.VerifyKey(self.identity_key + '=', encoder=nacl.encoding.Base64Encoder)
else:
verify_key = nacl.singing.VerifyKey(self.extensions[0].ext_data)
@@ -195,7 +206,7 @@ class Ed25519KeyCertificate(Certificate):
class CertificateExtension(object):
- KNOWN_TYPES = ['\x04']
+ KNOWN_TYPES = [b'\x04']
def __init__(self, ext_type, ext_flags, ext_data):
self.ext_type = ext_type
@@ -206,11 +217,11 @@ class CertificateExtension(object):
return self.ext_type in CertificateExtension.KNOWN_TYPES
def affects_validation(self):
- return self.ext_flags == '\x01'
+ return self.ext_flags == b'\x01'
class SignedWithEd25519KeyCertificateExtension(CertificateExtension):
- TYPE = '\x04'
+ TYPE = b'\x04'
def __init__(self, ext_type, ext_flags, ext_data):
super(SignedWithEd25519KeyCertificateExtension, self).__init__(ext_type, ext_flags, ext_data)
diff --git a/stem/descriptor/hidden_service_descriptor.py b/stem/descriptor/hidden_service_descriptor.py
index dc42286..9c87500 100644
--- a/stem/descriptor/hidden_service_descriptor.py
+++ b/stem/descriptor/hidden_service_descriptor.py
@@ -326,7 +326,7 @@ class HiddenServiceDescriptor(Descriptor):
# try decrypting the session key
- cipher = Cipher(algorithms.AES(authentication_cookie), modes.CTR(stem.util.str_tools._to_bytes('\x00' * len(iv))), default_backend())
+ 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()
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index a695966..9cd709b 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -35,6 +35,7 @@ import functools
import hashlib
import re
import base64
+import binascii
import stem.descriptor.extrainfo_descriptor
import stem.exit_policy
@@ -800,8 +801,8 @@ class RelayDescriptor(ServerDescriptor):
"""
signing_key_digest = hashlib.sha1(_bytes_for_block(self.signing_key)).digest()
- data = signing_key_digest + base64.b64decode(self.ed25519_master_key + b'=')
- return data.encode('hex').upper()
+ data = signing_key_digest + base64.b64decode(self.ed25519_master_key + '=')
+ return stem.util.str_tools._to_unicode(binascii.hexlify(data).upper())
def _compare(self, other, method):
if not isinstance(other, RelayDescriptor):
diff --git a/test/unit/descriptor/certificate.py b/test/unit/descriptor/certificate.py
index 61f6068..4e71e84 100644
--- a/test/unit/descriptor/certificate.py
+++ b/test/unit/descriptor/certificate.py
@@ -11,7 +11,7 @@ import test.runner
class TestCertificate(unittest.TestCase):
def test_with_invalid_version(self):
- cert_bytes = '\x02\x04'
+ cert_bytes = b'\x02\x04'
self.assertRaisesRegexp(
ValueError,
'Unknown Certificate version',
@@ -21,7 +21,7 @@ class TestCertificate(unittest.TestCase):
)
def test_with_invalid_type(self):
- cert_bytes = '\x01\x07'
+ cert_bytes = b'\x01\x07'
self.assertRaisesRegexp(
ValueError,
'Unknown Certificate type',
@@ -34,13 +34,14 @@ class TestCertificate(unittest.TestCase):
cert_bytes = '\x00' * 39 # First 40 bytes are standard fields
cert_bytes += '\x01' # n_extensions = 1
cert_bytes += '\x00\x08' # extension length = 8 bytes
+ cert_bytes += '\x04' # ext_type = 0x04
cert_bytes += stem.descriptor.certificate.SIGNATURE_LENGTH * '\x00' # pad empty signature block
self.assertRaisesRegexp(
ValueError,
'Certificate contained truncated extension',
stem.descriptor.certificate._parse_extensions,
- cert_bytes
+ stem.util.str_tools._to_bytes(cert_bytes)
)
def test_parse_extensions_invalid_certificate_extension_type(self):
@@ -54,7 +55,7 @@ class TestCertificate(unittest.TestCase):
ValueError,
'Invalid certificate extension type:',
stem.descriptor.certificate._parse_extensions,
- cert_bytes
+ stem.util.str_tools._to_bytes(cert_bytes)
)
def test_parse_extensions_invalid_n_extensions_count(self):
@@ -69,7 +70,7 @@ class TestCertificate(unittest.TestCase):
ValueError,
'n_extensions was 2 but parsed 1',
stem.descriptor.certificate._parse_extensions,
- cert_bytes
+ stem.util.str_tools._to_bytes(cert_bytes)
)
def test_ed25519_key_certificate_without_extensions(self):
@@ -81,7 +82,7 @@ class TestCertificate(unittest.TestCase):
ValueError,
'Ed25519KeyCertificate missing SignedWithEd25519KeyCertificateExtension extension',
stem.descriptor.certificate._parse_certificate,
- cert_bytes,
+ stem.util.str_tools._to_bytes(cert_bytes),
None,
validate = True
)
@@ -107,7 +108,7 @@ class TestCertificate(unittest.TestCase):
ValueError,
'Ed25519KeyCertificate signature invalid',
stem.descriptor.certificate._parse_certificate,
- cert_bytes,
+ stem.util.str_tools._to_bytes(cert_bytes),
master_key_base64,
validate = True
)
diff --git a/test/unit/manual.py b/test/unit/manual.py
index 5f939c3..671ed93 100644
--- a/test/unit/manual.py
+++ b/test/unit/manual.py
@@ -173,6 +173,9 @@ class TestManual(unittest.TestCase):
self.assertEqual(EXPECTED_CLI_OPTIONS, manual.commandline_options)
self.assertEqual(EXPECTED_SIGNALS, manual.signals)
self.assertEqual(EXPECTED_FILES, manual.files)
+ if EXPECTED_CONFIG_OPTIONS != manual.config_options:
+ print(EXPECTED_CONFIG_OPTIONS)
+ print(manual.config_options)
self.assertEqual(EXPECTED_CONFIG_OPTIONS, manual.config_options)
def test_parsing_with_unknown_options(self):