[tor-commits] [stem/master] Validate that signing key hash matches fingerprint
atagar at torproject.org
atagar at torproject.org
Fri May 25 16:06:25 UTC 2012
commit 40a971105ff162d0caa9e0a1c5fa3c0f7d3c2b72
Author: Damian Johnson <atagar at torproject.org>
Date: Fri May 25 08:59:03 2012 -0700
Validate that signing key hash matches fingerprint
When parsing server descriptors checking that their fingerprints match a hash
of their signing key as part of validation. This requires the rsa module which
is neither built in, nor is it packaged for debian distros. Installation of it
is easy, but requires pip or easy-install. Instructions are available at...
http://stuvel.eu/files/python-rsa-doc/installation.html
It looks like python's builtin crypto module might be capable of doing this as
well...
http://stackoverflow.com/questions/5000434/python-rsa-library
https://www.dlitz.net/software/pycrypto/api/current/Crypto.PublicKey.RSA._RSAobj-class.html#exportKey
However, those instructions include usage of a private member and the function
that they suggest dosn't exist on my system (python 2.7.1), so I feel pretty
justified in saying "the pycrypto builtin is crap for this use case, and
patches welcome if someone can figure out how to make it work".
All credit for this patch goes to Beck, who's been diving into the descriptor
crypto on...
https://trac.torproject.org/projects/tor/ticket/5810
---
stem/descriptor/server_descriptor.py | 36 ++++++++++++++++++++++++++++-
test/unit/descriptor/server_descriptor.py | 26 +++++++++++++++++++++
2 files changed, 61 insertions(+), 1 deletions(-)
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index f7d3437..7bf781f 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -31,9 +31,17 @@ import datetime
import stem.descriptor
import stem.descriptor.extrainfo_descriptor
import stem.version
+import stem.util.log as log
import stem.util.connection
import stem.util.tor_tools
+try:
+ import rsa
+ IS_RSA_AVAILABLE = True
+except ImportError:
+ log.info("Unable to import the rsa module. Because of this we'll be unable to verify server integrity.")
+ IS_RSA_AVAILABLE = False
+
# relay descriptors must have exactly one of the following
REQUIRED_FIELDS = (
"router",
@@ -569,6 +577,17 @@ class RelayDescriptor(ServerDescriptor):
self._digest = None
ServerDescriptor.__init__(self, raw_contents, validate, annotations)
+
+ # if we have a fingerprint then checks that our fingerprint is a hash of
+ # our signing key
+
+ if IS_RSA_AVAILABLE and validate and self.fingerprint:
+ pubkey = rsa.PublicKey.load_pkcs1(self.signing_key)
+ der_encoded = pubkey.save_pkcs1(format = "DER")
+ key_hash = hashlib.sha1(der_encoded).hexdigest()
+
+ if key_hash != self.fingerprint.lower():
+ raise ValueError("Hash of our signing key doesn't match our fingerprint. Signing key hash: %s, fingerprint: %s" % (key_hash, self.fingerprint.lower()))
def is_valid(self):
"""
@@ -578,7 +597,22 @@ class RelayDescriptor(ServerDescriptor):
True if our signature matches our content, False otherwise
"""
- raise NotImplementedError # TODO: implement
+ raise NotImplementedError # TODO: finish implementing
+
+ # without validation we may be missing our signature
+ if not self.signature: return False
+
+ # gets base64 encoded bytes of our signature without newlines nor the
+ # "-----[BEGIN|END] SIGNATURE-----" header/footer
+
+ sig_content = self.signature.replace("\n", "")[25:-23]
+ sig_bytes = base64.b64decode(sig_content)
+
+ # TODO: Decrypt the signature bytes with the signing key and remove
+ # the PKCS1 padding to get the original message, and encode the message
+ # in hex and compare it to the digest of the descriptor.
+
+ return True
def digest(self):
if self._digest is None:
diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py
index 53b4675..6ad92b2 100644
--- a/test/unit/descriptor/server_descriptor.py
+++ b/test/unit/descriptor/server_descriptor.py
@@ -309,6 +309,32 @@ class TestServerDescriptor(unittest.TestCase):
self.assertEquals(None, desc.socks_port)
self.assertEquals(None, desc.dir_port)
+ def test_fingerprint_valid(self):
+ """
+ Checks that a fingerprint matching the hash of our signing key will validate.
+ """
+
+ if not stem.descriptor.server_descriptor.IS_RSA_AVAILABLE:
+ self.skipTest("(rsa module unavailable)")
+
+ fingerprint = "4F0C 867D F0EF 6816 0568 C826 838F 482C EA7C FE44"
+ desc_text = _make_descriptor({"opt fingerprint": fingerprint})
+ desc = RelayDescriptor(desc_text)
+ self.assertEquals(fingerprint.replace(" ", ""), desc.fingerprint)
+
+ def test_fingerprint_invalid(self):
+ """
+ Checks that, with a correctly formed fingerprint, we'll fail validation if
+ it doesn't match the hash of our signing key.
+ """
+
+ if not stem.descriptor.server_descriptor.IS_RSA_AVAILABLE:
+ self.skipTest("(rsa module unavailable)")
+
+ fingerprint = "4F0C 867D F0EF 6816 0568 C826 838F 482C EA7C FE45"
+ desc_text = _make_descriptor({"opt fingerprint": fingerprint})
+ self._expect_invalid_attr(desc_text, "fingerprint", fingerprint.replace(" ", ""))
+
def test_minimal_bridge_descriptor(self):
"""
Basic sanity check that we can parse a descriptor with minimal attributes.
More information about the tor-commits
mailing list