From 03263156de8d18261a1683e25a11daf5d5313940 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Thu, 6 Feb 2014 13:16:07 +0000 Subject: [PATCH] Add a 'trustedpeer' option to keys.dat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If this option is specified in keys.dat then Bitmessage will connect to the host specified there instead of connecting to the hosts in the list of known nodes. It will also stop listening for incoming connections and the timing attack mitigation will be disabled. The expected use case is for example where a user is running a daemon on a dedicated machine in their local network and they occasionally want to check for messages using a second instance of the client on their laptop. In that case it would be much faster to catch up with the messages by directly downloading from the dedicated machine over the LAN. There is no need to connect to multiple peers or to do the timing attack mitigation because the daemon is trusted. The host is specified as hostname:port. Eg, ‘192.168.1.8:8444’. --- src/class_outgoingSynSender.py | 25 ++++++++++++++++++------- src/class_receiveDataThread.py | 24 +++++++++++------------- src/class_singleListener.py | 5 +++++ src/helper_startup.py | 13 +++++++++++++ src/shared.py | 12 ++++++++++++ 5 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/class_outgoingSynSender.py b/src/class_outgoingSynSender.py index f9343f62..fef781d3 100644 --- a/src/class_outgoingSynSender.py +++ b/src/class_outgoingSynSender.py @@ -22,26 +22,37 @@ class outgoingSynSender(threading.Thread): self.streamNumber = streamNumber self.selfInitiatedConnections = selfInitiatedConnections + def _getPeer(self): + # If the user has specified a trusted peer then we'll only + # ever connect to that. Otherwise we'll pick a random one from + # the known nodes + shared.knownNodesLock.acquire() + if shared.trustedPeer: + peer = shared.trustedPeer + shared.knownNodes[self.streamNumber][peer] = time.time() + else: + peer, = random.sample(shared.knownNodes[self.streamNumber], 1) + shared.knownNodesLock.release() + + return peer + def run(self): while shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'): time.sleep(2) while shared.safeConfigGetBoolean('bitmessagesettings', 'sendoutgoingconnections'): - while len(self.selfInitiatedConnections[self.streamNumber]) >= 8: # maximum number of outgoing connections = 8 + maximumConnections = 1 if shared.trustedPeer else 8 # maximum number of outgoing connections = 8 + while len(self.selfInitiatedConnections[self.streamNumber]) >= maximumConnections: time.sleep(10) if shared.shutdown: break random.seed() - shared.knownNodesLock.acquire() - peer, = random.sample(shared.knownNodes[self.streamNumber], 1) - shared.knownNodesLock.release() + peer = self._getPeer() shared.alreadyAttemptedConnectionsListLock.acquire() while peer in shared.alreadyAttemptedConnectionsList or peer.host in shared.connectedHostsList: shared.alreadyAttemptedConnectionsListLock.release() # print 'choosing new sample' random.seed() - shared.knownNodesLock.acquire() - peer, = random.sample(shared.knownNodes[self.streamNumber], 1) - shared.knownNodesLock.release() + peer = self._getPeer() time.sleep(1) # Clear out the shared.alreadyAttemptedConnectionsList every half # hour so that this program will again attempt a connection diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py index 2551f223..31ddce14 100644 --- a/src/class_receiveDataThread.py +++ b/src/class_receiveDataThread.py @@ -313,6 +313,14 @@ class receiveDataThread(threading.Thread): print 'Sending huge inv message with', numberOfObjects, 'objects to just this one peer' self.sendDataThreadQueue.put((0, 'sendRawData', headerData + payload)) + def _sleepForTimingAttackMitigation(self, sleepTime): + # We don't need to do the timing attack mitigation if we are + # only connected to the trusted peer because we can trust the + # peer not to attack + if sleepTime > 0 and doTimingAttackMitigation and shared.trustedPeer == None: + with shared.printLock: + print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.' + time.sleep(sleepTime) # We have received a broadcast message def recbroadcast(self, data): @@ -341,10 +349,7 @@ class receiveDataThread(threading.Thread): sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \ (time.time() - self.messageProcessingStartTime) - if sleepTime > 0 and doTimingAttackMitigation: - with shared.printLock: - print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.' - time.sleep(sleepTime) + self._sleepForTimingAttackMitigation(sleepTime) # We have received a msg message. def recmsg(self, data): @@ -373,10 +378,7 @@ class receiveDataThread(threading.Thread): sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \ (time.time() - self.messageProcessingStartTime) - if sleepTime > 0 and doTimingAttackMitigation: - with shared.printLock: - print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.' - time.sleep(sleepTime) + self._sleepForTimingAttackMitigation(sleepTime) # We have received a pubkey def recpubkey(self, data): @@ -387,11 +389,7 @@ class receiveDataThread(threading.Thread): lengthOfTimeWeShouldUseToProcessThisMessage = .1 sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - \ (time.time() - self.pubkeyProcessingStartTime) - if sleepTime > 0 and doTimingAttackMitigation: - with shared.printLock: - print 'Timing attack mitigation: Sleeping for', sleepTime, 'seconds.' - time.sleep(sleepTime) - + self._sleepForTimingAttackMitigation(sleepTime) # We have received an inv message def recinv(self, data): diff --git a/src/class_singleListener.py b/src/class_singleListener.py index ec1f47d4..49dbf9ce 100644 --- a/src/class_singleListener.py +++ b/src/class_singleListener.py @@ -22,6 +22,11 @@ class singleListener(threading.Thread): self.selfInitiatedConnections = selfInitiatedConnections def run(self): + # If there is a trusted peer then we don't want to accept + # incoming connections so we'll just abandon the thread + if shared.trustedPeer: + return + while shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'): time.sleep(1) helper_bootstrap.dns() diff --git a/src/helper_startup.py b/src/helper_startup.py index abc4958f..f9b2ff36 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -12,6 +12,17 @@ from namecoin import ensureNamecoinOptions storeConfigFilesInSameDirectoryAsProgramByDefault = False # The user may de-select Portable Mode in the settings if they want the config files to stay in the application data folder. +def _loadTrustedPeer(): + try: + trustedPeer = shared.config.get('bitmessagesettings', 'trustedpeer') + except ConfigParser.Error: + # This probably means the trusted peer wasn't specified so we + # can just leave it as None + return + + host, port = trustedPeer.split(':') + shared.trustedPeer = shared.Peer(host, int(port)) + def loadConfig(): if shared.appdata: shared.config.read(shared.appdata + 'keys.dat') @@ -122,6 +133,8 @@ def loadConfig(): with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) + _loadTrustedPeer() + def isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections(): try: VER_THIS=StrictVersion(platform.version()) diff --git a/src/shared.py b/src/shared.py index e6642c70..1ce570d6 100644 --- a/src/shared.py +++ b/src/shared.py @@ -92,6 +92,18 @@ namecoinDefaultRpcPort = "8336" # binary distributions vs source distributions. frozen = getattr(sys,'frozen', None) +# If the trustedpeer option is specified in keys.dat then this will +# contain a Peer which will be connected to instead of using the +# addresses advertised by other peers. The client will only connect to +# this peer and the timing attack mitigation will be disabled in order +# to download data faster. The expected use case is where the user has +# a fast connection to a trusted server where they run a BitMessage +# daemon permanently. If they then run a second instance of the client +# on a local machine periodically when they want to check for messages +# it will sync with the network a lot faster without compromising +# security. +trustedPeer = None + def isInSqlInventory(hash): queryreturn = sqlQuery('''select hash from inventory where hash=?''', hash) return queryreturn != []