commit 32188dd28f20170f8d2b2dd1da73e29b47bac1a2 Author: Damian Johnson atagar@torproject.org Date: Sun Jan 21 20:24:35 2018 -0800
Support CREATE_FAST and CREATED_FAST cells --- stem/client/cell.py | 125 ++++++++++++++++++++++++++++++++++++++--------- test/unit/client/cell.py | 31 ++++++++++++ 2 files changed, 133 insertions(+), 23 deletions(-)
diff --git a/stem/client/cell.py b/stem/client/cell.py index 3a596b9c..6f107b79 100644 --- a/stem/client/cell.py +++ b/stem/client/cell.py @@ -49,6 +49,7 @@ from stem.util import _hash_attr, datetime_to_unix
FIXED_PAYLOAD_LEN = 509 AUTH_CHALLENGE_SIZE = 32 +HASH_LEN = 20
class Cell(object): @@ -120,7 +121,7 @@ class Cell(object): raise ValueError('%s cell should have a payload of %i bytes, but only had %i' % (cls.NAME, payload_len, len(content)))
payload, content = split(content, payload_len) - return cls._unpack(payload, link_version, circ_id), content + return cls._unpack(payload, circ_id, link_version), content
@classmethod def _pack(cls, link_version, payload, circ_id = 0): @@ -144,6 +145,15 @@ class Cell(object): :raise: **ValueError** if cell type invalid or payload is too large """
+ if isinstance(cls, CircuitCell) and circ_id is None: + if cls.NAME.startswith('CREATE'): + # Since we're initiating the circuit we pick any value from a range + # that's determined by our link version. + + circ_id = 0x80000000 if link_version > 3 else 0x01 + else: + raise ValueError('%s cells require a circ_id' % cls.NAME) + cell = io.BytesIO() cell.write(Size.LONG.pack(circ_id) if link_version > 3 else Size.SHORT.pack(circ_id)) cell.write(Size.CHAR.pack(cls.VALUE)) @@ -189,30 +199,12 @@ class Cell(object): class CircuitCell(Cell): """ Cell concerning circuits. - """ - - @classmethod - def _pack(cls, link_version, payload, circ_id): - """ - Provides bytes that can be used on the wire for these cell attributes. - - :param str name: cell command - :param int link_version: link protocol version - :param bytes payload: cell payload - :param int circ_id: circuit id - - :raise: **ValueError** if cell type invalid or payload is too large - """ - - if circ_id is None and cls.NAME.startswith('CREATE'): - # Since we're initiating the circuit we pick any value from a range - # that's determined by our link version.
- circ_id = 0x80000000 if link_version > 3 else 0x01 - else: - raise ValueError('%s cells require a circ_id' % cls.NAME) + :var int circ_id: circuit id + """
- return Cell._pack(link_version, payload, circ_id) + def __init__(self, circ_id): + self.circ_id = circ_id
class PaddingCell(Cell): @@ -275,16 +267,103 @@ class DestroyCell(CircuitCell):
class CreateFastCell(CircuitCell): + """ + Create a circuit with our first hop. This is lighter weight than further hops + because we've already established the relay's identity and secret key. + + :var bytes key_material: randomized key material + """ + NAME = 'CREATE_FAST' VALUE = 5 IS_FIXED_SIZE = True
+ def __init__(self, circ_id, key_material): + super(CreateFastCell, self).__init__(circ_id) + self.key_material = key_material + + @classmethod + def pack(cls, link_version, circ_id, key_material = None): + """ + Provides a randomized circuit construction payload. + + :param int link_version: link protocol version + :param int circ_id: circuit id + :param bytes key_material: randomized key material + + :returns: **bytes** with our randomized key material + """ + + if key_material and len(key_material) != HASH_LEN: + raise ValueError('Key material should be %i bytes, but was %i' % (HASH_LEN, len(key_material))) + + return cls._pack(link_version, key_material if key_material else os.urandom(HASH_LEN), circ_id) + + @classmethod + def _unpack(cls, content, circ_id, link_version): + content = content.rstrip(ZERO) + + if len(content) != HASH_LEN: + raise ValueError('Key material should be %i bytes, but was %i' % (HASH_LEN, len(content))) + + return CreateFastCell(circ_id, content) + + def __hash__(self): + return _hash_attr(self, 'key_material') +
class CreatedFastCell(CircuitCell): + """ + CREATE_FAST reply. + + :var bytes key_material: randomized key material + :var bytes derivative_key: hash proving the relay knows our shared key + """ + NAME = 'CREATED_FAST' VALUE = 6 IS_FIXED_SIZE = True
+ def __init__(self, circ_id, key_material, derivative_key): + super(CreatedFastCell, self).__init__(circ_id) + self.key_material = key_material + self.derivative_key = derivative_key + + @classmethod + def pack(cls, link_version, circ_id, derivative_key, key_material = None): + """ + Provides a randomized circuit construction payload. + + :param int link_version: link protocol version + :param int circ_id: circuit id + :param bytes derivative_key: hash proving the relay knows our shared key + :param bytes key_material: randomized key material + + :returns: **bytes** with our randomized key material + """ + + if key_material and len(key_material) != HASH_LEN: + raise ValueError('Key material should be %i bytes, but was %i' % (HASH_LEN, len(key_material))) + elif len(derivative_key) != HASH_LEN: + raise ValueError('Derivatived key should be %i bytes, but was %i' % (HASH_LEN, len(derivative_key))) + + if not key_material: + key_material = os.urandom(HASH_LEN) + + return cls._pack(link_version, key_material + derivative_key, circ_id) + + @classmethod + def _unpack(cls, content, circ_id, link_version): + content = content.rstrip(ZERO) + + if len(content) != HASH_LEN * 2: + raise ValueError('Key material and derivatived key should be %i bytes, but was %i' % (HASH_LEN * 2, len(content))) + + return CreatedFastCell(circ_id, content[:HASH_LEN], content[HASH_LEN:]) + + def __hash__(self): + return _hash_attr(self, 'derivative_key', 'key_material') +
class VersionsCell(Cell): """ diff --git a/test/unit/client/cell.py b/test/unit/client/cell.py index 22de54d9..577114c3 100644 --- a/test/unit/client/cell.py +++ b/test/unit/client/cell.py @@ -13,6 +13,8 @@ from stem.client.cell import ( FIXED_PAYLOAD_LEN, Cell, PaddingCell, + CreateFastCell, + CreatedFastCell, VersionsCell, NetinfoCell, VPaddingCell, @@ -27,6 +29,14 @@ PADDING_CELLS = { '\x00\x00\x00' + RANDOM_PAYLOAD: RANDOM_PAYLOAD, }
+CREATE_FAST_CELLS = { + ('\x80\x00\x00\x00\x05\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce' + ZERO * 489): (2147483648, '\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce'), +} + +CREATED_FAST_CELLS = { + ('\x80\x00\x00\x00\x06\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce\x13Z\x99\xb2\x1e\xb6\x05\x85\x17\xfc\x1c\x00{\xa9\xae\x83^K\x99\xb2' + ZERO * 469): (2147483648, '\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce', '\x13Z\x99\xb2\x1e\xb6\x05\x85\x17\xfc\x1c\x00{\xa9\xae\x83^K\x99\xb2'), +} + VERSIONS_CELLS = { '\x00\x00\x07\x00\x00': [], '\x00\x00\x07\x00\x02\x00\x01': [1], @@ -118,6 +128,27 @@ class TestCell(unittest.TestCase): self.assertEqual(cell_bytes, PaddingCell.pack(2, payload)) self.assertEqual(payload, Cell.unpack(cell_bytes, 2)[0].payload)
+ def test_create_fast(self): + for cell_bytes, (circ_id, key_material) in CREATE_FAST_CELLS.items(): + self.assertEqual(cell_bytes, CreateFastCell.pack(5, circ_id, key_material)) + + cell = Cell.unpack(cell_bytes, 5)[0] + self.assertEqual(circ_id, cell.circ_id) + self.assertEqual(key_material, cell.key_material) + + self.assertRaisesRegexp(ValueError, 'Key material should be 20 bytes, but was 3', CreateFastCell.pack, 2, 5, 'boo') + + def test_created_fast(self): + for cell_bytes, (circ_id, key_material, derivative_key) in CREATED_FAST_CELLS.items(): + self.assertEqual(cell_bytes, CreatedFastCell.pack(5, circ_id, derivative_key, key_material)) + + cell = Cell.unpack(cell_bytes, 5)[0] + self.assertEqual(circ_id, cell.circ_id) + self.assertEqual(key_material, cell.key_material) + self.assertEqual(derivative_key, cell.derivative_key) + + self.assertRaisesRegexp(ValueError, 'Key material should be 20 bytes, but was 3', CreateFastCell.pack, 2, 5, 'boo') + def test_versions_packing(self): for cell_bytes, versions in VERSIONS_CELLS.items(): self.assertEqual(cell_bytes, VersionsCell.pack(versions))
tor-commits@lists.torproject.org