Onionr/src/onionrblocks/insert/main.py

270 lines
9.5 KiB
Python
Raw Normal View History

"""Onionr - Private P2P Communication.
2019-11-16 04:18:38 +00:00
Create and insert Onionr blocks
2019-11-16 04:18:38 +00:00
"""
from typing import Union
import ujson as 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
import onionrstorage
2019-12-27 07:53:18 +00:00
from .. import storagecounter
from onionrplugins import onionrevents as events
from etc import onionrvalues
import config
import onionrcrypto as crypto
import 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
from onionrproofs import subprocesspow
import logger
from onionrtypes import UserIDSecretKey
"""
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/>.
"""
2019-10-04 21:49:35 +00:00
def _check_upload_queue():
"""
Return the current upload queue len.
raises OverflowError if max, false if api not running
"""
max_upload_queue: int = 5000
2020-07-17 18:49:18 +00:00
queue = localcommand.local_command('/gethidden', max_wait=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]:
"""
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
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 = bytesconverter.bytes_to_str(
crypto.cryptoutils.get_pub_key_from_priv(our_private_key))
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 = {}
# metadata is full block metadata
# meta is internal, user specified metadata
2019-07-18 23:07:18 +00:00
# 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 encryptType not 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':
# Duplicate the time in encrypted messages to help prevent replays
meta['rply'] = createTime
if sign and asymPeer != our_pub_key:
2019-07-18 23:07:18 +00:00
try:
forwardEncrypted = onionrusers.OnionrUser(
asymPeer).forwardEncrypt(data)
2019-07-18 23:07:18 +00:00
data = forwardEncrypted[0]
meta['forwardEnc'] = True
# Expire time of key. no sense keeping block after that
expire = forwardEncrypted[2]
2019-07-18 23:07:18 +00:00
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')
2019-07-18 23:07:18 +00:00
# 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)
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()
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')
2019-07-18 23:07:18 +00:00
# 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): # noqa
if not len(str(int(expire))) < 20:
raise ValueError(
'expire must be valid int less than 20 digits in length')
2020-03-03 11:55:50 +00:00
# if expire is less than date, assume seconds into future
if expire < epoch.get_epoch():
expire = epoch.get_epoch() + expire
2019-07-18 23:07:18 +00:00
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
2019-07-18 23:07:18 +00:00
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 ephemerality')
"""
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,
'/daemon-event/upload_event',
2020-01-04 12:13:10 +00:00
post=True,
is_json=True,
2020-07-17 18:49:18 +00:00
post_data={'block': retData}
2020-01-04 12:13:10 +00:00
).get(timeout=5)
coredb.blockmetadb.add.add_to_block_DB(
retData, selfInsert=True, dataSaved=True)
if expire is None:
2020-03-03 11:55:50 +00:00
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)
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
if retData != False: # noqa
2019-07-18 23:07:18 +00:00
if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS:
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:
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,
post_data={'block_hash':
bytesconverter.bytes_to_str(crypto.hashers.sha3_hash(data))},
is_json=True
2020-01-04 12:13:10 +00:00
).get(timeout=5)
2019-10-04 21:49:35 +00:00
return retData