Ejemplo n.º 1
0
    def addForwardKey(self, newKey, expire=DEFAULT_KEY_EXPIRE):
        if not self._core._utils.validatePubKey(newKey):
            # Do not add if something went wrong with the key
            raise onionrexceptions.InvalidPubkey(newKey)

        conn = sqlite3.connect(self._core.peerDB, timeout=10)
        c = conn.cursor()

        # Get the time we're inserting the key at
        timeInsert = self._core._utils.getEpoch()

        # 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
Ejemplo n.º 2
0
    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=DATABASE_LOCK_TIMEOUT)
        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
                # Sleep if our time is the same to prevent dupe time records
                time.sleep(1)

        # 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
Ejemplo n.º 3
0
    def draft_message(self, recip=''):
        message = ''
        newLine = ''
        subject = ''
        entering = False
        if len(recip) == 0:
            entering = True
            while entering:
                try:
                    recip = logger.readline(
                        'Enter peer address, or -q to stop:').strip()
                    if recip in ('-q', 'q'):
                        raise EOFError
                    if not self.myCore._utils.validatePubKey(recip):
                        raise onionrexceptions.InvalidPubkey(
                            'Must be a valid ed25519 base32 encoded public key'
                        )
                except onionrexceptions.InvalidPubkey:
                    logger.warn('Invalid public key')
                except (KeyboardInterrupt, EOFError):
                    entering = False
                else:
                    break
            else:
                # if -q or ctrl-c/d, exit function here, otherwise we successfully got the public key
                return
        try:
            subject = logger.readline('Message subject: ')
        except (KeyboardInterrupt, EOFError):
            pass

        cancelEnter = False
        logger.info(
            'Enter your message, stop by entering -q on a new line. -c to cancel'
        )
        while newLine != '-q':
            try:
                newLine = input()
            except (KeyboardInterrupt, EOFError):
                cancelEnter = True
            if newLine == '-c':
                cancelEnter = True
                break
            if newLine == '-q':
                continue
            newLine += '\n'
            message += newLine

        if not cancelEnter:
            logger.info('Inserting encrypted message as Onionr block....')

            blockID = self.myCore.insertBlock(message,
                                              header='pm',
                                              encryptType='asym',
                                              asymPeer=recip,
                                              sign=self.doSigs,
                                              meta={'subject': subject})
Ejemplo n.º 4
0
 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")
     return (retData, forwardKey[0], forwardKey[1])
Ejemplo n.º 5
0
def _processForwardKey(api, myBlock):
    '''
        Get the forward secrecy key specified by the user for us to use
    '''
    peer = onionrusers.OnionrUser(myBlock.signer)
    key = myBlock.getMetadata('newFSKey')

    # We don't need to validate here probably, but it helps
    if stringvalidators.validate_pub_key(key):
        peer.addForwardKey(key)
    else:
        raise onionrexceptions.InvalidPubkey("%s is not a valid pubkey key" %
                                             (key, ))
Ejemplo n.º 6
0
 def forwardEncrypt(self, data):
     deleteTheirExpiredKeys(self._core, self.publicKey)
     deleteExpiredKeys(self._core)
     retData = ''
     forwardKey = self._getLatestForwardKey()
     if self._core._utils.validatePubKey(forwardKey[0]):
         retData = self._core._crypto.pubKeyEncrypt(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])
Ejemplo n.º 7
0
def friend_command(o_inst):
    friend = ''
    try:
        # Get the friend command
        action = sys.argv[2]
    except IndexError:
        logger.info('Syntax: friend add/remove/list [address]')
    else:
        action = action.lower()
        if action == 'list':
            # List out peers marked as our friend
            for friend in onionrusers.OnionrUser.list_friends(
                    o_inst.onionrCore):
                logger.info(friend.publicKey + ' - ' + friend.getName())
        elif action in ('add', 'remove'):
            try:
                friend = sys.argv[3]
                if not o_inst.onionrUtils.validatePubKey(friend):
                    raise onionrexceptions.InvalidPubkey(
                        'Public key is invalid')
                if friend not in o_inst.onionrCore.listPeers():
                    raise onionrexceptions.KeyNotKnown
                friend = onionrusers.OnionrUser(o_inst.onionrCore, friend)
            except IndexError:
                logger.warn('Friend ID is required.')
                action = 'error'  # set to 'error' so that the finally block does not process anything
            except onionrexceptions.KeyNotKnown:
                o_inst.onionrCore.addPeer(friend)
                friend = onionrusers.OnionrUser(o_inst.onionrCore, friend)
            finally:
                if action == 'add':
                    friend.setTrust(1)
                    logger.info('Added %s as friend.' % (friend.publicKey, ))
                elif action == 'remove':
                    friend.setTrust(0)
                    logger.info('Removed %s as friend.' % (friend.publicKey, ))
        else:
            logger.info('Syntax: friend add/remove/list [address]')
