Merge branch 'new-pm' into 'master'

New Plugins, bug fixes

See merge request beardog/Onionr!2
master
Kevin 2018-07-20 04:33:47 +00:00
commit eb00904fde
14 changed files with 358 additions and 294 deletions

View File

@ -25,7 +25,6 @@ from defusedxml import minidom
class OnionrCommunicatorDaemon: class OnionrCommunicatorDaemon:
def __init__(self, debug, developmentMode): def __init__(self, debug, developmentMode):
logger.warn('New (unstable) communicator is being used.')
# list of timer instances # list of timer instances
self.timers = [] self.timers = []
@ -57,6 +56,9 @@ class OnionrCommunicatorDaemon:
# list of new blocks to download, added to when new block lists are fetched from peers # list of new blocks to download, added to when new block lists are fetched from peers
self.blockQueue = [] self.blockQueue = []
# list of blocks currently downloading, avoid s
self.currentDownloading = []
# Clear the daemon queue for any dead messages # Clear the daemon queue for any dead messages
if os.path.exists(self._core.queueDB): if os.path.exists(self._core.queueDB):
self._core.clearDaemonQueue() self._core.clearDaemonQueue()
@ -77,9 +79,9 @@ class OnionrCommunicatorDaemon:
peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60) peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60)
OnionrCommunicatorTimers(self, self.lookupBlocks, 7, requiresPeer=True) OnionrCommunicatorTimers(self, self.lookupBlocks, 7, requiresPeer=True)
OnionrCommunicatorTimers(self, self.getBlocks, 10, requiresPeer=True) OnionrCommunicatorTimers(self, self.getBlocks, 10, requiresPeer=True)
OnionrCommunicatorTimers(self, self.clearOfflinePeer, 120) OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58)
OnionrCommunicatorTimers(self, self.lookupKeys, 125, requiresPeer=True) OnionrCommunicatorTimers(self, self.lookupKeys, 60, requiresPeer=True)
OnionrCommunicatorTimers(self, self.lookupAdders, 600, requiresPeer=True) OnionrCommunicatorTimers(self, self.lookupAdders, 60, requiresPeer=True)
# set loop to execute instantly to load up peer pool (replaced old pool init wait) # set loop to execute instantly to load up peer pool (replaced old pool init wait)
peerPoolTimer.count = (peerPoolTimer.frequency - 1) peerPoolTimer.count = (peerPoolTimer.frequency - 1)
@ -122,8 +124,7 @@ class OnionrCommunicatorDaemon:
peer = self.pickOnlinePeer() peer = self.pickOnlinePeer()
newAdders = self.peerAction(peer, action='pex') newAdders = self.peerAction(peer, action='pex')
self._core._utils.mergeAdders(newAdders) self._core._utils.mergeAdders(newAdders)
self.decrementThreadCount('lookupAdders')
self.decrementThreadCount('lookupKeys')
def lookupBlocks(self): def lookupBlocks(self):
'''Lookup new blocks & add them to download queue''' '''Lookup new blocks & add them to download queue'''
@ -154,6 +155,10 @@ class OnionrCommunicatorDaemon:
def getBlocks(self): def getBlocks(self):
'''download new blocks in queue''' '''download new blocks in queue'''
for blockHash in self.blockQueue: for blockHash in self.blockQueue:
if blockHash in self.currentDownloading:
logger.debug('ALREADY DOWNLOADING ' + blockHash)
continue
self.currentDownloading.append(blockHash)
logger.info("Attempting to download %s..." % blockHash) logger.info("Attempting to download %s..." % blockHash)
content = self.peerAction(self.pickOnlinePeer(), 'getData', data=blockHash) # block content from random peer (includes metadata) content = self.peerAction(self.pickOnlinePeer(), 'getData', data=blockHash) # block content from random peer (includes metadata)
if content != False: if content != False:
@ -171,7 +176,7 @@ class OnionrCommunicatorDaemon:
content = content.decode() # decode here because sha3Hash needs bytes above content = content.decode() # decode here because sha3Hash needs bytes above
metas = self._core._utils.getBlockMetadataFromData(content) # returns tuple(metadata, meta), meta is also in metadata metas = self._core._utils.getBlockMetadataFromData(content) # returns tuple(metadata, meta), meta is also in metadata
metadata = metas[0] metadata = metas[0]
meta = metas[1] #meta = metas[1]
if self._core._utils.validateMetadata(metadata): # check if metadata is valid if self._core._utils.validateMetadata(metadata): # check if metadata is valid
if self._core._crypto.verifyPow(content): # check if POW is enough/correct if self._core._crypto.verifyPow(content): # check if POW is enough/correct
logger.info('Block passed proof, saving.') logger.info('Block passed proof, saving.')
@ -191,6 +196,7 @@ class OnionrCommunicatorDaemon:
pass pass
logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash) logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
self.blockQueue.remove(blockHash) # remove from block queue both if success or false self.blockQueue.remove(blockHash) # remove from block queue both if success or false
self.currentDownloading.remove(blockHash)
self.decrementThreadCount('getBlocks') self.decrementThreadCount('getBlocks')
return return

