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 / > .
"""
2019-09-08 09:48:16 +00:00
from typing import Union
2020-04-03 09:02:36 +00:00
import ujson as json
2019-11-16 04:18:38 +00:00
2020-01-03 10:17:00 +00:00
from gevent import spawn
2019-07-18 23:07:18 +00:00
from onionrutils import bytesconverter , epoch
2019-09-21 23:49:24 +00:00
import filepaths , onionrstorage
2019-12-27 07:53:18 +00:00
from . . import storagecounter
2019-09-21 22:45:46 +00:00
from onionrplugins import onionrevents as events
2019-07-19 19:49:56 +00:00
from etc import powchoice , onionrvalues
2019-09-23 23:00:50 +00:00
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
2019-08-28 02:46:33 +00:00
import onionrproofs
2019-09-23 23:00:50 +00:00
from onionrproofs import subprocesspow
2019-09-08 09:48:16 +00:00
import logger
2019-11-04 06:31:13 +00:00
from onionrtypes import UserIDSecretKey
2019-10-04 21:49:35 +00:00
2019-11-27 19:39:48 +00:00
def _check_upload_queue ( ) :
""" Returns the current upload queue len
2019-11-27 20:07:51 +00:00
raises OverflowError if max , false if api not running
2019-11-27 19:39:48 +00:00
"""
max_upload_queue : int = 5000
queue = localcommand . local_command ( ' /gethidden ' , maxWait = 10 )
2019-11-27 20:07:51 +00:00
up_queue = False
2019-11-27 19:39:48 +00:00
2019-11-27 19:53:06 +00:00
try :
up_queue = len ( queue . splitlines ( ) )
except AttributeError :
pass
else :
if up_queue > = max_upload_queue :
raise OverflowError
2019-11-27 19:39:48 +00:00
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-09-08 09:48:16 +00:00
"""
2019-07-18 23:07:18 +00:00
Inserts a block into the network
encryptType must be specified to encrypt a block
2020-03-03 11:55:50 +00:00
if expire is less than date , assumes seconds into future .
if not assume exact epoch
2019-09-08 09:48:16 +00:00
"""
2019-11-04 06:31:13 +00:00
our_private_key = crypto . priv_key
our_pub_key = crypto . pub_key
2019-11-27 19:39:48 +00:00
is_offline = True
2019-11-27 20:07:51 +00:00
storage_counter = storagecounter . StorageCounter ( )
allocationReachedMessage = ' Cannot insert block, disk allocation reached. '
if storage_counter . is_full ( ) :
logger . error ( allocationReachedMessage )
raise onionrexceptions . DiskAllocationReached
2019-11-27 19:39:48 +00:00
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
2019-11-04 06:31:13 +00:00
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-11-04 06:31:13 +00:00
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-11-04 06:31:13 +00:00
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 )
2019-08-27 08:47:22 +00:00
if encryptType in ( ' asym ' , ' sym ' ) :
2019-07-18 23:07:18 +00:00
metadata [ ' encryptType ' ] = encryptType
else :
2019-09-28 01:38:47 +00:00
if not config . get ( ' general.store_plaintext_blocks ' , True ) : raise onionrexceptions . InvalidMetadata ( " Plaintext blocks are disabled, yet a plaintext block was being inserted " )
2019-08-27 08:47:22 +00:00
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
2020-02-17 12:13:57 +00:00
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
2020-02-17 12:13:57 +00:00
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 :
2019-11-04 06:31:13 +00:00
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 ( )
2019-09-10 20:25:50 +00:00
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
2019-11-27 19:39:48 +00:00
if len ( signature ) > 0 : # I don't like not pattern
2019-09-10 06:05:59 +00:00
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 ) :
2019-09-09 00:21:36 +00:00
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
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 :
2020-02-17 12:13:57 +00:00
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 )
2019-08-18 05:27:33 +00:00
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 ) ) )
2019-08-18 05:27:33 +00:00
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
2020-01-03 10:17:00 +00:00
spawn (
localcommand . local_command ,
' /daemon-event/remove_from_insert_queue_wrapper ' ,
2020-01-06 12:06:27 +00:00
post = True ,
2020-01-07 00:40:44 +00:00
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