Ejemplo n.º 8
0
    def insertBlock(self,
                    data,
                    header='txt',
                    sign=False,
                    encryptType='',
                    symKey='',
                    asymPeer='',
                    meta={},
                    expire=None,
                    disableForward=False):
        '''
            Inserts a block into the network
            encryptType must be specified to encrypt a block
        '''
        allocationReachedMessage = 'Cannot insert block, disk allocation reached.'
        if self._utils.storageCounter.isFull():
            logger.error(allocationReachedMessage)
            return False
        retData = False

        if type(data) is None:
            raise ValueError('Data cannot be none')

        createTime = self._utils.getRoundedEpoch()

        # check nonce
        #print(data)
        dataNonce = self._utils.bytesToStr(self._crypto.sha3Hash(data))
        try:
            with open(self.dataNonceFile, 'r') as nonces:
                if dataNonce in nonces:
                    return retData
        except FileNotFoundError:
            pass
        # record nonce
        with open(self.dataNonceFile, 'a') as nonceFile:
            nonceFile.write(dataNonce + '\n')

        if type(data) is bytes:
            data = data.decode()
        data = str(data)
        plaintext = data
        plaintextMeta = {}
        plaintextPeer = asymPeer

        retData = ''
        signature = ''
        signer = ''
        metadata = {}
        # metadata is full block metadata, meta is internal, user specified metadata

        # only use header if not set in provided meta

        meta['type'] = str(header)

        if encryptType in ('asym', 'sym', ''):
            metadata['encryptType'] = encryptType
        else:
            raise onionrexceptions.InvalidMetadata(
                'encryptType must be asym or sym, or blank')

        try:
            data = data.encode()
        except AttributeError:
            pass

        if encryptType == 'asym':
            meta[
                'rply'] = createTime  # Duplicate the time in encrypted messages to prevent replays
            if not disableForward and sign and asymPeer != self._crypto.pubKey:
                try:
                    forwardEncrypted = onionrusers.OnionrUser(
                        self, asymPeer).forwardEncrypt(data)
                    data = forwardEncrypted[0]
                    meta['forwardEnc'] = True
                    expire = forwardEncrypted[
                        2]  # Expire time of key. no sense keeping block after that
                except onionrexceptions.InvalidPubkey:
                    pass
                    #onionrusers.OnionrUser(self, asymPeer).generateForwardKey()
                fsKey = onionrusers.OnionrUser(self,
                                               asymPeer).generateForwardKey()
                #fsKey = onionrusers.OnionrUser(self, asymPeer).getGeneratedForwardKeys().reverse()
                meta['newFSKey'] = fsKey
        jsonMeta = json.dumps(meta)
        plaintextMeta = jsonMeta
        if sign:
            signature = self._crypto.edSign(jsonMeta.encode() + data,
                                            key=self._crypto.privKey,
                                            encodeResult=True)
            signer = self._crypto.pubKey

        if len(jsonMeta) > 1000:
            raise onionrexceptions.InvalidMetadata(
                'meta in json encoded form must not exceed 1000 bytes')

        user = onionrusers.OnionrUser(self, symKey)

        # encrypt block metadata/sig/content
        if encryptType == 'sym':

            if len(symKey) < self.requirements.passwordLength:
                raise onionrexceptions.SecurityError('Weak encryption key')
            jsonMeta = self._crypto.symmetricEncrypt(
                jsonMeta, key=symKey, returnEncoded=True).decode()
            data = self._crypto.symmetricEncrypt(data,
                                                 key=symKey,
                                                 returnEncoded=True).decode()
            signature = self._crypto.symmetricEncrypt(
                signature, key=symKey, returnEncoded=True).decode()
            signer = self._crypto.symmetricEncrypt(
                signer, key=symKey, returnEncoded=True).decode()
        elif encryptType == 'asym':
            if self._utils.validatePubKey(asymPeer):
                # Encrypt block data with forward secrecy key first, but not meta
                jsonMeta = json.dumps(meta)
                jsonMeta = self._crypto.pubKeyEncrypt(
                    jsonMeta, asymPeer, encodedData=True).decode()
                data = self._crypto.pubKeyEncrypt(data,
                                                  asymPeer,
                                                  encodedData=True).decode()
                signature = self._crypto.pubKeyEncrypt(
                    signature, asymPeer, encodedData=True).decode()
                signer = self._crypto.pubKeyEncrypt(signer,
                                                    asymPeer,
                                                    encodedData=True).decode()
                onionrusers.OnionrUser(self, asymPeer, saveUser=True)
            else:
                raise onionrexceptions.InvalidPubkey(
                    asymPeer + ' is not a valid base32 encoded ed25519 key')

        # compile metadata
        metadata['meta'] = jsonMeta
        metadata['sig'] = signature
        metadata['signer'] = signer
        metadata['time'] = createTime

        # ensure expire is integer and of sane length
        if type(expire) is not type(None):
            assert len(str(int(expire))) < 14
            metadata['expire'] = expire

        # send block data (and metadata) to POW module to get tokenized block data
        if self.use_subprocess:
            payload = subprocesspow.SubprocessPOW(data, metadata, self).start()
        else:
            payload = onionrproofs.POW(metadata, data).waitForResult()
        if payload != False:
            try:
                retData = self.setData(payload)
            except onionrexceptions.DiskAllocationReached:
                logger.error(allocationReachedMessage)
                retData = False
            else:
                # Tell the api server through localCommand to wait for the daemon to upload this block to make statistical analysis more difficult
                if self._utils.localCommand('/ping', maxWait=10) == 'pong!':
                    self._utils.localCommand('/waitforshare/' + retData,
                                             post=True,
                                             maxWait=5)
                    self.daemonQueueAdd('uploadBlock', retData)
                self.addToBlockDB(retData, selfInsert=True, dataSaved=True)
                self._utils.processBlockMetadata(retData)

        if retData != False:
            if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS:
                events.event('insertdeniable', {
                    'content': plaintext,
                    'meta': plaintextMeta,
                    'hash': retData,
                    'peer': self._utils.bytesToStr(asymPeer)
                },
                             onionr=self.onionrInst,
                             threaded=True)
            else:
                events.event('insertblock', {
                    'content': plaintext,
                    'meta': plaintextMeta,
                    'hash': retData,
                    'peer': self._utils.bytesToStr(asymPeer)
                },
                             onionr=self.onionrInst,
                             threaded=True)
        return retData