View File

@ -628,12 +628,15 @@ class Core:
return None return None
def getBlocksByType(self, blockType): def getBlocksByType(self, blockType, orderDate=True):
''' '''
Returns a list of blocks by the type Returns a list of blocks by the type
''' '''
conn = sqlite3.connect(self.blockDB) conn = sqlite3.connect(self.blockDB)
c = conn.cursor() c = conn.cursor()
if orderDate:
execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;'
else:
execute = 'SELECT hash FROM hashes WHERE dataType=?;' execute = 'SELECT hash FROM hashes WHERE dataType=?;'
args = (blockType,) args = (blockType,)
rows = list() rows = list()
@ -719,7 +722,7 @@ class Core:
# sign before encrypt, as unauthenticated crypto should not be a problem here # sign before encrypt, as unauthenticated crypto should not be a problem here
if sign: if sign:
signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True) signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True)
signer = self._crypto.pubKeyHashID() signer = self._crypto.pubKey
if len(jsonMeta) > 1000: if len(jsonMeta) > 1000:
raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes') raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes')
@ -728,15 +731,16 @@ class Core:
if encryptType == 'sym': if encryptType == 'sym':
if len(symKey) < self.requirements.passwordLength: if len(symKey) < self.requirements.passwordLength:
raise onionrexceptions.SecurityError('Weak encryption key') raise onionrexceptions.SecurityError('Weak encryption key')
jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True) jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True).decode()
data = self._crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True) data = self._crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True).decode()
signature = self._crypto.symmetricEncrypt(signature, key=symKey, returnEncoded=True) signature = self._crypto.symmetricEncrypt(signature, key=symKey, returnEncoded=True).decode()
signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True) signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True).decode()
elif encryptType == 'asym': elif encryptType == 'asym':
if self._utils.validatePubKey(asymPeer): if self._utils.validatePubKey(asymPeer):
jsonMeta = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True) jsonMeta = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True, anonymous=True).decode()
data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True) data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True, anonymous=True).decode()
signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True) signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True, anonymous=True).decode()
signer = self._crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True, anonymous=True).decode()
else: else:
raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key') raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key')

View File

@ -134,7 +134,7 @@ def raw(data, fd = sys.stdout):
with open(_outputfile, "a+") as f: with open(_outputfile, "a+") as f:
f.write(colors.filter(data) + '\n') f.write(colors.filter(data) + '\n')
def log(prefix, data, color = '', timestamp=True, fd = sys.stdout): def log(prefix, data, color = '', timestamp=True, fd = sys.stdout, prompt = True):
''' '''
Logs the data Logs the data
prefix : The prefix to the output prefix : The prefix to the output
@ -145,7 +145,7 @@ def log(prefix, data, color = '', timestamp=True, fd = sys.stdout):
if timestamp: if timestamp:
curTime = time.strftime("%m-%d %H:%M:%S") + ' ' curTime = time.strftime("%m-%d %H:%M:%S") + ' '
output = colors.reset + str(color) + '[' + colors.bold + str(prefix) + colors.reset + str(color) + '] ' + curTime + str(data) + colors.reset output = colors.reset + str(color) + ('[' + colors.bold + str(prefix) + colors.reset + str(color) + '] ' if prompt is True else '') + curTime + str(data) + colors.reset
if not get_settings() & USE_ANSI: if not get_settings() & USE_ANSI:
output = colors.filter(output) output = colors.filter(output)
@ -201,31 +201,37 @@ def confirm(default = 'y', message = 'Are you sure %s? '):
return default == 'y' return default == 'y'
# debug: when there is info that could be useful for debugging purposes only # debug: when there is info that could be useful for debugging purposes only
def debug(data, timestamp=True): def debug(data, error = None, timestamp = True, prompt = True):
if get_level() <= LEVEL_DEBUG: if get_level() <= LEVEL_DEBUG:
log('/', data, timestamp=timestamp) log('/', data, timestamp=timestamp, prompt = prompt)
if not error is None:
debug('Error: ' + str(error) + parse_error())
# info: when there is something to notify the user of, such as the success of a process # info: when there is something to notify the user of, such as the success of a process
def info(data, timestamp=False): def info(data, timestamp = False, prompt = True):
if get_level() <= LEVEL_INFO: if get_level() <= LEVEL_INFO:
log('+', data, colors.fg.green, timestamp=timestamp) log('+', data, colors.fg.green, timestamp = timestamp, prompt = prompt)
# warn: when there is a potential for something bad to happen # warn: when there is a potential for something bad to happen
def warn(data, timestamp=True): def warn(data, error = None, timestamp = True, prompt = True):
if not error is None:
debug('Error: ' + str(error) + parse_error())
if get_level() <= LEVEL_WARN: if get_level() <= LEVEL_WARN:
log('!', data, colors.fg.orange, timestamp=timestamp) log('!', data, colors.fg.orange, timestamp = timestamp, prompt = prompt)
# error: when only one function, module, or process of the program encountered a problem and must stop # error: when only one function, module, or process of the program encountered a problem and must stop
def error(data, error=None, timestamp=True): def error(data, error = None, timestamp = True, prompt = True):
if get_level() <= LEVEL_ERROR: if get_level() <= LEVEL_ERROR:
log('-', data, colors.fg.red, timestamp=timestamp, fd = sys.stderr) log('-', data, colors.fg.red, timestamp = timestamp, fd = sys.stderr, prompt = prompt)
if not error is None: if not error is None:
debug('Error: ' + str(error) + parse_error()) debug('Error: ' + str(error) + parse_error())
# fatal: when the something so bad has happened that the program must stop # fatal: when the something so bad has happened that the program must stop
def fatal(data, timestamp=True): def fatal(data, error = None, timestamp=True, prompt = True):
if not error is None:
debug('Error: ' + str(error) + parse_error())
if get_level() <= LEVEL_FATAL: if get_level() <= LEVEL_FATAL:
log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp=timestamp, fd = sys.stderr) log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp=timestamp, fd = sys.stderr, prompt = prompt)
# returns a formatted error message # returns a formatted error message
def parse_error(): def parse_error():

