2018-01-02 08:43:29 +00:00
|
|
|
'''
|
|
|
|
Onionr - P2P Microblogging Platform & Social network
|
2018-01-14 08:48:23 +00:00
|
|
|
|
|
|
|
Core Onionr library, useful for external programs. Handles peer processing and cryptography.
|
|
|
|
'''
|
|
|
|
'''
|
2018-01-02 08:43:29 +00:00
|
|
|
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/>.
|
|
|
|
'''
|
2018-01-25 05:30:41 +00:00
|
|
|
import sqlite3, os, sys, time, math, gnupg, base64, tarfile, getpass, simplecrypt, hashlib, nacl
|
2018-01-07 08:55:44 +00:00
|
|
|
from Crypto.Cipher import AES
|
|
|
|
from Crypto import Random
|
2018-01-19 09:16:38 +00:00
|
|
|
import netcontroller
|
2018-01-07 08:55:44 +00:00
|
|
|
|
2018-01-22 02:49:11 +00:00
|
|
|
if sys.version_info < (3, 6):
|
|
|
|
try:
|
|
|
|
import sha3
|
|
|
|
except ModuleNotFoundError:
|
|
|
|
sys.stderr.write('On Python 3 versions prior to 3.6.x, you need the sha3 module')
|
|
|
|
sys.exit(1)
|
|
|
|
|
2018-01-02 08:43:29 +00:00
|
|
|
class Core:
|
|
|
|
def __init__(self):
|
2018-01-14 08:48:23 +00:00
|
|
|
'''
|
|
|
|
Initialize Core Onionr library
|
|
|
|
'''
|
2018-01-02 08:43:29 +00:00
|
|
|
self.queueDB = 'data/queue.db'
|
2018-01-10 03:50:38 +00:00
|
|
|
self.peerDB = 'data/peers.db'
|
2018-01-20 07:23:09 +00:00
|
|
|
self.ownPGPID = ''
|
2018-01-21 05:49:16 +00:00
|
|
|
self.blockDB = 'data/blocks.db'
|
2018-01-21 09:12:41 +00:00
|
|
|
self.blockDataLocation = 'data/blocks/'
|
2018-01-10 03:50:38 +00:00
|
|
|
|
2018-01-02 08:43:29 +00:00
|
|
|
return
|
|
|
|
|
2018-01-20 07:23:09 +00:00
|
|
|
def generateMainPGP(self, myID):
|
2018-01-17 23:37:53 +00:00
|
|
|
''' Generate the main PGP key for our client. Should not be done often.
|
2018-01-14 08:48:23 +00:00
|
|
|
Uses own PGP home folder in the data/ directory. '''
|
2018-01-05 09:16:21 +00:00
|
|
|
# Generate main pgp key
|
2018-01-20 18:23:01 +00:00
|
|
|
if os.getenv('TRAVIS') == 'true':
|
|
|
|
gpg = gnupg.GPG(homedir='./data/pgp/')
|
|
|
|
else:
|
|
|
|
gpg = gnupg.GPG(gnupghome='./data/pgp/')
|
2018-01-20 07:23:09 +00:00
|
|
|
input_data = gpg.gen_key_input(key_type="RSA", key_length=2048, name_real=myID, name_email='anon@onionr')
|
|
|
|
#input_data = gpg.gen_key_input(key_type="RSA", key_length=1024)
|
2018-01-06 08:51:26 +00:00
|
|
|
key = gpg.gen_key(input_data)
|
2018-01-20 07:23:09 +00:00
|
|
|
# Write the key
|
|
|
|
myFingerpintFile = open('data/own-fingerprint.txt', 'w')
|
|
|
|
myFingerpintFile.write(key.fingerprint)
|
|
|
|
myFingerpintFile.close()
|
2018-01-06 08:51:26 +00:00
|
|
|
return
|
2018-01-10 03:50:38 +00:00
|
|
|
|
2018-01-10 08:40:25 +00:00
|
|
|
def addPeer(self, peerID, name=''):
|
2018-01-14 08:48:23 +00:00
|
|
|
''' 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. '''
|
2018-01-10 03:50:38 +00:00
|
|
|
# This function simply adds a peer to the DB
|
2018-01-10 08:40:25 +00:00
|
|
|
conn = sqlite3.connect(self.peerDB)
|
|
|
|
c = conn.cursor()
|
2018-01-14 00:07:13 +00:00
|
|
|
t = (peerID, name, 'unknown')
|
2018-01-17 23:37:53 +00:00
|
|
|
c.execute('Insert into peers (id, name, dateSeen) values(?, ?, ?);', t)
|
2018-01-10 08:40:25 +00:00
|
|
|
conn.commit()
|
|
|
|
conn.close()
|
|
|
|
return True
|
2018-01-10 03:50:38 +00:00
|
|
|
|
|
|
|
def createPeerDB(self):
|
2018-01-14 08:48:23 +00:00
|
|
|
'''
|
|
|
|
Generate the peer sqlite3 database and populate it with the peers table.
|
|
|
|
'''
|
2018-01-10 03:50:38 +00:00
|
|
|
# generate the peer database
|
|
|
|
conn = sqlite3.connect(self.peerDB)
|
|
|
|
c = conn.cursor()
|
|
|
|
c.execute('''
|
2018-01-17 23:37:53 +00:00
|
|
|
create table peers(
|
2018-01-10 03:50:38 +00:00
|
|
|
ID text not null,
|
|
|
|
name text,
|
|
|
|
pgpKey text,
|
|
|
|
hmacKey text,
|
|
|
|
forwardKey text,
|
|
|
|
dateSeen not null,
|
2018-01-17 18:35:24 +00:00
|
|
|
bytesStored int,
|
2018-01-10 03:50:38 +00:00
|
|
|
trust int);
|
|
|
|
''')
|
|
|
|
conn.commit()
|
|
|
|
conn.close()
|
2018-01-21 05:49:16 +00:00
|
|
|
def createBlockDB(self):
|
|
|
|
'''
|
|
|
|
Create a database for blocks
|
|
|
|
|
|
|
|
hash - the hash of a block
|
|
|
|
dateReceived - the date the block was recieved, not necessarily when it was created
|
|
|
|
decrypted - if we can successfully decrypt the block (does not describe its current state)
|
|
|
|
dataObtained - if the data has been obtained for the block
|
|
|
|
'''
|
|
|
|
if os.path.exists(self.blockDB):
|
|
|
|
raise Exception("Block database already exists")
|
|
|
|
conn = sqlite3.connect(self.blockDB)
|
|
|
|
c = conn.cursor()
|
|
|
|
c.execute('''create table hashes(
|
|
|
|
hash text not null,
|
|
|
|
dateReceived int,
|
|
|
|
decrypted int,
|
|
|
|
dataFound int
|
|
|
|
);
|
|
|
|
''')
|
|
|
|
conn.commit()
|
|
|
|
conn.close()
|
|
|
|
def addToBlockDB(self, newHash):
|
|
|
|
'''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')
|
|
|
|
conn = sqlite3.connect(self.blockDB)
|
|
|
|
c = conn.cursor()
|
|
|
|
currentTime = math.floor(time.time())
|
|
|
|
data = (newHash, currentTime, 0, 0)
|
|
|
|
c.execute('INSERT into hashes values(?, ?, ?, ?);', data)
|
|
|
|
conn.commit()
|
|
|
|
conn.close()
|
2018-01-17 23:37:53 +00:00
|
|
|
|
2018-01-21 09:12:41 +00:00
|
|
|
def getData(self,hash):
|
|
|
|
'''simply return the data associated to a hash'''
|
|
|
|
dataFile = open(self.blockDataLocation + hash + '.dat')
|
|
|
|
data = dataFile.read()
|
|
|
|
dataFile.close()
|
|
|
|
return data
|
|
|
|
|
2018-01-22 02:49:11 +00:00
|
|
|
def setData(self, data):
|
|
|
|
'''set the data assciated with a hash'''
|
|
|
|
hasher = hashlib.sha3_256
|
|
|
|
|
2018-01-08 09:25:32 +00:00
|
|
|
def dataDirEncrypt(self, password):
|
2018-01-14 08:48:23 +00:00
|
|
|
'''
|
|
|
|
Encrypt the data directory on Onionr shutdown
|
|
|
|
'''
|
2018-01-08 09:25:32 +00:00
|
|
|
# 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')
|
2018-01-05 09:16:21 +00:00
|
|
|
return
|
2018-01-08 09:25:32 +00:00
|
|
|
def dataDirDecrypt(self, password):
|
2018-01-14 08:48:23 +00:00
|
|
|
'''
|
|
|
|
Decrypt the data directory on startup
|
|
|
|
'''
|
2018-01-08 09:25:32 +00:00
|
|
|
# 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:
|
2018-01-09 22:58:12 +00:00
|
|
|
return (False, 'wrong password (or corrupted archive)')
|
2018-01-08 09:25:32 +00:00
|
|
|
else:
|
|
|
|
open('data.tar', 'wb').write(decrypted)
|
2018-01-09 22:58:12 +00:00
|
|
|
tar = tarfile.open('data.tar')
|
|
|
|
tar.extractall()
|
|
|
|
tar.close()
|
2018-01-08 09:25:32 +00:00
|
|
|
return (True, '')
|
2018-01-02 08:43:29 +00:00
|
|
|
def daemonQueue(self):
|
2018-01-14 08:48:23 +00:00
|
|
|
'''
|
|
|
|
Gives commands to the communication proccess/daemon by reading an sqlite3 database
|
|
|
|
'''
|
2018-01-04 07:12:46 +00:00
|
|
|
# This function intended to be used by the client
|
2018-01-02 08:43:29 +00:00
|
|
|
# Queue to exchange data between "client" and server.
|
|
|
|
retData = False
|
|
|
|
if not os.path.exists(self.queueDB):
|
2018-01-04 07:12:46 +00:00
|
|
|
conn = sqlite3.connect(self.queueDB)
|
|
|
|
c = conn.cursor()
|
2018-01-02 08:43:29 +00:00
|
|
|
# Create table
|
|
|
|
c.execute('''CREATE TABLE commands
|
|
|
|
(id integer primary key autoincrement, command text, data text, date text)''')
|
|
|
|
conn.commit()
|
|
|
|
else:
|
2018-01-04 07:12:46 +00:00
|
|
|
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()
|
2018-01-02 08:43:29 +00:00
|
|
|
conn.close()
|
|
|
|
|
|
|
|
return retData
|
|
|
|
|
|
|
|
def daemonQueueAdd(self, command, data=''):
|
2018-01-14 08:48:23 +00:00
|
|
|
'''
|
|
|
|
Add a command to the daemon queue, used by the communication daemon (communicator.py)
|
|
|
|
'''
|
2018-01-04 07:12:46 +00:00
|
|
|
# Intended to be used by the web server
|
2018-01-02 08:43:29 +00:00
|
|
|
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()
|
2018-01-15 21:21:32 +00:00
|
|
|
return
|
2018-01-17 23:37:53 +00:00
|
|
|
|
2018-01-16 08:32:17 +00:00
|
|
|
def generateHMAC(self):
|
|
|
|
'''
|
|
|
|
generate and return an HMAC key
|
|
|
|
'''
|
2018-01-17 23:37:53 +00:00
|
|
|
key = base64.b64encode(os.urandom(32))
|
2018-01-19 09:16:38 +00:00
|
|
|
return key
|