[tor-commits] [stem/master] Sha256 extrainfo descriptor digests

atagar at torproject.org atagar at torproject.org
Thu Nov 15 20:29:50 UTC 2018


commit 70d6e35047b3dc15fcd8438a9eb824e0105ed364
Author: Damian Johnson <atagar at torproject.org>
Date:   Mon Nov 12 10:38:55 2018 -0800

    Sha256 extrainfo descriptor digests
    
    When referencing digests tor now includes both sha1 and sha256 digests. As
    such, beginning to expand our digest() methods to do the same...
    
      https://trac.torproject.org/projects/tor/ticket/28398
    
    I'm starting with extrainfo descriptors because their hashes are referenced by
    our server descriptors, providing easy test data to see if we're doing this
    right or not...
    
      % curl http://128.31.0.39:9131/tor/server/fp/3BB34C63072D9D10E836EE42968713F7B9325F66 > /tmp/my_server_desc
      % curl http://128.31.0.39:9131/tor/server/extra/3BB34C63072D9D10E836EE42968713F7B9325F66 > /tmp/my_extrainfo_desc
    
      % grep extra /tmp/my_server_desc
      extra-info-digest 5BEBC13FDA976050D3A0632EE6508FD1BF1D1750 FNzZZtYPlMjBeb78Wv0zS5DUIPGB3TrpJ3k79MZURMU
    
      % python
      >>> import stem.descriptor
      >>> desc = next(stem.descriptor.parse_file('/tmp/my_extrainfo_desc', 'extra-info 1.0'))
    
      # Good! The below shows that our sha1 digest matches what our server
      # descriptor says it should be.
    
      >>> desc.digest(stem.descriptor.DigestHashType.SHA1)
      '5BEBC13FDA976050D3A0632EE6508FD1BF1D1750'
    
      # Bad! This should *not* mismatch. >:(
    
      >>> desc.digest(stem.descriptor.DigestHashType.SHA256)
      'ciuNPeDfpiBQfowP7N1g7jPsuHR9fwceTTFyknNdyvY'
    
    Unfortunately while I'm clearly doing something wrong, I'm puzzled about why we
    mismatch. Our dir-spec's extra-info-digest description is pretty clear...
    
      "sha256-digest" is a base64-encoded SHA256 digest of the extra-info
      document, computed over the same data.
    
    We're definitely hashing the same data (otherwise the sha1 wouldn't match).
    This code also certainly seems to be doing exactly what the spec says (base64
    encoding the sha256 digest). So... huh.
    
    Gonna punt this over to irl who requested this to see if he can spot what I'm
    doing wrong.
---
 stem/descriptor/__init__.py             |  5 +++++
 stem/descriptor/extrainfo_descriptor.py | 33 +++++++++++++++++++++++++++------
 2 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index 13c69f11..943106c4 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -118,6 +118,11 @@ skFtXhOHHqTRN4GPPrZsAIUOQGzQtGb66IQgT4tO/pj+P6QmSCCdTfhvGfgTCsC+
 WPi4Fl2qryzTb3QO5r5x7T8OsG2IBUET1bLQzmtbC560SYR49IvVAgMBAAE=
 """
 
+DigestHashType = stem.util.enum.UppercaseEnum(
+  'SHA1',
+  'SHA256',
+)
+
 DocumentHandler = stem.util.enum.UppercaseEnum(
   'ENTRIES',
   'DOCUMENT',
diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py
index 8d8894d1..2df4bc71 100644
--- a/stem/descriptor/extrainfo_descriptor.py
+++ b/stem/descriptor/extrainfo_descriptor.py
@@ -67,6 +67,7 @@ Extra-info descriptors are available from a few sources...
   ===================== ===========
 """
 
+import base64
 import functools
 import hashlib
 import re
@@ -79,6 +80,7 @@ import stem.util.str_tools
 from stem.descriptor import (
   PGP_BLOCK_END,
   Descriptor,
+  DigestHashType,
   create_signing_key,
   _descriptor_content,
   _read_until_keywords,
@@ -866,11 +868,16 @@ class ExtraInfoDescriptor(Descriptor):
     else:
       self._entries = entries
 
-  def digest(self):
+  def digest(self, hash_type = DigestHashType.SHA1):
     """
     Provides the upper-case hex encoded sha1 of our content. This value is part
     of the server descriptor entry for this relay.
 
+    .. versionchanged:: 1.8.0
+       Added the hash_type argument.
+
+    :param stem.descriptor.DigestHashType hash_type: digest hashing algorithm
+
     :returns: **str** with the upper-case hex digest value for this server
       descriptor
     """
@@ -946,11 +953,20 @@ class RelayExtraInfoDescriptor(ExtraInfoDescriptor):
     return cls(cls.content(attr, exclude, sign, signing_key), validate = validate)
 
   @lru_cache()
-  def digest(self):
+  def digest(self, hash_type = DigestHashType.SHA1):
     # our digest is calculated from everything except our signature
     raw_content, ending = str(self), '\nrouter-signature\n'
-    raw_content = raw_content[:raw_content.find(ending) + len(ending)]
-    return hashlib.sha1(stem.util.str_tools._to_bytes(raw_content)).hexdigest().upper()
+    raw_content = stem.util.str_tools._to_bytes(raw_content[:raw_content.find(ending) + len(ending)])
+
+    if hash_type == DigestHashType.SHA1:
+      return hashlib.sha1(raw_content).hexdigest().upper()
+    elif hash_type == DigestHashType.SHA256:
+      # descriptors drop '=' hash padding from its fields (such as our server
+      # descriptor's extra-info-digest), so doing the same here so we match
+
+      return base64.b64encode(hashlib.sha256(raw_content).digest()).rstrip('=')
+    else:
+      raise NotImplementedError('Extrainfo descriptor digests are only available in sha1 and sha256, not %s' % hash_type)
 
 
 class BridgeExtraInfoDescriptor(ExtraInfoDescriptor):
@@ -991,8 +1007,13 @@ class BridgeExtraInfoDescriptor(ExtraInfoDescriptor):
       ('router-digest', _random_fingerprint()),
     ))
 
-  def digest(self):
-    return self._digest
+  def digest(self, hash_type = DigestHashType.SHA1):
+    if hash_type == DigestHashType.SHA1:
+      return self._digest
+    elif hash_type == DigestHashType.SHA256:
+      return self.router_digest_sha256
+    else:
+      raise NotImplementedError('Bridge extrainfo digests are only available in sha1 and sha256, not %s' % hash_type)
 
   def _required_fields(self):
     excluded_fields = [





More information about the tor-commits mailing list