View File

@ -50,7 +50,6 @@ class Onionr:
Main Onionr class. This is for the CLI program, and does not handle much of the logic. Main Onionr class. This is for the CLI program, and does not handle much of the logic.
In general, external programs and plugins should not use this class. In general, external programs and plugins should not use this class.
''' '''
try: try:
os.chdir(sys.path[0]) os.chdir(sys.path[0])
except FileNotFoundError: except FileNotFoundError:
@ -181,15 +180,6 @@ class Onionr:
'listkeys': self.listKeys, 'listkeys': self.listKeys,
'list-keys': self.listKeys, 'list-keys': self.listKeys,
'addmsg': self.addMessage,
'addmessage': self.addMessage,
'add-msg': self.addMessage,
'add-message': self.addMessage,
'pm': self.sendEncrypt,
'getpms': self.getPMs,
'get-pms': self.getPMs,
'addpeer': self.addPeer, 'addpeer': self.addPeer,
'add-peer': self.addPeer, 'add-peer': self.addPeer,
'add-address': self.addAddress, 'add-address': self.addAddress,
@ -226,9 +216,6 @@ class Onionr:
'create-plugin': 'Creates directory structure for a plugin', 'create-plugin': 'Creates directory structure for a plugin',
'add-peer': 'Adds a peer to database', 'add-peer': 'Adds a peer to database',
'list-peers': 'Displays a list of peers', 'list-peers': 'Displays a list of peers',
'add-msg': 'Broadcasts a message to the Onionr network',
'pm': 'Adds a private message to block',
'get-pms': 'Shows private messages sent to you',
'add-file': 'Create an Onionr block from a file', 'add-file': 'Create an Onionr block from a file',
'import-blocks': 'import blocks from the disk (Onionr is transport-agnostic!)', 'import-blocks': 'import blocks from the disk (Onionr is transport-agnostic!)',
'listconn': 'list connected peers', 'listconn': 'list connected peers',
@ -341,32 +328,6 @@ class Onionr:
logger.info('Sending kex to command queue...') logger.info('Sending kex to command queue...')
self.onionrCore.daemonQueueAdd('kex') self.onionrCore.daemonQueueAdd('kex')
def sendEncrypt(self):
'''
Create a private message and send it
'''
invalidID = True
while invalidID:
try:
peer = logger.readline('Peer to send to: ')
except KeyboardInterrupt:
break
else:
if self.onionrUtils.validatePubKey(peer):
invalidID = False
else:
logger.error('Invalid peer ID')
else:
try:
message = logger.readline("Enter a message: ")
except KeyboardInterrupt:
pass
else:
logger.info("Sending message to: " + logger.colors.underline + peer)
self.onionrUtils.sendPM(peer, message)
def listKeys(self): def listKeys(self):
''' '''
Displays a list of keys (used to be called peers) (?) Displays a list of keys (used to be called peers) (?)
@ -447,13 +408,6 @@ class Onionr:
logger.error('Failed to insert block.', timestamp = False) logger.error('Failed to insert block.', timestamp = False)
return return
def getPMs(self):
'''
display PMs sent to us
'''
self.onionrUtils.loadPMs()
def enablePlugin(self): def enablePlugin(self):
''' '''
Enables and starts the given plugin Enables and starts the given plugin

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 core as onionrcore, logger, config, onionrexceptions import core as onionrcore, logger, config, onionrexceptions, nacl.exceptions
import json, os, sys, datetime, base64 import json, os, sys, datetime, base64
class Block: class Block:
@ -42,8 +42,6 @@ class Block:
# initialize variables # initialize variables
self.valid = True self.valid = True
self.raw = None self.raw = None
self.powHash = None
self.powToken = None
self.signed = False self.signed = False
self.signature = None self.signature = None
self.signedData = None self.signedData = None
@ -51,23 +49,69 @@ class Block:
self.parent = None self.parent = None
self.bheader = {} self.bheader = {}
self.bmetadata = {} self.bmetadata = {}
self.isEncrypted = False
self.decrypted = False
self.signer = None
self.validSig = False
# handle arguments # handle arguments
if self.getCore() is None: if self.getCore() is None:
self.core = onionrcore.Core() self.core = onionrcore.Core()
if not self.core._utils.validateHash(self.hash):
raise onionrexceptions.InvalidHexHash('specified block hash is not valid')
# update the blocks' contents if it exists # update the blocks' contents if it exists
if not self.getHash() is None: if not self.getHash() is None:
if not self.update(): if not self.core._utils.validateHash(self.hash):
logger.debug('Block hash %s is invalid.' % self.getHash())
raise onionrexceptions.InvalidHexHash('Block hash is invalid.')
elif not self.update():
logger.debug('Failed to open block %s.' % self.getHash()) logger.debug('Failed to open block %s.' % self.getHash())
else: else:
logger.debug('Did not update block') pass
#logger.debug('Did not update block.')
# logic # logic
def decrypt(self, anonymous=True, encodedData=True):
'''Decrypt a block, loading decrypted data into their vars'''
if self.decrypted:
return True
retData = False
core = self.getCore()
# decrypt data
if self.getHeader('encryptType') == 'asym':
try:
self.bcontent = core._crypto.pubKeyDecrypt(self.bcontent, anonymous=anonymous, encodedData=encodedData)
bmeta = core._crypto.pubKeyDecrypt(self.bmetadata, anonymous=anonymous, encodedData=encodedData)
try:
bmeta = bmeta.decode()
except AttributeError:
# yet another bytes fix
pass
self.bmetadata = json.loads(bmeta)
self.signature = core._crypto.pubKeyDecrypt(self.signature, anonymous=anonymous, encodedData=encodedData)
self.signer = core._crypto.pubKeyDecrypt(self.signer, anonymous=anonymous, encodedData=encodedData)
self.signedData = json.dumps(self.bmetadata) + self.bcontent.decode()
except nacl.exceptions.CryptoError:
pass
#logger.debug('Could not decrypt block. Either invalid key or corrupted data')
else:
retData = True
self.decrypted = True
else:
logger.warn('symmetric decryption is not yet supported by this API')
return retData
def verifySig(self):
'''Verify if a block's signature is signed by its claimed signer'''
core = self.getCore()
if core._crypto.edVerify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
self.validSig = True
else:
self.validSig = False
return self.validSig
def update(self, data = None, file = None): def update(self, data = None, file = None):
''' '''
Loads data from a block in to the current object. Loads data from a block in to the current object.
@ -118,14 +162,19 @@ class Block:
self.raw = str(blockdata) self.raw = str(blockdata)
self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')]) self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')])
self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:] self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:]
if self.bheader['encryptType'] in ('asym', 'sym'):
self.bmetadata = self.getHeader('meta', None)
self.isEncrypted = True
else:
self.bmetadata = json.loads(self.getHeader('meta', None)) self.bmetadata = json.loads(self.getHeader('meta', None))
self.parent = self.getMetadata('parent', None) self.parent = self.getMetadata('parent', None)
self.btype = self.getMetadata('type', None) self.btype = self.getMetadata('type', None)
self.powHash = self.getMetadata('powHash', None)
self.powToken = self.getMetadata('powToken', None)
self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '') self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
# TODO: detect if signer is hash of pubkey or not
self.signer = self.getHeader('signer', None)
self.signature = self.getHeader('sig', None) self.signature = self.getHeader('sig', None)
self.signedData = (None if not self.isSigned() else self.getHeader('meta') + '\n' + self.getContent()) # signed data is jsonMeta + block content (no linebreak)
self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent())
self.date = self.getCore().getBlockDate(self.getHash()) self.date = self.getCore().getBlockDate(self.getHash())
if not self.getDate() is None: if not self.getDate() is None:
@ -214,7 +263,6 @@ class Block:
Outputs: Outputs:
- (str): the type of the block - (str): the type of the block
''' '''
return self.btype return self.btype
def getRaw(self): def getRaw(self):
@ -471,6 +519,8 @@ class Block:
if not signer is None: if not signer is None:
if isinstance(signer, (str,)): if isinstance(signer, (str,)):
signer = [signer] signer = [signer]
if isinstance(signer, (bytes,)):
signer = [signer.decode()]
isSigner = False isSigner = False
for key in signer: for key in signer:
@ -483,12 +533,13 @@ class Block:
if relevant: if relevant:
relevant_blocks.append(block) relevant_blocks.append(block)
if bool(reverse): if bool(reverse):
relevant_blocks.reverse() relevant_blocks.reverse()
return relevant_blocks return relevant_blocks
except Exception as e: except Exception as e:
logger.debug(('Failed to get blocks: %s' % str(e)) + logger.parse_error()) logger.debug('Failed to get blocks.', error = e)
return list() return list()

