Code consistency updates

- Improved formatting
- Added comments
- URL encoded values in netcontroller.performGET
- Kept SQL statement case consistency
master
Arinerron 2018-02-03 19:44:29 -08:00
parent bdd1d9697b
commit 62cad7a6ea
No known key found for this signature in database
GPG Key ID: 99383627861C62F0
10 changed files with 275 additions and 147 deletions

View File

@ -25,10 +25,12 @@ import configparser, sys, random, threading, hmac, hashlib, base64, time, math,
from core import Core
import onionrutils
class API:
''' Main http api (flask)'''
'''
Main HTTP API (Flask)
'''
def validateToken(self, token):
'''
Validate if the client token (hmac) matches the given token
Validate that the client token (hmac) matches the given token
'''
if self.clientToken != token:
return False
@ -36,9 +38,10 @@ class API:
return True
def __init__(self, config, debug):
''' Initialize the api server, preping variables for later use
This initilization defines all of the API entry points and handlers for the endpoints and errors
'''
Initialize the api server, preping variables for later use
This initilization defines all of the API entry points and handlers for the endpoints and errors
This also saves the used host (random localhost IP address) to the data folder in host.txt
'''
if os.path.exists('dev-enabled'):
@ -75,6 +78,7 @@ class API:
Simply define the request as not having yet failed, before every request.
'''
self.requestFailed = False
return
@app.after_request
@ -87,6 +91,7 @@ class API:
resp.headers["Content-Security-Policy"] = "default-src 'none'"
resp.headers['X-Frame-Options'] = 'deny'
resp.headers['X-Content-Type-Options'] = "nosniff"
return resp
@app.route('/client/')
@ -112,6 +117,7 @@ class API:
elapsed = endTime - startTime
if elapsed < self._privateDelayTime:
time.sleep(self._privateDelayTime - elapsed)
return resp
@app.route('/public/')
@ -149,17 +155,21 @@ class API:
def notfound(err):
self.requestFailed = True
resp = Response("")
#resp.headers = getHeaders(resp)
return resp
@app.errorhandler(403)
def authFail(err):
self.requestFailed = True
resp = Response("403")
return resp
@app.errorhandler(401)
def clientError(err):
self.requestFailed = True
resp = Response("Invalid request")
return resp
logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...')
@ -168,7 +178,9 @@ class API:
app.run(host=self.host, port=bindPort, debug=True, threaded=True)
def validateHost(self, hostType):
''' Validate various features of the request including:
'''
Validate various features of the request including:
If private (/client/), is the host header local?
If public (/public/), is the host header onion or i2p?

View File

@ -1,23 +0,0 @@
'''
Simply define terminal control codes (mainly colors)
'''
class Colors:
def __init__(self):
'''
PURPLE='\033[95m'
BLUE='\033[94m'
GREEN='\033[92m'
YELLOW='\033[93m'
RED='\033[91m'
BOLD='\033[1m'
UNDERLINE='\033[4m'
RESET="\x1B[m"
'''
self.PURPLE='\033[95m'
self.BLUE='\033[94m'
self.GREEN='\033[92m'
self.YELLOW='\033[93m'
self.RED='\033[91m'
self.BOLD='\033[1m'
self.UNDERLINE='\033[4m'
self.RESET="\x1B[m"

View File

