shared.py
1 """ 2 Some shared functions 3 4 .. deprecated:: 0.6.3 5 Should be moved to different places and this file removed, 6 but it needs refactoring. 7 """ 8 9 10 # Libraries. 11 import hashlib 12 import os 13 import stat 14 import subprocess # nosec B404 15 import sys 16 from binascii import hexlify 17 18 from six.moves.reprlib import repr 19 20 # Project imports. 21 from . import highlevelcrypto 22 from . import state 23 from .addresses import decodeAddress, encodeVarint 24 from .bmconfigparser import config 25 from .debug import logger 26 from .helper_sql import sqlQuery 27 28 myECCryptorObjects = {} 29 MyECSubscriptionCryptorObjects = {} 30 # The key in this dictionary is the RIPE hash which is encoded 31 # in an address and value is the address itself. 32 myAddressesByHash = {} 33 # The key in this dictionary is the tag generated from the address. 34 myAddressesByTag = {} 35 broadcastSendersForWhichImWatching = {} 36 37 38 def isAddressInMyAddressBook(address): 39 """Is address in my addressbook?""" 40 queryreturn = sqlQuery( 41 '''select address from addressbook where address=?''', 42 address) 43 return queryreturn != [] 44 45 46 # At this point we should really just have a isAddressInMy(book, address)... 47 def isAddressInMySubscriptionsList(address): 48 """Am I subscribed to this address?""" 49 queryreturn = sqlQuery( 50 '''select * from subscriptions where address=?''', 51 str(address)) 52 return queryreturn != [] 53 54 55 def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address): 56 """ 57 Am I subscribed to this address, is it in my addressbook or whitelist? 58 """ 59 if isAddressInMyAddressBook(address): 60 return True 61 62 queryreturn = sqlQuery( 63 '''SELECT address FROM whitelist where address=?''' 64 ''' and enabled = '1' ''', 65 address) 66 if queryreturn != []: 67 return True 68 69 queryreturn = sqlQuery( 70 '''select address from subscriptions where address=?''' 71 ''' and enabled = '1' ''', 72 address) 73 if queryreturn != []: 74 return True 75 return False 76 77 78 def reloadMyAddressHashes(): 79 """Reload keys for user's addresses from the config file""" 80 logger.debug('reloading keys from keys.dat file') 81 myECCryptorObjects.clear() 82 myAddressesByHash.clear() 83 myAddressesByTag.clear() 84 # myPrivateKeys.clear() 85 86 keyfileSecure = checkSensitiveFilePermissions(os.path.join( 87 state.appdata, 'keys.dat')) 88 hasEnabledKeys = False 89 for addressInKeysFile in config.addresses(): 90 if not config.getboolean(addressInKeysFile, 'enabled'): 91 continue 92 93 hasEnabledKeys = True 94 95 addressVersionNumber, streamNumber, hashobj = decodeAddress( 96 addressInKeysFile)[1:] 97 if addressVersionNumber not in (2, 3, 4): 98 logger.error( 99 'Error in reloadMyAddressHashes: Can\'t handle' 100 ' address versions other than 2, 3, or 4.') 101 continue 102 103 # Returns a simple 32 bytes of information encoded in 64 Hex characters 104 try: 105 privEncryptionKey = hexlify( 106 highlevelcrypto.decodeWalletImportFormat(config.get( 107 addressInKeysFile, 'privencryptionkey').encode() 108 )) 109 except ValueError: 110 logger.error( 111 'Error in reloadMyAddressHashes: failed to decode' 112 ' one of the private keys for address %s', addressInKeysFile) 113 continue 114 # It is 32 bytes encoded as 64 hex characters 115 if len(privEncryptionKey) == 64: 116 myECCryptorObjects[hashobj] = \ 117 highlevelcrypto.makeCryptor(privEncryptionKey) 118 myAddressesByHash[hashobj] = addressInKeysFile 119 tag = highlevelcrypto.double_sha512( 120 encodeVarint(addressVersionNumber) 121 + encodeVarint(streamNumber) + hashobj)[32:] 122 myAddressesByTag[tag] = addressInKeysFile 123 124 if not keyfileSecure: 125 fixSensitiveFilePermissions(os.path.join( 126 state.appdata, 'keys.dat'), hasEnabledKeys) 127 128 129 def reloadBroadcastSendersForWhichImWatching(): 130 """ 131 Reinitialize runtime data for the broadcasts I'm subscribed to 132 from the config file 133 """ 134 broadcastSendersForWhichImWatching.clear() 135 MyECSubscriptionCryptorObjects.clear() 136 queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1') 137 logger.debug('reloading subscriptions...') 138 for row in queryreturn: 139 address, = row 140 # status 141 addressVersionNumber, streamNumber, hashobj = decodeAddress(address)[1:] 142 if addressVersionNumber == 2: 143 broadcastSendersForWhichImWatching[hashobj] = 0 144 # Now, for all addresses, even version 2 addresses, 145 # we should create Cryptor objects in a dictionary which we will 146 # use to attempt to decrypt encrypted broadcast messages. 147 148 if addressVersionNumber <= 3: 149 privEncryptionKey = hashlib.sha512( 150 encodeVarint(addressVersionNumber) 151 + encodeVarint(streamNumber) + hashobj 152 ).digest()[:32] 153 MyECSubscriptionCryptorObjects[hashobj] = \ 154 highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) 155 else: 156 doubleHashOfAddressData = highlevelcrypto.double_sha512( 157 encodeVarint(addressVersionNumber) 158 + encodeVarint(streamNumber) + hashobj 159 ) 160 tag = doubleHashOfAddressData[32:] 161 privEncryptionKey = doubleHashOfAddressData[:32] 162 MyECSubscriptionCryptorObjects[tag] = \ 163 highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) 164 165 166 def fixPotentiallyInvalidUTF8Data(text): 167 """Sanitise invalid UTF-8 strings""" 168 try: 169 text.decode('utf-8') 170 return text 171 except UnicodeDecodeError: 172 return 'Part of the message is corrupt. The message cannot be' \ 173 ' displayed the normal way.\n\n' + repr(text) 174 175 176 def checkSensitiveFilePermissions(filename): 177 """ 178 :param str filename: path to the file 179 :return: True if file appears to have appropriate permissions. 180 """ 181 if sys.platform == 'win32': 182 # .. todo:: This might deserve extra checks by someone familiar with 183 # Windows systems. 184 return True 185 elif sys.platform[:7] == 'freebsd': 186 # FreeBSD file systems are the same as major Linux file systems 187 present_permissions = os.stat(filename)[0] 188 disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO 189 return present_permissions & disallowed_permissions == 0 190 try: 191 # Skip known problems for non-Win32 filesystems 192 # without POSIX permissions. 193 fstype = subprocess.check_output( 194 ['/usr/bin/stat', '-f', '-c', '%T', filename], 195 stderr=subprocess.STDOUT 196 ) # nosec B603 197 if 'fuseblk' in fstype: 198 logger.info( 199 'Skipping file permissions check for %s.' 200 ' Filesystem fuseblk detected.', filename) 201 return True 202 except: # noqa:E722 203 # Swallow exception here, but we might run into trouble later! 204 logger.error('Could not determine filesystem type. %s', filename) 205 present_permissions = os.stat(filename)[0] 206 disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO 207 return present_permissions & disallowed_permissions == 0 208 209 210 # Fixes permissions on a sensitive file. 211 def fixSensitiveFilePermissions(filename, hasEnabledKeys): 212 """Try to change file permissions to be more restrictive""" 213 if hasEnabledKeys: 214 logger.warning( 215 'Keyfile had insecure permissions, and there were enabled' 216 ' keys. The truly paranoid should stop using them immediately.') 217 else: 218 logger.warning( 219 'Keyfile had insecure permissions, but there were no enabled keys.' 220 ) 221 try: 222 present_permissions = os.stat(filename)[0] 223 disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO 224 allowed_permissions = ((1 << 32) - 1) ^ disallowed_permissions 225 new_permissions = ( 226 allowed_permissions & present_permissions) 227 os.chmod(filename, new_permissions) 228 229 logger.info('Keyfile permissions automatically fixed.') 230 231 except Exception: 232 logger.exception('Keyfile permissions could not be fixed.') 233 raise