View File

@ -114,6 +114,11 @@ class OnionrCrypto:
'''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)''' '''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)'''
retVal = '' retVal = ''
try:
pubkey = pubkey.encode()
except AttributeError:
pass
if encodedData: if encodedData:
encoding = nacl.encoding.Base64Encoder encoding = nacl.encoding.Base64Encoder
else: else:
@ -127,7 +132,11 @@ class OnionrCrypto:
elif anonymous: elif anonymous:
key = nacl.signing.VerifyKey(key=pubkey, encoder=nacl.encoding.Base32Encoder).to_curve25519_public_key() key = nacl.signing.VerifyKey(key=pubkey, encoder=nacl.encoding.Base32Encoder).to_curve25519_public_key()
anonBox = nacl.public.SealedBox(key) anonBox = nacl.public.SealedBox(key)
retVal = anonBox.encrypt(data.encode(), encoder=encoding) try:
data = data.encode()
except AttributeError:
pass
retVal = anonBox.encrypt(data, encoder=encoding)
return retVal return retVal
def pubKeyDecrypt(self, data, pubkey='', anonymous=False, encodedData=False): def pubKeyDecrypt(self, data, pubkey='', anonymous=False, encodedData=False):

View File

@ -159,7 +159,11 @@ class POW:
self.metadata['powRandomToken'] = base64.b64encode(rand).decode() self.metadata['powRandomToken'] = base64.b64encode(rand).decode()
payload = json.dumps(self.metadata).encode() + b'\n' + self.data payload = json.dumps(self.metadata).encode() + b'\n' + self.data
token = myCore._crypto.sha3Hash(payload) token = myCore._crypto.sha3Hash(payload)
#print(token) try:
# on some versions, token is bytes
token = token.decode()
except AttributeError:
pass
if self.puzzle == token[0:self.difficulty]: if self.puzzle == token[0:self.difficulty]:
self.hashing = False self.hashing = False
iFound = True iFound = True

