From e1676ef1684aad5a54ad4d9d51050a9119e234ea Mon Sep 17 00:00:00 2001 From: Kevin Froman Date: Fri, 19 Jul 2019 19:01:16 -0500 Subject: [PATCH] progress in removing core --- onionr/apiservers/private/__init__.py | 1 - onionr/apiservers/public/__init__.py | 2 +- onionr/core.py | 351 ------------------ onionr/httpapi/apiutils/getblockdata.py | 4 +- onionr/httpapi/miscclientapi/getblocks.py | 2 +- onionr/netcontroller/netcontrol.py | 2 +- onionr/onionrblockapi.py | 18 +- onionr/onionrcommands/daemonlaunch.py | 4 +- onionr/onionrcommands/pubkeymanager.py | 11 +- onionr/onionrcrypto/__init__.py | 98 +---- onionr/onionrcrypto/cryptoutils/__init__.py | 5 + .../onionrcrypto/cryptoutils/randomshuffle.py | 13 + .../cryptoutils/replayvalidation.py | 6 + .../onionrcrypto/cryptoutils/safecompare.py | 12 + onionr/onionrcrypto/encryption/__init__.py | 24 ++ onionr/onionrcrypto/getourkeypair.py | 17 + onionr/onionrcrypto/signing/__init__.py | 44 +++ onionr/onionrproofs.py | 7 +- onionr/onionrutils/validatemetadata.py | 2 +- onionr/subprocesspow.py | 4 +- onionr/utils/readstatic.py | 2 +- 21 files changed, 152 insertions(+), 477 deletions(-) delete mode 100755 onionr/core.py create mode 100644 onionr/onionrcrypto/cryptoutils/__init__.py create mode 100644 onionr/onionrcrypto/cryptoutils/randomshuffle.py create mode 100644 onionr/onionrcrypto/cryptoutils/replayvalidation.py create mode 100644 onionr/onionrcrypto/cryptoutils/safecompare.py create mode 100644 onionr/onionrcrypto/encryption/__init__.py create mode 100644 onionr/onionrcrypto/getourkeypair.py create mode 100644 onionr/onionrcrypto/signing/__init__.py diff --git a/onionr/apiservers/private/__init__.py b/onionr/apiservers/private/__init__.py index 504712b6..e15a133e 100644 --- a/onionr/apiservers/private/__init__.py +++ b/onionr/apiservers/private/__init__.py @@ -41,7 +41,6 @@ class PrivateAPI: self.config = config self.debug = debug self.startTime = epoch.get_epoch() - self._crypto = onionrInst.onionrCrypto app = flask.Flask(__name__) bindPort = int(config.get('client.client.port', 59496)) self.bindPort = bindPort diff --git a/onionr/apiservers/public/__init__.py b/onionr/apiservers/public/__init__.py index 76f3fd79..e411ce75 100644 --- a/onionr/apiservers/public/__init__.py +++ b/onionr/apiservers/public/__init__.py @@ -34,7 +34,7 @@ class PublicAPI: self.i2pEnabled = config.get('i2p.host', False) self.hideBlocks = [] # Blocks to be denied sharing self.host = apiutils.setbindip.set_bind_IP(filepaths.public_API_host_file) - self.torAdder = gettransports.get_transports[0] + self.torAdder = gettransports.transports[0] self.bindPort = config.get('client.public.port') self.lastRequest = 0 self.hitCount = 0 # total rec requests to public api since server started diff --git a/onionr/core.py b/onionr/core.py deleted file mode 100755 index 0d0775c0..00000000 --- a/onionr/core.py +++ /dev/null @@ -1,351 +0,0 @@ -''' - Onionr - Private P2P Communication - - Core Onionr library, useful for external programs. Handles peer & data processing -''' -''' - 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 . -''' -import os, sys, json -import logger, netcontroller, config -from onionrblockapi import Block -import coredb -import deadsimplekv as simplekv -import onionrcrypto, onionrproofs, onionrevents as events, onionrexceptions -import onionrblacklist -from onionrusers import onionrusers -from onionrstorage import removeblock, setdata -import dbcreator, onionrstorage, serializeddata, subprocesspow -from etc import onionrvalues, powchoice -from onionrutils import localcommand, stringvalidators, bytesconverter, epoch -from onionrutils import blockmetadata -from utils import identifyhome -import storagecounter - -class Core: - def __init__(self, torPort=0): - ''' - Initialize Core Onionr library - ''' - # set data dir - self.dataDir = identifyhome.identify_home() - - self.usageFile = self.dataDir + 'disk-usage.txt' - self.config = config - self.maxBlockSize = 10000000 # max block size in bytes - - self.onionrInst = None - self.hsAddress = '' - self.i2pAddress = config.get('i2p.own_addr', None) - self.bootstrapFileLocation = 'static-data/bootstrap-nodes.txt' - self.bootstrapList = [] - self.requirements = onionrvalues.OnionrValues() - self.torPort = torPort - self.dataNonceFile = self.dataDir + 'block-nonces.dat' - self.forwardKeysFile = self.dataDir + 'forward-keys.db' - self.keyStore = simplekv.DeadSimpleKV(self.dataDir + 'cachedstorage.dat', refresh_seconds=5) - self.storage_counter = storagecounter.StorageCounter(self) - - # Socket data, defined here because of multithreading constraints with gevent - self.killSockets = False - self.startSocket = {} - self.socketServerConnData = {} - self.socketReasons = {} - self.socketServerResponseData = {} - - if os.path.exists(self.dataDir + '/hs/hostname'): - with open(self.dataDir + '/hs/hostname', 'r') as hs: - self.hsAddress = hs.read().strip() - - # Load bootstrap address list - if os.path.exists(self.bootstrapFileLocation): - with open(self.bootstrapFileLocation, 'r') as bootstrap: - bootstrap = bootstrap.read() - for i in bootstrap.split('\n'): - self.bootstrapList.append(i) - else: - logger.warn('Warning: address bootstrap file not found ' + self.bootstrapFileLocation) - - self.use_subprocess = powchoice.use_subprocess(self) - # Initialize the crypto object - self._crypto = onionrcrypto.OnionrCrypto(self) - self._blacklist = onionrblacklist.OnionrBlackList(self) - self.serializer = serializeddata.SerializedData(self) - - def addPeer(self, peerID, name=''): - ''' - Adds a public key to the key database (misleading function name) - ''' - return coredb.keydb.addkeys.add_peer(self, peerID, name) - - def addAddress(self, address): - ''' - Add an address to the address database (only tor currently) - ''' - return coredb.keydb.addkeys.add_address(self, address) - - def removeAddress(self, address): - ''' - Remove an address from the address database - ''' - return coredb.keydb.removekeys.remove_address(self, address) - - def removeBlock(self, block): - ''' - remove a block from this node (does not automatically blacklist) - - **You may want blacklist.addToDB(blockHash) - ''' - removeblock.remove_block(self, block) - - def createAddressDB(self): - ''' - Generate the address database - ''' - self.dbCreate.createAddressDB() - - def createPeerDB(self): - ''' - Generate the peer sqlite3 database and populate it with the peers table. - ''' - self.dbCreate.createPeerDB() - - def createBlockDB(self): - ''' - Create a database for blocks - ''' - self.dbCreate.createBlockDB() - - def setData(self, data): - ''' - Set the data assciated with a hash - ''' - return onionrstorage.setdata.set_data(self, data) - - def getData(self, hash): - ''' - Simply return the data associated to a hash - ''' - return onionrstorage.getData(self, hash) - - def listAdders(self, randomOrder=True, i2p=True, recent=0): - ''' - Return a list of addresses - ''' - return coredb.keydb.listkeys.list_adders(self, randomOrder, i2p, recent) - - def listPeers(self, randomOrder=True, getPow=False, trust=0): - ''' - Return a list of public keys (misleading function name) - - randomOrder determines if the list should be in a random order - trust sets the minimum trust to list - ''' - return coredb.keydb.listkeys.list_peers(self, randomOrder, getPow, trust) - - def getPeerInfo(self, peer, info): - ''' - Get info about a peer from their database entry - - id text 0 - name text, 1 - adders text, 2 - dateSeen not null, 3 - trust int 4 - hashID text 5 - ''' - return coredb.keydb.userinfo.get_user_info(self, peer, info) - - def setPeerInfo(self, peer, key, data): - ''' - Update a peer for a key - ''' - return coredb.keydb.userinfo.set_peer_info(self, peer, key, data) - - def getAddressInfo(self, address, info): - ''' - Get info about an address from its database entry - - address text, 0 - type int, 1 - knownPeer text, 2 - speed int, 3 - success int, 4 - powValue 5 - failure int 6 - lastConnect 7 - trust 8 - introduced 9 - ''' - return coredb.keydb.transportinfo.get_address_info(self, address, info) - - def setAddressInfo(self, address, key, data): - ''' - Update an address for a key - ''' - return coredb.keydb.transportinfo.set_address_info(self, address, key, data) - - def insertBlock(self, 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 - ''' - allocationReachedMessage = 'Cannot insert block, disk allocation reached.' - if self.storage_counter.isFull(): - logger.error(allocationReachedMessage) - return False - retData = False - - if type(data) is None: - raise ValueError('Data cannot be none') - - createTime = epoch.get_epoch() - - dataNonce = bytesconverter.bytes_to_str(self._crypto.sha3Hash(data)) - try: - with open(self.dataNonceFile, 'r') as nonces: - if dataNonce in nonces: - return retData - except FileNotFoundError: - pass - # record nonce - with open(self.dataNonceFile, 'a') as nonceFile: - nonceFile.write(dataNonce + '\n') - - if type(data) is bytes: - data = data.decode() - data = str(data) - plaintext = data - plaintextMeta = {} - plaintextPeer = asymPeer - - retData = '' - signature = '' - signer = '' - metadata = {} - # metadata is full block metadata, meta is internal, user specified metadata - - # only use header if not set in provided meta - - meta['type'] = str(header) - - if encryptType in ('asym', 'sym', ''): - metadata['encryptType'] = encryptType - else: - raise onionrexceptions.InvalidMetadata('encryptType must be asym or sym, or blank') - - try: - data = data.encode() - except AttributeError: - pass - - if encryptType == 'asym': - meta['rply'] = createTime # Duplicate the time in encrypted messages to prevent replays - if not disableForward and sign and asymPeer != self._crypto.pubKey: - try: - forwardEncrypted = onionrusers.OnionrUser(self, asymPeer).forwardEncrypt(data) - data = forwardEncrypted[0] - meta['forwardEnc'] = True - expire = forwardEncrypted[2] # Expire time of key. no sense keeping block after that - except onionrexceptions.InvalidPubkey: - pass - #onionrusers.OnionrUser(self, asymPeer).generateForwardKey() - fsKey = onionrusers.OnionrUser(self, asymPeer).generateForwardKey() - #fsKey = onionrusers.OnionrUser(self, asymPeer).getGeneratedForwardKeys().reverse() - meta['newFSKey'] = fsKey - jsonMeta = json.dumps(meta) - plaintextMeta = jsonMeta - if sign: - signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True) - signer = self._crypto.pubKey - - if len(jsonMeta) > 1000: - raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes') - - user = onionrusers.OnionrUser(self, symKey) - - # encrypt block metadata/sig/content - if encryptType == 'sym': - - if len(symKey) < self.requirements.passwordLength: - raise onionrexceptions.SecurityError('Weak encryption key') - jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True).decode() - data = self._crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True).decode() - signature = self._crypto.symmetricEncrypt(signature, key=symKey, returnEncoded=True).decode() - signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True).decode() - 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 = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True).decode() - data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True).decode() - signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True).decode() - signer = self._crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True).decode() - try: - onionrusers.OnionrUser(self, asymPeer, saveUser=True) - except ValueError: - # if peer is already known - pass - else: - raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key') - - # compile metadata - metadata['meta'] = jsonMeta - metadata['sig'] = signature - metadata['signer'] = signer - metadata['time'] = createTime - - # ensure expire is integer and of sane length - if type(expire) is not type(None): - assert len(str(int(expire))) < 14 - metadata['expire'] = expire - - # send block data (and metadata) to POW module to get tokenized block data - if self.use_subprocess: - payload = subprocesspow.SubprocessPOW(data, metadata, self).start() - else: - payload = onionrproofs.POW(metadata, data).waitForResult() - if payload != False: - try: - retData = self.setData(payload) - except onionrexceptions.DiskAllocationReached: - logger.error(allocationReachedMessage) - retData = False - else: - # Tell the api server through localCommand to wait for the daemon to upload this block to make statistical analysis more difficult - if localcommand.local_command(self, '/ping', maxWait=10) == 'pong!': - if self.config.get('general.security_level', 1) == 0: - localcommand.local_command(self, '/waitforshare/' + retData, post=True, maxWait=5) - 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(self, retData) - - if retData != False: - if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS: - events.event('insertdeniable', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': bytesconverter.bytes_to_str(asymPeer)}, onionr = self.onionrInst, threaded = True) - else: - events.event('insertblock', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': bytesconverter.bytes_to_str(asymPeer)}, onionr = self.onionrInst, threaded = True) - return retData - - def introduceNode(self): - ''' - Introduces our node into the network by telling X many nodes our HS address - ''' - if localcommand.local_command(self, '/ping', maxWait=10) == 'pong!': - coredb.daemonqueue.daemon_queue_add('announceNode') - logger.info('Introduction command will be processed.', terminal=True) - else: - logger.warn('No running node detected. Cannot introduce.', terminal=True) \ No newline at end of file diff --git a/onionr/httpapi/apiutils/getblockdata.py b/onionr/httpapi/apiutils/getblockdata.py index cb0736b5..1a8f7df0 100644 --- a/onionr/httpapi/apiutils/getblockdata.py +++ b/onionr/httpapi/apiutils/getblockdata.py @@ -2,8 +2,8 @@ import json import onionrblockapi from onionrutils import bytesconverter, stringvalidators class GetBlockData: - def __init__(self, client_api_inst): - self.client_api_inst = client_api_inst + def __init__(self, client_api_inst=None): + return def get_block_data(self, bHash, decrypt=False, raw=False, headerOnly=False): assert stringvalidators.validate_hash(bHash) diff --git a/onionr/httpapi/miscclientapi/getblocks.py b/onionr/httpapi/miscclientapi/getblocks.py index 3a9a4afe..0fb4f787 100644 --- a/onionr/httpapi/miscclientapi/getblocks.py +++ b/onionr/httpapi/miscclientapi/getblocks.py @@ -19,7 +19,7 @@ ''' from flask import Blueprint, Response, abort import onionrblockapi -from httpapi import apiutils +from .. import apiutils from onionrutils import stringvalidators from coredb import blockmetadb diff --git a/onionr/netcontroller/netcontrol.py b/onionr/netcontroller/netcontrol.py index e0b54804..fc68504d 100644 --- a/onionr/netcontroller/netcontrol.py +++ b/onionr/netcontroller/netcontrol.py @@ -129,7 +129,7 @@ HiddenServicePort 80 ''' + self.apiServerIP + ''':''' + str(self.hsPort) logger.fatal('Failed to start Tor. Maybe a stray instance of Tor used by Onionr is still running? This can also be a result of file permissions being too open', terminal=True) return False except KeyboardInterrupt: - logger.fatal('Got keyboard interrupt. Onionr will exit soon.', timestamp = False, level = logger.LEVEL_IMPORTANT, terminal=True) + logger.fatal('Got keyboard interrupt. Onionr will exit soon.', timestamp = False, terminal=True) return False logger.info('Finished starting Tor.', terminal=True) diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py index e85e4330..9ab2d426 100755 --- a/onionr/onionrblockapi.py +++ b/onionr/onionrblockapi.py @@ -19,14 +19,14 @@ ''' import logger, config, onionrexceptions, nacl.exceptions -import json, os, sys, datetime, base64, onionrstorage, onionrcrypto +import json, os, sys, datetime, base64, onionrstorage from onionrusers import onionrusers from onionrutils import stringvalidators, epoch from coredb import blockmetadb from onionrstorage import removeblock import onionrblocks +from onionrcrypto import encryption, cryptoutils as cryptoutils, signing class Block: - crypto = onionrcrypto.OnionrCrypto() blockCacheOrder = list() # NEVER write your own code that writes to this! blockCache = dict() # should never be accessed directly, look at Block.getCache() @@ -71,23 +71,23 @@ class Block: # decrypt data if self.getHeader('encryptType') == 'asym': try: - self.bcontent = crypto.pubKeyDecrypt(self.bcontent, encodedData=encodedData) - bmeta = crypto.pubKeyDecrypt(self.bmetadata, encodedData=encodedData) + self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=encodedData) + bmeta = encryption.pub_key_decrypt(self.bmetadata, encodedData=encodedData) try: bmeta = bmeta.decode() except AttributeError: # yet another bytes fix pass self.bmetadata = json.loads(bmeta) - self.signature = crypto.pubKeyDecrypt(self.signature, encodedData=encodedData) - self.signer = crypto.pubKeyDecrypt(self.signer, encodedData=encodedData) + self.signature = encryption.pub_key_decrypt(self.signature, encodedData=encodedData) + self.signer = encryption.pub_key_decrypt(self.signer, encodedData=encodedData) self.bheader['signer'] = self.signer.decode() self.signedData = json.dumps(self.bmetadata) + self.bcontent.decode() # Check for replay attacks try: if epoch.get_epoch() - blockmetadb.get_block_date(self.hash) > 60: - assert self.crypto.replayTimestampValidation(self.bmetadata['rply']) + assert cryptoutils.replay_validator(self.bmetadata['rply']) except (AssertionError, KeyError, TypeError) as e: if not self.bypassReplayCheck: # Zero out variables to prevent reading of replays @@ -124,7 +124,7 @@ class Block: Verify if a block's signature is signed by its claimed signer ''' - if crypto.edVerify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True): + if signing.ed_verify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True): self.validSig = True else: self.validSig = False @@ -425,7 +425,7 @@ class Block: if (not self.isSigned()) or (not stringvalidators.validate_pub_key(signer)): return False - return bool(crypto.edVerify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData)) + return bool(signing.ed_verify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData)) except: return False diff --git a/onionr/onionrcommands/daemonlaunch.py b/onionr/onionrcommands/daemonlaunch.py index 81030cd1..7785e20d 100755 --- a/onionr/onionrcommands/daemonlaunch.py +++ b/onionr/onionrcommands/daemonlaunch.py @@ -26,7 +26,7 @@ from netcontroller import NetController from onionrutils import localcommand import filepaths from coredb import daemonqueue - +from onionrcrypto import getourkeypair def _proper_shutdown(o_inst): localcommand.local_command('shutdown') sys.exit(1) @@ -72,7 +72,7 @@ def daemon(o_inst): logger.debug('Started .onion service: %s' % (logger.colors.underline + net.myID)) else: logger.debug('.onion service disabled') - logger.info('Using public key: %s' % (logger.colors.underline + o_inst._crypto.pubKey[:52]), terminal=True) + logger.info('Using public key: %s' % (logger.colors.underline + getourkeypair.get_keypair()[0][:52]), terminal=True) try: time.sleep(1) diff --git a/onionr/onionrcommands/pubkeymanager.py b/onionr/onionrcommands/pubkeymanager.py index 8b20a769..e315c40d 100755 --- a/onionr/onionrcommands/pubkeymanager.py +++ b/onionr/onionrcommands/pubkeymanager.py @@ -22,6 +22,7 @@ import sys, getpass import logger, onionrexceptions from onionrutils import stringvalidators, bytesconverter from onionrusers import onionrusers, contactmanager +from coredb import keydb import unpaddedbase32 def add_ID(o_inst): try: @@ -82,22 +83,22 @@ def friend_command(o_inst): action = action.lower() if action == 'list': # List out peers marked as our friend - for friend in contactmanager.ContactManager.list_friends(o_inst.): + for friend in contactmanager.ContactManager.list_friends(): logger.info(friend.publicKey + ' - ' + friend.get_info('name'), terminal=True) elif action in ('add', 'remove'): try: friend = sys.argv[3] if not stringvalidators.validate_pub_key(friend): raise onionrexceptions.InvalidPubkey('Public key is invalid') - if friend not in o_inst..listPeers(): + if friend not in keydb.listkeys.list_peers(): raise onionrexceptions.KeyNotKnown - friend = onionrusers.OnionrUser(o_inst., friend) + friend = onionrusers.OnionrUser(friend) except IndexError: logger.warn('Friend ID is required.', terminal=True) action = 'error' # set to 'error' so that the finally block does not process anything except onionrexceptions.KeyNotKnown: - o_inst..addPeer(friend) - friend = onionrusers.OnionrUser(o_inst., friend) + o_inst.addPeer(friend) + friend = onionrusers.OnionrUser(friend) finally: if action == 'add': friend.setTrust(1) diff --git a/onionr/onionrcrypto/__init__.py b/onionr/onionrcrypto/__init__.py index b1fa4af5..df5a9088 100755 --- a/onionr/onionrcrypto/__init__.py +++ b/onionr/onionrcrypto/__init__.py @@ -36,7 +36,7 @@ class OnionrCrypto: self.secrets = secrets self.deterministicRequirement = 25 # Min deterministic password/phrase length self.HASH_ID_ROUNDS = 2000 - self.keyManager = keymanager.KeyManager(self) + 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): @@ -52,47 +52,6 @@ class OnionrCrypto: self.keyManager.addKey(self.pubKey, self.privKey) return - def edVerify(self, data, key, sig, encodedData=True): - '''Verify signed data (combined in nacl) to an ed25519 key''' - try: - key = nacl.signing.VerifyKey(key=key, encoder=nacl.encoding.Base32Encoder) - except nacl.exceptions.ValueError: - #logger.debug('Signature by unknown key (cannot reverse hash)') - return False - except binascii.Error: - logger.warn('Could not load key for verification, invalid padding') - return False - retData = False - sig = base64.b64decode(sig) - try: - data = data.encode() - except AttributeError: - pass - if encodedData: - try: - retData = key.verify(data, sig) # .encode() is not the same as nacl.encoding - except nacl.exceptions.BadSignatureError: - pass - else: - try: - retData = key.verify(data, sig) - except nacl.exceptions.BadSignatureError: - pass - return retData - - def edSign(self, data, key, encodeResult=False): - '''Ed25519 sign data''' - try: - data = data.encode() - except AttributeError: - pass - key = nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) - retData = '' - if encodeResult: - retData = key.sign(data, encoder=nacl.encoding.Base64Encoder).signature.decode() # .encode() is not the same as nacl.encoding - else: - retData = key.sign(data).signature - return retData def pubKeyEncrypt(self, data, pubkey, encodedData=False): '''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)''' @@ -113,25 +72,6 @@ class OnionrCrypto: return retVal - def pubKeyDecrypt(self, data, pubkey='', privkey='', encodedData=False): - '''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)''' - decrypted = False - if encodedData: - encoding = nacl.encoding.Base64Encoder - else: - encoding = nacl.encoding.RawEncoder - if privkey == '': - privkey = self.privKey - ownKey = nacl.signing.SigningKey(seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key() - - if stringvalidators.validate_pub_key(privkey): - privkey = nacl.signing.SigningKey(seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key() - anonBox = nacl.public.SealedBox(privkey) - else: - anonBox = nacl.public.SealedBox(ownKey) - decrypted = anonBox.decrypt(data, encoder=encoding) - return decrypted - def symmetricEncrypt(self, data, key, encodedKey=False, returnEncoded=True): '''Encrypt data with a 32-byte key (Salsa20-Poly1305 MAC)''' if encodedKey: @@ -251,38 +191,4 @@ class OnionrCrypto: else: logger.debug("Invalid token, bad proof") - return retData - - @staticmethod - def replayTimestampValidation(timestamp): - if epoch.get_epoch() - int(timestamp) > 2419200: - return False - else: - return True - - @staticmethod - def safeCompare(one, two): - # Do encode here to avoid spawning core - try: - one = one.encode() - except AttributeError: - pass - try: - two = two.encode() - except AttributeError: - pass - return hmac.compare_digest(one, two) - - @staticmethod - def randomShuffle(theList): - myList = list(theList) - shuffledList = [] - myListLength = len(myList) + 1 - while myListLength > 0: - removed = secrets.randbelow(myListLength) - try: - shuffledList.append(myList.pop(removed)) - except IndexError: - pass - myListLength = len(myList) - return shuffledList \ No newline at end of file + return retData \ No newline at end of file diff --git a/onionr/onionrcrypto/cryptoutils/__init__.py b/onionr/onionrcrypto/cryptoutils/__init__.py new file mode 100644 index 00000000..4e1c5c30 --- /dev/null +++ b/onionr/onionrcrypto/cryptoutils/__init__.py @@ -0,0 +1,5 @@ +from . import safecompare, replayvalidation, randomshuffle + +replay_validator = replayvalidation.replay_timestamp_validation +random_shuffle = randomshuffle.random_shuffle +safe_compare = safecompare.safe_compare \ No newline at end of file diff --git a/onionr/onionrcrypto/cryptoutils/randomshuffle.py b/onionr/onionrcrypto/cryptoutils/randomshuffle.py new file mode 100644 index 00000000..05f859fc --- /dev/null +++ b/onionr/onionrcrypto/cryptoutils/randomshuffle.py @@ -0,0 +1,13 @@ +import secrets +def random_shuffle(theList): + myList = list(theList) + shuffledList = [] + myListLength = len(myList) + 1 + while myListLength > 0: + removed = secrets.randbelow(myListLength) + try: + shuffledList.append(myList.pop(removed)) + except IndexError: + pass + myListLength = len(myList) + return shuffledList \ No newline at end of file diff --git a/onionr/onionrcrypto/cryptoutils/replayvalidation.py b/onionr/onionrcrypto/cryptoutils/replayvalidation.py new file mode 100644 index 00000000..fbb581f3 --- /dev/null +++ b/onionr/onionrcrypto/cryptoutils/replayvalidation.py @@ -0,0 +1,6 @@ +import utils # onionr utils epoch, not this utils +def replay_timestamp_validation(timestamp): + if utils.epoch.get_epoch() - int(timestamp) > 2419200: + return False + else: + return True \ No newline at end of file diff --git a/onionr/onionrcrypto/cryptoutils/safecompare.py b/onionr/onionrcrypto/cryptoutils/safecompare.py new file mode 100644 index 00000000..ddd6b58e --- /dev/null +++ b/onionr/onionrcrypto/cryptoutils/safecompare.py @@ -0,0 +1,12 @@ +import hmac +def safe_compare(one, two): + # Do encode here to avoid spawning core + try: + one = one.encode() + except AttributeError: + pass + try: + two = two.encode() + except AttributeError: + pass + return hmac.compare_digest(one, two) \ No newline at end of file diff --git a/onionr/onionrcrypto/encryption/__init__.py b/onionr/onionrcrypto/encryption/__init__.py new file mode 100644 index 00000000..e676851e --- /dev/null +++ b/onionr/onionrcrypto/encryption/__init__.py @@ -0,0 +1,24 @@ +import nacl.encoding, nacl.public, nacl.signing +from .. import getourkeypair +pair = getourkeypair.get_keypair() +our_pub_key = pair[0] +our_priv_key = pair[1] + +def pub_key_decrypt(data, pubkey='', privkey='', encodedData=False): + '''pubkey decrypt (Curve25519, taken from Ed25519 pubkey)''' + decrypted = False + if encodedData: + encoding = nacl.encoding.Base64Encoder + else: + encoding = nacl.encoding.RawEncoder + if privkey == '': + privkey = our_priv_key + ownKey = nacl.signing.SigningKey(seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key() + + if stringvalidators.validate_pub_key(privkey): + privkey = nacl.signing.SigningKey(seed=privkey, encoder=nacl.encoding.Base32Encoder()).to_curve25519_private_key() + anonBox = nacl.public.SealedBox(privkey) + else: + anonBox = nacl.public.SealedBox(ownKey) + decrypted = anonBox.decrypt(data, encoder=encoding) + return decrypted \ No newline at end of file diff --git a/onionr/onionrcrypto/getourkeypair.py b/onionr/onionrcrypto/getourkeypair.py new file mode 100644 index 00000000..0eba1abc --- /dev/null +++ b/onionr/onionrcrypto/getourkeypair.py @@ -0,0 +1,17 @@ +import os +import keymanager, config, filepaths +from . import generate +def get_keypair(): + key_m = keymanager.KeyManager() + if os.path.exists(filepaths.keys_file): + if len(config.get('general.public_key', '')) > 0: + pubKey = config.get('general.public_key') + else: + pubKey = key_m.getPubkeyList()[0] + privKey = key_m.getPrivkey(pubKey) + else: + keys = generate.generate_pub_key() + pubKey = keys[0] + privKey = keys[1] + key_m.addKey(pubKey, privKey) + return (pubKey, privKey) diff --git a/onionr/onionrcrypto/signing/__init__.py b/onionr/onionrcrypto/signing/__init__.py new file mode 100644 index 00000000..a46aacc2 --- /dev/null +++ b/onionr/onionrcrypto/signing/__init__.py @@ -0,0 +1,44 @@ +import base64, binascii +import nacl.encoding, nacl.signing, nacl.exceptions +import logger +def ed_sign(data, key, encodeResult=False): + '''Ed25519 sign data''' + try: + data = data.encode() + except AttributeError: + pass + key = nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) + retData = '' + if encodeResult: + retData = key.sign(data, encoder=nacl.encoding.Base64Encoder).signature.decode() # .encode() is not the same as nacl.encoding + else: + retData = key.sign(data).signature + return retData + +def ed_verify(data, key, sig, encodedData=True): + '''Verify signed data (combined in nacl) to an ed25519 key''' + try: + key = nacl.signing.VerifyKey(key=key, encoder=nacl.encoding.Base32Encoder) + except nacl.exceptions.ValueError: + #logger.debug('Signature by unknown key (cannot reverse hash)') + return False + except binascii.Error: + logger.warn('Could not load key for verification, invalid padding') + return False + retData = False + sig = base64.b64decode(sig) + try: + data = data.encode() + except AttributeError: + pass + if encodedData: + try: + retData = key.verify(data, sig) # .encode() is not the same as nacl.encoding + except nacl.exceptions.BadSignatureError: + pass + else: + try: + retData = key.verify(data, sig) + except nacl.exceptions.BadSignatureError: + pass + return retData \ No newline at end of file diff --git a/onionr/onionrproofs.py b/onionr/onionrproofs.py index bc555077..349d8ec7 100755 --- a/onionr/onionrproofs.py +++ b/onionr/onionrproofs.py @@ -18,11 +18,10 @@ along with this program. If not, see . ''' import multiprocessing, nacl.encoding, nacl.hash, nacl.utils, time, math, threading, binascii, sys, json -import config, logger, onionrblockapi, storagecounter, onionrcrypto +import config, logger, onionrblockapi, storagecounter from onionrutils import bytesconverter - +from onionrcrypto import hashers config.reload() -crypto = onionrcrypto.OnionrCrypto() def getDifficultyModifier(): '''returns the difficulty modifier for block storage based on a variety of factors, currently only disk use. @@ -227,7 +226,7 @@ class POW: #token = nacl.hash.blake2b(rand + self.data).decode() self.metadata['pow'] = nonce payload = json.dumps(self.metadata).encode() + b'\n' + self.data - token = crypto.sha3Hash(payload) + token = hashers.sha3_hash(payload) try: # on some versions, token is bytes token = token.decode() diff --git a/onionr/onionrutils/validatemetadata.py b/onionr/onionrutils/validatemetadata.py index ef3cde95..6515753a 100644 --- a/onionr/onionrutils/validatemetadata.py +++ b/onionr/onionrutils/validatemetadata.py @@ -21,7 +21,7 @@ import json import logger, onionrexceptions from etc import onionrvalues from onionrutils import stringvalidators, epoch, bytesconverter -import config, onionrvalues, filepaths, onionrcrypto +import config, filepaths, onionrcrypto def validate_metadata(metadata, blockData): '''Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string''' # TODO, make this check sane sizes diff --git a/onionr/subprocesspow.py b/onionr/subprocesspow.py index 674aea00..95586ca0 100755 --- a/onionr/subprocesspow.py +++ b/onionr/subprocesspow.py @@ -49,9 +49,9 @@ class SubprocessPOW: self.data = bytesconverter.str_to_bytes(data) # Calculate difficulty. Dumb for now, may use good algorithm in the future. - self.difficulty = onionrproofs.getDifficultyForNewBlock(bytes(json_metadata + b'\n' + self.data) + self.difficulty = onionrproofs.getDifficultyForNewBlock(bytes(json_metadata + b'\n' + self.data)) - logger.info('Computing POW (difficulty: %s)...' % self.difficulty) + logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,)) self.mainHash = '0' * 64 self.puzzle = self.mainHash[0:min(self.difficulty, len(self.mainHash))] diff --git a/onionr/utils/readstatic.py b/onionr/utils/readstatic.py index adbe1cf6..7ad93556 100644 --- a/onionr/utils/readstatic.py +++ b/onionr/utils/readstatic.py @@ -1,6 +1,6 @@ import os def read_static(file, ret_bin=False): - static_file = os.path.realpath(__file__) + '../static-data/' + file + static_file = os.path.dirname(os.path.realpath(__file__)) + '/../static-data/' + file if ret_bin: mode = 'rb'