Various improvements

- Adds a lot more to the pluginmanager
- Refactors code
- Relocates functions
master
Arinerron 2018-05-12 20:45:32 -07:00
parent adf007bf30
commit fe4261c4a2
No known key found for this signature in database
GPG Key ID: 99383627861C62F0
7 changed files with 172 additions and 114 deletions

View File

@ -565,31 +565,26 @@ class OnionrCommunicate:
except AttributeError: except AttributeError:
pass pass
if not self.verifyPow(blockContent, blockMeta2): if not self._crypto.verifyPow(blockContent, blockMeta2):
logger.warn("%s has invalid or insufficient proof of work token, deleting..." % str(i)) logger.warn("%s has invalid or insufficient proof of work token, deleting..." % str(i))
self._core.removeBlock(i) self._core.removeBlock(i)
continue continue
try:
blockMetadata['sig']
blockMeta2['id']
except KeyError:
pass
else: else:
#blockData = json.dumps(blockMetadata['meta']) + blockMetadata[blockMetadata.rfind(b'}') + 1:] if (('sig' in blockMetadata) and ('id' in blockMeta2)):
#blockData = json.dumps(blockMetadata['meta']) + blockMetadata[blockMetadata.rfind(b'}') + 1:]
creator = self._utils.getPeerByHashId(blockMeta2['id']) creator = self._utils.getPeerByHashId(blockMeta2['id'])
try: try:
creator = creator.decode() creator = creator.decode()
except AttributeError: except AttributeError:
pass pass
if self._core._crypto.edVerify(blockMetaData['meta'] + blockContent, creator, blockMetadata['sig'], encodedData=True): if self._core._crypto.edVerify(blockMetaData['meta'] + blockContent, creator, blockMetadata['sig'], encodedData=True):
logger.info('%s was signed' % str(i)) logger.info('%s was signed' % str(i))
self._core.updateBlockInfo(i, 'sig', 'true') self._core.updateBlockInfo(i, 'sig', 'true')
else: else:
logger.warn('%s has an invalid signature' % str(i)) logger.warn('%s has an invalid signature' % str(i))
self._core.updateBlockInfo(i, 'sig', 'false') self._core.updateBlockInfo(i, 'sig', 'false')
try: try:
logger.info('Block type is %s' % str(blockMeta2['type'])) logger.info('Block type is %s' % str(blockMeta2['type']))
self._core.updateBlockInfo(i, 'dataType', blockMeta2['type']) self._core.updateBlockInfo(i, 'dataType', blockMeta2['type'])
@ -605,12 +600,7 @@ class OnionrCommunicate:
return return
def removeBlockFromProcessingList(self, block): def removeBlockFromProcessingList(self, block):
try: return block in blocksProcessing
self.blocksProcessing.remove(block)
except ValueError:
return False
else:
return True
def downloadBlock(self, hash, peerTries=3): def downloadBlock(self, hash, peerTries=3):
''' '''
@ -666,41 +656,6 @@ class OnionrCommunicate:
return retVal return retVal
def verifyPow(self, blockContent, metadata):
'''
Verifies the proof of work associated with a block
'''
retData = False
try:
metadata['powToken']
metadata['powHash']
token = metadata['powToken']
except KeyError:
return False
dataLen = len(blockContent)
expectedHash = self._crypto.blake2bHash(base64.b64decode(metadata['powToken']) + self._crypto.blake2bHash(blockContent.encode()))
difficulty = 0
try:
expectedHash = expectedHash.decode()
except AttributeError:
pass
if metadata['powHash'] == expectedHash:
difficulty = math.floor(dataLen/1000000)
mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode()
puzzle = mainHash[0:difficulty]
if metadata['powHash'][0:difficulty] == puzzle:
logger.info('Validated block pow')
retData = True
else:
logger.warn("Invalid token (#1)")
else:
logger.warn('Invalid token (#2): Expected hash %s, got hash %s...' % (metadata['powHash'], expectedHash))
return retData
def urlencode(self, data): def urlencode(self, data):
''' '''
URL encodes the data URL encodes the data

View File

