Dmitri Bogomolov
c1ca7044d2
not addresses, where it's supposed to be because it uses pyelliptic.arithmetic, addresses.decodeBase58() returns int which needs to be encoded. Defined encodeWalletImportFormat() and replaced all uses.
234 lines
8.1 KiB
Python
234 lines
8.1 KiB
Python
"""
|
|
Some shared functions
|
|
|
|
.. deprecated:: 0.6.3
|
|
Should be moved to different places and this file removed,
|
|
but it needs refactoring.
|
|
"""
|
|
from __future__ import division
|
|
|
|
# Libraries.
|
|
import hashlib
|
|
import os
|
|
import stat
|
|
import subprocess # nosec B404
|
|
import sys
|
|
from binascii import hexlify
|
|
|
|
# Project imports.
|
|
import highlevelcrypto
|
|
import state
|
|
from addresses import decodeAddress, encodeVarint
|
|
from bmconfigparser import config
|
|
from debug import logger
|
|
from helper_sql import sqlQuery
|
|
|
|
|
|
myECCryptorObjects = {}
|
|
MyECSubscriptionCryptorObjects = {}
|
|
# The key in this dictionary is the RIPE hash which is encoded
|
|
# in an address and value is the address itself.
|
|
myAddressesByHash = {}
|
|
# The key in this dictionary is the tag generated from the address.
|
|
myAddressesByTag = {}
|
|
broadcastSendersForWhichImWatching = {}
|
|
|
|
|
|
def isAddressInMyAddressBook(address):
|
|
"""Is address in my addressbook?"""
|
|
queryreturn = sqlQuery(
|
|
'''select address from addressbook where address=?''',
|
|
address)
|
|
return queryreturn != []
|
|
|
|
|
|
# At this point we should really just have a isAddressInMy(book, address)...
|
|
def isAddressInMySubscriptionsList(address):
|
|
"""Am I subscribed to this address?"""
|
|
queryreturn = sqlQuery(
|
|
'''select * from subscriptions where address=?''',
|
|
str(address))
|
|
return queryreturn != []
|
|
|
|
|
|
def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address):
|
|
"""
|
|
Am I subscribed to this address, is it in my addressbook or whitelist?
|
|
"""
|
|
if isAddressInMyAddressBook(address):
|
|
return True
|
|
|
|
queryreturn = sqlQuery(
|
|
'''SELECT address FROM whitelist where address=?'''
|
|
''' and enabled = '1' ''',
|
|
address)
|
|
if queryreturn != []:
|
|
return True
|
|
|
|
queryreturn = sqlQuery(
|
|
'''select address from subscriptions where address=?'''
|
|
''' and enabled = '1' ''',
|
|
address)
|
|
if queryreturn != []:
|
|
return True
|
|
return False
|
|
|
|
|
|
def reloadMyAddressHashes():
|
|
"""Reload keys for user's addresses from the config file"""
|
|
logger.debug('reloading keys from keys.dat file')
|
|
myECCryptorObjects.clear()
|
|
myAddressesByHash.clear()
|
|
myAddressesByTag.clear()
|
|
# myPrivateKeys.clear()
|
|
|
|
keyfileSecure = checkSensitiveFilePermissions(os.path.join(
|
|
state.appdata, 'keys.dat'))
|
|
hasEnabledKeys = False
|
|
for addressInKeysFile in config.addresses():
|
|
isEnabled = config.getboolean(addressInKeysFile, 'enabled')
|
|
if not isEnabled:
|
|
continue
|
|
|
|
hasEnabledKeys = True
|
|
|
|
addressVersionNumber, streamNumber, hashobj = decodeAddress(
|
|
addressInKeysFile)[1:]
|
|
if addressVersionNumber not in (2, 3, 4):
|
|
logger.error(
|
|
'Error in reloadMyAddressHashes: Can\'t handle'
|
|
' address versions other than 2, 3, or 4.')
|
|
continue
|
|
|
|
# Returns a simple 32 bytes of information encoded in 64 Hex characters
|
|
try:
|
|
privEncryptionKey = hexlify(
|
|
highlevelcrypto.decodeWalletImportFormat(
|
|
config.get(addressInKeysFile, 'privencryptionkey')
|
|
))
|
|
except ValueError:
|
|
logger.error(
|
|
'Error in reloadMyAddressHashes: failed to decode'
|
|
' one of the private keys for address %s', addressInKeysFile)
|
|
continue
|
|
# It is 32 bytes encoded as 64 hex characters
|
|
if len(privEncryptionKey) == 64:
|
|
myECCryptorObjects[hashobj] = \
|
|
highlevelcrypto.makeCryptor(privEncryptionKey)
|
|
myAddressesByHash[hashobj] = addressInKeysFile
|
|
tag = hashlib.sha512(hashlib.sha512(
|
|
encodeVarint(addressVersionNumber)
|
|
+ encodeVarint(streamNumber) + hashobj).digest()).digest()[32:]
|
|
myAddressesByTag[tag] = addressInKeysFile
|
|
|
|
if not keyfileSecure:
|
|
fixSensitiveFilePermissions(os.path.join(
|
|
state.appdata, 'keys.dat'), hasEnabledKeys)
|
|
|
|
|
|
def reloadBroadcastSendersForWhichImWatching():
|
|
"""
|
|
Reinitialize runtime data for the broadcasts I'm subscribed to
|
|
from the config file
|
|
"""
|
|
broadcastSendersForWhichImWatching.clear()
|
|
MyECSubscriptionCryptorObjects.clear()
|
|
queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1')
|
|
logger.debug('reloading subscriptions...')
|
|
for row in queryreturn:
|
|
address, = row
|
|
# status
|
|
addressVersionNumber, streamNumber, hashobj = decodeAddress(address)[1:]
|
|
if addressVersionNumber == 2:
|
|
broadcastSendersForWhichImWatching[hashobj] = 0
|
|
# Now, for all addresses, even version 2 addresses,
|
|
# we should create Cryptor objects in a dictionary which we will
|
|
# use to attempt to decrypt encrypted broadcast messages.
|
|
|
|
if addressVersionNumber <= 3:
|
|
privEncryptionKey = hashlib.sha512(
|
|
encodeVarint(addressVersionNumber)
|
|
+ encodeVarint(streamNumber) + hashobj
|
|
).digest()[:32]
|
|
MyECSubscriptionCryptorObjects[hashobj] = \
|
|
highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))
|
|
else:
|
|
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(
|
|
encodeVarint(addressVersionNumber)
|
|
+ encodeVarint(streamNumber) + hashobj
|
|
).digest()).digest()
|
|
tag = doubleHashOfAddressData[32:]
|
|
privEncryptionKey = doubleHashOfAddressData[:32]
|
|
MyECSubscriptionCryptorObjects[tag] = \
|
|
highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))
|
|
|
|
|
|
def fixPotentiallyInvalidUTF8Data(text):
|
|
"""Sanitise invalid UTF-8 strings"""
|
|
try:
|
|
text.decode('utf-8')
|
|
return text
|
|
except UnicodeDecodeError:
|
|
return 'Part of the message is corrupt. The message cannot be' \
|
|
' displayed the normal way.\n\n' + repr(text)
|
|
|
|
|
|
def checkSensitiveFilePermissions(filename):
|
|
"""
|
|
:param str filename: path to the file
|
|
:return: True if file appears to have appropriate permissions.
|
|
"""
|
|
if sys.platform == 'win32':
|
|
# .. todo:: This might deserve extra checks by someone familiar with
|
|
# Windows systems.
|
|
return True
|
|
elif sys.platform[:7] == 'freebsd':
|
|
# FreeBSD file systems are the same as major Linux file systems
|
|
present_permissions = os.stat(filename)[0]
|
|
disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO
|
|
return present_permissions & disallowed_permissions == 0
|
|
try:
|
|
# Skip known problems for non-Win32 filesystems
|
|
# without POSIX permissions.
|
|
fstype = subprocess.check_output(
|
|
['/usr/bin/stat', '-f', '-c', '%T', filename],
|
|
stderr=subprocess.STDOUT
|
|
) # nosec B603
|
|
if 'fuseblk' in fstype:
|
|
logger.info(
|
|
'Skipping file permissions check for %s.'
|
|
' Filesystem fuseblk detected.', filename)
|
|
return True
|
|
except: # noqa:E722
|
|
# Swallow exception here, but we might run into trouble later!
|
|
logger.error('Could not determine filesystem type. %s', filename)
|
|
present_permissions = os.stat(filename)[0]
|
|
disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO
|
|
return present_permissions & disallowed_permissions == 0
|
|
|
|
|
|
# Fixes permissions on a sensitive file.
|
|
def fixSensitiveFilePermissions(filename, hasEnabledKeys):
|
|
"""Try to change file permissions to be more restrictive"""
|
|
if hasEnabledKeys:
|
|
logger.warning(
|
|
'Keyfile had insecure permissions, and there were enabled'
|
|
' keys. The truly paranoid should stop using them immediately.')
|
|
else:
|
|
logger.warning(
|
|
'Keyfile had insecure permissions, but there were no enabled keys.'
|
|
)
|
|
try:
|
|
present_permissions = os.stat(filename)[0]
|
|
disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO
|
|
allowed_permissions = ((1 << 32) - 1) ^ disallowed_permissions
|
|
new_permissions = (
|
|
allowed_permissions & present_permissions)
|
|
os.chmod(filename, new_permissions)
|
|
|
|
logger.info('Keyfile permissions automatically fixed.')
|
|
|
|
except Exception:
|
|
logger.exception('Keyfile permissions could not be fixed.')
|
|
raise
|