2019-06-26 20:13:36 +00:00
|
|
|
'''
|
|
|
|
Onionr - Private P2P Communication
|
|
|
|
|
|
|
|
validate new block's metadata
|
|
|
|
'''
|
|
|
|
'''
|
|
|
|
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-06-23 07:00:27 +00:00
|
|
|
import json
|
|
|
|
import logger, onionrexceptions
|
|
|
|
from etc import onionrvalues
|
2019-06-26 00:15:04 +00:00
|
|
|
from onionrutils import stringvalidators, epoch, bytesconverter
|
2019-07-20 00:01:16 +00:00
|
|
|
import config, filepaths, onionrcrypto
|
2019-07-19 19:49:56 +00:00
|
|
|
def validate_metadata(metadata, blockData):
|
2019-06-23 07:00:27 +00:00
|
|
|
'''Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string'''
|
|
|
|
# TODO, make this check sane sizes
|
2019-07-19 19:49:56 +00:00
|
|
|
crypto = onionrcrypto.OnionrCrypto()
|
|
|
|
requirements = onionrvalues.OnionrValues()
|
2019-06-23 07:00:27 +00:00
|
|
|
retData = False
|
|
|
|
maxClockDifference = 120
|
|
|
|
|
|
|
|
# convert to dict if it is json string
|
|
|
|
if type(metadata) is str:
|
|
|
|
try:
|
|
|
|
metadata = json.loads(metadata)
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Validate metadata dict for invalid keys to sizes that are too large
|
2019-07-19 19:49:56 +00:00
|
|
|
maxAge = config.get("general.max_block_age", onionrvalues.OnionrValues().default_expire)
|
2019-06-23 07:00:27 +00:00
|
|
|
if type(metadata) is dict:
|
|
|
|
for i in metadata:
|
|
|
|
try:
|
2019-07-19 19:49:56 +00:00
|
|
|
requirements.blockMetadataLengths[i]
|
2019-06-23 07:00:27 +00:00
|
|
|
except KeyError:
|
|
|
|
logger.warn('Block has invalid metadata key ' + i)
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
testData = metadata[i]
|
|
|
|
try:
|
|
|
|
testData = len(testData)
|
|
|
|
except (TypeError, AttributeError) as e:
|
|
|
|
testData = len(str(testData))
|
2019-07-19 19:49:56 +00:00
|
|
|
if requirements.blockMetadataLengths[i] < testData:
|
2019-06-23 07:00:27 +00:00
|
|
|
logger.warn('Block metadata key ' + i + ' exceeded maximum size')
|
|
|
|
break
|
|
|
|
if i == 'time':
|
2019-06-23 17:41:07 +00:00
|
|
|
if not stringvalidators.is_integer_string(metadata[i]):
|
2019-06-23 07:00:27 +00:00
|
|
|
logger.warn('Block metadata time stamp is not integer string or int')
|
|
|
|
break
|
2019-06-25 23:07:35 +00:00
|
|
|
isFuture = (metadata[i] - epoch.get_epoch())
|
2019-06-23 07:00:27 +00:00
|
|
|
if isFuture > maxClockDifference:
|
|
|
|
logger.warn('Block timestamp is skewed to the future over the max %s: %s' (maxClockDifference, isFuture))
|
|
|
|
break
|
2019-06-25 23:07:35 +00:00
|
|
|
if (epoch.get_epoch() - metadata[i]) > maxAge:
|
2019-06-23 07:00:27 +00:00
|
|
|
logger.warn('Block is outdated: %s' % (metadata[i],))
|
|
|
|
break
|
|
|
|
elif i == 'expire':
|
|
|
|
try:
|
2019-06-25 23:07:35 +00:00
|
|
|
assert int(metadata[i]) > epoch.get_epoch()
|
2019-06-23 07:00:27 +00:00
|
|
|
except AssertionError:
|
2019-06-25 23:07:35 +00:00
|
|
|
logger.warn('Block is expired: %s less than %s' % (metadata[i], epoch.get_epoch()))
|
2019-06-23 07:00:27 +00:00
|
|
|
break
|
|
|
|
elif i == 'encryptType':
|
|
|
|
try:
|
|
|
|
assert metadata[i] in ('asym', 'sym', '')
|
|
|
|
except AssertionError:
|
|
|
|
logger.warn('Invalid encryption mode')
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
# if metadata loop gets no errors, it does not break, therefore metadata is valid
|
|
|
|
# make sure we do not have another block with the same data content (prevent data duplication and replay attacks)
|
2019-07-19 19:49:56 +00:00
|
|
|
nonce = bytesconverter.bytes_to_str(crypto.sha3Hash(blockData))
|
2019-06-23 07:00:27 +00:00
|
|
|
try:
|
2019-07-19 19:49:56 +00:00
|
|
|
with open(filepaths.data_nonce_file, 'r') as nonceFile:
|
2019-06-23 07:00:27 +00:00
|
|
|
if nonce in nonceFile.read():
|
|
|
|
retData = False # we've seen that nonce before, so we can't pass metadata
|
|
|
|
raise onionrexceptions.DataExists
|
|
|
|
except FileNotFoundError:
|
|
|
|
retData = True
|
|
|
|
except onionrexceptions.DataExists:
|
|
|
|
# do not set retData to True, because nonce has been seen before
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
retData = True
|
|
|
|
else:
|
|
|
|
logger.warn('In call to utils.validateMetadata, metadata must be JSON string or a dictionary object')
|
|
|
|
|
|
|
|
return retData
|