Module src.onionrusers.onionrusers
Onionr - Private P2P Communication
Contains abstractions for interacting with users of Onionr
Expand source code
'''
    Onionr - Private P2P Communication
    Contains abstractions for interacting with users of Onionr
'''
'''
    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 logger, onionrexceptions, json, sqlite3, time
from onionrutils import stringvalidators, bytesconverter, epoch
import unpaddedbase32
import nacl.exceptions
from coredb import keydb, dbfiles
import onionrcrypto
def deleteExpiredKeys():
    # Fetch the keys we generated for the peer, that are still around
    conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
    c = conn.cursor()
    curTime = epoch.get_epoch()
    c.execute("DELETE from myForwardKeys where expire <= ?", (curTime,))
    conn.commit()
    conn.execute("VACUUM")
    conn.close()
    return
def deleteTheirExpiredKeys(pubkey):
    conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
    c = conn.cursor()
    # Prepare the insert
    command = (pubkey, epoch.get_epoch())
    c.execute("DELETE from forwardKeys where peerKey = ? and expire <= ?", command)
    conn.commit()
    conn.close()
DEFAULT_KEY_EXPIRE = 604800
class OnionrUser:
    def __init__(self, publicKey, saveUser=False):
        '''
            OnionrUser is an abstraction for "users" of the network. 
            
            Takes a base32 encoded ed25519 public key, and a bool saveUser
            saveUser determines if we should add a user to our peer database or not.
        '''
        publicKey = unpaddedbase32.repad(bytesconverter.str_to_bytes(publicKey)).decode()
        self.trust = 0
        self.publicKey = publicKey
        if saveUser:
            try:
                keydb.addkeys.add_peer(publicKey)
            except (AssertionError, ValueError) as e:
                pass
        self.trust = keydb.userinfo.get_user_info(self.publicKey, 'trust')
        return
    def setTrust(self, newTrust):
        '''Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate'''
        keydb.userinfo.set_user_info(self.publicKey, 'trust', newTrust)
    def isFriend(self):
        if keydb.userinfo.set_peer_info(self.publicKey, 'trust') == 1:
            return True
        return False
    def getName(self):
        retData = 'anonymous'
        name = keydb.userinfo.get_user_info(self.publicKey, 'name')
        try:
            if len(name) > 0:
                retData = name
        except ValueError:
            pass
        return retData
    def encrypt(self, data):
        encrypted = onionrcrypto.encryption.pub_key_encrypt(data, self.publicKey, encodedData=True)
        return encrypted
    def decrypt(self, data):
        decrypted = onionrcrypto.encryption.pub_key_decrypt(data, self.publicKey, encodedData=True)
        return decrypted
    def forwardEncrypt(self, data):
        deleteTheirExpiredKeys(self.publicKey)
        deleteExpiredKeys()
        retData = ''
        forwardKey = self._getLatestForwardKey()
        if stringvalidators.validate_pub_key(forwardKey[0]):
            retData = onionrcrypto.encryption.pub_key_encrypt(data, forwardKey[0], encodedData=True)
        else:
            raise onionrexceptions.InvalidPubkey("No valid forward secrecy key available for this user")
        #self.generateForwardKey()
        return (retData, forwardKey[0], forwardKey[1])
    def forwardDecrypt(self, encrypted):
        retData = ""
        for key in self.getGeneratedForwardKeys(False):
            try:
                retData = onionrcrypto.encryption.pub_key_decrypt(encrypted, privkey=key[1], encodedData=True)
            except nacl.exceptions.CryptoError:
                retData = False
            else:
                break
        else:
            raise onionrexceptions.DecryptionError("Could not decrypt forward secrecy content")
        return retData
    def _getLatestForwardKey(self):
        # Get the latest forward secrecy key for a peer
        key = ""
        conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
        c = conn.cursor()
        # TODO: account for keys created at the same time (same epoch)
        for row in c.execute("SELECT forwardKey, max(EXPIRE) FROM forwardKeys WHERE peerKey = ? ORDER BY expire DESC", (self.publicKey,)):
            key = (row[0], row[1])
            break
        conn.commit()
        conn.close()
        return key
    def _getForwardKeys(self):
        conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
        c = conn.cursor()
        keyList = []
        for row in c.execute("SELECT forwardKey, date FROM forwardKeys WHERE peerKey = ? ORDER BY expire DESC", (self.publicKey,)):
            keyList.append((row[0], row[1]))
        conn.commit()
        conn.close()
        return list(keyList)
    def generateForwardKey(self, expire=DEFAULT_KEY_EXPIRE):
        # Generate a forward secrecy key for the peer
        conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
        c = conn.cursor()
        # Prepare the insert
        time = epoch.get_epoch()
        newKeys = onionrcrypto.generate()
        newPub = bytesconverter.bytes_to_str(newKeys[0])
        newPriv = bytesconverter.bytes_to_str(newKeys[1])
        command = (self.publicKey, newPub, newPriv, time, expire + time)
        c.execute("INSERT INTO myForwardKeys VALUES(?, ?, ?, ?, ?);", command)
        conn.commit()
        conn.close()
        return newPub
    def getGeneratedForwardKeys(self, genNew=True):
        # Fetch the keys we generated for the peer, that are still around
        conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10)
        c = conn.cursor()
        pubkey = self.publicKey
        pubkey = bytesconverter.bytes_to_str(pubkey)
        command = (pubkey,)
        keyList = [] # list of tuples containing pub, private for peer
        for result in c.execute("SELECT * FROM myForwardKeys WHERE peer = ?", command):
            keyList.append((result[1], result[2]))
        if len(keyList) == 0:
            if genNew:
                self.generateForwardKey()
                keyList = self.getGeneratedForwardKeys()
        return list(keyList)
    def addForwardKey(self, newKey, expire=DEFAULT_KEY_EXPIRE):
        newKey = bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(newKey)))
        if not stringvalidators.validate_pub_key(newKey):
            # Do not add if something went wrong with the key
            raise onionrexceptions.InvalidPubkey(newKey)
        conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10)
        c = conn.cursor()
        # Get the time we're inserting the key at
        timeInsert = epoch.get_epoch()
        # Look at our current keys for duplicate key data or time
        for entry in self._getForwardKeys():
            if entry[0] == newKey:
                return False
            if entry[1] == timeInsert:
                timeInsert += 1
                time.sleep(1) # Sleep if our time is the same in order to prevent duplicate time records
        # Add a forward secrecy key for the peer
        # Prepare the insert
        command = (self.publicKey, newKey, timeInsert, timeInsert + expire)
        c.execute("INSERT INTO forwardKeys VALUES(?, ?, ?, ?);", command)
        conn.commit()
        conn.close()
        return True
    
    @classmethod
    def list_friends(cls):
        friendList = []
        for x in keydb.listkeys.list_peers(trust=1):
            friendList.append(cls(x))
        return list(friendList)
Functions
def deleteExpiredKeys()- 
Expand source code
def deleteExpiredKeys(): # Fetch the keys we generated for the peer, that are still around conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10) c = conn.cursor() curTime = epoch.get_epoch() c.execute("DELETE from myForwardKeys where expire <= ?", (curTime,)) conn.commit() conn.execute("VACUUM") conn.close() return def deleteTheirExpiredKeys(pubkey)- 
Expand source code
def deleteTheirExpiredKeys(pubkey): conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10) c = conn.cursor() # Prepare the insert command = (pubkey, epoch.get_epoch()) c.execute("DELETE from forwardKeys where peerKey = ? and expire <= ?", command) conn.commit() conn.close() 
Classes
class OnionrUser (publicKey, saveUser=False)- 
OnionrUser is an abstraction for "users" of the network.
Takes a base32 encoded ed25519 public key, and a bool saveUser saveUser determines if we should add a user to our peer database or not.
Expand source code
class OnionrUser: def __init__(self, publicKey, saveUser=False): ''' OnionrUser is an abstraction for "users" of the network. Takes a base32 encoded ed25519 public key, and a bool saveUser saveUser determines if we should add a user to our peer database or not. ''' publicKey = unpaddedbase32.repad(bytesconverter.str_to_bytes(publicKey)).decode() self.trust = 0 self.publicKey = publicKey if saveUser: try: keydb.addkeys.add_peer(publicKey) except (AssertionError, ValueError) as e: pass self.trust = keydb.userinfo.get_user_info(self.publicKey, 'trust') return def setTrust(self, newTrust): '''Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate''' keydb.userinfo.set_user_info(self.publicKey, 'trust', newTrust) def isFriend(self): if keydb.userinfo.set_peer_info(self.publicKey, 'trust') == 1: return True return False def getName(self): retData = 'anonymous' name = keydb.userinfo.get_user_info(self.publicKey, 'name') try: if len(name) > 0: retData = name except ValueError: pass return retData def encrypt(self, data): encrypted = onionrcrypto.encryption.pub_key_encrypt(data, self.publicKey, encodedData=True) return encrypted def decrypt(self, data): decrypted = onionrcrypto.encryption.pub_key_decrypt(data, self.publicKey, encodedData=True) return decrypted def forwardEncrypt(self, data): deleteTheirExpiredKeys(self.publicKey) deleteExpiredKeys() retData = '' forwardKey = self._getLatestForwardKey() if stringvalidators.validate_pub_key(forwardKey[0]): retData = onionrcrypto.encryption.pub_key_encrypt(data, forwardKey[0], encodedData=True) else: raise onionrexceptions.InvalidPubkey("No valid forward secrecy key available for this user") #self.generateForwardKey() return (retData, forwardKey[0], forwardKey[1]) def forwardDecrypt(self, encrypted): retData = "" for key in self.getGeneratedForwardKeys(False): try: retData = onionrcrypto.encryption.pub_key_decrypt(encrypted, privkey=key[1], encodedData=True) except nacl.exceptions.CryptoError: retData = False else: break else: raise onionrexceptions.DecryptionError("Could not decrypt forward secrecy content") return retData def _getLatestForwardKey(self): # Get the latest forward secrecy key for a peer key = "" conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10) c = conn.cursor() # TODO: account for keys created at the same time (same epoch) for row in c.execute("SELECT forwardKey, max(EXPIRE) FROM forwardKeys WHERE peerKey = ? ORDER BY expire DESC", (self.publicKey,)): key = (row[0], row[1]) break conn.commit() conn.close() return key def _getForwardKeys(self): conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10) c = conn.cursor() keyList = [] for row in c.execute("SELECT forwardKey, date FROM forwardKeys WHERE peerKey = ? ORDER BY expire DESC", (self.publicKey,)): keyList.append((row[0], row[1])) conn.commit() conn.close() return list(keyList) def generateForwardKey(self, expire=DEFAULT_KEY_EXPIRE): # Generate a forward secrecy key for the peer conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10) c = conn.cursor() # Prepare the insert time = epoch.get_epoch() newKeys = onionrcrypto.generate() newPub = bytesconverter.bytes_to_str(newKeys[0]) newPriv = bytesconverter.bytes_to_str(newKeys[1]) command = (self.publicKey, newPub, newPriv, time, expire + time) c.execute("INSERT INTO myForwardKeys VALUES(?, ?, ?, ?, ?);", command) conn.commit() conn.close() return newPub def getGeneratedForwardKeys(self, genNew=True): # Fetch the keys we generated for the peer, that are still around conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10) c = conn.cursor() pubkey = self.publicKey pubkey = bytesconverter.bytes_to_str(pubkey) command = (pubkey,) keyList = [] # list of tuples containing pub, private for peer for result in c.execute("SELECT * FROM myForwardKeys WHERE peer = ?", command): keyList.append((result[1], result[2])) if len(keyList) == 0: if genNew: self.generateForwardKey() keyList = self.getGeneratedForwardKeys() return list(keyList) def addForwardKey(self, newKey, expire=DEFAULT_KEY_EXPIRE): newKey = bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(newKey))) if not stringvalidators.validate_pub_key(newKey): # Do not add if something went wrong with the key raise onionrexceptions.InvalidPubkey(newKey) conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10) c = conn.cursor() # Get the time we're inserting the key at timeInsert = epoch.get_epoch() # Look at our current keys for duplicate key data or time for entry in self._getForwardKeys(): if entry[0] == newKey: return False if entry[1] == timeInsert: timeInsert += 1 time.sleep(1) # Sleep if our time is the same in order to prevent duplicate time records # Add a forward secrecy key for the peer # Prepare the insert command = (self.publicKey, newKey, timeInsert, timeInsert + expire) c.execute("INSERT INTO forwardKeys VALUES(?, ?, ?, ?);", command) conn.commit() conn.close() return True @classmethod def list_friends(cls): friendList = [] for x in keydb.listkeys.list_peers(trust=1): friendList.append(cls(x)) return list(friendList)Static methods
def list_friends()- 
Expand source code
@classmethod def list_friends(cls): friendList = [] for x in keydb.listkeys.list_peers(trust=1): friendList.append(cls(x)) return list(friendList) 
Methods
def addForwardKey(self, newKey, expire=604800)- 
Expand source code
def addForwardKey(self, newKey, expire=DEFAULT_KEY_EXPIRE): newKey = bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(newKey))) if not stringvalidators.validate_pub_key(newKey): # Do not add if something went wrong with the key raise onionrexceptions.InvalidPubkey(newKey) conn = sqlite3.connect(dbfiles.user_id_info_db, timeout=10) c = conn.cursor() # Get the time we're inserting the key at timeInsert = epoch.get_epoch() # Look at our current keys for duplicate key data or time for entry in self._getForwardKeys(): if entry[0] == newKey: return False if entry[1] == timeInsert: timeInsert += 1 time.sleep(1) # Sleep if our time is the same in order to prevent duplicate time records # Add a forward secrecy key for the peer # Prepare the insert command = (self.publicKey, newKey, timeInsert, timeInsert + expire) c.execute("INSERT INTO forwardKeys VALUES(?, ?, ?, ?);", command) conn.commit() conn.close() return True def decrypt(self, data)- 
Expand source code
def decrypt(self, data): decrypted = onionrcrypto.encryption.pub_key_decrypt(data, self.publicKey, encodedData=True) return decrypted def encrypt(self, data)- 
Expand source code
def encrypt(self, data): encrypted = onionrcrypto.encryption.pub_key_encrypt(data, self.publicKey, encodedData=True) return encrypted def forwardDecrypt(self, encrypted)- 
Expand source code
def forwardDecrypt(self, encrypted): retData = "" for key in self.getGeneratedForwardKeys(False): try: retData = onionrcrypto.encryption.pub_key_decrypt(encrypted, privkey=key[1], encodedData=True) except nacl.exceptions.CryptoError: retData = False else: break else: raise onionrexceptions.DecryptionError("Could not decrypt forward secrecy content") return retData def forwardEncrypt(self, data)- 
Expand source code
def forwardEncrypt(self, data): deleteTheirExpiredKeys(self.publicKey) deleteExpiredKeys() retData = '' forwardKey = self._getLatestForwardKey() if stringvalidators.validate_pub_key(forwardKey[0]): retData = onionrcrypto.encryption.pub_key_encrypt(data, forwardKey[0], encodedData=True) else: raise onionrexceptions.InvalidPubkey("No valid forward secrecy key available for this user") #self.generateForwardKey() return (retData, forwardKey[0], forwardKey[1]) def generateForwardKey(self, expire=604800)- 
Expand source code
def generateForwardKey(self, expire=DEFAULT_KEY_EXPIRE): # Generate a forward secrecy key for the peer conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10) c = conn.cursor() # Prepare the insert time = epoch.get_epoch() newKeys = onionrcrypto.generate() newPub = bytesconverter.bytes_to_str(newKeys[0]) newPriv = bytesconverter.bytes_to_str(newKeys[1]) command = (self.publicKey, newPub, newPriv, time, expire + time) c.execute("INSERT INTO myForwardKeys VALUES(?, ?, ?, ?, ?);", command) conn.commit() conn.close() return newPub def getGeneratedForwardKeys(self, genNew=True)- 
Expand source code
def getGeneratedForwardKeys(self, genNew=True): # Fetch the keys we generated for the peer, that are still around conn = sqlite3.connect(dbfiles.forward_keys_db, timeout=10) c = conn.cursor() pubkey = self.publicKey pubkey = bytesconverter.bytes_to_str(pubkey) command = (pubkey,) keyList = [] # list of tuples containing pub, private for peer for result in c.execute("SELECT * FROM myForwardKeys WHERE peer = ?", command): keyList.append((result[1], result[2])) if len(keyList) == 0: if genNew: self.generateForwardKey() keyList = self.getGeneratedForwardKeys() return list(keyList) def getName(self)- 
Expand source code
def getName(self): retData = 'anonymous' name = keydb.userinfo.get_user_info(self.publicKey, 'name') try: if len(name) > 0: retData = name except ValueError: pass return retData def isFriend(self)- 
Expand source code
def isFriend(self): if keydb.userinfo.set_peer_info(self.publicKey, 'trust') == 1: return True return False def setTrust(self, newTrust)- 
Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate
Expand source code
def setTrust(self, newTrust): '''Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate''' keydb.userinfo.set_user_info(self.publicKey, 'trust', newTrust)