Key file permissions #306

Merged
Atheros1 merged 16 commits from master into master 2013-07-15 21:51:27 +02:00
6 changed files with 141 additions and 55 deletions

View File

@ -29,6 +29,8 @@ import os
from pyelliptic.openssl import OpenSSL from pyelliptic.openssl import OpenSSL
import pickle import pickle
import platform import platform
import debug
from debug import logger
try: try:
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
@ -1874,7 +1876,14 @@ class MyForm(QtGui.QMainWindow):
shared.knownNodesLock.release() shared.knownNodesLock.release()
os.remove(shared.appdata + 'keys.dat') os.remove(shared.appdata + 'keys.dat')
os.remove(shared.appdata + 'knownnodes.dat') os.remove(shared.appdata + 'knownnodes.dat')
previousAppdataLocation = shared.appdata
shared.appdata = '' shared.appdata = ''
debug.restartLoggingInUpdatedAppdataLocation()
try:
os.remove(previousAppdataLocation + 'debug.log')
os.remove(previousAppdataLocation + 'debug.log.1')
except:
pass
if shared.appdata == '' and not self.settingsDialogInstance.ui.checkBoxPortableMode.isChecked(): # If we ARE using portable mode now but the user selected that we shouldn't... if shared.appdata == '' and not self.settingsDialogInstance.ui.checkBoxPortableMode.isChecked(): # If we ARE using portable mode now but the user selected that we shouldn't...
shared.appdata = shared.lookupAppdataFolder() shared.appdata = shared.lookupAppdataFolder()
@ -1894,6 +1903,12 @@ class MyForm(QtGui.QMainWindow):
shared.knownNodesLock.release() shared.knownNodesLock.release()
os.remove('keys.dat') os.remove('keys.dat')
os.remove('knownnodes.dat') os.remove('knownnodes.dat')
debug.restartLoggingInUpdatedAppdataLocation()
try:
os.remove('debug.log')
os.remove('debug.log.1')
except:
pass
def click_radioButtonBlacklist(self): def click_radioButtonBlacklist(self):
if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'white': if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'white':

View File

@ -5,6 +5,7 @@ import time
import shutil # used for moving the messages.dat file import shutil # used for moving the messages.dat file
import sys import sys
import os import os
from debug import logger
# This thread exists because SQLITE3 is so un-threadsafe that we must # This thread exists because SQLITE3 is so un-threadsafe that we must
# submit queries to it and it puts results back in a different queue. They # submit queries to it and it puts results back in a different queue. They

View File

