From d8af21ab16a3747ed0ec8527648147668657d955 Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Thu, 15 Aug 2019 03:36:05 -0500 Subject: [PATCH] * started endpoint for non-app specific control of direct connections * fixed signature bugs in direct connections and forward secrecy * removed chain and parent stuff from onionrblocks --- AUTHORS.MD | 4 +- onionr/httpapi/directconnections/__init__.py | 34 ++++ onionr/httpapi/miscclientapi/endpoints.py | 9 - onionr/onionrblockapi.py | 170 +----------------- onionr/onionrexceptions.py | 3 + onionr/onionrutils/blockmetadata/process.py | 11 +- .../default-plugins/chat/controlapi.py | 3 - .../default-plugins/chat/peerserver.py | 3 +- 8 files changed, 53 insertions(+), 184 deletions(-) create mode 100644 onionr/httpapi/directconnections/__init__.py diff --git a/AUTHORS.MD b/AUTHORS.MD index 9320c86b..a65607d5 100644 --- a/AUTHORS.MD +++ b/AUTHORS.MD @@ -4,9 +4,9 @@ Onionr is created by a team of hard working volunteers. In no order of importance, these people make Onionr happen: -* [Beardog (Kevin Froman)](https://www.chaoswebs.net/) - Project owner and core developer +* [Beardog (Kevin Froman)](https://www.chaoswebs.net/) - Project founder, owner and core developer * [InvisaMage](https://invisamage.com/) - Web UI Bulma design * [Arinerron](https://arinerron.com/) - Logger and config modules, testing and other contributions * [Anhar Ismail](https://github.com/anharismail) - Created Onionr's logo -+ Other contributors and testers \ No newline at end of file ++ Other contributors and testers diff --git a/onionr/httpapi/directconnections/__init__.py b/onionr/httpapi/directconnections/__init__.py new file mode 100644 index 00000000..2ab59578 --- /dev/null +++ b/onionr/httpapi/directconnections/__init__.py @@ -0,0 +1,34 @@ +''' + Onionr - Private P2P Communication + + Misc client API endpoints too small to need their own file and that need access to the client api inst +''' +''' + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +''' +from flask import Response, Blueprint + +def _in_pool(pubkey, communicator): + if pubkey in communicator.active_services + + +class DirectConnectionManagement: + def __init__(self, client_api): + direct_conn_management_bp = Blueprint('direct_conn_management', __name__) + self.direct_conn_management_bp = direct_conn_management_bp + communicator = client_api._too_many.get('OnionrCommunicatorDaemon') + + @direct_conn_management_bp.route('/isconnected/') + def is_connected(pubkey): + return \ No newline at end of file diff --git a/onionr/httpapi/miscclientapi/endpoints.py b/onionr/httpapi/miscclientapi/endpoints.py index bab41413..70f6eaf5 100644 --- a/onionr/httpapi/miscclientapi/endpoints.py +++ b/onionr/httpapi/miscclientapi/endpoints.py @@ -28,15 +28,6 @@ class PrivateEndpoints: private_endpoints_bp = Blueprint('privateendpoints', __name__) self.private_endpoints_bp = private_endpoints_bp - @private_endpoints_bp.route('/serviceactive/') - def serviceActive(pubkey): - try: - if pubkey in client_api.onionrInst.communicatorInst.active_services: - return Response('true') - except AttributeError as e: - pass - return Response('false') - @private_endpoints_bp.route('/www/', endpoint='www') def wwwPublic(path): if not config.get("www.private.run", True): diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index ca97f927..e263805b 100755 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -49,7 +49,6 @@ class Block: self.signature = None self.signedData = None self.blockFile = None - self.parent = None self.bheader = {} self.bmetadata = {} self.isEncrypted = False @@ -84,6 +83,10 @@ class Block: self.bheader['signer'] = self.signer.decode() self.signedData = json.dumps(self.bmetadata) + self.bcontent.decode() + if not self.signer is None: + if not self.verifySig(): + raise onionrexceptions.SignatureError("Block has invalid signature") + # Check for replay attacks try: if epoch.get_epoch() - blockmetadb.get_block_date(self.hash) > 60: @@ -130,7 +133,6 @@ class Block: self.validSig = False return self.validSig - def update(self, data = None, file = None): ''' Loads data from a block in to the current object. @@ -167,7 +169,6 @@ class Block: self.isEncrypted = True else: self.bmetadata = json.loads(self.getHeader('meta', None)) - self.parent = self.getMetadata('parent', None) self.btype = self.getMetadata('type', None) self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '') # TODO: detect if signer is hash of pubkey or not @@ -182,9 +183,6 @@ class Block: self.date = datetime.datetime.fromtimestamp(self.getDate()) self.valid = True - - if len(self.getRaw()) <= config.get('allocations.blockCache', 500000): - self.cache() if self.autoDecrypt: self.decrypt() @@ -331,24 +329,6 @@ class Block: return str(self.bcontent) - def getParent(self): - ''' - Returns the Block's parent Block, or None - - Outputs: - - (Block): the Block's parent - ''' - - if type(self.parent) == str: - if self.parent == self.getHash(): - self.parent = self - elif Block.exists(self.parent): - self.parent = Block(self.getMetadata('parent')) - else: - self.parent = None - - return self.parent - def getDate(self): ''' Returns the date that the block was received, if loaded from file @@ -459,10 +439,7 @@ class Block: - (Block): the Block instance ''' - if key == 'parent' and (not val is None) and (not val == self.getParent().getHash()): - self.setParent(val) - else: - self.bmetadata[key] = val + self.bmetadata[key] = val return self def setContent(self, bcontent): @@ -479,27 +456,9 @@ class Block: self.bcontent = str(bcontent) return self - def setParent(self, parent): - ''' - Sets the Block's parent - - Inputs: - - parent (Block/str): the Block's parent, to be stored in metadata - - Outputs: - - (Block): the Block instance - ''' - - if type(parent) == str: - parent = Block(parent) - - self.parent = parent - self.setMetadata('parent', (None if parent is None else self.getParent().getHash())) - return self - # static functions - def getBlocks(type = None, signer = None, signed = None, parent = None, reverse = False, limit = None): + def getBlocks(type = None, signer = None, signed = None, reverse = False, limit = None): ''' Returns a list of Block objects based on supplied filters @@ -515,9 +474,6 @@ class Block: try: - if (not parent is None) and (not isinstance(parent, Block)): - parent = Block(hash = parent) - relevant_blocks = list() blocks = (blockmetadb.get_block_list() if type is None else blockmetadb.get_blocks_by_type(type)) @@ -544,14 +500,6 @@ class Block: if not isSigner: relevant = False - if not parent is None: - blockParent = block.getParent() - - if blockParent is None: - relevant = False - else: - relevant = parent.getHash() == blockParent.getHash() - if relevant and (limit is None or len(relevant_Blocks) <= int(limit)): relevant_blocks.append(block) @@ -564,71 +512,6 @@ class Block: return list() - def mergeChain(child, file = None, maximumFollows = 1000): - ''' - Follows a child Block to its root parent Block, merging content - - Inputs: - - child (str/Block): the child Block to be followed - - file (str/file): the file to write the content to, instead of returning it - - maximumFollows (int): the maximum number of Blocks to follow - ''' - - maximumFollows = max(0, maximumFollows) - - # type conversions - if type(child) == list: - child = child[-1] - if type(child) == str: - child = Block(child) - if (not file is None) and (type(file) == str): - file = open(file, 'ab') - - # only store hashes to avoid intensive memory usage - blocks = [child.getHash()] - - # generate a list of parent Blocks - while True: - # end if the maximum number of follows has been exceeded - if len(blocks) - 1 >= maximumFollows: - break - - block = Block(blocks[-1]).getParent() - - # end if there is no parent Block - if block is None: - break - - # end if the Block is pointing to a previously parsed Block - if block.getHash() in blocks: - break - - # end if the block is not valid - if not block.isValid(): - break - - blocks.append(block.getHash()) - - buffer = b'' - - # combine block contents - for hash in blocks: - block = Block(hash) - contents = block.getContent() - contents = base64.b64decode(contents.encode()) - - if file is None: - try: - buffer += contents.encode() - except AttributeError: - buffer += contents - else: - file.write(contents) - if file is not None: - file.close() - - return (None if not file is None else buffer) - def exists(bHash): ''' Checks if a block is saved to file or not @@ -652,44 +535,3 @@ class Block: ret = isinstance(onionrstorage.getData(bHash), type(None)) return not ret - - def getCache(hash = None): - # give a list of the hashes of the cached blocks - if hash is None: - return list(Block.blockCache.keys()) - - # if they inputted self or a Block, convert to hash - if type(hash) == Block: - hash = hash.getHash() - - # just to make sure someone didn't put in a bool or something lol - hash = str(hash) - - # if it exists, return its content - if hash in Block.getCache(): - return Block.blockCache[hash] - - return None - - def cache(block, override = False): - # why even bother if they're giving bad data? - if not type(block) == Block: - return False - - # only cache if written to file - if block.getHash() is None: - return False - - # if it's already cached, what are we here for? - if block.getHash() in Block.getCache() and not override: - return False - - # dump old cached blocks if the size exceeds the maximum - if sys.getsizeof(Block.blockCacheOrder) >= config.get('allocations.block_cache_total', 50000000): # 50MB default cache size - del Block.blockCache[blockCacheOrder.pop(0)] - - # cache block content - Block.blockCache[block.getHash()] = block.getRaw() - Block.blockCacheOrder.append(block.getHash()) - - return True diff --git a/onionr/onionrexceptions.py b/onionr/onionrexceptions.py index 5bb82c6c..f19a4c62 100755 --- a/onionr/onionrexceptions.py +++ b/onionr/onionrexceptions.py @@ -43,6 +43,9 @@ class DecryptionError(Exception): class PasswordStrengthError(Exception): pass +class SignatureError(Exception): + pass + # block exceptions class ReplayAttack(Exception): diff --git a/onionr/onionrutils/blockmetadata/process.py b/onionr/onionrutils/blockmetadata/process.py index d09b3218..4ad4991a 100644 --- a/onionr/onionrutils/blockmetadata/process.py +++ b/onionr/onionrutils/blockmetadata/process.py @@ -42,11 +42,12 @@ def process_block_metadata(blockHash: str): signer = bytesconverter.bytes_to_str(myBlock.signer) valid = myBlock.verifySig() - if myBlock.getMetadata('newFSKey') is not None: - try: - onionrusers.OnionrUser(signer).addForwardKey(myBlock.getMetadata('newFSKey')) - except onionrexceptions.InvalidPubkey: - logger.warn('%s has invalid forward secrecy key to add: %s' % (signer, myBlock.getMetadata('newFSKey'))) + if valid: + if myBlock.getMetadata('newFSKey') is not None: + try: + onionrusers.OnionrUser(signer).addForwardKey(myBlock.getMetadata('newFSKey')) + except onionrexceptions.InvalidPubkey: + logger.warn('%s has invalid forward secrecy key to add: %s' % (signer, myBlock.getMetadata('newFSKey'))) try: if len(blockType) <= onionrvalues.MAX_BLOCK_TYPE_LENGTH: diff --git a/onionr/static-data/default-plugins/chat/controlapi.py b/onionr/static-data/default-plugins/chat/controlapi.py index c6b8c490..4c765f7a 100755 --- a/onionr/static-data/default-plugins/chat/controlapi.py +++ b/onionr/static-data/default-plugins/chat/controlapi.py @@ -68,6 +68,3 @@ def get_messages(peer): existing = list(existing) key_store.delete('r' + peer) return Response(json.dumps(existing)) - -#@flask_blueprint.route('/chatapi/connect/') -#def create_connection(peer) \ No newline at end of file diff --git a/onionr/static-data/default-plugins/chat/peerserver.py b/onionr/static-data/default-plugins/chat/peerserver.py index 3189c948..58dc3d7f 100755 --- a/onionr/static-data/default-plugins/chat/peerserver.py +++ b/onionr/static-data/default-plugins/chat/peerserver.py @@ -43,6 +43,7 @@ def pingdirect(): @direct_blueprint.route('/chat/sendto', methods=['POST', 'GET']) def sendto(): + """Endpoint peers send chat messages to""" try: msg = request.get_json(force=True) except json.JSONDecodeError: @@ -50,9 +51,9 @@ def sendto(): else: msg = json.dumps(msg) localcommand.local_command('/chat/addrec/%s' % (g.peer,), post=True, postData=msg) - print('msg from', g.peer, msg) return Response('success') @direct_blueprint.route('/chat/poll') def poll_chat(): + """Endpoints peers get new messages from""" return Response(localcommand.local_command('/chat/gets/%s' % (g.peer,))) \ No newline at end of file