commit 25bf050edf71a7653970aa9ab41d70e4b4219b63 Author: Damian Johnson atagar@torproject.org Date: Tue Jun 27 08:31:40 2017 -0700
Randomize created server descirptor fields
Now that we're providing a method to create descriptors to callers (rather than just our own tests) we should randomize the default attributes. Gonna do this with all descriptor types, but starting with server descriptors since we just added signing for 'em. --- run_tests.py | 40 +++++++++++++------------------ stem/descriptor/__init__.py | 22 +++++++++++++++++ stem/descriptor/server_descriptor.py | 36 +++++++++++++++------------- test/unit/descriptor/__init__.py | 6 ++--- test/unit/descriptor/export.py | 5 +++- test/unit/descriptor/server_descriptor.py | 9 ++++--- test/unit/tutorial.py | 2 +- 7 files changed, 71 insertions(+), 49 deletions(-)
diff --git a/run_tests.py b/run_tests.py index 802a6b4..c0d9cbc 100755 --- a/run_tests.py +++ b/run_tests.py @@ -310,10 +310,8 @@ def main():
_print_static_issues(static_check_issues)
- runtime_label = '(%i seconds)' % (time.time() - start_time) - if error_tracker.has_errors_occured(): - println('TESTING FAILED %s' % runtime_label, ERROR, STDERR) + println('TESTING FAILED (%i seconds)' % (time.time() - start_time), ERROR, STDERR)
for line in error_tracker: println(' %s' % line, ERROR, STDERR) @@ -329,7 +327,7 @@ def main(): if skipped_tests > 0: println('%i TESTS WERE SKIPPED' % skipped_tests, STATUS)
- println('TESTING PASSED %s\n' % runtime_label, SUCCESS) + println('TESTING PASSED (%i seconds)\n' % (time.time() - start_time), SUCCESS)
new_capabilities = test.get_new_capabilities()
@@ -369,26 +367,23 @@ def _print_static_issues(static_check_issues): def _run_test(args, test_class, output_filters, logging_buffer): start_time = time.time()
+ # Test classes look like... + # + # test.unit.util.conf.TestConf.test_parse_enum_csv + # + # We want to strip the 'test.unit.' or 'test.integ.' prefix since it's + # redundant. We also want to drop the test class name. The individual test + # name at the end it optional (only present if we used the '--test' + # argument). + + label_comp = test_class.split('.')[2:] + del label_comp[-1 if label_comp[-1][0].isupper() else -2] + test_label = ' %-52s' % ('.'.join(label_comp) + '...') + if args.verbose: test.output.print_divider(test_class) else: - # Test classes look like... - # - # test.unit.util.conf.TestConf.test_parse_enum_csv - # - # We want to strip the 'test.unit.' or 'test.integ.' prefix since it's - # redundant. We also want to drop the test class name. The individual test - # name at the end it optional (only present if we used the '--test' - # argument). - - label_comp = test_class.split('.')[2:] - del label_comp[-1 if label_comp[-1][0].isupper() else -2] - label = '.'.join(label_comp) - - label = ' %s...' % label - label = '%-54s' % label - - println(label, STATUS, NO_NL) + println(test_label, STATUS, NO_NL)
try: suite = unittest.TestLoader().loadTestsFromName(test_class) @@ -413,7 +408,7 @@ def _run_test(args, test_class, output_filters, logging_buffer): println(' success (%0.2fs)' % (time.time() - start_time), SUCCESS) else: if args.quiet: - println(label, STATUS, NO_NL, STDERR) + println(test_label, STATUS, NO_NL, STDERR) println(' failed (%0.2fs)' % (time.time() - start_time), ERROR, STDERR) println(test.output.apply_filters(test_results.getvalue(), *output_filters), STDERR) else: @@ -421,7 +416,6 @@ def _run_test(args, test_class, output_filters, logging_buffer): println(test.output.apply_filters(test_results.getvalue(), *output_filters), NO_NL)
test.output.print_logging(logging_buffer) - return run_result
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py index bf336fb..80901af 100644 --- a/stem/descriptor/__init__.py +++ b/stem/descriptor/__init__.py @@ -42,6 +42,7 @@ import codecs import copy import hashlib import os +import random import re import string import tarfile @@ -952,6 +953,27 @@ def _get_pseudo_pgp_block(remaining_contents): return None
+def _random_ipv4_address(): + return '%i.%i.%i.%i' % (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + + +def _random_date(): + return '%i-%02i-%02i %02i:%02i:%02i' % (random.randint(2000, 2015), random.randint(1, 12), random.randint(1, 20), random.randint(0, 23), random.randint(0, 59), random.randint(0, 59)) + + +def _random_crypto_blob(block_type = None): + """ + Provides a random string that can be used for crypto blocks. + """ + + crypto_blob = stem.util.str_tools._split_by_length(base64.b64encode('%0140x' % random.randrange(16 ** 140)), 64) + + if block_type: + return '\n-----BEGIN %s-----\n%s\n-----END %s-----' % (block_type, crypto_blob, block_type) + else: + return crypto_blob + + def _descriptor_components(raw_contents, validate, extra_keywords = (), non_ascii_fields = ()): """ Initial breakup of the server descriptor contents to make parsing easier. diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py index 8dbd0f0..607908d 100644 --- a/stem/descriptor/server_descriptor.py +++ b/stem/descriptor/server_descriptor.py @@ -35,7 +35,9 @@ import base64 import binascii import functools import hashlib +import random import re +import sys
import stem.descriptor.certificate import stem.descriptor.extrainfo_descriptor @@ -49,7 +51,6 @@ import stem.version from stem.util import str_type
from stem.descriptor import ( - CRYPTO_BLOB, PGP_BLOCK_END, Descriptor, _descriptor_content, @@ -65,6 +66,9 @@ from stem.descriptor import ( _parse_forty_character_hex, _parse_protocol_line, _parse_key_block, + _random_ipv4_address, + _random_date, + _random_crypto_blob, )
try: @@ -112,19 +116,6 @@ SINGLE_FIELDS = ( DEFAULT_IPV6_EXIT_POLICY = stem.exit_policy.MicroExitPolicy('reject 1-65535') REJECT_ALL_POLICY = stem.exit_policy.ExitPolicy('reject *:*')
-RELAY_SERVER_HEADER = ( - ('router', 'caerSidi 71.35.133.197 9001 0 0'), - ('published', '2012-03-01 17:15:27'), - ('bandwidth', '153600 256000 104590'), - ('reject', '*:*'), - ('onion-key', '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % CRYPTO_BLOB), - ('signing-key', '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % CRYPTO_BLOB), -) - -RELAY_SERVER_FOOTER = ( - ('router-signature', '\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----' % CRYPTO_BLOB), -) - BRIDGE_SERVER_HEADER = ( ('router', 'Unnamed 10.45.227.253 9001 0 0'), ('router-digest', '006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4'), @@ -818,6 +809,19 @@ class RelayDescriptor(ServerDescriptor):
@classmethod def content(cls, attr = None, exclude = (), sign = False, private_signing_key = None): + base_header = ( + ('router', 'Unnamed%i %s 9001 0 0' % (random.randint(0, sys.maxint), _random_ipv4_address())), + ('published', _random_date()), + ('bandwidth', '153600 256000 104590'), + ('reject', '*:*'), + ('onion-key', _random_crypto_blob('RSA PUBLIC KEY')), + ('signing-key', _random_crypto_blob('RSA PUBLIC KEY')), + ) + + base_footer = ( + ('router-signature', _random_crypto_blob('SIGNATURE')), + ) + if sign: if not stem.prereq.is_crypto_available(): raise ImportError('Signing requires the cryptography module') @@ -861,12 +865,12 @@ class RelayDescriptor(ServerDescriptor): format = serialization.PublicFormat.PKCS1, ).strip()
- content = _descriptor_content(attr, exclude, sign, RELAY_SERVER_HEADER) + b'\nrouter-signature\n' + content = _descriptor_content(attr, exclude, sign, base_header) + b'\nrouter-signature\n' signature = base64.b64encode(private_signing_key.sign(content, padding.PKCS1v15(), hashes.SHA1()))
return content + b'\n'.join([b'-----BEGIN SIGNATURE-----'] + stem.util.str_tools._split_by_length(signature, 64) + [b'-----END SIGNATURE-----\n']) else: - return _descriptor_content(attr, exclude, sign, RELAY_SERVER_HEADER, RELAY_SERVER_FOOTER) + return _descriptor_content(attr, exclude, sign, base_header, base_footer)
@classmethod def create(cls, attr = None, exclude = (), validate = True, sign = False, private_signing_key = None): diff --git a/test/unit/descriptor/__init__.py b/test/unit/descriptor/__init__.py index cd35ae1..bf0e861 100644 --- a/test/unit/descriptor/__init__.py +++ b/test/unit/descriptor/__init__.py @@ -29,7 +29,7 @@ def base_expect_invalid_attr(cls, default_attr, default_value, test, desc_attrs, return base_expect_invalid_attr_for_text(cls, default_attr, default_value, test, cls.content(desc_attrs), attr, expected_value)
-def base_expect_invalid_attr_for_text(cls, default_attr, default_value, test, desc_text, attr = None, expected_value = None): +def base_expect_invalid_attr_for_text(cls, default_attr, default_prefix, test, desc_text, attr = None, expected_value = None): """ Asserts that construction will fail due to desc_text having a malformed attribute. If an attr is provided then we check that it matches an expected @@ -44,7 +44,7 @@ def base_expect_invalid_attr_for_text(cls, default_attr, default_value, test, de # constructed without validation
test.assertEqual(expected_value, getattr(desc, attr)) - elif default_attr and default_value: - test.assertEqual(default_value, getattr(desc, default_attr)) # check a default attribute + elif default_attr and default_prefix: + test.assertTrue(getattr(desc, default_attr).startswith(default_prefix)) # check a default attribute
return desc diff --git a/test/unit/descriptor/export.py b/test/unit/descriptor/export.py index cd005d1..a33c1d4 100644 --- a/test/unit/descriptor/export.py +++ b/test/unit/descriptor/export.py @@ -25,7 +25,10 @@ class TestExport(unittest.TestCase): self.skipTest('(header added in python 2.7)') return
- desc = RelayDescriptor.create() + desc = RelayDescriptor.create({ + 'router': 'caerSidi 71.35.133.197 9001 0 0', + 'published': '2012-03-01 17:15:27', + })
desc_csv = export_csv(desc, included_fields = ('nickname', 'address', 'published'), header = False) expected = 'caerSidi,71.35.133.197,2012-03-01 17:15:27\n' diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py index 227df12..2c47bdf 100644 --- a/test/unit/descriptor/server_descriptor.py +++ b/test/unit/descriptor/server_descriptor.py @@ -40,8 +40,8 @@ TARFILE_FINGERPRINTS = set([ str_type('1F43EE37A0670301AD9CB555D94AFEC2C89FDE86'), ])
-expect_invalid_attr = functools.partial(base_expect_invalid_attr, RelayDescriptor, 'nickname', 'caerSidi') -expect_invalid_attr_for_text = functools.partial(base_expect_invalid_attr_for_text, RelayDescriptor, 'nickname', 'caerSidi') +expect_invalid_attr = functools.partial(base_expect_invalid_attr, RelayDescriptor, 'nickname', 'Unnamed') +expect_invalid_attr_for_text = functools.partial(base_expect_invalid_attr_for_text, RelayDescriptor, 'nickname', 'Unnamed')
class TestServerDescriptor(unittest.TestCase): @@ -440,11 +440,10 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= attributes. """
- desc = RelayDescriptor.create() + desc = RelayDescriptor.create({'router': 'caerSidi 71.35.133.197 9001 0 0'}) self.assertEqual('caerSidi', desc.nickname) self.assertEqual('71.35.133.197', desc.address) self.assertEqual(None, desc.fingerprint) - self.assertTrue(stem.descriptor.CRYPTO_BLOB in desc.onion_key)
def test_with_opt(self): """ @@ -625,7 +624,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= self.assertRaises(ValueError, list, desc_iter)
desc_text = b'@pepperjack very tasty\n@mushrooms not so much\n' - desc_text += RelayDescriptor.content() + desc_text += RelayDescriptor.content({'router': 'caerSidi 71.35.133.197 9001 0 0'}) desc_iter = stem.descriptor.server_descriptor._parse_file(io.BytesIO(desc_text))
desc_entries = list(desc_iter) diff --git a/test/unit/tutorial.py b/test/unit/tutorial.py index cdfb014..b0f5b71 100644 --- a/test/unit/tutorial.py +++ b/test/unit/tutorial.py @@ -162,7 +162,7 @@ class TestTutorial(unittest.TestCase): @patch('stem.descriptor.reader.DescriptorReader', spec = DescriptorReader) def test_mirror_mirror_on_the_wall_4(self, reader_mock, stdout_mock): reader = reader_mock().__enter__() - reader.__iter__.return_value = iter([RelayDescriptor.create()]) + reader.__iter__.return_value = iter([RelayDescriptor.create({'router': 'caerSidi 71.35.133.197 9001 0 0'})])
exec_documentation_example('past_descriptors.py') self.assertEqual('found relay caerSidi (None)\n', stdout_mock.getvalue())