diff --git a/onionr/onionr.py b/onionr/onionr.py index 378eb892..62dc2fda 100755 --- a/onionr/onionr.py +++ b/onionr/onionr.py @@ -34,6 +34,8 @@ from utils import detectoptimization if detectoptimization.detect_optimization(): sys.stderr.write('Error, Onionr cannot be run in optimized mode\n') sys.exit(1) +from utils import createdirs +createdirs.create_dirs() import os, base64, random, shutil, time, platform, signal from threading import Thread import config, logger, onionrplugins as plugins, onionrevents as events @@ -42,7 +44,7 @@ from netcontroller import NetController from onionrblockapi import Block import onionrproofs, onionrexceptions, communicator, setupconfig import onionrcommands as commands # Many command definitions are here -from utils import identifyhome, createdirs +from utils import identifyhome from coredb import keydb import filepaths @@ -75,7 +77,6 @@ class Onionr: # Load global configuration data data_exists = Onionr.setupConfig(self.dataDir, self) - createdirs.create_dirs() if netcontroller.tor_binary() is None: logger.error('Tor is not installed', terminal=True) diff --git a/onionr/onionrblacklist.py b/onionr/onionrblacklist.py index 3542a1f5..d0702e5e 100755 --- a/onionr/onionrblacklist.py +++ b/onionr/onionrblacklist.py @@ -21,7 +21,7 @@ import sqlite3, os import logger, onionrcrypto from onionrutils import epoch, bytesconverter from coredb import dbfiles -crypto = onionrcrypto.OnionrCrypto() + class OnionrBlackList: def __init__(self): self.blacklistDB = dbfiles.blacklist_db @@ -31,7 +31,7 @@ class OnionrBlackList: return def inBlacklist(self, data): - hashed = bytesconverter.bytes_to_str(crypto.sha3Hash(data)) + hashed = bytesconverter.bytes_to_str(onionrcrypto.hashers.sha3_hash(data)) retData = False if not hashed.isalnum(): @@ -100,6 +100,7 @@ class OnionrBlackList: 1=peer 2=pubkey ''' + # we hash the data so we can remove data entirely from our node's disk hashed = bytesconverter.bytes_to_str(crypto.sha3Hash(data)) if len(hashed) > 64: diff --git a/onionr/onionrblocks/__init__.py b/onionr/onionrblocks/__init__.py index 3e3c20ba..93e9fd3f 100644 --- a/onionr/onionrblocks/__init__.py +++ b/onionr/onionrblocks/__init__.py @@ -1 +1,3 @@ -from . import insert \ No newline at end of file +from . import insert + +insert = insert.insert_block \ No newline at end of file diff --git a/onionr/onionrblocks/insert.py b/onionr/onionrblocks/insert.py index b4727511..073ae9e0 100644 --- a/onionr/onionrblocks/insert.py +++ b/onionr/onionrblocks/insert.py @@ -3,15 +3,18 @@ from onionrutils import bytesconverter, epoch import storagecounter, filepaths, onionrstorage import onionrevents as events from etc import powchoice, onionrvalues -def insert_block(onionr_inst, data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta = {}, expire=None, disableForward=False): +import config, onionrcrypto as crypto, subprocesspow, onionrexceptions +from onionrusers import onionrusers +from onionrutils import localcommand, blockmetadata +import coredb +def insert_block(data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta = {}, expire=None, disableForward=False): ''' Inserts a block into the network encryptType must be specified to encrypt a block ''' - use_subprocess = powchoice.use_subprocess(onionr_inst.config) + use_subprocess = powchoice.use_subprocess(config) requirements = onionrvalues.OnionrValues() storage_counter = storagecounter.StorageCounter() - crypto = onionr_inst.crypto allocationReachedMessage = 'Cannot insert block, disk allocation reached.' if storage_counter.isFull(): logger.error(allocationReachedMessage) @@ -23,7 +26,7 @@ def insert_block(onionr_inst, data, header='txt', sign=False, encryptType='', sy createTime = epoch.get_epoch() - dataNonce = bytesconverter.bytes_to_str(hashers.sha3_hash(data)) + dataNonce = bytesconverter.bytes_to_str(crypto.hashers.sha3_hash(data)) try: with open(filepaths.data_nonce_file, 'r') as nonces: if dataNonce in nonces: @@ -78,31 +81,23 @@ def insert_block(onionr_inst, data, header='txt', sign=False, encryptType='', sy jsonMeta = json.dumps(meta) plaintextMeta = jsonMeta if sign: - signature = crypto.edSign(jsonMeta.encode() + data, key=crypto.privKey, encodeResult=True) + signature = crypto.signing.ed_sign(jsonMeta.encode() + data, key=crypto.priv_key, encodeResult=True) signer = crypto.pubKey if len(jsonMeta) > 1000: raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes') - user = onionrusers.OnionrUser(symKey) - # encrypt block metadata/sig/content if encryptType == 'sym': - - if len(symKey) < requirements.passwordLength: - raise onionrexceptions.SecurityError('Weak encryption key') - jsonMeta = crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True).decode() - data = crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True).decode() - signature = crypto.symmetricEncrypt(signature, key=symKey, returnEncoded=True).decode() - signer = crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True).decode() + raise NotImplementedError("not yet implemented") elif encryptType == 'asym': if stringvalidators.validate_pub_key(asymPeer): # Encrypt block data with forward secrecy key first, but not meta jsonMeta = json.dumps(meta) - jsonMeta = crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True).decode() - data = crypto.pubKeyEncrypt(data, asymPeer, encodedData=True).decode() - signature = crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True).decode() - signer = crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True).decode() + jsonMeta = crypto.encryption.pub_key_encrypt(jsonMeta, asymPeer, encodedData=True).decode() + data = crypto.encryption.pub_key_encrypt(data, asymPeer, encodedData=True).decode() + signature = crypto.pub_key_encrypt(signature, asymPeer, encodedData=True).decode() + signer = crypto.pub_key_encrypt(signer, asymPeer, encodedData=True).decode() try: onionrusers.OnionrUser(asymPeer, saveUser=True) except ValueError: @@ -141,8 +136,8 @@ def insert_block(onionr_inst, data, header='txt', sign=False, encryptType='', sy coredb.daemonqueue.daemon_queue_add('uploadBlock', retData) else: pass - coredb.blockmetadb.add_to_block_DB(retData, selfInsert=True, dataSaved=True) - coredb.blockmetadata.process_block_metadata(retData) + coredb.blockmetadb.add.add_to_block_DB(retData, selfInsert=True, dataSaved=True) + blockmetadata.process_block_metadata(retData) ''' if retData != False: if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS: diff --git a/onionr/onionrcommands/onionrstatistics.py b/onionr/onionrcommands/onionrstatistics.py index 4a9e6350..6511af08 100755 --- a/onionr/onionrcommands/onionrstatistics.py +++ b/onionr/onionrcommands/onionrstatistics.py @@ -25,7 +25,6 @@ from onionrutils import checkcommunicator, mnemonickeys from utils import sizeutils from coredb import blockmetadb, daemonqueue, keydb import onionrcrypto -crypto = onionrcrypto.OnionrCrypto() def show_stats(o_inst): try: # define stats messages here @@ -89,7 +88,7 @@ def show_details(o_inst): details = { 'Node Address' : o_inst.get_hostname(), 'Web Password' : o_inst.getWebPassword(), - 'Public Key' : crypto.pubKey, + 'Public Key' : onionrcrypto.pub_key, 'Human-readable Public Key' : mnemonickeys.get_human_readable_ID() } diff --git a/onionr/onionrcrypto/__init__.py b/onionr/onionrcrypto/__init__.py index df5a9088..d650d647 100755 --- a/onionr/onionrcrypto/__init__.py +++ b/onionr/onionrcrypto/__init__.py @@ -17,178 +17,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ''' -import os, binascii, base64, hashlib, time, sys, hmac, secrets -import nacl.signing, nacl.encoding, nacl.public, nacl.hash, nacl.pwhash, nacl.utils, nacl.secret -import unpaddedbase32 -import logger, onionrproofs -from onionrutils import stringvalidators, epoch, bytesconverter -import filepaths -import onionrexceptions, keymanager, onionrutils -import config -from . import generate, hashers -config.reload() -class OnionrCrypto: - def __init__(self): - self._keyFile = filepaths.keys_file - self.pubKey = None - self.privKey = None - self.secrets = secrets - self.deterministicRequirement = 25 # Min deterministic password/phrase length - self.HASH_ID_ROUNDS = 2000 - self.keyManager = keymanager.KeyManager() - - # Load our own pub/priv Ed25519 keys, gen & save them if they don't exist - if os.path.exists(self._keyFile): - if len(config.get('general.public_key', '')) > 0: - self.pubKey = config.get('general.public_key') - else: - self.pubKey = self.keyManager.getPubkeyList()[0] - self.privKey = self.keyManager.getPrivkey(self.pubKey) - else: - keys = self.generatePubKey() - self.pubKey = keys[0] - self.privKey = keys[1] - self.keyManager.addKey(self.pubKey, self.privKey) - return +from . import generate, hashers, getourkeypair, signing, encryption - def pubKeyEncrypt(self, data, pubkey, encodedData=False): - '''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)''' - pubkey = unpaddedbase32.repad(bytesconverter.str_to_bytes(pubkey)) - retVal = '' - box = None - data = bytesconverter.str_to_bytes(data) - - pubkey = nacl.signing.VerifyKey(pubkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_public_key() - - if encodedData: - encoding = nacl.encoding.Base64Encoder - else: - encoding = nacl.encoding.RawEncoder - - box = nacl.public.SealedBox(pubkey) - retVal = box.encrypt(data, encoder=encoding) - - return retVal - - def symmetricEncrypt(self, data, key, encodedKey=False, returnEncoded=True): - '''Encrypt data with a 32-byte key (Salsa20-Poly1305 MAC)''' - if encodedKey: - encoding = nacl.encoding.Base64Encoder - else: - encoding = nacl.encoding.RawEncoder - - # Make sure data is bytes - if type(data) != bytes: - data = data.encode() - - box = nacl.secret.SecretBox(key, encoder=encoding) - - if returnEncoded: - encoding = nacl.encoding.Base64Encoder - else: - encoding = nacl.encoding.RawEncoder - - encrypted = box.encrypt(data, encoder=encoding) - return encrypted - - def symmetricDecrypt(self, data, key, encodedKey=False, encodedMessage=False, returnEncoded=False): - '''Decrypt data to a 32-byte key (Salsa20-Poly1305 MAC)''' - if encodedKey: - encoding = nacl.encoding.Base64Encoder - else: - encoding = nacl.encoding.RawEncoder - box = nacl.secret.SecretBox(key, encoder=encoding) - - if encodedMessage: - encoding = nacl.encoding.Base64Encoder - else: - encoding = nacl.encoding.RawEncoder - decrypted = box.decrypt(data, encoder=encoding) - if returnEncoded: - decrypted = base64.b64encode(decrypted) - return decrypted - - def generateSymmetric(self): - '''Generate a symmetric key (bytes) and return it''' - return binascii.hexlify(nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)) - - def generatePubKey(self): - '''Generate a Ed25519 public key pair, return tuple of base32encoded pubkey, privkey''' - return generate.generate_pub_key() - - def generateDeterministic(self, passphrase, bypassCheck=False): - '''Generate a Ed25519 public key pair from a password''' - passStrength = self.deterministicRequirement - passphrase = bytesconverter.str_to_bytes(passphrase) # Convert to bytes if not already - # Validate passphrase length - if not bypassCheck: - if len(passphrase) < passStrength: - raise onionrexceptions.PasswordStrengthError("Passphase must be at least %s characters" % (passStrength,)) - # KDF values - kdf = nacl.pwhash.argon2id.kdf - salt = b"U81Q7llrQcdTP0Ux" # Does not need to be unique or secret, but must be 16 bytes - ops = nacl.pwhash.argon2id.OPSLIMIT_SENSITIVE - mem = nacl.pwhash.argon2id.MEMLIMIT_SENSITIVE - - key = kdf(32, passphrase, salt, opslimit=ops, memlimit=mem) # Generate seed for ed25519 key - key = nacl.signing.SigningKey(key) - return (key.verify_key.encode(nacl.encoding.Base32Encoder).decode(), key.encode(nacl.encoding.Base32Encoder).decode()) - - def pubKeyHashID(self, pubkey=''): - '''Accept a ed25519 public key, return a truncated result of X many sha3_256 hash rounds''' - if pubkey == '': - pubkey = self.pubKey - prev = '' - pubkey = bytesconverter.str_to_bytes(pubkey) - for i in range(self.HASH_ID_ROUNDS): - try: - prev = prev.encode() - except AttributeError: - pass - hasher = hashlib.sha3_256() - hasher.update(pubkey + prev) - prev = hasher.hexdigest() - result = prev - return result - - def sha3Hash(self, data): - return hashers.sha3_hash(data) - - def blake2bHash(self, data): - return hashers.blake2b_hash(data) - - def verifyPow(self, blockContent): - ''' - Verifies the proof of work associated with a block - ''' - retData = False - - dataLen = len(blockContent) - - try: - blockContent = blockContent.encode() - except AttributeError: - pass - - blockHash = self.sha3Hash(blockContent) - try: - blockHash = blockHash.decode() # bytes on some versions for some reason - except AttributeError: - pass - - difficulty = onionrproofs.getDifficultyForNewBlock(blockContent, ourBlock=False) - - if difficulty < int(config.get('general.minimum_block_pow')): - difficulty = int(config.get('general.minimum_block_pow')) - mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode() - puzzle = mainHash[:difficulty] - - if blockHash[:difficulty] == puzzle: - # logger.debug('Validated block pow') - retData = True - else: - logger.debug("Invalid token, bad proof") - - return retData \ No newline at end of file +keypair = getourkeypair.get_keypair() +pub_key = keypair[0] +priv_key = keypair[1] diff --git a/onionr/onionrcrypto/encryption/__init__.py b/onionr/onionrcrypto/encryption/__init__.py index e676851e..d334bc78 100644 --- a/onionr/onionrcrypto/encryption/__init__.py +++ b/onionr/onionrcrypto/encryption/__init__.py @@ -1,9 +1,29 @@ import nacl.encoding, nacl.public, nacl.signing from .. import getourkeypair +import unpaddedbase32 pair = getourkeypair.get_keypair() our_pub_key = pair[0] our_priv_key = pair[1] +def pub_key_encrypt(data, pubkey, encodedData=False): + '''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)''' + pubkey = unpaddedbase32.repad(bytesconverter.str_to_bytes(pubkey)) + retVal = '' + box = None + data = bytesconverter.str_to_bytes(data) + + pubkey = nacl.signing.VerifyKey(pubkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_public_key() + + if encodedData: + encoding = nacl.encoding.Base64Encoder + else: + encoding = nacl.encoding.RawEncoder + + box = nacl.public.SealedBox(pubkey) + retVal = box.encrypt(data, encoder=encoding) + + return retVal + def pub_key_decrypt(data, pubkey='', privkey='', encodedData=False): '''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)''' decrypted = False diff --git a/onionr/onionrevents.py b/onionr/onionrevents.py index beaeca01..f8d16653 100755 --- a/onionr/onionrevents.py +++ b/onionr/onionrevents.py @@ -33,11 +33,11 @@ def __event_caller(event_name, data = {}, onionr = None): try: call(plugins.get_plugin(plugin), event_name, data, get_pluginapi(onionr, data)) except ModuleNotFoundError as e: - logger.warn('Disabling nonexistant plugin "%s"...' % plugin) + logger.warn('Disabling nonexistant plugin "%s"...' % plugin, terminal=True) plugins.disable(plugin, onionr, stop_event = False) except Exception as e: - logger.warn('Event "%s" failed for plugin "%s".' % (event_name, plugin)) - logger.debug(str(e)) + logger.warn('Event "%s" failed for plugin "%s".' % (event_name, plugin), terminal=True) + logger.debug(str(e), terminal=True) def event(event_name, data = {}, onionr = None, threaded = True): diff --git a/onionr/onionrpluginapi.py b/onionr/onionrpluginapi.py index 1208b267..f5731e69 100755 --- a/onionr/onionrpluginapi.py +++ b/onionr/onionrpluginapi.py @@ -18,7 +18,7 @@ along with this program. If not, see . ''' -import onionrplugins, logger, onionrcrypto +import onionrplugins, logger from onionrutils import localcommand from coredb import daemonqueue class DaemonAPI: @@ -154,7 +154,6 @@ class pluginapi: self.plugins = PluginAPI(self) self.commands = CommandAPI(self) self.web = WebAPI(self) - self.crypto = onionrcrypto.OnionrCrypto() def get_onionr(self): return self.onionr @@ -162,9 +161,6 @@ class pluginapi: def get_data(self): return self.data - def get_crypto(self): - return self.crypto - def get_daemonapi(self): return self.daemon diff --git a/onionr/onionrproofs.py b/onionr/onionrproofs.py index 349d8ec7..1388dcd0 100755 --- a/onionr/onionrproofs.py +++ b/onionr/onionrproofs.py @@ -26,7 +26,6 @@ def getDifficultyModifier(): '''returns the difficulty modifier for block storage based on a variety of factors, currently only disk use. ''' - classInst = coreOrUtilsInst retData = 0 useFunc = storagecounter.StorageCounter().getPercent diff --git a/onionr/onionrstorage/__init__.py b/onionr/onionrstorage/__init__.py index c642baad..46e0aac9 100755 --- a/onionr/onionrstorage/__init__.py +++ b/onionr/onionrstorage/__init__.py @@ -24,14 +24,7 @@ import filepaths, onionrcrypto, dbcreator, onionrexceptions from onionrcrypto import hashers DB_ENTRY_SIZE_LIMIT = 10000 # Will be a config option -def dbCreate(): - try: - dbcreator.DBCreator().createBlockDataDB() - except FileExistsError: - pass - def _dbInsert(blockHash, data): - dbCreate() conn = sqlite3.connect(dbfiles.block_data_db, timeout=10) c = conn.cursor() data = (blockHash, data) @@ -40,7 +33,6 @@ def _dbInsert(blockHash, data): conn.close() def _dbFetch(blockHash): - dbCreate() conn = sqlite3.connect(dbfiles.block_data_db, timeout=10) c = conn.cursor() for i in c.execute('SELECT data from blockData where hash = ?', (blockHash,)): @@ -54,7 +46,6 @@ def deleteBlock(blockHash): if os.path.exists('%s/%s.dat' % (filepaths.block_data_location, blockHash)): os.remove('%s/%s.dat' % (filepaths.block_data_location, blockHash)) return True - dbCreate() conn = sqlite3.connect(dbfiles.block_data_db, timeout=10) c = conn.cursor() data = (blockHash,) @@ -91,7 +82,7 @@ def getData(bHash): with open(fileLocation, 'rb') as block: retData = block.read() else: - retData = _dbFetch(coreInst, bHash) + retData = _dbFetch(bHash) if retData is None: raise onionrexceptions.NoDataAvailable("Block data for %s is not available" % [bHash]) return retData \ No newline at end of file diff --git a/onionr/onionrstorage/setdata.py b/onionr/onionrstorage/setdata.py index 096612f8..6558d3f9 100644 --- a/onionr/onionrstorage/setdata.py +++ b/onionr/onionrstorage/setdata.py @@ -1,12 +1,11 @@ import sys, sqlite3 -import onionrstorage, onionrexceptions, onionrcrypto +import onionrstorage, onionrexceptions, onionrcrypto as crypto import filepaths, storagecounter from coredb import dbfiles def set_data(data): ''' Set the data assciated with a hash ''' - crypto = onionrcrypto.OnionrCrypto() storage_counter = storagecounter.StorageCounter() data = data dataSize = sys.getsizeof(data) @@ -14,7 +13,7 @@ def set_data(data): if not type(data) is bytes: data = data.encode() - dataHash = crypto.sha3Hash(data) + dataHash = crypto.hashers.sha3_hash(data) if type(dataHash) is bytes: dataHash = dataHash.decode() diff --git a/onionr/onionrutils/localcommand.py b/onionr/onionrutils/localcommand.py index 54df8640..073182bd 100644 --- a/onionr/onionrutils/localcommand.py +++ b/onionr/onionrutils/localcommand.py @@ -30,9 +30,13 @@ def get_hostname(): maxWait = 3 while True: if cache.get('client_api') is None: - hostname = getclientapiserver.get_client_API_server() - cache.put('hostname', hostname) - cache.flush() + try: + hostname = getclientapiserver.get_client_API_server() + except FileNotFoundError: + hostname = False + else: + cache.put('hostname', hostname) + cache.flush() else: hostname = cache.get('hostname') if hostname == '' or hostname is None: @@ -48,9 +52,11 @@ def local_command(command, data='', silent = True, post=False, postData = {}, ma ''' # TODO: URL encode parameters, just as an extra measure. May not be needed, but should be added regardless. hostname = get_hostname() + if hostname == False: return False if data != '': data = '&data=' + urllib.parse.quote_plus(data) payload = 'http://%s/%s%s' % (hostname, command, data) + try: if post: retData = requests.post(payload, data=postData, headers={'token': config.get('client.webpassword'), 'Connection':'close'}, timeout=(maxWait, maxWait)).text diff --git a/onionr/onionrutils/mnemonickeys.py b/onionr/onionrutils/mnemonickeys.py index 8310f072..527b455f 100644 --- a/onionr/onionrutils/mnemonickeys.py +++ b/onionr/onionrutils/mnemonickeys.py @@ -23,6 +23,6 @@ import onionrcrypto def get_human_readable_ID(pub=''): '''gets a human readable ID from a public key''' if pub == '': - pub = onionrcrypto.OnionrCrypto().pubKey + pub = onionrcrypto.pub_key pub = base64.b16encode(base64.b32decode(pub)).decode() return ' '.join(pgpwords.wordify(pub)) diff --git a/onionr/static-data/default-plugins/flow/main.py b/onionr/static-data/default-plugins/flow/main.py index d98f6585..4d5954b7 100755 --- a/onionr/static-data/default-plugins/flow/main.py +++ b/onionr/static-data/default-plugins/flow/main.py @@ -96,7 +96,6 @@ def on_init(api, data = None): inputted is executed. Could be called when daemon is starting or when just the client is running. ''' - # Doing this makes it so that the other functions can access the api object # by simply referencing the variable `pluginapi`. global pluginapi diff --git a/onionr/subprocesspow.py b/onionr/subprocesspow.py index 95586ca0..730d2307 100755 --- a/onionr/subprocesspow.py +++ b/onionr/subprocesspow.py @@ -22,9 +22,8 @@ import subprocess, os import multiprocessing, threading, time, json from multiprocessing import Pipe, Process -import onionrblockapi, config, onionrutils, logger, onionrproofs, onionrcrypto +import onionrblockapi, config, onionrutils, logger, onionrproofs, onionrcrypto as crypto from onionrutils import bytesconverter -crypto = onionrcrypto.OnionrCrypto() class SubprocessPOW: def __init__(self, data, metadata, subproc_count=None): ''' @@ -105,7 +104,7 @@ class SubprocessPOW: # Serialize metadata, combine with block data payload = json.dumps(metadata).encode() + b'\n' + data # Check sha3_256 hash of block, compare to puzzle. Send payload if puzzle finished - token = crypto.sha3Hash(payload) + token = crypto.hashers.sha3_hash(payload) token = bytesconverter.bytes_to_str(token) # ensure token is string if puzzle == token[0:difficulty]: pipe.send(payload)