View File

@ -59,7 +59,7 @@ class OnionrUtils:
High level function to encrypt a message to a peer and insert it as a block High level function to encrypt a message to a peer and insert it as a block
''' '''
self._core.insertBlock(message, header='pm', sign=True, encryptType='sym', symKey=pubkey) self._core.insertBlock(message, header='pm', sign=True, encryptType='asym', asymPeer=pubkey)
return return
@ -117,7 +117,8 @@ class OnionrUtils:
else: else:
logger.warn("Failed to add key") logger.warn("Failed to add key")
else: else:
logger.warn('%s pow failed' % key[0]) pass
#logger.debug('%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)
@ -137,7 +138,8 @@ class OnionrUtils:
logger.info('Added %s to db.' % adder, 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) pass
#logger.debug('%s is either our address or already in our DB' % adder)
return retVal return retVal
except Exception as error: except Exception as error:
logger.error('Failed to merge adders.', error = error) logger.error('Failed to merge adders.', error = error)
@ -204,18 +206,23 @@ class OnionrUtils:
def getBlockMetadataFromData(self, blockData): def getBlockMetadataFromData(self, blockData):
''' '''
accepts block contents as string and returns a tuple of metadata, meta (meta being internal metadata) accepts block contents as string, returns a tuple of metadata, meta (meta being internal metadata, which will be returned as an encrypted base64 string if it is encrypted, dict if not).
''' '''
meta = {}
try: try:
blockData = blockData.encode() blockData = blockData.encode()
except AttributeError: except AttributeError:
pass pass
metadata = json.loads(blockData[:blockData.find(b'\n')].decode()) metadata = json.loads(blockData[:blockData.find(b'\n')].decode())
data = blockData[blockData.find(b'\n'):].decode() data = blockData[blockData.find(b'\n'):].decode()
if not metadata['encryptType'] in ('asym', 'sym'):
try: try:
meta = json.loads(metadata['meta']) meta = json.loads(metadata['meta'])
except KeyError: except KeyError:
meta = {} pass
meta = metadata['meta']
return (metadata, meta, data) return (metadata, meta, data)
def checkPort(self, port, host=''): def checkPort(self, port, host=''):
@ -251,7 +258,14 @@ class OnionrUtils:
Read metadata from a block and cache it to the block database Read metadata from a block and cache it to the block database
''' '''
myBlock = Block(blockHash, self._core) myBlock = Block(blockHash, self._core)
self._core.updateBlockInfo(blockHash, 'dataType', myBlock.getType()) if myBlock.isEncrypted:
myBlock.decrypt()
blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks
try:
if len(blockType) <= 10:
self._core.updateBlockInfo(blockHash, 'dataType', blockType)
except TypeError:
pass
def escapeAnsi(self, line): def escapeAnsi(self, line):
''' '''
@ -426,52 +440,6 @@ class OnionrUtils:
except: except:
return False return False
def loadPMs(self):
'''
Find, decrypt, and return array of PMs (array of dictionary, {from, text})
'''
blocks = Block.getBlocks(type = 'pm', core = self._core)
message = ''
sender = ''
for i in blocks:
try:
blockContent = i.getContent()
try:
message = self._core._crypto.pubKeyDecrypt(blockContent, encodedData=True, anonymous=True)
except nacl.exceptions.CryptoError as e:
pass
else:
try:
message = message.decode()
except AttributeError:
pass
try:
message = json.loads(message)
except json.decoder.JSONDecodeError:
pass
else:
logger.debug('Decrypted %s:' % i.getHash())
logger.info(message["msg"])
signer = message["id"]
sig = message["sig"]
if self.validatePubKey(signer):
if self._core._crypto.edVerify(message["msg"], signer, sig, encodedData=True):
logger.info("Good signature by %s" % signer)
else:
logger.warn("Bad signature by %s" % signer)
else:
logger.warn('Bad sender id: %s' % signer)
except FileNotFoundError:
pass
except Exception as error:
logger.error('Failed to open block %s.' % i, error=error)
return
def getPeerByHashId(self, hash): def getPeerByHashId(self, hash):
''' '''
Return the pubkey of the user if known from the hash Return the pubkey of the user if known from the hash