@ -277,11 +277,12 @@ class Core:
return return
def getData(self,hash): def getData(self, hash):
''' '''
Simply return the data associated to a hash Simply return the data associated to a hash
''' '''
try: try:
# logger.debug('Opening %s' % (str(self.blockDataLocation) + str(hash) + '.dat'))
dataFile = open(self.blockDataLocation + hash + '.dat', 'rb') dataFile = open(self.blockDataLocation + hash + '.dat', 'rb')
data = dataFile.read() data = dataFile.read()
dataFile.close() dataFile.close()
@ -576,22 +577,22 @@ class Core:
return return
def getBlockList(self, unsaved = False): def getBlockList(self, unsaved = False): # TODO: Use unsaved
''' '''
Get list of our blocks Get list of our blocks
''' '''
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB)
c = conn.cursor() c = conn.cursor()
retData = ''
if unsaved: if unsaved:
execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();' execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();'
else: else:
execute = 'SELECT hash FROM hashes ORDER BY RANDOM();' execute = 'SELECT hash FROM hashes ORDER BY RANDOM();'
rows = list()
for row in c.execute(execute): for row in c.execute(execute):
for i in row: for i in row:
retData += i + "\n" rows.append(i)
return retData return rows
def getBlocksByType(self, blockType): def getBlocksByType(self, blockType):
''' '''
@ -599,14 +600,14 @@ class Core:
''' '''
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB)
c = conn.cursor() c = conn.cursor()
retData = ''
execute = 'SELECT hash FROM hashes WHERE dataType=?;' execute = 'SELECT hash FROM hashes WHERE dataType=?;'
args = (blockType,) args = (blockType,)
rows = list()
for row in c.execute(execute, args): for row in c.execute(execute, args):
for i in row: for i in row:
retData += i + "\n" rows.append(i)
return retData.split('\n') return rows
def setBlockType(self, hash, blockType): def setBlockType(self, hash, blockType):
''' '''

View File

@ -108,7 +108,7 @@ class Onionr:
if not os.path.exists('data/blocks/'): if not os.path.exists('data/blocks/'):
os.mkdir('data/blocks/') os.mkdir('data/blocks/')
# Copy default plugins into plugins folder # Copy default plugins into plugins folder
if not os.path.exists(plugins.get_plugins_folder()): if not os.path.exists(plugins.get_plugins_folder()):
if os.path.exists('static-data/default-plugins/'): if os.path.exists('static-data/default-plugins/'):
names = [f for f in os.listdir("static-data/default-plugins/") if not os.path.isfile(f)] names = [f for f in os.listdir("static-data/default-plugins/") if not os.path.isfile(f)]

View File

@ -26,7 +26,7 @@ class OnionrCrypto:
self.keyPowFile = 'data/keyPow.txt' self.keyPowFile = 'data/keyPow.txt'
self.pubKey = None self.pubKey = None
self.privKey = None self.privKey = None
self.pubKeyPowToken = None self.pubKeyPowToken = None
self.pubKeyPowHash = None self.pubKeyPowHash = None
@ -89,7 +89,7 @@ class OnionrCrypto:
except nacl.exceptions.BadSignatureError: except nacl.exceptions.BadSignatureError:
pass pass
return retData return retData
def edSign(self, data, key, encodeResult=False): def edSign(self, data, key, encodeResult=False):
'''Ed25519 sign data''' '''Ed25519 sign data'''
try: try:
@ -199,7 +199,7 @@ class OnionrCrypto:
if returnEncoded: if returnEncoded:
decrypted = base64.b64encode(decrypted) decrypted = base64.b64encode(decrypted)
return decrypted return decrypted
def generateSymmetricPeer(self, peer): def generateSymmetricPeer(self, peer):
'''Generate symmetric key for a peer and save it to the peer database''' '''Generate symmetric key for a peer and save it to the peer database'''
key = self.generateSymmetric() key = self.generateSymmetric()
@ -215,7 +215,7 @@ class OnionrCrypto:
private_key = nacl.signing.SigningKey.generate() private_key = nacl.signing.SigningKey.generate()
public_key = private_key.verify_key.encode(encoder=nacl.encoding.Base32Encoder()) public_key = private_key.verify_key.encode(encoder=nacl.encoding.Base32Encoder())
return (public_key.decode(), private_key.encode(encoder=nacl.encoding.Base32Encoder()).decode()) return (public_key.decode(), private_key.encode(encoder=nacl.encoding.Base32Encoder()).decode())
def pubKeyHashID(self, pubkey=''): def pubKeyHashID(self, pubkey=''):
'''Accept a ed25519 public key, return a truncated result of X many sha3_256 hash rounds''' '''Accept a ed25519 public key, return a truncated result of X many sha3_256 hash rounds'''
if pubkey == '': if pubkey == '':
@ -237,10 +237,43 @@ class OnionrCrypto:
hasher = hashlib.sha3_256() hasher = hashlib.sha3_256()
hasher.update(data) hasher.update(data)
return hasher.hexdigest() return hasher.hexdigest()
def blake2bHash(self, data): def blake2bHash(self, data):
try: try:
data = data.encode() data = data.encode()
except AttributeError: except AttributeError:
pass pass
return nacl.hash.blake2b(data) return nacl.hash.blake2b(data)
def verifyPow(self, blockContent, metadata):
'''
Verifies the proof of work associated with a block
'''
retData = False
if not (('powToken' in metadata) and ('powHash' in metadata)):
return False
dataLen = len(blockContent)
expectedHash = self.blake2bHash(base64.b64decode(metadata['powToken']) + self.blake2bHash(blockContent.encode()))
difficulty = 0
try:
expectedHash = expectedHash.decode()
except AttributeError:
pass
if metadata['powHash'] == expectedHash:
difficulty = math.floor(dataLen / 1000000)
mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode()
puzzle = mainHash[:difficulty]
if metadata['powHash'][:difficulty] == puzzle:
logger.info('Validated block pow')
retData = True
else:
logger.warn("Invalid token (#1)")
else:
logger.warn('Invalid token (#2): Expected hash %s, got hash %s...' % (metadata['powHash'], expectedHash))
return retData

