diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py index 296dda6b..c3d5c1fd 100644 --- a/src/helper_bootstrap.py +++ b/src/helper_bootstrap.py @@ -33,7 +33,7 @@ def dns(): print 'Adding', item[4][0], 'to knownNodes based on DNS boostrap method' shared.knownNodes[1][item[4][0]] = (8080, int(time.time())) except: - print 'bootstrap8080.bitmessage.org DNS bootstraping failed.' + print 'bootstrap8080.bitmessage.org DNS bootstrapping failed.' try: for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80): print 'Adding', item[4][0], 'to knownNodes based on DNS boostrap method' diff --git a/src/helper_startup.py b/src/helper_startup.py index e6590b0e..96df6356 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -74,5 +74,7 @@ def loadConfig(): print 'Creating new config files in', shared.appdata if not os.path.exists(shared.appdata): os.makedirs(shared.appdata) + if not sys.platform.startswith('win'): + os.umask(0o077) with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) diff --git a/src/shared.py b/src/shared.py index c706038a..85246906 100644 --- a/src/shared.py +++ b/src/shared.py @@ -8,21 +8,26 @@ maximumAgeOfNodesThatIAdvertiseToOthers = 10800 # Equals three hours useVeryEasyProofOfWorkForTesting = False # If you set this to True while on the normal network, you won't be able to send or sometimes receive messages. -import threading -import sys -from addresses import * -import highlevelcrypto -import Queue -import pickle -import os -import time +# Libraries. import ConfigParser -import socket +import os +import pickle +import Queue import random +import socket +import sys +import stat +import threading +import time + +# Project imports. +from addresses import * +from debug import logger import highlevelcrypto import shared from debug import logger + config = ConfigParser.SafeConfigParser() myECCryptorObjects = {} MyECSubscriptionCryptorObjects = {} @@ -137,6 +142,7 @@ def lookupAppdataFolder(): logger.info("Moving data folder to %s" % (dataFolder)) move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder) except IOError: + # Old directory may not exist. pass dataFolder = dataFolder + '/' return dataFolder @@ -182,23 +188,26 @@ def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address): return False def safeConfigGetBoolean(section,field): - try: - return config.getboolean(section,field) - except: - return False + try: + return config.getboolean(section,field) + except: + return False def decodeWalletImportFormat(WIFstring): fullString = arithmetic.changebase(WIFstring,58,256) privkey = fullString[:-4] if fullString[-4:] != hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: - sys.stderr.write('Major problem! When trying to decode one of your private keys, the checksum failed. Here is the PRIVATE key: %s\n' % str(WIFstring)) + logger.error('Major problem! When trying to decode one of your private keys, the checksum ' + 'failed. Here is the PRIVATE key: %s\n' % str(WIFstring)) return "" else: #checksum passed if privkey[0] == '\x80': return privkey[1:] else: - sys.stderr.write('Major problem! When trying to decode one of your private keys, the checksum passed but the key doesn\'t begin with hex 80. Here is the PRIVATE key: %s\n' % str(WIFstring)) + logger.error('Major problem! When trying to decode one of your private keys, the ' + 'checksum passed but the key doesn\'t begin with hex 80. Here is the ' + 'PRIVATE key: %s\n' % str(WIFstring)) return "" @@ -207,19 +216,32 @@ def reloadMyAddressHashes(): myECCryptorObjects.clear() myAddressesByHash.clear() #myPrivateKeys.clear() + + keyfileSecure = checkSensitiveFilePermissions(appdata + 'keys.dat') configSections = config.sections() + hasEnabledKeys = False for addressInKeysFile in configSections: if addressInKeysFile <> 'bitmessagesettings': isEnabled = config.getboolean(addressInKeysFile, 'enabled') if isEnabled: + hasEnabledKeys = True status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) if addressVersionNumber == 2 or addressVersionNumber == 3: - privEncryptionKey = decodeWalletImportFormat(config.get(addressInKeysFile, 'privencryptionkey')).encode('hex') #returns a simple 32 bytes of information encoded in 64 Hex characters, or null if there was an error + # Returns a simple 32 bytes of information encoded in 64 Hex characters, + # or null if there was an error. + privEncryptionKey = decodeWalletImportFormat( + config.get(addressInKeysFile, 'privencryptionkey')).encode('hex') + if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey) myAddressesByHash[hash] = addressInKeysFile + else: - sys.stderr.write('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2 or 3.\n') + logger.error('Error in reloadMyAddressHashes: Can\'t handle address ' + 'versions other than 2 or 3.\n') + + if not keyfileSecure: + fixSensitiveFilePermissions(appdata + 'keys.dat', hasEnabledKeys) def reloadBroadcastSendersForWhichImWatching(): logger.debug('reloading subscriptions...') @@ -270,6 +292,7 @@ def doCleanShutdown(): sqlSubmitQueue.put('exit') sqlLock.release() logger.info('Finished flushing inventory.') + # Wait long enough to guarantee that any running proof of work worker threads will check the # shutdown variable and exit. If the main thread closes before they do then they won't stop. time.sleep(.25) @@ -306,3 +329,39 @@ def fixPotentiallyInvalidUTF8Data(text): except: output = 'Part of the message is corrupt. The message cannot be displayed the normal way.\n\n' + repr(text) return output + +# Checks sensitive file permissions for inappropriate umask during keys.dat creation. +# (Or unwise subsequent chmod.) +# +# Returns true iff file appears to have appropriate permissions. +def checkSensitiveFilePermissions(filename): + if sys.platform == 'win32': + # TODO: This might deserve extra checks by someone familiar with + # Windows systems. + return True + else: + 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): + 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, e: + logger.exception('Keyfile permissions could not be fixed.') + raise +