From be0e724b2394c3c704147ea61f402b4757f4e806 Mon Sep 17 00:00:00 2001 From: f97ada87 Date: Sat, 30 Sep 2017 19:19:44 +1000 Subject: [PATCH] implement stealth ack objects --- src/api.py | 6 +++-- src/bitmessagecurses/__init__.py | 7 ++++-- src/bitmessageqt/__init__.py | 6 +++-- src/bitmessageqt/account.py | 4 +++- src/class_objectProcessor.py | 17 +++++--------- src/class_singleWorker.py | 19 +++++++++++---- src/class_smtpServer.py | 4 +++- src/helper_ackPayload.py | 40 ++++++++++++++++++++++++++++++++ 8 files changed, 79 insertions(+), 24 deletions(-) create mode 100644 src/helper_ackPayload.py diff --git a/src/api.py b/src/api.py index e20854fc..edb9e23d 100644 --- a/src/api.py +++ b/src/api.py @@ -35,6 +35,7 @@ import network.stats # Classes from helper_sql import sqlQuery,sqlExecute,SqlBulkExecute,sqlStoredProcedure +from helper_ackPayload import genAckPayload from debug import logger from inventory import Inventory from version import softwareVersion @@ -679,7 +680,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): if not fromAddressEnabled: raise APIError(14, 'Your fromAddress is disabled. Cannot send.') - ackdata = OpenSSL.rand(32) + stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') + ackdata = genAckPayload(streamNumber, stealthLevel) t = ('', toAddress, @@ -740,7 +742,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): fromAddress, 'enabled') except: raise APIError(13, 'could not find your fromAddress in the keys.dat file.') - ackdata = OpenSSL.rand(32) + ackdata = genAckPayload(streamNumber, 0) toAddress = '[Broadcast subscribers]' ripe = '' diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index 381d7c7a..fc1d74b2 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -20,6 +20,7 @@ import curses import dialog from dialog import Dialog from helper_sql import * +from helper_ackPayload import genAckPayload from addresses import * import ConfigParser @@ -778,7 +779,8 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F if len(shared.connectedHostsList) == 0: set_background_title(d, "Not connected warning") scrollbox(d, unicode("Because you are not currently connected to the network, ")) - ackdata = OpenSSL.rand(32) + stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') + ackdata = genAckPayload(streamNumber, stealthLevel) sqlExecute( "INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", "", @@ -802,7 +804,8 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F set_background_title(d, "Empty sender error") scrollbox(d, unicode("You must specify an address to send the message from.")) else: - ackdata = OpenSSL.rand(32) + # dummy ackdata, no need for stealth + ackdata = genAckPayload(streamNumber, 0) recv = BROADCAST_STR ripe = "" sqlExecute( diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index bb90bb47..09669616 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -52,6 +52,7 @@ import random import string from datetime import datetime, timedelta from helper_sql import * +from helper_ackPayload import genAckPayload import helper_search import l10n import openclpow @@ -1879,7 +1880,8 @@ class MyForm(settingsmixin.SMainWindow): if shared.statusIconColor == 'red': self.statusBar().showMessage(_translate( "MainWindow", "Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.")) - ackdata = OpenSSL.rand(32) + stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') + ackdata = genAckPayload(streamNumber, stealthLevel) t = () sqlExecute( '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', @@ -1933,7 +1935,7 @@ class MyForm(settingsmixin.SMainWindow): # We don't actually need the ackdata for acknowledgement since # this is a broadcast message, but we can use it to update the # user interface when the POW is done generating. - ackdata = OpenSSL.rand(32) + ackdata = genAckPayload(streamNumber, 0) toAddress = str_broadcast_subscribers ripe = '' t = ('', # msgid. We don't know what this will be until the POW is done. diff --git a/src/bitmessageqt/account.py b/src/bitmessageqt/account.py index eee6c7b4..92d497f8 100644 --- a/src/bitmessageqt/account.py +++ b/src/bitmessageqt/account.py @@ -5,6 +5,7 @@ import re import sys import inspect from helper_sql import * +from helper_ackPayload import genAckPayload from addresses import decodeAddress from bmconfigparser import BMConfigParser from foldertree import AccountMixin @@ -166,7 +167,8 @@ class GatewayAccount(BMAccount): def send(self): status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.toAddress) - ackdata = OpenSSL.rand(32) + stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') + ackdata = genAckPayload(streamNumber, stealthLevel) t = () sqlExecute( '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index e59645bc..876eff62 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -21,6 +21,7 @@ import helper_inbox import helper_msgcoding import helper_sent from helper_sql import * +from helper_ackPayload import genAckPayload import protocol import queues import state @@ -97,15 +98,9 @@ class objectProcessor(threading.Thread): # Let's check whether this is a message acknowledgement bound for us. if len(data) < 32: return - readPosition = 20 # bypass the nonce, time, and object type - # chomp version number - versionNumber, varIntLength = decodeVarint( - data[readPosition:readPosition + 10]) - readPosition += varIntLength - # chomp stream number - streamNumber, varIntLength = decodeVarint( - data[readPosition:readPosition + 10]) - readPosition += varIntLength + + # bypass nonce and time, retain object type/version/stream + body + readPosition = 16 if data[readPosition:] in shared.ackdataForWhichImWatching: logger.info('This object is an acknowledgement bound for me.') @@ -558,8 +553,8 @@ class objectProcessor(threading.Thread): message = time.strftime("%a, %Y-%m-%d %H:%M:%S UTC", time.gmtime( )) + ' Message ostensibly from ' + fromAddress + ':\n\n' + body fromAddress = toAddress # The fromAddress for the broadcast that we are about to send is the toAddress (my address) for the msg message we are currently processing. - ackdataForBroadcast = OpenSSL.rand( - 32) # We don't actually need the ackdataForBroadcast for acknowledgement since this is a broadcast message but we can use it to update the user interface when the POW is done generating. + # We don't actually need the ackdataForBroadcast for acknowledgement since this is a broadcast message but we can use it to update the user interface when the POW is done generating. + ackdata = genAckPayload(streamNumber, 0) toAddress = '[Broadcast subscribers]' ripe = '' diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index 58eb33c6..322bb20e 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -81,6 +81,16 @@ class singleWorker(threading.Thread, StoppableThread): logger.info('Watching for ackdata ' + hexlify(ackdata)) shared.ackdataForWhichImWatching[ackdata] = 0 + # Fix legacy (headerless) watched ackdata to include header + for oldack in shared.ackdataForWhichImWatching.keys(): + if (len(oldack)==32): + # attach legacy header, always constant (msg/1/1) + newack = '\x00\x00\x00\x02\x01\x01' + oldack + shared.ackdataForWhichImWatching[newack] = 0 + sqlExecute('UPDATE sent SET ackdata=? WHERE ackdata=?', + newack, oldack ) + del shared.ackdataForWhichImWatching[oldack] + self.stop.wait( 10) # give some time for the GUI to start before we start on existing POW tasks. @@ -967,11 +977,10 @@ class singleWorker(threading.Thread, StoppableThread): TTL = 28*24*60*60 # 4 weeks TTL = int(TTL + random.randrange(-300, 300)) # Add some randomness to the TTL embeddedTime = int(time.time() + TTL) - payload = pack('>Q', (embeddedTime)) - payload += '\x00\x00\x00\x02' # object type: msg - payload += encodeVarint(1) # msg version - payload += encodeVarint(toStreamNumber) + ackdata - + + # type/version/stream already included + payload = pack('>Q', (embeddedTime)) + ackdata + target = 2 ** 64 / (defaults.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + defaults.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+defaults.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) logger.info('(For ack message) Doing proof of work. TTL set to ' + str(TTL)) diff --git a/src/class_smtpServer.py b/src/class_smtpServer.py index 3bc81a61..b62a7130 100644 --- a/src/class_smtpServer.py +++ b/src/class_smtpServer.py @@ -14,6 +14,7 @@ from addresses import decodeAddress from bmconfigparser import BMConfigParser from debug import logger from helper_sql import sqlExecute +from helper_ackPayload import genAckPayload from helper_threading import StoppableThread from pyelliptic.openssl import OpenSSL import queues @@ -65,7 +66,8 @@ class smtpServerPyBitmessage(smtpd.SMTPServer): def send(self, fromAddress, toAddress, subject, message): status, addressVersionNumber, streamNumber, ripe = decodeAddress(toAddress) - ackdata = OpenSSL.rand(32) + stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') + ackdata = genAckPayload(streamNumber, stealthLevel) t = () sqlExecute( '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', diff --git a/src/helper_ackPayload.py b/src/helper_ackPayload.py new file mode 100644 index 00000000..ef99ec2a --- /dev/null +++ b/src/helper_ackPayload.py @@ -0,0 +1,40 @@ +import hashlib +import highlevelcrypto +import random +import helper_random +from binascii import hexlify, unhexlify +from struct import pack, unpack +from addresses import encodeVarint + +# This function generates payload objects for message acknowledgements +# Several stealth levels are available depending on the privacy needs; +# a higher level means better stealth, but also higher cost (size+POW) +# - level 0: a random 32-byte sequence with a message header appended +# - level 1: a getpubkey request for a (random) dummy key hash +# - level 2: a standard message, encrypted to a random pubkey + +def genAckPayload(streamNumber=1, stealthLevel=0): + if (stealthLevel==2): # Generate privacy-enhanced payload + # Generate a dummy privkey and derive the pubkey + dummyPubKeyHex = highlevelcrypto.privToPub(hexlify(helper_random.randomBytes(32))) + # Generate a dummy message of random length + # (the smallest possible standard-formatted message is 234 bytes) + dummyMessage = helper_random.randomBytes(random.randint(234, 800)) + # Encrypt the message using standard BM encryption (ECIES) + ackdata = highlevelcrypto.encrypt(dummyMessage, dummyPubKeyHex) + acktype = 2 # message + version = 1 + + elif (stealthLevel==1): # Basic privacy payload (random getpubkey) + ackdata = helper_random.randomBytes(32) + acktype = 0 # getpubkey + version = 4 + + else: # Minimum viable payload (non stealth) + ackdata = helper_random.randomBytes(32) + acktype = 2 # message + version = 1 + + ackobject = pack('>I', acktype) + encodeVarint(version) + encodeVarint(streamNumber) + ackdata + + return ackobject