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
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
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})
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])
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, ))
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])
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]')
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
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