View File

@ -21,4 +21,4 @@
class OnionrValues: class OnionrValues:
def __init__(self): def __init__(self):
self.passwordLength = 20 self.passwordLength = 20
self.blockMetadataLengths = {'meta': 1000, 'sig': 88, 'signer': 64, 'time': 10, 'powRandomToken': 1000, 'encryptType': 4} self.blockMetadataLengths = {'meta': 1000, 'sig': 200, 'signer': 200, 'time': 10, 'powRandomToken': 1000, 'encryptType': 4} #TODO properly refine values to minimum needed

View File

@ -47,25 +47,24 @@ class OnionrFlow:
self.flowRunning = False self.flowRunning = False
if len(message) > 0: if len(message) > 0:
self.myCore.insertBlock(message) Block(content = message, type = 'txt', core = self.myCore).save()
logger.info("Flow is exiting, goodbye") logger.info("Flow is exiting, goodbye")
return return
def showOutput(self): def showOutput(self):
while self.flowRunning: while self.flowRunning:
for blockHash in self.myCore.getBlocksByType('txt'): for block in Block.getBlocks(type = 'txt', core = self.myCore):
if blockHash in self.alreadyOutputed: if block.getHash() in self.alreadyOutputed:
continue continue
if not self.flowRunning: if not self.flowRunning:
break break
logger.info('\n------------------------') logger.info('\n------------------------', prompt = False)
block = Block(blockHash, self.myCore)
content = block.getContent() content = block.getContent()
# Escape new lines, remove trailing whitespace, and escape ansi sequences # Escape new lines, remove trailing whitespace, and escape ansi sequences
content = self.myCore._utils.escapeAnsi(content.replace('\n', '\\n').replace('\r', '\\r').strip()) content = self.myCore._utils.escapeAnsi(content.replace('\n', '\\n').replace('\r', '\\r').strip())
logger.info("\n" + block.getDate().strftime("%m/%d %H:%M") + ' - ' + '\033[0;0m' + content) logger.info(block.getDate().strftime("%m/%d %H:%M") + ' - ' + logger.colors.reset + content, prompt = False)
self.alreadyOutputed.append(blockHash) self.alreadyOutputed.append(block.getHash())
try: try:
time.sleep(5) time.sleep(5)
except KeyboardInterrupt: except KeyboardInterrupt:
@ -84,6 +83,6 @@ def on_init(api, data = None):
global pluginapi global pluginapi
pluginapi = api pluginapi = api
flow = OnionrFlow() flow = OnionrFlow()
api.commands.register(['flow'], flow.start) api.commands.register('flow', flow.start)
api.commands.register_help('flow', 'Open the flow messaging interface') api.commands.register_help('flow', 'Open the flow messaging interface')
return return

View File

