improved project structuring
This commit is contained in:
parent
a89435cfd3
commit
813e98a801
20 changed files with 64 additions and 14 deletions
1
onionr/__init__.py
Normal file
1
onionr/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
#
|
165
onionr/api.py
Executable file
165
onionr/api.py
Executable file
|
@ -0,0 +1,165 @@
|
|||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network
|
||||
|
||||
This file handles all incoming http requests to the client, using Flask
|
||||
'''
|
||||
'''
|
||||
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/>.
|
||||
'''
|
||||
import flask
|
||||
from flask import request, Response, abort
|
||||
from multiprocessing import Process
|
||||
import configparser, sys, random, threading, hmac, hashlib, base64, time, math, gnupg, os
|
||||
|
||||
from core import Core
|
||||
class API:
|
||||
''' Main http api (flask)'''
|
||||
def validateToken(self, token):
|
||||
'''
|
||||
Validate if the client token (hmac) matches the given token
|
||||
'''
|
||||
if self.clientToken != token:
|
||||
return False
|
||||
else:
|
||||
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
|
||||
|
||||
This also saves the used host (random localhost IP address) to the data folder in host.txt
|
||||
'''
|
||||
if os.path.exists('dev-enabled'):
|
||||
print('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
|
||||
self._developmentMode = True
|
||||
else:
|
||||
self._developmentMode = False
|
||||
self.config = config
|
||||
self.debug = debug
|
||||
self._privateDelayTime = 3
|
||||
self._core = Core()
|
||||
app = flask.Flask(__name__)
|
||||
bindPort = int(self.config['CLIENT']['PORT'])
|
||||
self.bindPort = bindPort
|
||||
self.clientToken = self.config['CLIENT']['CLIENT HMAC']
|
||||
print(self.clientToken)
|
||||
|
||||
if not debug and not self._developmentMode:
|
||||
hostNums = [random.randint(1, 255), random.randint(1, 255), random.randint(1, 255)]
|
||||
self.host = '127.' + str(hostNums[0]) + '.' + str(hostNums[1]) + '.' + str(hostNums[2])
|
||||
else:
|
||||
self.host = '127.0.0.1'
|
||||
hostFile = open('data/host.txt', 'w')
|
||||
hostFile.write(self.host)
|
||||
hostFile.close()
|
||||
|
||||
@app.before_request
|
||||
def beforeReq():
|
||||
'''
|
||||
Simply define the request as not having yet failed, before every request.
|
||||
'''
|
||||
self.requestFailed = False
|
||||
return
|
||||
|
||||
@app.after_request
|
||||
def afterReq(resp):
|
||||
if not self.requestFailed:
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
else:
|
||||
resp.headers['server'] = 'Onionr'
|
||||
resp.headers['content-type'] = 'text/plain'
|
||||
resp.headers["Content-Security-Policy"] = "default-src 'none'"
|
||||
resp.headers['x-frame-options'] = 'deny'
|
||||
return resp
|
||||
|
||||
@app.route('/client/')
|
||||
def private_handler():
|
||||
startTime = math.floor(time.time())
|
||||
# we should keep a hash DB of requests (with hmac) to prevent replays
|
||||
action = request.args.get('action')
|
||||
#if not self.debug:
|
||||
token = request.args.get('token')
|
||||
if not self.validateToken(token):
|
||||
abort(403)
|
||||
self.validateHost('private')
|
||||
if action == 'hello':
|
||||
resp = Response('Hello, World! ' + request.host)
|
||||
elif action == 'shutdown':
|
||||
request.environ.get('werkzeug.server.shutdown')()
|
||||
resp = Response('Goodbye')
|
||||
elif action == 'stats':
|
||||
resp = Response('something')
|
||||
elif action == 'init':
|
||||
# generate PGP key
|
||||
self._core.generateMainPGP()
|
||||
pass
|
||||
else:
|
||||
resp = Response('(O_o) Dude what? (invalid command)')
|
||||
endTime = math.floor(time.time())
|
||||
elapsed = endTime - startTime
|
||||
if elapsed < self._privateDelayTime:
|
||||
time.sleep(self._privateDelayTime - elapsed)
|
||||
return resp
|
||||
|
||||
@app.route('/public/')
|
||||
def public_handler():
|
||||
# Public means it is publicly network accessible
|
||||
self.validateHost('public')
|
||||
action = request.args.get('action')
|
||||
requestingPeer = request.args.get('myID')
|
||||
|
||||
if action == 'firstConnect':
|
||||
pass
|
||||
|
||||
@app.errorhandler(404)
|
||||
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
|
||||
|
||||
print('Starting client on ' + self.host + ':' + str(bindPort))
|
||||
print('Client token:', self.clientToken)
|
||||
|
||||
app.run(host=self.host, port=bindPort, debug=True, threaded=True)
|
||||
|
||||
def validateHost(self, hostType):
|
||||
''' 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?
|
||||
|
||||
Was x-request-with used?
|
||||
'''
|
||||
if self.debug:
|
||||
return
|
||||
# Validate host header, to protect against DNS rebinding attacks
|
||||
host = self.host
|
||||
if hostType == 'private':
|
||||
if not request.host.startswith('127'):
|
||||
abort(403)
|
||||
elif hostType == 'public':
|
||||
if not request.host.endswith('onion') and not request.hosst.endswith('i2p'):
|
||||
abort(403)
|
||||
# Validate x-requested-with, to protect against CSRF/metadata leaks
|
||||
if not self._developmentMode:
|
||||
try:
|
||||
request.headers['x-requested-with']
|
||||
except:
|
||||
# we exit rather than abort to avoid fingerprinting
|
||||
sys.exit(1)
|
23
onionr/colors.py
Normal file
23
onionr/colors.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
'''
|
||||
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"
|
60
onionr/communicator.py
Executable file
60
onionr/communicator.py
Executable file
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env python3
|
||||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network.
|
||||
|
||||
This file contains both the OnionrCommunicate class for communcating with peers
|
||||
and code to operate as a daemon, getting commands from the command queue database (see core.Core.daemonQueue)
|
||||
'''
|
||||
'''
|
||||
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/>.
|
||||
'''
|
||||
import sqlite3, requests, hmac, hashlib, time, sys, os
|
||||
import core
|
||||
class OnionrCommunicate:
|
||||
def __init__(self):
|
||||
''' OnionrCommunicate
|
||||
|
||||
This class handles communication with nodes in the Onionr network.
|
||||
'''
|
||||
self._core = core.Core()
|
||||
while True:
|
||||
command = self._core.daemonQueue()
|
||||
print('Daemon heartbeat')
|
||||
if command != False:
|
||||
if command[0] == 'shutdown':
|
||||
print('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 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
|
||||
shouldRun = False
|
||||
debug = False
|
||||
developmentMode = False
|
||||
if os.path.exists('dev-enabled'):
|
||||
developmentMode = True
|
||||
try:
|
||||
if sys.argv[1] == 'run':
|
||||
shouldRun = True
|
||||
except IndexError:
|
||||
pass
|
||||
if shouldRun:
|
||||
OnionrCommunicate(debug, developmentMode)
|
150
onionr/core.py
Normal file
150
onionr/core.py
Normal file
|
@ -0,0 +1,150 @@
|
|||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network
|
||||
|
||||
Core Onionr library, useful for external programs. Handles peer processing and cryptography.
|
||||
'''
|
||||
'''
|
||||
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/>.
|
||||
'''
|
||||
import sqlite3, os, time, math, gnupg, base64, tarfile, getpass, simplecrypt
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto import Random
|
||||
|
||||
|
||||
class Core:
|
||||
def __init__(self):
|
||||
'''
|
||||
Initialize Core Onionr library
|
||||
'''
|
||||
self.queueDB = 'data/queue.db'
|
||||
self.peerDB = 'data/peers.db'
|
||||
|
||||
#self.daemonQueue() # Call to create the DB if it doesn't exist
|
||||
return
|
||||
|
||||
def generateMainPGP(self):
|
||||
''' 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
|
||||
gpg = gnupg.GPG(gnupghome='data/pgp/')
|
||||
input_data = gpg.gen_key_input(key_type="RSA", key_length=2048, name_real='anon', name_comment='Onionr key', name_email='anon@onionr')
|
||||
key = gpg.gen_key(input_data)
|
||||
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. '''
|
||||
# This function simply adds a peer to the DB
|
||||
conn = sqlite3.connect(self.peerDB)
|
||||
c = conn.cursor()
|
||||
t = (peerID, name, 'unknown')
|
||||
c.execute('Insert into users (id, name, dateSeen) values(?, ?, ?);', t)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
def createPeerDB(self):
|
||||
'''
|
||||
Generate the peer sqlite3 database and populate it with the peers table.
|
||||
'''
|
||||
# generate the peer database
|
||||
conn = sqlite3.connect(self.peerDB)
|
||||
c = conn.cursor()
|
||||
c.execute('''
|
||||
create table users(
|
||||
ID text not null,
|
||||
name text,
|
||||
pgpKey text,
|
||||
hmacKey text,
|
||||
forwardKey text,
|
||||
dateSeen not null,
|
||||
trust int);
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def dataDirEncrypt(self, password):
|
||||
'''
|
||||
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")
|
||||
for name in ['data']:
|
||||
tar.add(name)
|
||||
tar.close()
|
||||
tarData = open('data.tar', 'r', encoding = "ISO-8859-1").read()
|
||||
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()
|
||||
try:
|
||||
decrypted = simplecrypt.decrypt(password, data)
|
||||
except simplecrypt.DecryptionException:
|
||||
return (False, 'wrong password (or corrupted archive)')
|
||||
else:
|
||||
open('data.tar', 'wb').write(decrypted)
|
||||
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.
|
||||
retData = False
|
||||
if not os.path.exists(self.queueDB):
|
||||
conn = sqlite3.connect(self.queueDB)
|
||||
c = conn.cursor()
|
||||
# Create table
|
||||
c.execute('''CREATE TABLE commands
|
||||
(id integer primary key autoincrement, command text, data text, date text)''')
|
||||
conn.commit()
|
||||
else:
|
||||
conn = sqlite3.connect(self.queueDB)
|
||||
c = conn.cursor()
|
||||
for row in c.execute('SELECT command, data, date, min(ID) FROM commands group by id'):
|
||||
retData = row
|
||||
break
|
||||
if retData != False:
|
||||
c.execute('delete from commands where id = ?', (retData[3],))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return retData
|
||||
|
||||
def daemonQueueAdd(self, command, data=''):
|
||||
'''
|
||||
Add a command to the daemon queue, used by the communication daemon (communicator.py)
|
||||
'''
|
||||
# Intended to be used by the web server
|
||||
date = math.floor(time.time())
|
||||
conn = sqlite3.connect(self.queueDB)
|
||||
c = conn.cursor()
|
||||
t = (command, data, date)
|
||||
c.execute('INSERT into commands (command, data, date) values (?, ?, ?)', t)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
16
onionr/gui.py
Executable file
16
onionr/gui.py
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/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/>.
|
||||
'''
|
142
onionr/onionr.py
Executable file
142
onionr/onionr.py
Executable file
|
@ -0,0 +1,142 @@
|
|||
#!/usr/bin/env python3
|
||||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network.
|
||||
|
||||
Onionr is the name for both the protocol and the original/reference software.
|
||||
|
||||
Run with 'help' for usage.
|
||||
'''
|
||||
'''
|
||||
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/>.
|
||||
'''
|
||||
import sys, os, configparser, base64, random, getpass, shutil, subprocess, requests
|
||||
import gui, api, colors, core
|
||||
from onionrutils import OnionrUtils
|
||||
from colors import Colors
|
||||
|
||||
class Onionr:
|
||||
def __init__(self):
|
||||
'''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.
|
||||
|
||||
'''
|
||||
if os.path.exists('dev-enabled'):
|
||||
print('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
|
||||
self._developmentMode = True
|
||||
else:
|
||||
self._developmentMode = False
|
||||
|
||||
colors = Colors()
|
||||
|
||||
self.onionrCore = core.Core()
|
||||
self.onionrUtils = OnionrUtils()
|
||||
|
||||
# Get configuration and Handle commands
|
||||
|
||||
self.debug = False # Whole application debugging
|
||||
try:
|
||||
os.chdir(sys.path[0])
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
if os.path.exists('data-encrypted.dat'):
|
||||
while True:
|
||||
print('Enter password to decrypt:')
|
||||
password = getpass.getpass()
|
||||
result = self.onionrCore.dataDirDecrypt(password)
|
||||
if os.path.exists('data/'):
|
||||
break
|
||||
else:
|
||||
print('Failed to decrypt: ' + result[1])
|
||||
else:
|
||||
if not os.path.exists('data/'):
|
||||
os.mkdir('data/')
|
||||
|
||||
if not os.path.exists('data/peers.db'):
|
||||
self.onionrCore.createPeerDB()
|
||||
pass
|
||||
|
||||
# Get configuration
|
||||
self.config = configparser.ConfigParser()
|
||||
if os.path.exists('data/config.ini'):
|
||||
self.config.read('data/config.ini')
|
||||
else:
|
||||
# Generate default config
|
||||
# Hostname should only be set if different from 127.x.x.x. Important for DNS rebinding attack prevention.
|
||||
if self.debug:
|
||||
randomPort = 8080
|
||||
else:
|
||||
randomPort = random.randint(1024, 65535)
|
||||
self.config['CLIENT'] = {'CLIENT HMAC': base64.b64encode(os.urandom(32)).decode('utf-8'), 'PORT': randomPort, 'API VERSION': '0.0.0'}
|
||||
with open('data/config.ini', 'w') as configfile:
|
||||
self.config.write(configfile)
|
||||
command = ''
|
||||
try:
|
||||
command = sys.argv[1].lower()
|
||||
except IndexError:
|
||||
command = ''
|
||||
finally:
|
||||
if command == 'start':
|
||||
if os.path.exists('.onionr-lock'):
|
||||
self.onionrUtils.printErr('Cannot start. Daemon is already running, or it did not exit cleanly.\n(if you are sure that there is not a daemon running, delete .onionr-lock & try again).')
|
||||
else:
|
||||
if not self.debug and not self._developmentMode:
|
||||
lockFile = open('.onionr-lock', 'w')
|
||||
lockFile.write('')
|
||||
lockFile.close()
|
||||
self.daemon()
|
||||
if not self.debug and not self._developmentMode:
|
||||
os.remove('.onionr-lock')
|
||||
elif command == 'stop':
|
||||
self.killDaemon()
|
||||
elif command == 'stats':
|
||||
self.showStats()
|
||||
elif command == 'help' or command == '--help':
|
||||
self.showHelp()
|
||||
elif command == '':
|
||||
print('Do', sys.argv[0], ' --help for Onionr help.')
|
||||
else:
|
||||
print(colors.RED, 'Invalid Command', colors.RESET)
|
||||
|
||||
if not self._developmentMode:
|
||||
encryptionPassword = self.onionrUtils.getPassword('Enter password to encrypt directory.')
|
||||
self.onionrCore.dataDirEncrypt(encryptionPassword)
|
||||
shutil.rmtree('data/')
|
||||
return
|
||||
def daemon(self):
|
||||
''' Start the Onionr communication daemon
|
||||
'''
|
||||
if not os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
||||
subprocess.Popen(["./communicator.py", "run"])
|
||||
print('Started communicator')
|
||||
api.API(self.config, self.debug)
|
||||
return
|
||||
def killDaemon(self):
|
||||
'''Shutdown the Onionr Daemon'''
|
||||
print('Killing the running daemon')
|
||||
try:
|
||||
self.onionrUtils.localCommand('shutdown')
|
||||
except requests.exceptions.ConnectionError:
|
||||
pass
|
||||
else:
|
||||
self.onionrCore.daemonQueueAdd('shutdown')
|
||||
return
|
||||
def showStats(self):
|
||||
'''Display statistics and exit'''
|
||||
return
|
||||
def showHelp(self):
|
||||
'''Show help for Onionr'''
|
||||
return
|
||||
|
||||
Onionr()
|
50
onionr/onionrutils.py
Normal file
50
onionr/onionrutils.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network
|
||||
|
||||
OnionrUtils offers various useful functions to Onionr. Relatively misc.
|
||||
'''
|
||||
'''
|
||||
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/>.
|
||||
'''
|
||||
# Misc functions that do not fit in the main api, but are useful
|
||||
import getpass, sys, requests, configparser, os
|
||||
class OnionrUtils():
|
||||
'''Various useful functions'''
|
||||
def __init__(self):
|
||||
return
|
||||
def printErr(self, text='an error occured'):
|
||||
'''Print an error message to stderr with a new line'''
|
||||
sys.stderr.write(text + '\n')
|
||||
def localCommand(self, command):
|
||||
'''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'])
|
||||
def getPassword(self, message='Enter password: '):
|
||||
'''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)
|
||||
pass1 = getpass.getpass()
|
||||
print('Confirm password: ')
|
||||
pass2 = getpass.getpass()
|
||||
if pass1 != pass2:
|
||||
print("Passwords do not match.")
|
||||
input()
|
||||
else:
|
||||
break
|
||||
return pass1
|
113
onionr/tests.py
Executable file
113
onionr/tests.py
Executable file
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env python3
|
||||
'''
|
||||
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/>.
|
||||
'''
|
||||
import unittest, sys, os, base64, tarfile, shutil, simplecrypt
|
||||
|
||||
class OnionrTests(unittest.TestCase):
|
||||
def testPython3(self):
|
||||
if sys.version_info.major != 3:
|
||||
print(sys.version_info.major)
|
||||
self.assertTrue(False)
|
||||
else:
|
||||
self.assertTrue(True)
|
||||
def testNone(self):
|
||||
print('--------------------------')
|
||||
print('Running simple program run test')
|
||||
# Test just running ./onionr with no arguments
|
||||
blank = os.system('./onionr.py')
|
||||
if blank != 0:
|
||||
self.assertTrue(False)
|
||||
else:
|
||||
self.assertTrue(True)
|
||||
def testPeer_a_DBCreation(self):
|
||||
print('--------------------------')
|
||||
print('Running peer db creation test')
|
||||
if os.path.exists('data/peers.db'):
|
||||
os.remove('data/peers.db')
|
||||
import core
|
||||
myCore = core.Core()
|
||||
myCore.createPeerDB()
|
||||
if os.path.exists('data/peers.db'):
|
||||
self.assertTrue(True)
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
def testPeer_b_addPeerToDB(self):
|
||||
print('--------------------------')
|
||||
print('Running peer db insertion test')
|
||||
import core
|
||||
myCore = core.Core()
|
||||
if not os.path.exists('data/peers.db'):
|
||||
myCore.createPeerDB()
|
||||
if myCore.addPeer('test'):
|
||||
self.assertTrue(True)
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
def testData_b_Encrypt(self):
|
||||
self.assertTrue(True)
|
||||
return
|
||||
print('--------------------------')
|
||||
print('Running data dir encrypt test')
|
||||
import core
|
||||
myCore = core.Core()
|
||||
myCore.dataDirEncrypt('password')
|
||||
if os.path.exists('data-encrypted.dat'):
|
||||
self.assertTrue(True)
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
def testData_a_Decrypt(self):
|
||||
self.assertTrue(True)
|
||||
return
|
||||
print('--------------------------')
|
||||
print('Running data dir decrypt test')
|
||||
import core
|
||||
myCore = core.Core()
|
||||
myCore.dataDirDecrypt('password')
|
||||
if os.path.exists('data/'):
|
||||
self.assertTrue(True)
|
||||
else:
|
||||
self.assertTrue(False)
|
||||
def testPGPGen(self):
|
||||
print('--------------------------')
|
||||
print('Testing PGP key generation')
|
||||
if os.path.exists('data/pgp/'):
|
||||
self.assertTrue(True)
|
||||
else:
|
||||
import core
|
||||
myCore = core.Core()
|
||||
myCore.generateMainPGP()
|
||||
if os.path.exists('data/pgp/'):
|
||||
self.assertTrue(True)
|
||||
def testQueue(self):
|
||||
print('--------------------------')
|
||||
print('running daemon queue test')
|
||||
# test if the daemon queue can read/write data
|
||||
import core
|
||||
myCore = core.Core()
|
||||
if not os.path.exists('data/queue.db'):
|
||||
myCore.daemonQueue()
|
||||
while True:
|
||||
command = myCore.daemonQueue()
|
||||
if command == False:
|
||||
print('The queue is empty (false)')
|
||||
break
|
||||
else:
|
||||
print(command[0])
|
||||
myCore.daemonQueueAdd('testCommand', 'testData')
|
||||
command = myCore.daemonQueue()
|
||||
if command[0] == 'testCommand':
|
||||
if myCore.daemonQueue() == False:
|
||||
print('Succesfully added and read command')
|
||||
unittest.main()
|
43
onionr/timedHmac.py
Normal file
43
onionr/timedHmac.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network. Run with 'help' for usage.
|
||||
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/>.
|
||||
'''
|
||||
import hmac, base64, time, math
|
||||
class TimedHMAC:
|
||||
def __init__(self, base64Key, data, hashAlgo):
|
||||
'''
|
||||
base64Key = base64 encoded key
|
||||
data = data to hash
|
||||
expire = time expiry in epoch
|
||||
hashAlgo = string in hashlib.algorithms_available
|
||||
|
||||
Maximum of 10 seconds grace period
|
||||
'''
|
||||
self.data = data
|
||||
self.expire = math.floor(time.time())
|
||||
self.hashAlgo = hashAlgo
|
||||
self.b64Key = base64Key
|
||||
generatedHMAC = hmac.HMAC(base64.b64decode(base64Key).decode(), digestmod=self.hashAlgo)
|
||||
generatedHMAC.update(data + expire)
|
||||
self.HMACResult = generatedHMAC.hexdigest()
|
||||
return
|
||||
|
||||
def check(self, data):
|
||||
# Check a hash (and verify time is sane)
|
||||
testHash = hmac.HMAC(base64.b64decode(base64Key).decode(), digestmod=self.hashAlgo)
|
||||
testHash.update(data + math.floor(time.time()))
|
||||
testHash = testHash.hexdigest()
|
||||
if hmac.compare_digest(testHash, self.HMACResult):
|
||||
return true
|
||||
else:
|
||||
return false
|
Loading…
Add table
Add a link
Reference in a new issue