commit b43bd7b72de2a50c094601771bf43ed33301071a Author: Damian Johnson atagar@torproject.org Date: Sat Jan 13 18:44:46 2018 -0800
Support unpacking CERTS cells --- stem/client/__init__.py | 19 +++++++++++++++++++ stem/client/cell.py | 34 ++++++++++++++++++++++++++++++++-- test/unit/client/cell.py | 33 +++++++++++++++++++++++++++++---- 3 files changed, 80 insertions(+), 6 deletions(-)
diff --git a/stem/client/__init__.py b/stem/client/__init__.py index c3b18c6c..6909c1a7 100644 --- a/stem/client/__init__.py +++ b/stem/client/__init__.py @@ -17,6 +17,7 @@ a wrapper for :class:`~stem.socket.RelaySocket`, much the same way as +- pop - decodes content with remainder """
+import collections import struct
ZERO = '\x00' @@ -26,6 +27,24 @@ __all__ = [ ]
+class Certificate(collections.namedtuple('Certificate', ['type', 'value'])): + """ + Relay certificate as defined in tor-spec section 4.2. Certificate types + are... + + ==================== =========== + Type Value Description + ==================== =========== + 1 Link key certificate certified by RSA1024 identity + 2 RSA1024 Identity certificate + 3 RSA1024 AUTHENTICATE cell link certificate + ==================== =========== + + :var int type: certificate type + :var bytes value: certificate value + """ + + class Size(object): """ Unsigned `struct.pack format diff --git a/stem/client/cell.py b/stem/client/cell.py index 4d4667d0..e3793491 100644 --- a/stem/client/cell.py +++ b/stem/client/cell.py @@ -40,7 +40,7 @@ import inspect import sys
from stem import UNDEFINED -from stem.client import ZERO, Size +from stem.client import ZERO, Certificate, Size
FIXED_PAYLOAD_LEN = 509
@@ -123,7 +123,7 @@ class Cell(object): if len(content) < payload_len: raise ValueError('%s cell should have a payload of %i bytes, but only had %i' % (cls.NAME, payload_len, len(content)))
- payload = content[:payload_len].rstrip(ZERO) # strip padding + payload = content[:payload_len] content = content[payload_len:]
cells.append(cls._unpack(payload, link_version, circ_id)) @@ -328,10 +328,40 @@ class VPaddingCell(Cell):
class CertsCell(Cell): + """ + Certificate held by the relay we're communicating with. + + :var list certificates: :class:`~stem.client.Certificate` of the relay + """ + NAME = 'CERTS' VALUE = 129 IS_FIXED_SIZE = False
+ def __init__(self, certs): + self.certificates = certs + + @classmethod + def _unpack(cls, content, circ_id, link_version): + cert_count, content = Size.CHAR.pop(content) + certs = [] + + for i in range(cert_count): + if not content: + raise ValueError('CERTS cell indicates it should have %i certificates, but only contained %i' % (cert_count, len(certs))) + + cert_type, content = Size.CHAR.pop(content) + cert_size, content = Size.SHORT.pop(content) + + if cert_size > len(content): + raise ValueError('CERTS cell should have a certificate with %i bytes, but only had %i remaining' % (cert_size, len(content))) + + cert_bytes = content[:cert_size] + content = content[cert_size:] + certs.append(Certificate(cert_type, cert_bytes)) + + return CertsCell(certs) +
class AuthChallengeCell(Cell): NAME = 'AUTH_CHALLENGE' diff --git a/test/unit/client/cell.py b/test/unit/client/cell.py index 3a2a55f2..b1001eb7 100644 --- a/test/unit/client/cell.py +++ b/test/unit/client/cell.py @@ -4,6 +4,7 @@ Unit tests for the stem.client.cell.
import unittest
+from stem.client import Certificate from stem.client.cell import Cell, VersionsCell from test.unit.client import test_data
@@ -34,7 +35,7 @@ class TestCell(unittest.TestCase):
def test_unpack_for_new_link(self): # TODO: we need to support more cell types before we can test this - self.assertRaisesRegexp(NotImplementedError, 'Unpacking not yet implemented for CERTS cells', Cell.unpack, test_data('new_link_cells'), 2) + self.assertRaisesRegexp(NotImplementedError, 'Unpacking not yet implemented for AUTH_CHALLENGE cells', Cell.unpack, test_data('new_link_cells'), 2)
def test_versions_pack(self): self.assertEqual('\x00\x00\x07\x00\x00', VersionsCell.pack([])) @@ -42,6 +43,30 @@ class TestCell(unittest.TestCase): self.assertEqual('\x00\x00\x07\x00\x06\x00\x01\x00\x02\x00\x03', VersionsCell.pack([1, 2, 3]))
def test_versions_unpack(self): - self.assertEqual([], Cell.unpack('\x00\x00\x07\x00\x00', 2)[0].versions) - self.assertEqual([1], Cell.unpack('\x00\x00\x07\x00\x02\x00\x01', 2)[0].versions) - self.assertEqual([1, 2, 3], Cell.unpack('\x00\x00\x07\x00\x06\x00\x01\x00\x02\x00\x03', 2)[0].versions) + test_data = { + '\x00\x00\x07\x00\x00': [], + '\x00\x00\x07\x00\x02\x00\x01': [1], + '\x00\x00\x07\x00\x06\x00\x01\x00\x02\x00\x03': [1, 2, 3], + } + + for cell_bytes, expected in test_data.items(): + self.assertEqual(expected, Cell.unpack(cell_bytes, 2)[0].versions) + + def test_certs_unpack(self): + test_data = { + '\x00\x00\x81\x00\x01\x00': [], + '\x00\x00\x81\x00\x04\x01\x01\x00\x00': [Certificate(type = 1, value = '')], + '\x00\x00\x81\x00\x05\x01\x01\x00\x01\x08': [Certificate(type = 1, value = '\x08')], + } + + for cell_bytes, expected in test_data.items(): + self.assertEqual(expected, Cell.unpack(cell_bytes, 2)[0].certificates) + + # extra bytes after the last certificate should be ignored + + self.assertEqual([Certificate(type = 1, value = '\x08')], Cell.unpack('\x00\x00\x81\x00\x07\x01\x01\x00\x01\x08\x06\x04', 2)[0].certificates) + + # ... but truncated or missing certificates should error + + self.assertRaisesRegexp(ValueError, 'CERTS cell should have a certificate with 3 bytes, but only had 1 remaining', Cell.unpack, '\x00\x00\x81\x00\x05\x01\x01\x00\x03\x08', 2) + self.assertRaisesRegexp(ValueError, 'CERTS cell indicates it should have 2 certificates, but only contained 1', Cell.unpack, '\x00\x00\x81\x00\x05\x02\x01\x00\x01\x08', 2)
tor-commits@lists.torproject.org