Ejemplo n.º 9
0
def insert_block(data: Union[str, bytes],
                 header: str = 'txt',
                 sign: bool = False,
                 encryptType: str = '',
                 symKey: str = '',
                 asymPeer: str = '',
                 meta: dict = {},
                 expire: Union[int, None] = None,
                 disableForward: bool = False,
                 signing_key: UserIDSecretKey = '') -> Union[str, bool]:
    """
    Create and insert a block into the network.

    encryptType must be specified to encrypt a block
    if expire is less than date, assumes seconds into future.
        if not assume exact epoch
    """
    our_private_key = crypto.priv_key
    our_pub_key = crypto.pub_key

    storage_counter = storagecounter.StorageCounter()

    allocationReachedMessage = 'Cannot insert block, disk allocation reached.'
    if storage_counter.is_full():
        logger.error(allocationReachedMessage)
        raise onionrexceptions.DiskAllocationReached

    if signing_key != '':
        # if it was specified to use an alternative private key
        our_private_key = signing_key
        our_pub_key = bytesconverter.bytes_to_str(
            crypto.cryptoutils.get_pub_key_from_priv(our_private_key))

    retData = False

    if type(data) is None:
        raise ValueError('Data cannot be none')

    createTime = epoch.get_epoch()

    dataNonce = bytesconverter.bytes_to_str(crypto.hashers.sha3_hash(data))
    try:
        with open(filepaths.data_nonce_file, 'r') as nonces:
            if dataNonce in nonces:
                return retData
    except FileNotFoundError:
        pass
    # record nonce
    with open(filepaths.data_nonce_file, 'a') as nonceFile:
        nonceFile.write(dataNonce + '\n')

    plaintext = data
    plaintextMeta = {}
    plaintextPeer = asymPeer

    retData = ''
    signature = ''
    signer = ''
    metadata = {}

    # metadata is full block metadata
    # meta is internal, user specified metadata

    # only use header if not set in provided meta

    meta['type'] = str(header)

    if encryptType in ('asym', 'sym'):
        metadata['encryptType'] = encryptType
    else:
        if not config.get('general.store_plaintext_blocks', True):
            raise onionrexceptions.InvalidMetadata(
                "Plaintext blocks are disabled, " +
                "yet a plaintext block was being inserted")
        if encryptType not in ('', None):
            raise onionrexceptions.InvalidMetadata(
                'encryptType must be asym or sym, or blank')

    try:
        data = data.encode()
    except AttributeError:
        pass

    if encryptType == 'asym':
        # Duplicate the time in encrypted messages to help prevent replays
        meta['rply'] = createTime
        if sign and asymPeer != our_pub_key:
            try:
                forwardEncrypted = onionrusers.OnionrUser(
                    asymPeer).forwardEncrypt(data)
                data = forwardEncrypted[0]
                meta['forwardEnc'] = True
                # Expire time of key. no sense keeping block after that
                expire = forwardEncrypted[2]
            except onionrexceptions.InvalidPubkey:
                pass
            if not disableForward:
                fsKey = onionrusers.OnionrUser(asymPeer).generateForwardKey()
                meta['newFSKey'] = fsKey
    jsonMeta = json.dumps(meta)
    plaintextMeta = jsonMeta
    if sign:
        signature = crypto.signing.ed_sign(jsonMeta.encode() + data,
                                           key=our_private_key,
                                           encodeResult=True)
        signer = our_pub_key

    if len(jsonMeta) > 1000:
        raise onionrexceptions.InvalidMetadata(
            'meta in json encoded form must not exceed 1000 bytes')

    # encrypt block metadata/sig/content
    if encryptType == 'sym':
        raise NotImplementedError("not yet implemented")
    elif encryptType == 'asym':
        if stringvalidators.validate_pub_key(asymPeer):
            # Encrypt block data with forward secrecy key first, but not meta
            jsonMeta = json.dumps(meta)
            jsonMeta = crypto.encryption.pub_key_encrypt(
                jsonMeta, asymPeer, encodedData=True).decode()
            data = crypto.encryption.pub_key_encrypt(data,
                                                     asymPeer,
                                                     encodedData=False)
            signature = crypto.encryption.pub_key_encrypt(
                signature, asymPeer, encodedData=True).decode()
            signer = crypto.encryption.pub_key_encrypt(
                signer, asymPeer, encodedData=True).decode()
            try:
                onionrusers.OnionrUser(asymPeer, saveUser=True)
            except ValueError:
                # if peer is already known
                pass
        else:
            raise onionrexceptions.InvalidPubkey(
                asymPeer + ' is not a valid base32 encoded ed25519 key')

    # compile metadata
    metadata['meta'] = jsonMeta
    if len(signature) > 0:  # I don't like not pattern
        metadata['sig'] = signature
        metadata['signer'] = signer
    metadata['time'] = createTime

    # ensure expire is integer and of sane length
    if type(expire) is not type(None):  # noqa
        if not len(str(int(expire))) < 20:
            raise ValueError(
                'expire must be valid int less than 20 digits in length')
        # if expire is less than date, assume seconds into future
        if expire < epoch.get_epoch():
            expire = epoch.get_epoch() + expire
        metadata['expire'] = expire

    # send block data (and metadata) to POW module to get tokenized block data
    payload = subprocesspow.SubprocessPOW(data, metadata).start()
    if payload != False:  # noqa
        try:
            retData = onionrstorage.set_data(payload)
        except onionrexceptions.DiskAllocationReached:
            logger.error(allocationReachedMessage)
            retData = False
        else:
            if disableForward:
                logger.warn(
                    f'{retData} asym encrypted block created w/o ephemerality')
            """
            Tell the api server through localCommand to wait for the daemon to
            upload this block to make statistical analysis more difficult
            """
            spawn(localcommand.local_command,
                  '/daemon-event/upload_event',
                  post=True,
                  is_json=True,
                  post_data={
                      'block': retData
                  }).get(timeout=5)
            coredb.blockmetadb.add.add_to_block_DB(retData,
                                                   selfInsert=True,
                                                   dataSaved=True)

            if expire is None:
                coredb.blockmetadb.update_block_info(
                    retData, 'expire', createTime + min(
                        onionrvalues.DEFAULT_EXPIRE,
                        config.get('general.max_block_age',
                                   onionrvalues.DEFAULT_EXPIRE)))
            else:
                coredb.blockmetadb.update_block_info(retData, 'expire', expire)

            blockmetadata.process_block_metadata(retData)

    if retData != False:  # noqa
        if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS:
            events.event('insertdeniable', {
                'content': plaintext,
                'meta': plaintextMeta,
                'hash': retData,
                'peer': bytesconverter.bytes_to_str(asymPeer)
            },
                         threaded=True)
        else:
            events.event('insertblock', {
                'content': plaintext,
                'meta': plaintextMeta,
                'hash': retData,
                'peer': bytesconverter.bytes_to_str(asymPeer)
            },
                         threaded=True)

    spawn(localcommand.local_command,
          '/daemon-event/remove_from_insert_queue_wrapper',
          post=True,
          post_data={
              'block_hash': retData
          },
          is_json=True).get(timeout=5)
    return retData