Onionr/onionr/onionrblocks/insert.py

177 lines
7.4 KiB
Python
Raw Normal View History

from typing import Union
2019-07-18 23:07:18 +00:00
import json
from onionrutils import bytesconverter, epoch
import filepaths, onionrstorage
from . import storagecounter
from onionrplugins import onionrevents as events
2019-07-19 19:49:56 +00:00
from etc import powchoice, onionrvalues
import config, onionrcrypto as crypto, onionrexceptions
2019-07-20 15:52:03 +00:00
from onionrusers import onionrusers
2019-07-21 16:15:20 +00:00
from onionrutils import localcommand, blockmetadata, stringvalidators
2019-07-20 15:52:03 +00:00
import coredb
import onionrproofs
from onionrproofs import subprocesspow
import logger
from onionrtypes import UserIDSecretKey
2019-10-04 21:49:35 +00:00
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]:
"""
2019-07-18 23:07:18 +00:00
Inserts a block into the network
encryptType must be specified to encrypt a block
"""
our_private_key = crypto.priv_key
our_pub_key = crypto.pub_key
2019-11-04 10:52:38 +00:00
if signing_key != '':
# if it was specified to use an alternative private key
our_private_key = signing_key
our_pub_key = crypto.cryptoutils.get_pub_key_from_priv(our_private_key)
2019-07-20 15:52:03 +00:00
use_subprocess = powchoice.use_subprocess(config)
2019-07-18 23:07:18 +00:00
storage_counter = storagecounter.StorageCounter()
allocationReachedMessage = 'Cannot insert block, disk allocation reached.'
if storage_counter.is_full():
2019-07-18 23:07:18 +00:00
logger.error(allocationReachedMessage)
raise onionrexceptions.DiskAllocationReached
2019-07-18 23:07:18 +00:00
retData = False
if type(data) is None:
raise ValueError('Data cannot be none')
createTime = epoch.get_epoch()
2019-07-20 15:52:03 +00:00
dataNonce = bytesconverter.bytes_to_str(crypto.hashers.sha3_hash(data))
2019-07-18 23:07:18 +00:00
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')
2019-10-07 05:51:30 +00:00
#if type(data) is bytes:
# data = data.decode()
#data = str(data)
2019-07-18 23:07:18 +00:00
plaintext = data
plaintextMeta = {}
plaintextPeer = asymPeer
retData = ''
signature = ''
signer = ''
metadata = {}
2019-07-18 23:07:18 +00:00
# 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'):
2019-07-18 23:07:18 +00:00
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 not encryptType in ('', None):
raise onionrexceptions.InvalidMetadata('encryptType must be asym or sym, or blank')
2019-07-18 23:07:18 +00:00
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 != our_pub_key:
2019-07-18 23:07:18 +00:00
try:
forwardEncrypted = onionrusers.OnionrUser(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(asymPeer).generateForwardKey()
#fsKey = onionrusers.OnionrUser(self, asymPeer).getGeneratedForwardKeys().reverse()
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
2019-07-18 23:07:18 +00:00
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':
2019-07-20 15:52:03 +00:00
raise NotImplementedError("not yet implemented")
2019-07-18 23:07:18 +00:00
elif encryptType == 'asym':
if stringvalidators.validate_pub_key(asymPeer):
# Encrypt block data with forward secrecy key first, but not meta
jsonMeta = json.dumps(meta)
2019-07-20 15:52:03 +00:00
jsonMeta = crypto.encryption.pub_key_encrypt(jsonMeta, asymPeer, encodedData=True).decode()
data = crypto.encryption.pub_key_encrypt(data, asymPeer, encodedData=False)#.decode()
2019-07-21 16:15:20 +00:00
signature = crypto.encryption.pub_key_encrypt(signature, asymPeer, encodedData=True).decode()
signer = crypto.encryption.pub_key_encrypt(signer, asymPeer, encodedData=True).decode()
2019-07-18 23:07:18 +00:00
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
2019-07-18 23:07:18 +00:00
metadata['time'] = createTime
# ensure expire is integer and of sane length
if type(expire) is not type(None):
if not len(str(int(expire))) < 20: raise ValueError('expire must be valid int less than 20 digits in length')
2019-07-18 23:07:18 +00:00
metadata['expire'] = expire
# send block data (and metadata) to POW module to get tokenized block data
if use_subprocess:
payload = subprocesspow.SubprocessPOW(data, metadata).start()
else:
payload = onionrproofs.POW(metadata, data).waitForResult()
if payload != False:
try:
2019-07-24 17:22:19 +00:00
retData = onionrstorage.set_data(payload)
2019-07-18 23:07:18 +00:00
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 localcommand.local_command('/ping', maxWait=10) == 'pong!':
if config.get('general.security_level', 1) == 0:
localcommand.local_command('/waitforshare/' + retData, post=True, maxWait=5)
coredb.daemonqueue.daemon_queue_add('uploadBlock', retData)
else:
pass
2019-07-20 15:52:03 +00:00
coredb.blockmetadb.add.add_to_block_DB(retData, selfInsert=True, dataSaved=True)
if expire is None:
coredb.blockmetadb.update_block_info(retData, 'expire', createTime + onionrvalues.DEFAULT_EXPIRE)
else:
coredb.blockmetadb.update_block_info(retData, 'expire', expire)
2019-07-20 15:52:03 +00:00
blockmetadata.process_block_metadata(retData)
2019-07-24 18:23:31 +00:00
2019-07-18 23:07:18 +00:00
if retData != False:
if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS:
2019-07-24 18:23:31 +00:00
events.event('insertdeniable', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': bytesconverter.bytes_to_str(asymPeer)}, threaded = True)
2019-07-18 23:07:18 +00:00
else:
2019-07-24 18:23:31 +00:00
events.event('insertblock', {'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': bytesconverter.bytes_to_str(asymPeer)}, threaded = True)
coredb.daemonqueue.daemon_queue_add('remove_from_insert_list', data=dataNonce)
2019-10-04 21:49:35 +00:00
return retData