onionr/src/onionrblocks/insert/main.py

232 lines
8.7 KiB
Python
Raw Normal View History

2019-11-16 04:18:38 +00:00
"""
Onionr - Private P2P Communication
Create and insert Onionr blocks
"""
"""
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/>.
"""
from typing import Union
2019-07-18 23:07:18 +00:00
import json
2019-11-16 04:18:38 +00:00
from gevent import spawn
2019-07-18 23:07:18 +00:00
from onionrutils import bytesconverter, epoch
import filepaths, onionrstorage
2019-12-27 07:53:18 +00:00
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 _check_upload_queue():
"""Returns the current upload queue len
raises OverflowError if max, false if api not running
"""
max_upload_queue: int = 5000
queue = localcommand.local_command('/gethidden', maxWait=10)
up_queue = False
try:
up_queue = len(queue.splitlines())
except AttributeError:
pass
else:
if up_queue >= max_upload_queue:
raise OverflowError
return up_queue
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
is_offline = True
storage_counter = storagecounter.StorageCounter()
allocationReachedMessage = 'Cannot insert block, disk allocation reached.'
if storage_counter.is_full():
logger.error(allocationReachedMessage)
raise onionrexceptions.DiskAllocationReached
if not _check_upload_queue() is False: is_offline = False
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
2019-11-12 05:32:18 +00:00
our_pub_key = bytesconverter.bytes_to_str(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-12-22 19:42:10 +00:00
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')
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 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
if not disableForward:
fsKey = onionrusers.OnionrUser(asymPeer).generateForwardKey()
meta['newFSKey'] = fsKey
2019-07-18 23:07:18 +00:00
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:
if disableForward:
logger.warn(f'{retData} asym encrypted block created w/o forward secrecy')
2019-07-18 23:07:18 +00:00
# Tell the api server through localCommand to wait for the daemon to upload this block to make statistical analysis more difficult
2020-01-04 12:13:10 +00:00
spawn(
localcommand.local_command,
f'/daemon-event/upload_event',
post=True,
is_json=True,
postData={'block': retData}
).get(timeout=5)
2019-07-20 15:52:03 +00:00
coredb.blockmetadb.add.add_to_block_DB(retData, selfInsert=True, dataSaved=True)
if expire is None:
2019-12-22 19:42:10 +00:00
coredb.blockmetadb.update_block_info(retData, 'expire',
createTime + onionrvalues.DEFAULT_EXPIRE)
else:
coredb.blockmetadb.update_block_info(retData, 'expire', expire)
2019-12-22 19:42:10 +00:00
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)
2020-01-04 12:13:10 +00:00
spawn(
localcommand.local_command,
'/daemon-event/remove_from_insert_queue_wrapper',
post=True,
postData={'block_hash': retData},
is_json=True
2020-01-04 12:13:10 +00:00
).get(timeout=5)
2019-10-04 21:49:35 +00:00
return retData