[tor-commits] [bridgedb/master] Add method for verifying a Bridge's extrainfo router-signature.

isis at torproject.org isis at torproject.org
Sat Mar 21 02:02:58 UTC 2015


commit 6b4c3f08545009a68cb6b2a24c2357603e484609
Author: Isis Lovecruft <isis at torproject.org>
Date:   Fri Dec 5 18:43:49 2014 -0800

    Add method for verifying a Bridge's extrainfo router-signature.
    
     * ADD new method bridgedb.bridges.Bridge._verifyExtraInfoSignature().
     * ADD new function bridgedb.crypto.removePKCS1Padding().
---
 lib/bridgedb/bridges.py |  109 ++++++++++++++++++++++++++++++++++++++++++++++-
 lib/bridgedb/crypto.py  |   41 ++++++++++++++++++
 2 files changed, 149 insertions(+), 1 deletion(-)

diff --git a/lib/bridgedb/bridges.py b/lib/bridgedb/bridges.py
index 17486e2..d2e528d 100644
--- a/lib/bridgedb/bridges.py
+++ b/lib/bridgedb/bridges.py
@@ -11,13 +11,20 @@
 
 from __future__ import print_function
 
+import base64
+import codecs
 import hashlib
 import ipaddr
 import logging
 import os
 
+from Crypto.Util import asn1
+from Crypto.Util.number import bytes_to_long
+from Crypto.Util.number import long_to_bytes
+
 from bridgedb import safelog
 from bridgedb import bridgerequest
+from bridgedb.crypto import removePKCS1Padding
 from bridgedb.parse.addr import isIPAddress
 from bridgedb.parse.addr import isIPv6
 from bridgedb.parse.addr import isValidIP
@@ -55,6 +62,10 @@ class ServerDescriptorWithoutNetworkstatus(MalformedBridgeInfo):
     mentioned in the latest ``@type bridge-networkstatus`` document.
     """
 
+class InvalidExtraInfoSignature(MalformedBridgeInfo):
+    """Raised if the signature on an ``@type bridge-extrainfo`` is invalid."""
+
+
 class Flags(object):
     """All the flags which a :class:`Bridge` may have."""
 
@@ -1174,7 +1185,91 @@ class Bridge(object):
 
         self.extrainfoDigest = descriptor.extrainfoDigest
 
-    def updateFromExtraInfoDescriptor(self, descriptor):
+    def _verifyExtraInfoSignature(self, descriptor):
+        """Verify the signature on the contents of this :class:`Bridge`'s
+        ``@type bridge-extrainfo`` descriptor.
+
+        :type descriptor:
+            :api:`stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor`
+        :param descriptor: An ``@type bridge-extrainfo`` descriptor for this
+            :class:`Bridge`, parsed with Stem.
+        :raises InvalidExtraInfoSignature: if the signature was invalid,
+            missing, malformed, or couldn't be verified successfully.
+        :returns: ``None`` if the signature was valid and verifiable.
+        """
+        # The blocksize is always 128 bits for a 1024-bit key
+        BLOCKSIZE = 128
+
+        TOR_SIGNING_KEY_HEADER = u'-----BEGIN RSA PUBLIC KEY-----\n'
+        TOR_SIGNING_KEY_FOOTER = u'-----END RSA PUBLIC KEY-----'
+        TOR_BEGIN_SIGNATURE = u'-----BEGIN SIGNATURE-----\n'
+        TOR_END_SIGNATURE = u'-----END SIGNATURE-----\n'
+
+        logging.info("Verifying extrainfo signature for %s..." % self)
+
+        # Get the bytes of the descriptor signature without the headers:
+        document, signature = descriptor.get_bytes().split(TOR_BEGIN_SIGNATURE)
+        signature = signature.replace(TOR_END_SIGNATURE, '')
+        signature = signature.replace('\n', '')
+        signature = signature.strip()
+
+        try:
+            # Get the ASN.1 sequence:
+            sequence = asn1.DerSequence()
+
+            key = self.signingKey
+            key = key.strip(TOR_SIGNING_KEY_HEADER)
+            key = key.strip(TOR_SIGNING_KEY_FOOTER)
+            key = key.replace('\n', '')
+            key = base64.b64decode(key)
+
+            sequence.decode(key)
+
+            modulus = sequence[0]
+            publicExponent = sequence[1]
+
+            # The public exponent of RSA signing-keys should always be 65537,
+            # but we're not going to turn them down if they want to use a
+            # potentially dangerous exponent.
+            if publicExponent != 65537:  # pragma: no cover
+                logging.warn("Odd RSA exponent in signing-key for %s: %s" %
+                             (self, publicExponent))
+
+            # Base64 decode the signature:
+            signatureDecoded = base64.b64decode(signature)
+
+            # Convert the signature to a long:
+            signatureLong = bytes_to_long(signatureDecoded)
+
+            # Decrypt the long signature with the modulus and public exponent:
+            decryptedInt = pow(signatureLong, publicExponent, modulus)
+
+            # Then convert it back to a byte array:
+            decryptedBytes = long_to_bytes(decryptedInt, BLOCKSIZE)
+
+            # Remove the PKCS#1 padding from the signature:
+            unpadded = removePKCS1Padding(decryptedBytes)
+
+            # This is the hexadecimal SHA-1 hash digest of the descriptor document
+            # as it was signed:
+            signedDigest = codecs.encode(unpadded, 'hex_codec')
+            actualDigest = hashlib.sha1(document).hexdigest()
+
+        except Exception as error:
+            logging.debug("Error verifying extrainfo signature: %s" % error)
+            raise InvalidExtraInfoSignature(
+                "Extrainfo signature for %s couldn't be decoded: %s" %
+                (self, signature))
+        else:
+            if signedDigest != actualDigest:
+                raise InvalidExtraInfoSignature(
+                    ("The extrainfo digest signed by bridge %s didn't match the "
+                     "actual digest.\nSigned digest: %s\nActual digest: %s") %
+                    (self, signedDigest, actualDigest))
+            else:
+                logging.info("Extrainfo signature was verified successfully!")
+
+    def updateFromExtraInfoDescriptor(self, descriptor, verify=True):
         """Update this bridge's information from an extrainfo descriptor.
 
         .. todo:: The ``transport`` attribute of Stem's
