[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Refinements to previous checkin after code review/feedback
commit 5da6b9790da266f96258c7c6d6a439ca2ef06529
Author: Eoin o Fearghail <eoin.o.fearghail@xxxxxxxxx>
Date: Tue Nov 27 21:04:14 2012 +0000
Refinements to previous checkin after code review/feedback
cf https://trac.torproject.org/projects/tor/ticket/5810
Removed most of the logging code
_digest function now returns the digest in uppercase hex
digest value is now calculated once & cached for evermore.
moved key string manipulation code to a separate function as it is used
more than once, cf _get_key_bytes()
reverted change to test/integ/descriptor/server_descriptor as _digest now returns uppercase hex
added some documentation to _digest()
added some documentation to sign_descriptor_content()
---
stem/descriptor/server_descriptor.py | 190 ++++++++++++++--------------
stem/prereq.py | 2 +-
test/integ/descriptor/server_descriptor.py | 2 +-
test/mocking.py | 15 ++-
4 files changed, 109 insertions(+), 100 deletions(-)
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index 9f3dbb3..bed6b89 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -596,121 +596,106 @@ class RelayDescriptor(ServerDescriptor):
# validate the descriptor if required
if validate:
- # ensure the digest of the descriptor has been calculated
- self.digest()
self._validate_content()
def digest(self):
- # Digest is calculated from everything in the
- # descriptor except the router-signature.
- raw_descriptor = str(self)
- start_token = "router "
- sig_token = "\nrouter-signature\n"
- start = raw_descriptor.find(start_token)
- sig_start = raw_descriptor.find(sig_token)
- end = sig_start + len(sig_token)
- if start >= 0 and sig_start > 0 and end > start:
- for_digest = raw_descriptor[start:end]
- digest_hash = hashlib.sha1(for_digest)
- self._digest = digest_hash.hexdigest()
- else:
- log.warn("unable to calculate digest for descriptor")
- raise ValueError("unable to calculate digest for descriptor")
+ """
+ Get the digest for this descriptor.
+ If the digest has not already been calculated it will be done inline.
+ :raises: ValueError if the digest canot be calculated
+ :returns: the digest string encoded in uppercase hex
+ """
+
+ if self._digest is None:
+ # Digest is calculated from everything in the
+ # descriptor except the router-signature.
+ raw_descriptor = str(self)
+ start_token = "router "
+ sig_token = "\nrouter-signature\n"
+ start = raw_descriptor.find(start_token)
+ sig_start = raw_descriptor.find(sig_token)
+ end = sig_start + len(sig_token)
+ if start >= 0 and sig_start > 0 and end > start:
+ for_digest = raw_descriptor[start:end]
+ digest_hash = hashlib.sha1(for_digest)
+ self._digest = digest_hash.hexdigest().upper()
+ else:
+ raise ValueError("unable to calculate digest for descriptor")
return self._digest
def _validate_content(self):
"""
- Validates that our content matches our signature.
-
- :raises a ValueError if signature does not match content,
+ Validates that the descriptor content matches the signature.
+ :raises: ValueError if the signature does not match the content
"""
- if not self.signature:
- log.warn("Signature missing")
- raise ValueError("Signature missing")
-
- # strips off the '-----BEGIN RSA PUBLIC KEY-----' header and corresponding footer
- key_as_string = ''.join(self.signing_key.split('\n')[1:4])
+ key_as_bytes = RelayDescriptor._get_key_bytes(self.signing_key)
- # calculate the signing key hash
- key_as_der = base64.b64decode(key_as_string)
- key_der_as_hash = hashlib.sha1(key_as_der).hexdigest()
-
- # if we have a fingerprint then check that our fingerprint is a hash of
- # our signing key
+ # ensure the fingerprint is a hash of the signing key
if self.fingerprint:
+ # calculate the signing key hash
+ key_der_as_hash = hashlib.sha1(key_as_bytes).hexdigest()
if key_der_as_hash != self.fingerprint.lower():
- log.warn("Hash of our signing key doesn't match our fingerprint. Signing key hash: %s, fingerprint: %s" % (key_der_as_hash, self.fingerprint.lower()))
+ log.warn("Signing key hash: %s != fingerprint: %s" % (key_der_as_hash, self.fingerprint.lower()))
raise ValueError("Fingerprint does not match hash")
- else:
- log.notice("No fingerprint for this descriptor")
- try:
- self._verify_descriptor(key_as_der)
- log.info("Descriptor verified.")
- except ValueError, e:
- log.warn("Failed to verify descriptor: %s" % e)
- raise e
+ self._verify_descriptor(key_as_bytes)
def _verify_descriptor(self, key_as_der):
if not stem.prereq.is_crypto_available():
return
- else:
- from Crypto.Util import asn1
- from Crypto.Util.number import bytes_to_long, long_to_bytes
-
- # get the ASN.1 sequence
- seq = asn1.DerSequence()
- seq.decode(key_as_der)
- modulus = seq[0]
- public_exponent = seq[1] #should always be 65537
-
- # convert the descriptor signature to an int before decrypting it
- sig_as_string = ''.join(self.signature.split('\n')[1:4])
- sig_as_bytes = base64.b64decode(sig_as_string)
- sig_as_long = bytes_to_long(sig_as_bytes)
-
- # use the public exponent[e] & the modulus[n] to decrypt the int
- decrypted_int = pow(sig_as_long, public_exponent ,modulus)
- # block size will always be 128 for a 1024 bit key
- blocksize = 128
- # convert the int to a byte array.
- decrypted_bytes = long_to_bytes(decrypted_int, blocksize)
-
- ############################################################################
- ## The decrypted bytes should have a structure exactly along these lines.
- ## 1 byte - [null '\x00']
- ## 1 byte - [block type identifier '\x01'] - Should always be 1
- ## N bytes - [padding '\xFF' ]
- ## 1 byte - [separator '\x00' ]
- ## M bytes - [message]
- ## Total - 128 bytes
- ## More info here http://www.ietf.org/rfc/rfc2313.txt
- ## esp the Notes in section 8.1
- ############################################################################
- try:
- if decrypted_bytes.index('\x00\x01') != 0:
- log.warn("Verification failed, identifier missing")
- raise ValueError("Verification failed, identifier missing")
- except ValueError:
- log.warn("Verification failed, Malformed data")
- raise ValueError("Verification failed, Malformed data")
-
- try:
- identifier_offset = 2
- # Find the separator
- seperator_index = decrypted_bytes.index('\x00', identifier_offset)
- except ValueError:
- log.warn("Verification failed, seperator not found")
- raise ValueError("Verification failed, seperator not found")
-
- digest = decrypted_bytes[seperator_index+1:]
- # The local digest is stored in hex so need to encode the decrypted digest
- digest_hex = digest.encode('hex')
- if digest_hex != self._digest:
- log.warn("Decrypted digest does not match local digest")
- raise ValueError("Decrypted digest does not match local digest")
+
+ from Crypto.Util import asn1
+ from Crypto.Util.number import bytes_to_long, long_to_bytes
+
+ # get the ASN.1 sequence
+ seq = asn1.DerSequence()
+ seq.decode(key_as_der)
+ modulus = seq[0]
+ public_exponent = seq[1] # should always be 65537
+
+ sig_as_bytes = RelayDescriptor._get_key_bytes(self.signature)
+ # convert the descriptor signature to an int
+ sig_as_long = bytes_to_long(sig_as_bytes)
+ # use the public exponent[e] & the modulus[n] to decrypt the int
+ decrypted_int = pow(sig_as_long, public_exponent, modulus)
+ # block size will always be 128 for a 1024 bit key
+ blocksize = 128
+ # convert the int to a byte array.
+ decrypted_bytes = long_to_bytes(decrypted_int, blocksize)
+
+ ############################################################################
+ ## The decrypted bytes should have a structure exactly along these lines.
+ ## 1 byte - [null '\x00']
+ ## 1 byte - [block type identifier '\x01'] - Should always be 1
+ ## N bytes - [padding '\xFF' ]
+ ## 1 byte - [separator '\x00' ]
+ ## M bytes - [message]
+ ## Total - 128 bytes
+ ## More info here http://www.ietf.org/rfc/rfc2313.txt
+ ## esp the Notes in section 8.1
+ ############################################################################
+ try:
+ if decrypted_bytes.index('\x00\x01') != 0:
+ raise ValueError("Verification failed, identifier missing")
+ except ValueError:
+ raise ValueError("Verification failed, Malformed data")
+
+ try:
+ identifier_offset = 2
+ # find the separator
+ seperator_index = decrypted_bytes.index('\x00', identifier_offset)
+ except ValueError:
+ raise ValueError("Verification failed, seperator not found")
+
+ digest = decrypted_bytes[seperator_index+1:]
+ # The local digest is stored in uppercase hex;
+ # - so decode it from hex
+ # - and convert it to lower case
+ local_digest = self.digest().lower().decode('hex')
+ if digest != local_digest:
+ raise ValueError("Decrypted digest does not match local digest")
def _parse(self, entries, validate):
entries = dict(entries) # shallow copy since we're destructive
@@ -746,6 +731,19 @@ class RelayDescriptor(ServerDescriptor):
return 1
return str(self).strip() > str(other).strip()
+
+ @staticmethod
+ def _get_key_bytes(key_string):
+ # Remove the newlines from the key string & strip off the
+ # '-----BEGIN RSA PUBLIC KEY-----' header and
+ # '-----END RSA PUBLIC KEY-----' footer
+ key_as_string = ''.join(key_string.split('\n')[1:4])
+
+ # get the key representation in bytes
+ key_bytes = base64.b64decode(key_as_string)
+
+ return key_bytes
+
class BridgeDescriptor(ServerDescriptor):
"""
diff --git a/stem/prereq.py b/stem/prereq.py
index d205954..0bc159b 100644
--- a/stem/prereq.py
+++ b/stem/prereq.py
@@ -62,7 +62,7 @@ def is_python_27():
def is_crypto_available():
global IS_CRYPTO_AVAILABLE
- if IS_CRYPTO_AVAILABLE == None:
+ if IS_CRYPTO_AVAILABLE is None:
try:
from Crypto.PublicKey import RSA
from Crypto.Util import asn1
diff --git a/test/integ/descriptor/server_descriptor.py b/test/integ/descriptor/server_descriptor.py
index bfe13a7..4ce2eb5 100644
--- a/test/integ/descriptor/server_descriptor.py
+++ b/test/integ/descriptor/server_descriptor.py
@@ -86,7 +86,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
self.assertEquals(expected_signing_key, desc.signing_key)
self.assertEquals(expected_signature, desc.signature)
self.assertEquals([], desc.get_unrecognized_lines())
- self.assertEquals("2C7B27BEAB04B4E2459D89CA6D5CD1CC5F95A689", desc.digest().upper())
+ self.assertEquals("2C7B27BEAB04B4E2459D89CA6D5CD1CC5F95A689", desc.digest())
def test_old_descriptor(self):
"""
diff --git a/test/mocking.py b/test/mocking.py
index 4c4ea25..8329bc0 100644
--- a/test/mocking.py
+++ b/test/mocking.py
@@ -788,6 +788,16 @@ def get_network_status_document_v3(attr = None, exclude = (), authorities = None
return stem.descriptor.networkstatus.NetworkStatusDocumentV3(desc_content, validate = True)
def sign_descriptor_content(desc_content):
+ """
+ Add a valid signature to the supplied descriptor string.
+ If the python-crypto library is available the function will generate a key
+ pair, and use it to sign the descriptor string. Any existing fingerprint,
+ signing-key or router-signature data will be overwritten.
+ If crypto is unavailable the code will return the unaltered descriptor
+ string.
+ :param string desc_content: the descriptor string to sign
+ :returns: a descriptor string, signed if crypto available, unaltered otherwise
+ """
if not stem.prereq.is_crypto_available():
return desc_content
@@ -839,9 +849,10 @@ def sign_descriptor_content(desc_content):
ft_end = desc_content.find("\n", ft_start+1)
desc_content = desc_content[:ft_start]+new_fp+desc_content[ft_end:]
- # calculate the new digest for the descriptor
+ # create a temporary object to use to calculate the digest
tempDesc = stem.descriptor.server_descriptor.RelayDescriptor(desc_content, validate=False)
- new_digest_hex = tempDesc.digest()
+ # calculate the new digest for the descriptor
+ new_digest_hex = tempDesc.digest().lower()
# remove the hex encoding
new_digest = new_digest_hex.decode('hex')
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits