[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [bridgedb/develop] Add method for verifying a Bridge's extrainfo router-signature.
commit 6b4c3f08545009a68cb6b2a24c2357603e484609
Author: Isis Lovecruft <isis@xxxxxxxxxxxxxx>
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.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits