[tor-commits] [stem/master] Support unpacking CERTS cells

atagar at torproject.org atagar at torproject.org
Sun Jan 21 02:04:04 UTC 2018


commit b43bd7b72de2a50c094601771bf43ed33301071a
Author: Damian Johnson <atagar at 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)





More information about the tor-commits mailing list