@ -1,135 +0,0 @@
#!/usr/bin/python
'''
Onionr - P2P Microblogging Platform & Social network
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 <https://www.gnu.org/licenses/>.
'''
# Imports some useful libraries
import logger, config, core
import os, sqlite3, threading
from onionrblockapi import Block
plugin_name = 'gui'
def send():
global message
block = Block()
block.setType('txt')
block.setContent(message)
logger.debug('Sent message in block %s.' % block.save(sign = True))
def sendMessage():
global sendEntry
global message
message = sendEntry.get()
t = threading.Thread(target = send)
t.start()
sendEntry.delete(0, len(message))
def update():
global listedBlocks, listbox, runningCheckDelayCount, runningCheckDelay, root, daemonStatus
for i in Block.getBlocks(type = 'txt'):
if i.getContent().strip() == '' or i.getHash() in listedBlocks:
continue
listbox.insert(99999, str(i.getContent()))
listedBlocks.append(i.getHash())
listbox.see(99999)
runningCheckDelayCount += 1
if runningCheckDelayCount == runningCheckDelay:
resp = pluginapi.daemon.local_command('ping')
if resp == 'pong':
daemonStatus.config(text = "Onionr Daemon Status: Running")
else:
daemonStatus.config(text = "Onionr Daemon Status: Not Running")
runningCheckDelayCount = 0
root.after(10000, update)
def reallyOpenGUI():
import tkinter
global root, runningCheckDelay, runningCheckDelayCount, scrollbar, listedBlocks, nodeInfo, keyInfo, idText, idEntry, pubKeyEntry, listbox, daemonStatus, sendEntry
root = tkinter.Tk()
root.title("Onionr GUI")
runningCheckDelay = 5
runningCheckDelayCount = 4
scrollbar = tkinter.Scrollbar(root)
scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
listedBlocks = []
nodeInfo = tkinter.Frame(root)
keyInfo = tkinter.Frame(root)
hostname = pluginapi.get_onionr().get_hostname()
logger.debug('Onionr Hostname: %s' % hostname)
idText = hostname
idEntry = tkinter.Entry(nodeInfo)
tkinter.Label(nodeInfo, text = "Node Address: ").pack(side=tkinter.LEFT)
idEntry.pack()
idEntry.insert(0, idText.strip())
idEntry.configure(state="readonly")
nodeInfo.pack()
pubKeyEntry = tkinter.Entry(keyInfo)
tkinter.Label(keyInfo, text="Public key: ").pack(side=tkinter.LEFT)
pubKeyEntry.pack()
pubKeyEntry.insert(0, pluginapi.get_core()._crypto.pubKey)
pubKeyEntry.configure(state="readonly")
keyInfo.pack()
sendEntry = tkinter.Entry(root)
sendBtn = tkinter.Button(root, text='Send Message', command=sendMessage)
sendEntry.pack(side=tkinter.TOP, pady=5)
sendBtn.pack(side=tkinter.TOP)
listbox = tkinter.Listbox(root, yscrollcommand=tkinter.Scrollbar.set, height=15)
listbox.pack(fill=tkinter.BOTH, pady=25)
daemonStatus = tkinter.Label(root, text="Onionr Daemon Status: unknown")
daemonStatus.pack()
scrollbar.config(command=tkinter.Listbox.yview)
root.after(2000, update)
root.mainloop()
def openGUI():
t = threading.Thread(target = reallyOpenGUI)
t.daemon = False
t.start()
def on_init(api, data = None):
global pluginapi
pluginapi = api
api.commands.register(['gui', 'launch-gui', 'open-gui'], openGUI)
api.commands.register_help('gui', 'Opens a graphical interface for Onionr')
return

View File

@ -1,5 +1,5 @@
{ {
"name" : "gui", "name" : "pms",
"version" : "1.0", "version" : "1.0",
"author" : "onionr" "author" : "onionr"
} }

View File