@@ -1186,5 +1281,17 @@ class Bridge(object):
         :type descriptor:
             :api:`stem.descriptor.extrainfo_descriptor.BridgeExtraInfoDescriptor`
         :param descriptor: DOCDOC
+        :param bool verify: If ``True``, check that the ``router-signature``
+            on the extrainfo **descriptor** is a valid signature from
+            :data:`signingkey`.
         """
+        if verify:
+            try:
+                self._verifyExtraInfoSignature(descriptor)
+            except InvalidExtraInfoSignature as error:
+                logging.warn(error)
+                logging.info(("Tossing extrainfo descriptor due to an invalid "
+                              "signature."))
+                return
+
         self.descriptors['extrainfo'] = descriptor
diff --git a/lib/bridgedb/crypto.py b/lib/bridgedb/crypto.py
index e8cf8fd..63e532b 100644
--- a/lib/bridgedb/crypto.py
+++ b/lib/bridgedb/crypto.py
@@ -87,6 +87,9 @@ GPGME_CONTEXT_HOMEDIR = '.gnupg'
 GPGME_CONTEXT_BINARY = which('gpg2') or which('gpg')  # These will be lists
 
 
+class PKCS1PaddingError(Exception):
+    """Raised when there is a problem adding or removing PKCS#1 padding."""
+
 class RSAKeyGenerationError(Exception):
     """Raised when there was an error creating an RSA keypair."""
 
@@ -273,6 +276,44 @@ def getHMACFunc(key, hex=True):
             return h_tmp.digest()
     return hmac_fn
 
+def removePKCS1Padding(message):
+    """Remove PKCS#1 padding from a **message**.
+
+    (PKCS#1 v1.0? see https://bugs.torproject.org/13042)
+
+    Each block is 128 bytes total in size:
+
+        * 2 bytes for the type info ('\x00\x01')
+        * 1 byte for the separator ('\x00')
+        * variable length padding ('\xFF')
+        * variable length for the **message**
+
+    For more information on the structure of PKCS#1 padding, see :rfc:`2313`,
+    particularly the notes in §8.1.
+
+    :param str message: A message which is PKCS#1 padded.
+    :raises PKCS1PaddingError: if there is an issue parsing the **message**.
+    :rtype: bytes
+    :returns: The message without the PKCS#1 padding.
+    """
+    padding = b'\xFF'
+    typeinfo = b'\x00\x01'
+    separator = b'\x00'
+
+    unpadded = None
+
+    try:
+        if message.index(typeinfo) != 0:
+            raise PKCS1PaddingError("Couldn't find PKCS#1 identifier bytes!")
+        start = message.index(separator, 2) + 1  # 2 bytes for the typeinfo,
+                                                 # and 1 byte for the separator.
+    except ValueError:
+        raise PKCS1PaddingError("Couldn't find PKCS#1 separator byte!")
+    else:
+        unpadded = message[start:]
+
+    return unpadded
+
 def _createGPGMEErrorInterpreters():
     """Create a mapping of GPGME ERRNOs ←→ human-readable error names/causes.
 





More information about the tor-commits mailing list