View File

@ -18,7 +18,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
''' '''
import onionrplugins as plugins, logger import onionrplugins, logger
class DaemonAPI: class DaemonAPI:
def __init__(self, pluginapi): def __init__(self, pluginapi):
@ -52,34 +52,34 @@ class PluginAPI:
self.pluginapi = pluginapi self.pluginapi = pluginapi
def start(self, name): def start(self, name):
plugins.start(name) onionrplugins.start(name)
def stop(self, name): def stop(self, name):
plugins.stop(name) onionrplugins.stop(name)
def reload(self, name): def reload(self, name):
plugins.reload(name) onionrplugins.reload(name)
def enable(self, name): def enable(self, name):
plugins.enable(name) onionrplugins.enable(name)
def disable(self, name): def disable(self, name):
plugins.disable(name) onionrplugins.disable(name)
def event(self, name, data = {}): def event(self, name, data = {}):
events.event(name, data = data, onionr = self.pluginapi.get_onionr()) events.event(name, data = data, onionr = self.pluginapi.get_onionr())
def is_enabled(self, name): def is_enabled(self, name):
return plugins.is_enabled(name) return onionrplugins.is_enabled(name)
def get_enabled_plugins(self): def get_enabled_plugins(self):
return plugins.get_enabled() return onionrplugins.get_enabled()
def get_folder(self, name = None, absolute = True): def get_folder(self, name = None, absolute = True):
return plugins.get_plugins_folder(name = name, absolute = absolute) return onionrplugins.get_plugins_folder(name = name, absolute = absolute)
def get_data_folder(self, name, absolute = True): def get_data_folder(self, name, absolute = True):
return plugins.get_plugin_data_folder(name, absolute = absolute) return onionrplugins.get_plugin_data_folder(name, absolute = absolute)
def daemon_event(self, event, plugin = None): def daemon_event(self, event, plugin = None):
return # later make local command like /client/?action=makeEvent&event=eventname&module=modulename return # later make local command like /client/?action=makeEvent&event=eventname&module=modulename
@ -153,6 +153,9 @@ class pluginapi:
def get_utils(self): def get_utils(self):
return self.get_onionr().onionrUtils return self.get_onionr().onionrUtils
def get_crypto():
return self.get_core().crypto
def get_daemonapi(self): def get_daemonapi(self):
return self.daemon return self.daemon

View File