@ -0,0 +1,198 @@
'''
Onionr - P2P Microblogging Platform & Social network
This default plugin handles private messages in an email like fashion
'''
'''
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 <https://www.gnu.org/licenses/>.
'''
# Imports some useful libraries
import logger, config, threading, time, readline, datetime
from onionrblockapi import Block
import onionrexceptions
plugin_name = 'pms'
PLUGIN_VERSION = '0.0.1'
def draw_border(text):
#https://stackoverflow.com/a/20757491
lines = text.splitlines()
width = max(len(s) for s in lines)
res = ['' + '' * width + '']
for s in lines:
res.append('' + (s + ' ' * width)[:width] + '')
res.append('' + '' * width + '')
return '\n'.join(res)
class MailStrings:
def __init__(self, mailInstance):
self.mailInstance = mailInstance
self.programTag = 'OnionrMail v%s' % (PLUGIN_VERSION)
choices = ['view inbox', 'view sentbox', 'send message', 'help', 'quit']
self.mainMenuChoices = choices
self.mainMenu = '''\n
-----------------
1. %s
2. %s
3. %s
4. %s
5. %s''' % (choices[0], choices[1], choices[2], choices[3], choices[4])
class OnionrMail:
def __init__(self, pluginapi):
self.myCore = pluginapi.get_core()
#self.dataFolder = pluginapi.get_data_folder()
self.strings = MailStrings(self)
return
def inbox(self):
blockCount = 0
pmBlockMap = {}
pmBlocks = {}
logger.info('Decrypting messages...')
choice = ''
# this could use a lot of memory if someone has recieved a lot of messages
for blockHash in self.myCore.getBlocksByType('pm'):
pmBlocks[blockHash] = Block(blockHash, core=self.myCore)
pmBlocks[blockHash].decrypt()
while choice not in ('-q', 'q', 'quit'):
blockCount = 0
for blockHash in pmBlocks:
if not pmBlocks[blockHash].decrypted:
continue
blockCount += 1
pmBlockMap[blockCount] = blockHash
blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M")
print('%s. %s: %s' % (blockCount, blockDate, blockHash))
try:
choice = logger.readline('Enter a block number, -r to refresh, or -q to stop: ').strip().lower()
except (EOFError, KeyboardInterrupt):
choice = '-q'
if choice in ('-q', 'q', 'quit'):
continue
if choice in ('-r', 'r', 'refresh'):
# dirty hack
self.inbox()
return
try:
choice = int(choice)
except ValueError:
pass
else:
try:
pmBlockMap[choice]
readBlock = pmBlocks[pmBlockMap[choice]]
except KeyError:
pass
else:
readBlock.verifySig()
print('Message recieved from', readBlock.signer)
print('Valid signature:', readBlock.validSig)
if not readBlock.validSig:
logger.warn('This message has an INVALID signature. Anyone could have sent this message.')
logger.readline('Press enter to continue to message.')
print(draw_border(self.myCore._utils.escapeAnsi(readBlock.bcontent.decode().strip())))
return
def draftMessage(self):
message = ''
newLine = ''
recip = ''
entering = True
while entering:
try:
recip = logger.readline('Enter peer address, or q to stop:').strip()
if recip in ('-q', 'q'):
raise EOFError
if not self.myCore._utils.validatePubKey(recip):
raise onionrexceptions.InvalidPubkey('Must be a valid ed25519 base32 encoded public key')
except onionrexceptions.InvalidPubkey:
logger.warn('Invalid public key')
except (KeyboardInterrupt, EOFError):
entering = False
else:
break
else:
# if -q or ctrl-c/d, exit function here, otherwise we successfully got the public key
return
print('Enter your message, stop by entering -q on a new line.')
while newLine != '-q':
try:
newLine = input()
except (KeyboardInterrupt, EOFError):
pass
if newLine == '-q':
continue
newLine += '\n'
message += newLine
print('Inserting encrypted message as Onionr block....')
self.myCore.insertBlock(message, header='pm', encryptType='asym', asymPeer=recip, sign=True)
def menu(self):
choice = ''
while True:
print(self.strings.programTag + '\n\nOur ID: ' + self.myCore._crypto.pubKey + self.strings.mainMenu.title()) # print out main menu
try:
choice = logger.readline('Enter 1-%s:\n' % (len(self.strings.mainMenuChoices))).lower().strip()
except (KeyboardInterrupt, EOFError):
choice = '5'
if choice in (self.strings.mainMenuChoices[0], '1'):
self.inbox()
elif choice in (self.strings.mainMenuChoices[1], '2'):
logger.warn('not implemented yet')
elif choice in (self.strings.mainMenuChoices[2], '3'):
self.draftMessage()
elif choice in (self.strings.mainMenuChoices[3], '4'):
logger.warn('not implemented yet')
elif choice in (self.strings.mainMenuChoices[4], '5'):
logger.info('Goodbye.')
break
elif choice == '':
pass
else:
logger.warn('Invalid choice.')
return
def on_init(api, data = None):
'''
This event is called after Onionr is initialized, but before the command
inputted is executed. Could be called when daemon is starting or when
just the client is running.
'''
pluginapi = api
mail = OnionrMail(pluginapi)
api.commands.register(['mail'], mail.menu)
api.commands.register_help('mail', 'Interact with OnionrMail')
return

View File

@ -1,6 +1,5 @@
![Onionr logo](./docs/onionr-logo.png) ![Onionr logo](./docs/onionr-logo.png)
[![Build Status](https://travis-ci.org/beardog108/onionr.svg?branch=master)](https://travis-ci.org/beardog108/onionr)
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/) [![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/)
@ -10,10 +9,11 @@ Major work in progress.
***THIS SOFTWARE IS NOT USABLE OR SECURE YET.*** ***THIS SOFTWARE IS NOT USABLE OR SECURE YET.***
**The main repo for this software is at https://gitlab.com/beardog/Onionr/**
**Roadmap/features:** **Roadmap/features:**
Check the [GitHub Project](https://github.com/beardog108/onionr/projects/1) to see progress towards the alpha release. Check the [Gitlab Project](https://gitlab.com/beardog/Onionr/milestones/1) to see progress towards the alpha release.
* [X] Fully p2p/decentralized, no trackers or other single points of failure * [X] Fully p2p/decentralized, no trackers or other single points of failure
* [X] High level of anonymity * [X] High level of anonymity