From a3aa8e3ae6478da4003deaa3359549396b92a246 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Sun, 15 Apr 2018 21:22:19 -0500 Subject: [PATCH] work on pm and gui improvements & some bug fixes --- onionr/api.py | 24 +++++++++++++++++++--- onionr/core.py | 14 +++++++++++++ onionr/gui.py | 48 +++++++++++++++++++++++++++++++++++-------- onionr/onionr.py | 7 ++++--- onionr/onionrutils.py | 29 +++++++++++++++++++------- 5 files changed, 101 insertions(+), 21 deletions(-) diff --git a/onionr/api.py b/onionr/api.py index d8aa07a4..48d0242f 100755 --- a/onionr/api.py +++ b/onionr/api.py @@ -32,7 +32,8 @@ class API: ''' Validate that the client token (hmac) matches the given token ''' - if self.clientToken != token: + + if not hmac.compare_digest(self.clientToken.strip(), token.strip()): return False else: return True @@ -63,6 +64,11 @@ class API: bindPort = int(config.get('client')['port']) self.bindPort = bindPort self.clientToken = config.get('client')['client_hmac'] + self.timeBypassToken = base64.b16encode(os.urandom(32)).decode() + + with open('data/time-bypass.txt', 'w') as bypass: + bypass.write(self.timeBypassToken) + if not os.environ.get("WERKZEUG_RUN_MAIN") == "true": logger.debug('Your HMAC token: ' + logger.colors.underline + self.clientToken) @@ -99,11 +105,16 @@ class API: @app.route('/client/') def private_handler(): + if request.args.get('timingToken') is None: + timingToken = '' + else: + timingToken = request.args.get('timingToken') startTime = math.floor(time.time()) # we should keep a hash DB of requests (with hmac) to prevent replays action = request.args.get('action') #if not self.debug: token = request.args.get('token') + if not self.validateToken(token): abort(403) self.validateHost('private') @@ -112,14 +123,19 @@ class API: elif action == 'shutdown': request.environ.get('werkzeug.server.shutdown')() resp = Response('Goodbye') + elif action == 'ping': + resp = Response('pong') elif action == 'stats': resp = Response('me_irl') else: resp = Response('(O_o) Dude what? (invalid command)') endTime = math.floor(time.time()) elapsed = endTime - startTime - if elapsed < self._privateDelayTime: - time.sleep(self._privateDelayTime - elapsed) + + # if bypass token not used, delay response to prevent timing attacks + if not hmac.compare_digest(timingToken, self.timeBypassToken): + if elapsed < self._privateDelayTime: + time.sleep(self._privateDelayTime - elapsed) return resp @@ -208,9 +224,11 @@ class API: host = self.host if hostType == 'private': if not request.host.startswith('127') and not self._utils.checkIsIP(request.host): + print("WHAT") abort(403) elif hostType == 'public': if not request.host.endswith('onion') and not request.host.endswith('i2p'): + print("WHAT2") abort(403) # Validate x-requested-with, to protect against CSRF/metadata leaks if not self._developmentMode: diff --git a/onionr/core.py b/onionr/core.py index 5a0aac32..b75697af 100644 --- a/onionr/core.py +++ b/onionr/core.py @@ -513,3 +513,17 @@ class Core: conn.close() return + + def insertBlock(self, data, header='txt'): + ''' + Inserts a block into the network + ''' + retData = '' + if len(data) == 0: + logger.error('Will not insert empty block') + else: + addedHash = self.setData('-' + header + '-' + data) + self.addToBlockDB(addedHash, selfInsert=True) + self.setBlockType(addedHash, header) + retData = addedHash + return retData \ No newline at end of file diff --git a/onionr/gui.py b/onionr/gui.py index 3dd410ec..3626316e 100755 --- a/onionr/gui.py +++ b/onionr/gui.py @@ -19,30 +19,55 @@ import os, sqlite3, core class OnionrGUI: def __init__(self, myCore): self.root = Tk() + self.myCore = myCore # onionr core self.root.title("PyOnionr") - w = Label(self.root, text="Onionr", width=10) - w.config(font=("Sans-Serif", 22)) - w.pack() + self.runningCheckDelay = 5 + self.runningCheckDelayCount = 0 + scrollbar = Scrollbar(self.root) scrollbar.pack(side=RIGHT, fill=Y) self.listedBlocks = [] + self.nodeInfo = Frame(self.root) + self.keyInfo = Frame(self.root) + idText = open('./data/hs/hostname', 'r').read() - idLabel = Label(self.root, text="ID: " + idText) - idLabel.pack(pady=5) + #idLabel = Label(self.info, text="Node Address: " + idText) + #idLabel.pack(pady=5) + + idEntry = Entry(self.nodeInfo) + Label(self.nodeInfo, text="Node Address: ").pack(side=LEFT) + idEntry.pack() + idEntry.insert(0, idText.strip()) + idEntry.configure(state="readonly") + + self.nodeInfo.pack() + + pubKeyEntry = Entry(self.keyInfo) + + Label(self.keyInfo, text="Public key: ").pack(side=LEFT) + + pubKeyEntry.pack() + pubKeyEntry.insert(0, self.myCore._crypto.pubKey) + pubKeyEntry.configure(state="readonly") + + self.keyInfo.pack() self.sendEntry = Entry(self.root) sendBtn = Button(self.root, text='Send Message', command=self.sendMessage) - self.sendEntry.pack() - sendBtn.pack() + self.sendEntry.pack(side=TOP, pady=5) + sendBtn.pack(side=TOP) self.listbox = Listbox(self.root, yscrollcommand=scrollbar.set, height=15) #listbox.insert(END, str(i)) - self.listbox.pack(fill=BOTH) + self.listbox.pack(fill=BOTH, pady=25) + + self.daemonStatus = Label(self.root, text="Onionr Daemon Status: unknown") + self.daemonStatus.pack() scrollbar.config(command=self.listbox.yview) self.root.after(2000, self.update) @@ -66,5 +91,12 @@ class OnionrGUI: self.listbox.see(END) blocksList = os.listdir('./data/blocks/') # dir is your directory path number_blocks = len(blocksList) + self.runningCheckDelayCount += 1 + if self.runningCheckDelayCount == self.runningCheckDelay: + if self.myCore._utils.localCommand('ping') == 'pong': + self.daemonStatus.config(text="Onionr Daemon Status: Running") + else: + self.daemonStatus.config(text="Onionr Daemon Status: Not Running") + self.runningCheckDelayCount = 0 self.root.after(10000, self.update) diff --git a/onionr/onionr.py b/onionr/onionr.py index d6ba3096..40d324ab 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -111,7 +111,7 @@ class Onionr: randomPort = random.randint(1024, 65535) if self.onionrUtils.checkPort(randomPort): break - config.set('client', {'participate': 'true', 'client_hmac': base64.b64encode(os.urandom(32)).decode('utf-8'), 'port': randomPort, 'api_version': API_VERSION}, True) + config.set('client', {'participate': 'true', 'client_hmac': base64.b16encode(os.urandom(32)).decode('utf-8'), 'port': randomPort, 'api_version': API_VERSION}, True) self.cmds = { '': self.showHelpSuggestion, @@ -316,14 +316,15 @@ class Onionr: return - def addMessage(self): + def addMessage(self, header="txt"): ''' Broadcasts a message to the Onionr network ''' while True: + messageToAdd = '-txt-' + logger.readline('Broadcast message to network: ') - if len(messageToAdd) >= 1: + if len(messageToAdd) - 5 >= 1: break addedHash = self.onionrCore.setData(messageToAdd) diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py index c13de386..0ac673f8 100644 --- a/onionr/onionrutils.py +++ b/onionr/onionrutils.py @@ -18,7 +18,7 @@ along with this program. If not, see . ''' # Misc functions that do not fit in the main api, but are useful -import getpass, sys, requests, os, socket, hashlib, logger, sqlite3, config, binascii +import getpass, sys, requests, os, socket, hashlib, logger, sqlite3, config, binascii, time import nacl.signing, nacl.encoding if sys.version_info < (3, 6): @@ -35,7 +35,15 @@ class OnionrUtils: def __init__(self, coreInstance): self.fingerprintFile = 'data/own-fingerprint.txt' self._core = coreInstance + + self.timingToken = '' + return + + def getTimeBypassToken(self): + if os.path.exists('data/time-bypass.txt'): + with open('data/time-bypass.txt', 'r') as bypass: + self.timingToken = bypass.read() def sendPM(self, pubkey, message): '''High level function to encrypt a message to a peer and insert it as a block''' @@ -44,9 +52,13 @@ class OnionrUtils: #if self._core.getPeerInfo(pubkey, 'pubkeyExchanged') == 1: # pass - encrypted = self._core._crypto.pubKeyEncrypt(message, pubkey, anonymous=True, encodedData=True) - logger.info(encrypted) - return + encrypted = self._core._crypto.pubKeyEncrypt(message, pubkey, anonymous=True, encodedData=True).decode() + block = self._core.insertBlock(encrypted, header='pm') + + if block == '': + logger.error('Could not send PM') + else: + logger.info('Sent PM, hash: ' + block) return @@ -99,11 +111,14 @@ class OnionrUtils: ''' config.reload() - + self.getTimeBypassToken() # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless. - requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('client')['port']) + '/client/?action=' + command + '&token=' + str(config.get('client')['client_hmac'])) + try: + retData = requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config.get('client')['port']) + '/client/?action=' + command + '&token=' + str(config.get('client')['client_hmac']) + '&timingToken=' + self.timingToken).text + except requests.ConnectionError: + retData = False - return + return retData def getPassword(self, message='Enter password: ', confirm = True): '''