@ -71,7 +71,7 @@ class OnionrUtils:
if block == '': if block == '':
logger.error('Could not send PM') logger.error('Could not send PM')
else: else:
logger.info('Sent PM, hash: ' + block) logger.info('Sent PM, hash: %s' % block)
except Exception as error: except Exception as error:
logger.error('Failed to send PM.', error=error) logger.error('Failed to send PM.', error=error)
@ -103,14 +103,14 @@ class OnionrUtils:
for key in newKeyList.split(','): for key in newKeyList.split(','):
key = key.split('-') key = key.split('-')
if len(key[0]) > 60 or len(key[1]) > 1000: if len(key[0]) > 60 or len(key[1]) > 1000:
logger.warn(key[0] + ' or its pow value is too large.') logger.warn('%s or its pow value is too large.' % key[0])
continue continue
if self._core._crypto.blake2bHash(base64.b64decode(key[1]) + key[0].encode()).startswith('0000'): if self._core._crypto.blake2bHash(base64.b64decode(key[1]) + key[0].encode()).startswith('0000'):
if not key[0] in self._core.listPeers(randomOrder=False) and type(key) != None and key[0] != self._core._crypto.pubKey: if not key[0] in self._core.listPeers(randomOrder=False) and type(key) != None and key[0] != self._core._crypto.pubKey:
if self._core.addPeer(key[0], key[1]): if self._core.addPeer(key[0], key[1]):
retVal = True retVal = True
else: else:
logger.warn(key[0] + 'pow failed') logger.warn('%s pow failed' % key[0])
return retVal return retVal
except Exception as error: except Exception as error:
logger.error('Failed to merge keys.', error=error) logger.error('Failed to merge keys.', error=error)
@ -127,7 +127,7 @@ class OnionrUtils:
for adder in newAdderList.split(','): for adder in newAdderList.split(','):
if not adder in self._core.listAdders(randomOrder = False) and adder.strip() != self.getMyAddress(): if not adder in self._core.listAdders(randomOrder = False) and adder.strip() != self.getMyAddress():
if self._core.addAddress(adder): if self._core.addAddress(adder):
logger.info('Added ' + adder + ' to db.', timestamp = True) logger.info('Added %s to db.' % adder, timestamp = True)
retVal = True retVal = True
else: else:
logger.debug('%s is either our address or already in our DB' % adder) logger.debug('%s is either our address or already in our DB' % adder)
@ -156,7 +156,7 @@ class OnionrUtils:
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 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 Exception as error: except Exception as error:
if not silent: if not silent:
logger.error('Failed to make local request (command: ' + str(command) + ').', error=error) logger.error('Failed to make local request (command: %s).' % command, error=error)
retData = False retData = False
return retData return retData
@ -362,8 +362,7 @@ class OnionrUtils:
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
pass pass
else: else:
print('--------------------') logger.info('Decrypted %s:' % i)
logger.info('Decrypted ' + i + ':')
logger.info(message["msg"]) logger.info(message["msg"])
signer = message["id"] signer = message["id"]
@ -371,16 +370,16 @@ class OnionrUtils:
if self.validatePubKey(signer): if self.validatePubKey(signer):
if self._core._crypto.edVerify(message["msg"], signer, sig, encodedData=True): if self._core._crypto.edVerify(message["msg"], signer, sig, encodedData=True):
logger.info("Good signature by " + signer) logger.info("Good signature by %s" % signer)
else: else:
logger.warn("Bad signature by " + signer) logger.warn("Bad signature by %s" % signer)
else: else:
logger.warn("Bad sender id: " + signer) logger.warn('Bad sender id: %s' % signer)
except FileNotFoundError: except FileNotFoundError:
pass pass
except Exception as error: except Exception as error:
logger.error('Failed to open block ' + str(i) + '.', error=error) logger.error('Failed to open block %s.' % i, error=error)
return return
def getPeerByHashId(self, hash): def getPeerByHashId(self, hash):
@ -438,14 +437,14 @@ class OnionrUtils:
scanDir += '/' scanDir += '/'
for block in glob.glob(scanDir + "*.dat"): for block in glob.glob(scanDir + "*.dat"):
if block.replace(scanDir, '').replace('.dat', '') not in blockList: if block.replace(scanDir, '').replace('.dat', '') not in blockList:
logger.info("Found new block on dist " + block) logger.info('Found new block on dist %s' % block)
with open(block, 'rb') as newBlock: with open(block, 'rb') as newBlock:
block = block.replace(scanDir, '').replace('.dat', '') block = block.replace(scanDir, '').replace('.dat', '')
if self._core._crypto.sha3Hash(newBlock.read()) == block.replace('.dat', ''): if self._core._crypto.sha3Hash(newBlock.read()) == block.replace('.dat', ''):
self._core.addToBlockDB(block.replace('.dat', ''), dataSaved=True) self._core.addToBlockDB(block.replace('.dat', ''), dataSaved=True)
logger.info('Imported block.') logger.info('Imported block %s.' % block)
else: else:
logger.warn('Failed to verify hash for ' + block) logger.warn('Failed to verify hash for %s' % block)
def progressBar(self, value = 0, endvalue = 100, width = None): def progressBar(self, value = 0, endvalue = 100, width = None):

View File

