Module src.onionrutils.validatemetadata
Onionr - Private P2P Communication
validate new block's metadata
Expand source code
"""
    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/>.
"""
import json
import logger, onionrexceptions
from etc import onionrvalues
from . import stringvalidators, epoch, bytesconverter
import config, filepaths, onionrcrypto
def validate_metadata(metadata, block_data) -> bool:
    """Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string"""
    ret_data = False
    max_clock_difference = onionrvalues.MAX_BLOCK_CLOCK_SKEW
    # 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
    maxAge = min(config.get("general.max_block_age", onionrvalues.DEFAULT_EXPIRE), onionrvalues.DEFAULT_EXPIRE)
    if type(metadata) is dict:
        for i in metadata:
            try:
                onionrvalues.BLOCK_METADATA_LENGTHS[i]
            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))
                if onionrvalues.BLOCK_METADATA_LENGTHS[i] < testData:
                    logger.warn('Block metadata key ' + i + ' exceeded maximum size')
                    break
            if i == 'time':
                if not stringvalidators.is_integer_string(metadata[i]):
                    logger.warn('Block metadata time stamp is not integer string or int')
                    break
                isFuture = (metadata[i] - epoch.get_epoch())
                if isFuture > max_clock_difference:
                    logger.warn('Block timestamp is skewed to the future over the max %s: %s', (max_clock_difference, isFuture))
                    break
                if (epoch.get_epoch() - metadata[i]) > maxAge:
                    logger.warn('Block is outdated: %s' % (metadata[i],))
                    break
            elif i == 'expire':
                try:
                    if not int(metadata[i]) > epoch.get_epoch(): raise ValueError
                except ValueError:
                    logger.warn('Block is expired: %s less than %s' % (metadata[i], epoch.get_epoch()))
                    break
            elif i == 'encryptType':
                try:
                    if not metadata[i] in ('asym', 'sym', ''): raise ValueError
                    if not config.get('general.store_plaintext_blocks', True):
                        break
                except ValueError:
                    logger.warn('Invalid encryption mode')
                    break
            elif i == 'sig':
                try:
                    metadata['encryptType']
                except KeyError:
                    signer = metadata['signer']
                    sig = metadata['sig']
                    encodedMeta = bytesconverter.str_to_bytes(metadata['meta'])
                    encodedBlock = bytesconverter.str_to_bytes(block_data)
                    if not onionrcrypto.signing.ed_verify(encodedMeta + encodedBlock[1:], signer, sig):
                        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)
            # Make sure time is set (validity was checked above if it is)
            try:
                metadata['time']
            except KeyError:
                return False
            nonce = bytesconverter.bytes_to_str(onionrcrypto.hashers.sha3_hash(block_data))
            try:
                with open(filepaths.data_nonce_file, 'r') as nonceFile:
                    if nonce in nonceFile.read():
                        # we've seen that nonce before, so we can't pass metadata
                        raise onionrexceptions.DataExists
            except FileNotFoundError:
                ret_data = True
            except onionrexceptions.DataExists:
                # do not set ret_data to True, because data has been seen before
                logger.warn(f'{nonce} seen before')
                raise onionrexceptions.DataExists
            else:
                ret_data = True
    else:
        logger.warn('In call to utils.validateMetadata, metadata must be JSON string or a dictionary object')
    return ret_data
Functions
def validate_metadata(metadata, block_data)- 
Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string
Expand source code
def validate_metadata(metadata, block_data) -> bool: """Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string""" ret_data = False max_clock_difference = onionrvalues.MAX_BLOCK_CLOCK_SKEW # 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 maxAge = min(config.get("general.max_block_age", onionrvalues.DEFAULT_EXPIRE), onionrvalues.DEFAULT_EXPIRE) if type(metadata) is dict: for i in metadata: try: onionrvalues.BLOCK_METADATA_LENGTHS[i] 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)) if onionrvalues.BLOCK_METADATA_LENGTHS[i] < testData: logger.warn('Block metadata key ' + i + ' exceeded maximum size') break if i == 'time': if not stringvalidators.is_integer_string(metadata[i]): logger.warn('Block metadata time stamp is not integer string or int') break isFuture = (metadata[i] - epoch.get_epoch()) if isFuture > max_clock_difference: logger.warn('Block timestamp is skewed to the future over the max %s: %s', (max_clock_difference, isFuture)) break if (epoch.get_epoch() - metadata[i]) > maxAge: logger.warn('Block is outdated: %s' % (metadata[i],)) break elif i == 'expire': try: if not int(metadata[i]) > epoch.get_epoch(): raise ValueError except ValueError: logger.warn('Block is expired: %s less than %s' % (metadata[i], epoch.get_epoch())) break elif i == 'encryptType': try: if not metadata[i] in ('asym', 'sym', ''): raise ValueError if not config.get('general.store_plaintext_blocks', True): break except ValueError: logger.warn('Invalid encryption mode') break elif i == 'sig': try: metadata['encryptType'] except KeyError: signer = metadata['signer'] sig = metadata['sig'] encodedMeta = bytesconverter.str_to_bytes(metadata['meta']) encodedBlock = bytesconverter.str_to_bytes(block_data) if not onionrcrypto.signing.ed_verify(encodedMeta + encodedBlock[1:], signer, sig): 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) # Make sure time is set (validity was checked above if it is) try: metadata['time'] except KeyError: return False nonce = bytesconverter.bytes_to_str(onionrcrypto.hashers.sha3_hash(block_data)) try: with open(filepaths.data_nonce_file, 'r') as nonceFile: if nonce in nonceFile.read(): # we've seen that nonce before, so we can't pass metadata raise onionrexceptions.DataExists except FileNotFoundError: ret_data = True except onionrexceptions.DataExists: # do not set ret_data to True, because data has been seen before logger.warn(f'{nonce} seen before') raise onionrexceptions.DataExists else: ret_data = True else: logger.warn('In call to utils.validateMetadata, metadata must be JSON string or a dictionary object') return ret_data