@ -19,11 +19,13 @@ and code to operate as a daemon, getting commands from the command queue databas
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
import sqlite3, requests, hmac, hashlib, time, sys, os, logger
import sqlite3, requests, hmac, hashlib, time, sys, os, logger, urllib.parse
import core, onionrutils
class OnionrCommunicate:
def __init__(self, debug, developmentMode):
''' OnionrCommunicate
'''
OnionrCommunicate
This class handles communication with nodes in the Onionr network.
'''
@ -63,29 +65,46 @@ class OnionrCommunicate:
logger.warn('Daemon recieved exit command.')
break
time.sleep(1)
return
def getRemotePeerKey(self, peerID):
'''This function contacts a peer and gets their main PGP key.
'''
This function contacts a peer and gets their main PGP key.
This is safe because Tor or I2P is used, but it does not ensure that the person is who they say they are
'''
url = 'http://' + peerID + '/public/?action=getPGP'
r = requests.get(url, headers=headers)
response = r.text
return response
def shareHMAC(self, peerID, key):
'''This function shares an HMAC key to a peer
'''
This function shares an HMAC key to a peer
'''
return
def getPeerProof(self, peerID):
'''This function gets the current peer proof requirement'''
'''
This function gets the current peer proof requirement
'''
return
def sendPeerProof(self, peerID, data):
'''This function sends the proof result to a peer previously fetched with getPeerProof'''
'''
This function sends the proof result to a peer previously fetched with getPeerProof
'''
return
def lookupBlocks(self):
'''Lookup blocks and merge new ones'''
'''
Lookup blocks and merge new ones
'''
peerList = self._core.listPeers()
blocks = ''
for i in peerList:
@ -120,20 +139,26 @@ class OnionrCommunicate:
else:
logger.debug('Adding ' + i + ' to hash database...')
self._core.addToBlockDB(i)
return
def processBlocks(self):
'''
Work with the block database and download any missing blocks
This is meant to be called from the communicator daemon on its timer.
'''
for i in self._core.getBlockList(True).split("\n"):
if i != "":
logger.warn('UNSAVED BLOCK: ' + i)
data = self.downloadBlock(i)
return
def downloadBlock(self, hash):
'''download a block from random order of peers'''
'''
Download a block from random order of peers
'''
peerList = self._core.listPeers()
blocks = ''
for i in peerList:
@ -155,22 +180,33 @@ class OnionrCommunicate:
else:
logger.warn("Failed to validate " + hash)
return
def urlencode(self, data):
'''
URL encodes the data
'''
return urllib.parse.quote_plus(data)
def performGet(self, action, peer, data=None, type='tor'):
'''Performs a request to a peer through Tor or i2p (currently only tor)'''
'''
Performs a request to a peer through Tor or i2p (currently only Tor)
'''
if not peer.endswith('.onion') and not peer.endswith('.onion/'):
raise PeerError('Currently only Tor .onion peers are supported. You must manually specify .onion')
socksPort = sys.argv[2]
'''We use socks5h to use tor as DNS'''
proxies = {'http': 'socks5h://127.0.0.1:' + str(socksPort), 'https': 'socks5h://127.0.0.1:' + str(socksPort)}
headers = {'user-agent': 'PyOnionr'}
url = 'http://' + peer + '/public/?action=' + action
url = 'http://' + peer + '/public/?action=' + urlencode(action)
if data != None:
url = url + '&data=' + data
url = url + '&data=' + urlencode(data)
try:
r = requests.get(url, headers=headers, proxies=proxies, timeout=(15, 30))
except requests.exceptions.RequestException as e:
logger.warn(action + " failed with peer " + peer + ": " + str(e))
return False
return r.text

View File

@ -54,34 +54,40 @@ class Core:
return
def generateMainPGP(self, myID):
''' Generate the main PGP key for our client. Should not be done often.
Uses own PGP home folder in the data/ directory. '''
# Generate main pgp key
'''
Generate the main PGP key for our client. Should not be done often.
Uses own PGP home folder in the data/ directory
'''
gpg = gnupg.GPG(homedir='./data/pgp/')
input_data = gpg.gen_key_input(key_type="RSA", key_length=1024, name_real=myID, name_email='anon@onionr', testing=True)
#input_data = gpg.gen_key_input(key_type="RSA", key_length=1024)
key = gpg.gen_key(input_data)
logger.info("Generating PGP key, this will take some time..")
while key.status != "key created":
time.sleep(0.5)
print(key.status)
logger.info("Finished generating PGP key")
# Write the key
myFingerpintFile = open('data/own-fingerprint.txt', 'w')
myFingerpintFile.write(key.fingerprint)
myFingerpintFile.close()
return
def addPeer(self, peerID, name=''):
''' Add a peer by their ID, with an optional name, to the peer database.'''
''' DOES NO SAFETY CHECKS if the ID is valid, but prepares the insertion. '''
'''
Add a peer by their ID, with an optional name, to the peer database
DOES NO SAFETY CHECKS if the ID is valid, but prepares the insertion
'''
# This function simply adds a peer to the DB
if not self._utils.validateID(peerID):
return False
conn = sqlite3.connect(self.peerDB)
c = conn.cursor()
t = (peerID, name, 'unknown')
c.execute('insert into peers (id, name, dateSeen) values(?, ?, ?);', t)
c.execute('INSERT INTO peers (id, name, dateSeen) VALUES(?, ?, ?);', t)
conn.commit()
conn.close()
return True
@ -93,8 +99,7 @@ class Core:
# generate the peer database
conn = sqlite3.connect(self.peerDB)
c = conn.cursor()
c.execute('''
create table peers(
c.execute('''CREATE TABLE peers(
ID text not null,
name text,
pgpKey text,
@ -107,6 +112,9 @@ class Core:
''')
conn.commit()
conn.close()
return
def createBlockDB(self):
'''
Create a database for blocks
@ -122,19 +130,25 @@ class Core:
raise Exception("Block database already exists")
conn = sqlite3.connect(self.blockDB)
c = conn.cursor()
c.execute('''create table hashes(
c.execute('''CREATE TABLE hashes(
hash text not null,
dateReceived int,
decrypted int,
dataType text,
dataFound int,
dataSaved int
);
dataSaved int);
''')
conn.commit()
conn.close()
return
def addToBlockDB(self, newHash, selfInsert=False):
'''add a hash value to the block db (should be in hex format)'''
'''
Add a hash value to the block db
Should be in hex format!
'''
if not os.path.exists(self.blockDB):
raise Exception('Block db does not exist')
if self._utils.hasBlock(newHash):
@ -147,22 +161,29 @@ class Core:
else:
selfInsert = 0
data = (newHash, currentTime, 0, '', 0, selfInsert)
c.execute('INSERT into hashes values(?, ?, ?, ?, ?, ?);', data)
c.execute('INSERT INTO hashes VALUES(?, ?, ?, ?, ?, ?);', data)
conn.commit()
conn.close()
return
def getData(self,hash):
'''simply return the data associated to a hash'''
'''
Simply return the data associated to a hash
'''
try:
dataFile = open(self.blockDataLocation + hash + '.dat')
data = dataFile.read()
dataFile.close()
except FileNotFoundError:
data = False
return data
def setData(self, data):
'''set the data assciated with a hash'''
'''
Set the data assciated with a hash
'''
data = data.encode()
hasher = hashlib.sha3_256()
hasher.update(data)
@ -171,7 +192,7 @@ class Core:
dataHash = dataHash.decode()
blockFileName = self.blockDataLocation + dataHash + '.dat'
if os.path.exists(blockFileName):
pass # to do, properly check if block is already saved elsewhere
pass # TODO: properly check if block is already saved elsewhere
#raise Exception("Data is already set for " + dataHash)
else:
blockFile = open(blockFileName, 'w')
@ -180,7 +201,7 @@ class Core:
conn = sqlite3.connect(self.blockDB)
c = conn.cursor()
c.execute("UPDATE hashes set dataSaved=1 where hash = '" + dataHash + "';")
c.execute("UPDATE hashes SET dataSaved=1 WHERE hash = '" + dataHash + "';")
conn.commit()
conn.close()
@ -190,7 +211,6 @@ class Core:
'''
Encrypt the data directory on Onionr shutdown
'''
# Encrypt data directory (don't delete it in this function)
if os.path.exists('data.tar'):
os.remove('data.tar')
tar = tarfile.open("data.tar", "w")
@ -201,12 +221,13 @@ class Core:
encrypted = simplecrypt.encrypt(password, tarData)
open('data-encrypted.dat', 'wb').write(encrypted)
os.remove('data.tar')
return
def dataDirDecrypt(self, password):
'''
Decrypt the data directory on startup
'''
# Decrypt data directory
if not os.path.exists('data-encrypted.dat'):
return (False, 'encrypted archive does not exist')
data = open('data-encrypted.dat', 'rb').read()
@ -219,13 +240,15 @@ class Core:
tar = tarfile.open('data.tar')
tar.extractall()
tar.close()
return (True, '')
def daemonQueue(self):
'''
Gives commands to the communication proccess/daemon by reading an sqlite3 database
This function intended to be used by the client. Queue to exchange data between "client" and server.
'''
# This function intended to be used by the client
# Queue to exchange data between "client" and server.
retData = False
if not os.path.exists(self.queueDB):
conn = sqlite3.connect(self.queueDB)
@ -241,7 +264,7 @@ class Core:
retData = row
break
if retData != False:
c.execute('delete from commands where id = ?', (retData[3],))
c.execute('DELETE FROM commands WHERE id=?;', (retData[3],))
conn.commit()
conn.close()
@ -256,12 +279,16 @@ class Core:
conn = sqlite3.connect(self.queueDB)
c = conn.cursor()
t = (command, data, date)
c.execute('INSERT into commands (command, data, date) values (?, ?, ?)', t)
c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t)
conn.commit()
conn.close()
return
def clearDaemonQueue(self):
'''clear the daemon queue (somewhat dangerousous)'''
'''
Clear the daemon queue (somewhat dangerous)
'''
conn = sqlite3.connect(self.queueDB)
c = conn.cursor()
try:
@ -271,33 +298,38 @@ class Core:
pass
conn.close()
def generateHMAC(self):
return
def generateHMAC(self, length=32):
'''
generate and return an HMAC key
Generate and return an HMAC key
'''
key = base64.b64encode(os.urandom(32))
key = base64.b64encode(os.urandom(length))
return key
def listPeers(self, randomOrder=True):
'''Return a list of peers
'''
Return a list of peers
randomOrder determines if the list should be in a random order
'''
conn = sqlite3.connect(self.peerDB)
c = conn.cursor()
if randomOrder:
peers = c.execute('SELECT * FROM peers order by RANDOM();')
peers = c.execute('SELECT * FROM peers ORDER BY RANDOM();')
else:
peers = c.execute('SELECT * FROM peers;')
peerList = []
for i in peers:
peerList.append(i[0])
conn.close()
return peerList
def getPeerInfo(self, peer, info):
'''
get info about a peer
Get info about a peer from their database entry
id text 0
name text, 1
@ -309,7 +341,6 @@ class Core:
bytesStored int, 8
trust int 9
'''
# Lookup something about a peer from their database entry
conn = sqlite3.connect(self.peerDB)
c = conn.cursor()
command = (peer,)
@ -325,21 +356,29 @@ class Core:
else:
iterCount += 1
conn.close()
return retVal
def setPeerInfo(self, peer, key, data):
'''update a peer for a key'''
'''
Update a peer for a key
'''
conn = sqlite3.connect(self.peerDB)
c = conn.cursor()
command = (data, peer)
# TODO: validate key on whitelist
if key not in ('id', 'text', 'name', 'pgpKey', 'hmacKey', 'blockDBHash', 'forwardKey', 'dateSeen', 'bytesStored', 'trust'):
raise Exception("Got invalid database key when setting peer info")
c.execute('UPDATE peers SET ' + key + ' = ? where id=?', command)
c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command)
conn.commit()
conn.close()
return
def getBlockList(self, unsaved=False):
'''get list of our blocks'''
'''
Get list of our blocks
'''
conn = sqlite3.connect(self.blockDB)
c = conn.cursor()
retData = ''
@ -350,24 +389,33 @@ class Core:
for row in c.execute(execute):
for i in row:
retData += i + "\n"
return retData
def getBlocksByType(self, blockType):
'''
Returns a list of blocks by the type
'''
conn = sqlite3.connect(self.blockDB)
c = conn.cursor()
retData = ''
execute = 'SELECT hash FROM hashes where dataType=?'
execute = 'SELECT hash FROM hashes WHERE dataType=?;'
args = (blockType,)
for row in c.execute(execute, args):
for i in row:
retData += i + "\n"
return retData.split('\n')
def setBlockType(self, hash, blockType):
'''
Sets the type of block
'''
conn = sqlite3.connect(self.blockDB)
c = conn.cursor()
#if blockType not in ("txt"):
# return
c.execute("UPDATE hashes SET dataType='" + blockType + "' WHERE hash = '" + hash + "';")
conn.commit()
conn.close()
return

View File

@ -19,7 +19,7 @@
'''
import subprocess, os, random, sys, logger, time, signal
class NetController:
'''NetController
'''
This class handles hidden service setup on Tor and I2P
'''
def __init__(self, hsPort):
@ -36,9 +36,13 @@ class NetController:
os.remove(self.torConfigLocation)
torrc.close()
'''
return
def generateTorrc(self):
'''generate a torrc file for our tor instance'''
'''
Generate a torrc file for our tor instance
'''
if os.path.exists(self.torConfigLocation):
os.remove(self.torConfigLocation)
torrcData = '''SocksPort ''' + str(self.socksPort) + '''
@ -48,10 +52,12 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + '''
torrc = open(self.torConfigLocation, 'w')
torrc.write(torrcData)
torrc.close()
return
def startTor(self):
'''Start Tor with onion service on port 80 & socks proxy on random port
'''
Start Tor with onion service on port 80 & socks proxy on random port
'''
self.generateTorrc()
if os.path.exists('./tor'):
@ -80,9 +86,13 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + '''
torPidFile = open('data/torPid.txt', 'w')
torPidFile.write(str(tor.pid))
torPidFile.close()
return True
def killTor(self):
'''properly kill tor based on pid saved to file'''
'''
Properly kill tor based on pid saved to file
'''
try:
pid = open('data/torPid.txt', 'r')
pidN = pid.read()
@ -95,3 +105,5 @@ HiddenServicePort 80 127.0.0.1:''' + str(self.hsPort) + '''
return
os.kill(int(pidN), signal.SIGTERM)
os.remove('data/torPid.txt')
return

View File

@ -234,4 +234,3 @@ class Onionr:
return
Onionr()

View File

@ -22,9 +22,12 @@ import nacl
class OnionrCrypto:
def __init__(self):
return
def symmetricPeerEncrypt(self, data, key):
return
def symmetricPeerDecrypt(self, data, key):
return
def rsaEncrypt(self, peer, data):
return

View File

@ -32,15 +32,22 @@ class OnionrUtils:
self._core = coreInstance
return
def localCommand(self, command):
'''Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers.'''
'''
Send a command to the local http API server, securely. Intended for local clients, DO NOT USE for remote peers.
'''
config = configparser.ConfigParser()
if os.path.exists('data/config.ini'):
config.read('data/config.ini')
else:
return
requests.get('http://' + open('data/host.txt', 'r').read() + ':' + str(config['CLIENT']['PORT']) + '/client/?action=' + command + '&token=' + config['CLIENT']['CLIENT HMAC'])
return
def getPassword(self, message='Enter password: ', confirm = True):
'''Get a password without showing the users typing and confirm the input'''
'''
Get a password without showing the users typing and confirm the input
'''
# Get a password safely with confirmation and return it
while True:
print(message)
@ -55,9 +62,13 @@ class OnionrUtils:
break
else:
break
return pass1
def checkPort(self, port, host = ''):
'''Checks if a port is available, returns bool'''
def checkPort(self, port, host=''):
'''
Checks if a port is available, returns bool
'''
# inspired by https://www.reddit.com/r/learnpython/comments/2i4qrj/how_to_write_a_python_script_that_checks_to_see/ckzarux/
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
retVal = False
@ -68,36 +79,49 @@ class OnionrUtils:
retVal = True
finally:
sock.close()
return retVal
def checkIsIP(self, ip):
'''Check if a string is a valid ipv4 address'''
'''
Check if a string is a valid IPv4 address
'''
try:
socket.inet_aton(ip)
except:
return False
else:
return True
def exportMyPubkey(self):
'''Export our PGP key if it exists'''
'''
Export our PGP key if it exists
'''
if not os.path.exists(self.fingerprintFile):
raise Exception("No fingerprint found, cannot export our PGP key.")
gpg = gnupg.GPG(homedir='./data/pgp/')
with open(self.fingerprintFile,'r') as f:
fingerprint = f.read()
ascii_armored_public_keys = gpg.export_keys(fingerprint)
return ascii_armored_public_keys
def getBlockDBHash(self):
'''Return a sha3_256 hash of the blocks DB'''
'''
Return a sha3_256 hash of the blocks DB
'''
with open(self._core.blockDB, 'rb') as data:
data = data.read()
hasher = hashlib.sha3_256()
hasher.update(data)
dataHash = hasher.hexdigest()
return dataHash
def hasBlock(self, hash):
'''detect if we have a block in the list or not'''
'''
Check for new block in the list
'''
conn = sqlite3.connect(self._core.blockDB)
c = conn.cursor()
if not self.validateHash(hash):
@ -113,7 +137,9 @@ class OnionrUtils:
return False
def validateHash(self, data, length=64):
'''Validate if a string is a valid hex formatted hash'''
'''
Validate if a string is a valid hex formatted hash
'''
retVal = True
if data == False or data == True:
return False
@ -125,9 +151,13 @@ class OnionrUtils:
int(data, 16)
except ValueError:
retVal = False
return retVal
def validateID(self, id):
'''validate if a user ID is a valid tor or i2p hidden service'''
'''
Validate if a user ID is a valid tor or i2p hidden service
'''
idLength = len(id)
retVal = True
idNoDomain = ''
@ -165,4 +195,5 @@ class OnionrUtils:
retVal = False
if not idNoDomain.isalnum():
retVal = False
return retVal

View File

@ -23,6 +23,7 @@ class OnionrTests(unittest.TestCase):
self.assertTrue(False)
else:
self.assertTrue(True)
def testNone(self):
logger.debug('--------------------------')
logger.info('Running simple program run test...')
@ -32,6 +33,7 @@ class OnionrTests(unittest.TestCase):
self.assertTrue(False)
else:
self.assertTrue(True)
def testPeer_a_DBCreation(self):
logger.debug('--------------------------')
logger.info('Running peer db creation test...')
@ -44,6 +46,7 @@ class OnionrTests(unittest.TestCase):
self.assertTrue(True)
else:
self.assertTrue(False)
def testPeer_b_addPeerToDB(self):
logger.debug('--------------------------')
logger.info('Running peer db insertion test...')
@ -55,6 +58,7 @@ class OnionrTests(unittest.TestCase):
self.assertTrue(True)
else:
self.assertTrue(False)
def testData_b_Encrypt(self):
self.assertTrue(True)
return
@ -67,6 +71,7 @@ class OnionrTests(unittest.TestCase):
self.assertTrue(True)
else:
self.assertTrue(False)
def testData_a_Decrypt(self):
self.assertTrue(True)
return
@ -79,6 +84,7 @@ class OnionrTests(unittest.TestCase):
self.assertTrue(True)
else:
self.assertTrue(False)
def testPGPGen(self):
logger.debug('--------------------------')
logger.info('Running PGP key generation test...')
@ -93,6 +99,7 @@ class OnionrTests(unittest.TestCase):
myCore.generateMainPGP(torID)
if os.path.exists('data/pgp/'):
self.assertTrue(True)
def testHMACGen(self):
logger.debug('--------------------------')
logger.info('Running HMAC generation test...')
@ -104,6 +111,7 @@ class OnionrTests(unittest.TestCase):
self.assertTrue(True)
else:
self.assertTrue(False)
def testQueue(self):
logger.debug('--------------------------')
logger.info('Running daemon queue test...')
@ -124,4 +132,5 @@ class OnionrTests(unittest.TestCase):
if command[0] == 'testCommand':
if myCore.daemonQueue() == False:
logger.info('Succesfully added and read command')
unittest.main()

View File

@ -30,6 +30,7 @@ class TimedHMAC:
generatedHMAC = hmac.HMAC(base64.b64decode(base64Key).decode(), digestmod=self.hashAlgo)
generatedHMAC.update(data + expire)
self.HMACResult = generatedHMAC.hexdigest()
return
def check(self, data):