@ -4,7 +4,7 @@
# useful libraries # useful libraries
import logger, config import logger, config
import os, sys, json, time, random import os, sys, json, time, random, shutil, base64, getpass, datetime
plugin_name = 'pluginmanager' plugin_name = 'pluginmanager'
@ -56,6 +56,78 @@ def check():
if not os.path.isfile(keys_file): if not os.path.isfile(keys_file):
writeKeys() writeKeys()
# plugin management
def installBlock(hash, overwrite = True):
logger.debug('install')
def pluginToBlock(plugin, import_block = True):
try:
directory = pluginapi.get_pluginapi().get_folder(plugin)
data_directory = pluginapi.get_pluginapi().get_data_folder(plugin)
zipfile = pluginapi.get_pluginapi().get_data_folder(plugin_name) + 'plugin.zip'
if os.path.exists(directory) and not os.path.isfile(directory):
if os.path.exists(data_directory) and not os.path.isfile(data_directory):
shutil.rmtree(data_directory)
if os.path.exists(zipfile) and os.path.isfile(zipfile):
os.remove(zipfile)
if os.path.exists(directory + '__pycache__') and not os.path.isfile(directory + '__pycache__'):
shutil.rmtree(directory + '__pycache__')
shutil.make_archive(zipfile[:-4], 'zip', directory)
data = base64.b64encode(open(zipfile, 'rb').read())
metadata = {'author' : getpass.getuser(), 'date' : str(datetime.datetime.now()), 'content' : data.decode('utf-8'), 'name' : plugin, 'compiled-by' : plugin_name}
hash = pluginapi.get_core().insertBlock(json.dumps(metadata), header = 'plugin', sign = True)
if import_block:
pluginapi.get_utils().importNewBlocks()
return hash
else:
logger.error('Plugin %s does not exist.' % plugin)
except Exception as e:
logger.error('Failed to convert plugin to block.', error = e, timestamp = False)
return False
def parseBlock(hash, key):# deal with block metadata
blockContent = pluginapi.get_core().getData(hash)
try:
blockMetadata = json.loads(blockContent[:blockContent.decode().find(b'}') + 1].decode())
try:
blockMeta2 = json.loads(blockMetadata['meta'])
except KeyError:
blockMeta2 = {'type': ''}
pass
blockContent = blockContent[blockContent.rfind(b'}') + 1:]
try:
blockContent = blockContent.decode()
except AttributeError:
pass
if not pluginapi.get_crypto().verifyPow(blockContent, blockMeta2):
logger.debug("(pluginmanager): %s has invalid or insufficient proof of work" % str(hash))
return False
if not (('sig' in blockMetadata) and ('id' in blockMeta2)):
logger.debug('(pluginmanager): %s is missing required parameters' % hash)
return False
else:
if pluginapi.get_crypto().edVerify(blockMetaData['meta'] + blockContent, key, blockMetadata['sig'], encodedData=True):
logger.debug('(pluginmanager): %s was signed' % str(hash))
return True
else:
logger.debug('(pluginmanager): %s has an invalid signature' % str(hash))
return False
except json.decoder.JSONDecodeError as e:
logger.error('(pluginmanager): Could not decode block metadata.', error = e, timestamp = False)
return False
# command handlers # command handlers
def help(): def help():
@ -101,13 +173,7 @@ def commandInstallPlugin():
blockhash = str(pkobh) blockhash = str(pkobh)
logger.debug('Using block %s...' % blockhash) logger.debug('Using block %s...' % blockhash)
logger.info('Downloading plugin...') installBlock(blockhash)
for i in range(0, 100):
pluginapi.get_utils().progressBar(i, 100)
time.sleep(random.random() / 5)
logger.info('Finished downloading plugin, verifying and installing...')
time.sleep(1)
logger.info('Installation successful.')
elif valid_key and not real_key: elif valid_key and not real_key:
logger.error('Public key not found. Try adding the node by address manually, if possible.') logger.error('Public key not found. Try adding the node by address manually, if possible.')
logger.debug('Is valid key, but the key is not a known one.') logger.debug('Is valid key, but the key is not a known one.')
@ -117,13 +183,14 @@ def commandInstallPlugin():
saveKey(pluginname, pkobh) saveKey(pluginname, pkobh)
logger.info('Downloading plugin...') blocks = pluginapi.get_core().getBlocksByType('plugin')
for i in range(0, 100):
pluginapi.get_utils().progressBar(i, 100) for hash in blocks:
time.sleep(random.random() / 5) logger.debug('Scanning block for plugin: %s' % hash)
logger.info('Finished downloading plugin, verifying and installing...') if parseBlock(hash, publickey):
time.sleep(1) logger.info('success')
logger.info('Installation successful.') else:
logger.warn('fail')
else: else:
logger.error('Unknown data "%s"; must be public key or block hash.' % str(pkobh)) logger.error('Unknown data "%s"; must be public key or block hash.' % str(pkobh))
return return