@ -23,7 +23,8 @@ import shared
# TODO(xj9): Get from a config file. # TODO(xj9): Get from a config file.
log_level = 'DEBUG' log_level = 'DEBUG'
logging.config.dictConfig({ def configureLogging():
logging.config.dictConfig({
'version': 1, 'version': 1,
'formatters': { 'formatters': {
'default': { 'default': {
@ -64,7 +65,17 @@ logging.config.dictConfig({
'level': log_level, 'level': log_level,
'handlers': ['console'], 'handlers': ['console'],
}, },
}) })
# TODO (xj9): Get from a config file. # TODO (xj9): Get from a config file.
#logger = logging.getLogger('console_only') #logger = logging.getLogger('console_only')
configureLogging()
logger = logging.getLogger('both') logger = logging.getLogger('both')
def restartLoggingInUpdatedAppdataLocation():
global logger
for i in list(logger.handlers):
logger.removeHandler(i)
i.flush()
i.close()
configureLogging()
logger = logging.getLogger('both')

View File

@ -33,7 +33,7 @@ def dns():
print 'Adding', item[4][0], 'to knownNodes based on DNS boostrap method' print 'Adding', item[4][0], 'to knownNodes based on DNS boostrap method'
shared.knownNodes[1][item[4][0]] = (8080, int(time.time())) shared.knownNodes[1][item[4][0]] = (8080, int(time.time()))
except: except:
print 'bootstrap8080.bitmessage.org DNS bootstraping failed.' print 'bootstrap8080.bitmessage.org DNS bootstrapping failed.'
try: try:
for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80): for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80):
print 'Adding', item[4][0], 'to knownNodes based on DNS boostrap method' print 'Adding', item[4][0], 'to knownNodes based on DNS boostrap method'

View File

@ -74,5 +74,7 @@ def loadConfig():
print 'Creating new config files in', shared.appdata print 'Creating new config files in', shared.appdata
if not os.path.exists(shared.appdata): if not os.path.exists(shared.appdata):
os.makedirs(shared.appdata) os.makedirs(shared.appdata)
if not sys.platform.startswith('win'):
os.umask(0o077)
with open(shared.appdata + 'keys.dat', 'wb') as configfile: with open(shared.appdata + 'keys.dat', 'wb') as configfile:
shared.config.write(configfile) shared.config.write(configfile)

View File

@ -8,22 +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. 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 # Libraries.
import sys
from addresses import *
import highlevelcrypto
import Queue
import pickle
import os
import time
import ConfigParser import ConfigParser
import socket import os
import pickle
import Queue
import random import random
import socket
import sys
import stat
import threading
import time
# Project imports.
from addresses import *
import highlevelcrypto import highlevelcrypto
import shared import shared
import helper_startup import helper_startup
config = ConfigParser.SafeConfigParser() config = ConfigParser.SafeConfigParser()
myECCryptorObjects = {} myECCryptorObjects = {}
MyECSubscriptionCryptorObjects = {} MyECSubscriptionCryptorObjects = {}
@ -143,6 +147,7 @@ def lookupAppdataFolder():
else: else:
print stringToLog print stringToLog
except IOError: except IOError:
# Old directory may not exist.
pass pass
dataFolder = dataFolder + '/' dataFolder = dataFolder + '/'
return dataFolder return dataFolder
@ -197,14 +202,17 @@ def decodeWalletImportFormat(WIFstring):
fullString = arithmetic.changebase(WIFstring,58,256) fullString = arithmetic.changebase(WIFstring,58,256)
privkey = fullString[:-4] privkey = fullString[:-4]
if fullString[-4:] != hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[: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 "" return ""
else: else:
#checksum passed #checksum passed
if privkey[0] == '\x80': if privkey[0] == '\x80':
return privkey[1:] return privkey[1:]
else: 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 "" return ""
@ -213,19 +221,32 @@ def reloadMyAddressHashes():
myECCryptorObjects.clear() myECCryptorObjects.clear()
myAddressesByHash.clear() myAddressesByHash.clear()
#myPrivateKeys.clear() #myPrivateKeys.clear()
keyfileSecure = checkSensitiveFilePermissions(appdata + 'keys.dat')
configSections = config.sections() configSections = config.sections()
hasEnabledKeys = False
for addressInKeysFile in configSections: for addressInKeysFile in configSections:
if addressInKeysFile <> 'bitmessagesettings': if addressInKeysFile <> 'bitmessagesettings':
isEnabled = config.getboolean(addressInKeysFile, 'enabled') isEnabled = config.getboolean(addressInKeysFile, 'enabled')
if isEnabled: if isEnabled:
hasEnabledKeys = True
status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile) status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile)
if addressVersionNumber == 2 or addressVersionNumber == 3: 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 if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters
myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey) myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey)
myAddressesByHash[hash] = addressInKeysFile myAddressesByHash[hash] = addressInKeysFile
else: 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(): def reloadBroadcastSendersForWhichImWatching():
logger.debug('reloading subscriptions...') logger.debug('reloading subscriptions...')
@ -276,6 +297,7 @@ def doCleanShutdown():
sqlSubmitQueue.put('exit') sqlSubmitQueue.put('exit')
sqlLock.release() sqlLock.release()
logger.info('Finished flushing inventory.') logger.info('Finished flushing inventory.')
# Wait long enough to guarantee that any running proof of work worker threads will check the # 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. # shutdown variable and exit. If the main thread closes before they do then they won't stop.
time.sleep(.25) time.sleep(.25)
@ -313,5 +335,40 @@ def fixPotentiallyInvalidUTF8Data(text):
output = 'Part of the message is corrupt. The message cannot be displayed the normal way.\n\n' + repr(text) output = 'Part of the message is corrupt. The message cannot be displayed the normal way.\n\n' + repr(text)
return output 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
helper_startup.loadConfig() helper_startup.loadConfig()
from debug import logger from debug import logger