# 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)