PyBitmessage/src/shared.py

435 lines
16 KiB
Python
Raw Normal View History

2015-08-22 10:48:49 +02:00
from __future__ import division
2015-01-21 18:38:25 +01:00
# Libraries.
import os
import sys
import stat
import time
2017-09-21 17:24:51 +02:00
import threading
2014-08-27 09:14:32 +02:00
import traceback
2017-09-21 17:24:51 +02:00
import hashlib
import subprocess
from struct import unpack
2016-03-23 23:26:57 +01:00
from binascii import hexlify
2017-09-21 17:24:51 +02:00
from pyelliptic import arithmetic
# Project imports.
2017-09-21 17:24:51 +02:00
import state
import highlevelcrypto
2017-09-21 17:24:51 +02:00
from bmconfigparser import BMConfigParser
from debug import logger
from addresses import (
decodeAddress, encodeVarint, decodeVarint, varintDecodeError
2017-09-21 17:24:51 +02:00
)
from helper_sql import sqlQuery, sqlExecute
verbose = 1
# This is obsolete with the change to protocol v3
# but the singleCleaner thread still hasn't been updated
# so we need this a little longer.
maximumAgeOfAnObjectThatIAmWillingToAccept = 216000
# Equals 4 weeks. You could make this longer if you want
# but making it shorter would not be advisable because
# there is a very small possibility that it could keep you
# from obtaining a needed pubkey for a period of time.
lengthOfTimeToHoldOnToAllPubkeys = 2419200
maximumAgeOfNodesThatIAdvertiseToOthers = 10800 # Equals three hours
# If you set this to True while on the normal network,
# you won't be able to send or sometimes receive messages.
useVeryEasyProofOfWorkForTesting = False
2013-05-02 17:53:54 +02:00
myECCryptorObjects = {}
MyECSubscriptionCryptorObjects = {}
2017-09-21 17:24:51 +02:00
# 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 = {}
2013-05-02 17:53:54 +02:00
broadcastSendersForWhichImWatching = {}
printLock = threading.Lock()
statusIconColor = 'red'
2017-09-21 17:24:51 +02:00
# List of hosts to which we are connected. Used to guarantee
# that the outgoingSynSender threads won't connect to the same
# remote node twice.
connectedHostsList = {}
thisapp = None # singleton lock instance
alreadyAttemptedConnectionsList = {
} # This is a list of nodes to which we have already attempted a connection
alreadyAttemptedConnectionsListLock = threading.Lock()
2017-09-21 17:24:51 +02:00
# used to clear out the alreadyAttemptedConnectionsList periodically
# so that we will retry connecting to hosts to which we have already
# tried to connect.
alreadyAttemptedConnectionsListResetTime = int(time.time())
# A list of the amounts of time it took to successfully decrypt msg messages
successfullyDecryptMessageTimings = []
ackdataForWhichImWatching = {}
2017-09-21 17:24:51 +02:00
# used by API command clientStatus
clientHasReceivedIncomingConnections = False
numberOfMessagesProcessed = 0
numberOfBroadcastsProcessed = 0
numberOfPubkeysProcessed = 0
2018-01-25 11:58:29 +01:00
2017-09-21 17:24:51 +02:00
# If True, the singleCleaner will write it to disk eventually.
needToWriteKnownNodesToDisk = False
maximumLengthOfTimeToBotherResendingMessages = 0
timeOffsetWrongCount = 0
2013-05-02 17:53:54 +02:00
2017-09-21 17:24:51 +02:00
2013-05-02 17:53:54 +02:00
def isAddressInMyAddressBook(address):
queryreturn = sqlQuery(
'''select address from addressbook where address=?''',
address)
2013-05-02 17:53:54 +02:00
return queryreturn != []
2017-09-21 17:24:51 +02:00
# At this point we should really just have a isAddressInMy(book, address)...
def isAddressInMySubscriptionsList(address):
queryreturn = sqlQuery(
'''select * from subscriptions where address=?''',
str(address))
return queryreturn != []
2013-06-14 04:03:03 +02:00
2017-09-21 17:24:51 +02:00
2013-05-02 17:53:54 +02:00
def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address):
if isAddressInMyAddressBook(address):
return True
2017-09-21 17:24:51 +02:00
queryreturn = sqlQuery(
'''SELECT address FROM whitelist where address=?'''
''' and enabled = '1' ''',
address)
if queryreturn != []:
2013-05-02 17:53:54 +02:00
return True
queryreturn = sqlQuery(
2017-09-21 17:24:51 +02:00
'''select address from subscriptions where address=?'''
''' and enabled = '1' ''',
address)
2017-09-21 17:24:51 +02:00
if queryreturn != []:
2013-05-02 17:53:54 +02:00
return True
return False
2017-09-21 17:24:51 +02:00
2013-05-02 17:53:54 +02:00
def decodeWalletImportFormat(WIFstring):
2017-09-21 17:24:51 +02:00
fullString = arithmetic.changebase(WIFstring, 58, 256)
2013-05-02 17:53:54 +02:00
privkey = fullString[:-4]
2017-09-21 17:24:51 +02:00
if fullString[-4:] != \
hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]:
logger.critical(
'Major problem! When trying to decode one of your'
' private keys, the checksum failed. Here are the first'
' 6 characters of the PRIVATE key: %s',
str(WIFstring)[:6]
)
os._exit(0)
2017-09-21 17:24:51 +02:00
# return ""
elif privkey[0] == '\x80': # checksum passed
return privkey[1:]
logger.critical(
'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', WIFstring
)
os._exit(0)
2013-05-02 17:53:54 +02:00
def reloadMyAddressHashes():
logger.debug('reloading keys from keys.dat file')
2013-05-02 17:53:54 +02:00
myECCryptorObjects.clear()
myAddressesByHash.clear()
2013-09-15 03:06:26 +02:00
myAddressesByTag.clear()
2017-09-21 17:24:51 +02:00
# myPrivateKeys.clear()
keyfileSecure = checkSensitiveFilePermissions(state.appdata + 'keys.dat')
hasEnabledKeys = False
for addressInKeysFile in BMConfigParser().addresses():
isEnabled = BMConfigParser().getboolean(addressInKeysFile, 'enabled')
if isEnabled:
hasEnabledKeys = True
2018-03-22 12:23:36 +01:00
# status
_, addressVersionNumber, streamNumber, hash = \
2017-09-21 17:24:51 +02:00
decodeAddress(addressInKeysFile)
if addressVersionNumber in (2, 3, 4):
# Returns a simple 32 bytes of information encoded
# in 64 Hex characters, or null if there was an error.
privEncryptionKey = hexlify(decodeWalletImportFormat(
2017-09-21 17:24:51 +02:00
BMConfigParser().get(addressInKeysFile, 'privencryptionkey'))
)
2017-09-21 17:24:51 +02:00
# It is 32 bytes encoded as 64 hex characters
if len(privEncryptionKey) == 64:
myECCryptorObjects[hash] = \
highlevelcrypto.makeCryptor(privEncryptionKey)
myAddressesByHash[hash] = addressInKeysFile
2017-09-21 17:24:51 +02:00
tag = hashlib.sha512(hashlib.sha512(
encodeVarint(addressVersionNumber) +
encodeVarint(streamNumber) + hash).digest()
).digest()[32:]
myAddressesByTag[tag] = addressInKeysFile
else:
2017-09-21 17:24:51 +02:00
logger.error(
'Error in reloadMyAddressHashes: Can\'t handle'
' address versions other than 2, 3, or 4.\n'
)
2013-06-27 12:44:49 +02:00
if not keyfileSecure:
fixSensitiveFilePermissions(state.appdata + 'keys.dat', hasEnabledKeys)
2013-05-02 17:53:54 +02:00
2017-09-21 17:24:51 +02:00
2013-05-02 17:53:54 +02:00
def reloadBroadcastSendersForWhichImWatching():
broadcastSendersForWhichImWatching.clear()
MyECSubscriptionCryptorObjects.clear()
queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1')
2013-09-15 03:06:26 +02:00
logger.debug('reloading subscriptions...')
2013-05-02 17:53:54 +02:00
for row in queryreturn:
address, = row
2018-03-22 12:48:07 +01:00
# status
_, addressVersionNumber, streamNumber, hash = decodeAddress(address)
2013-05-02 17:53:54 +02:00
if addressVersionNumber == 2:
broadcastSendersForWhichImWatching[hash] = 0
2017-09-21 17:24:51 +02:00
# 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.
2013-09-15 03:06:26 +02:00
if addressVersionNumber <= 3:
2017-09-21 17:24:51 +02:00
privEncryptionKey = hashlib.sha512(
encodeVarint(addressVersionNumber) +
encodeVarint(streamNumber) + hash
).digest()[:32]
MyECSubscriptionCryptorObjects[hash] = \
highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))
2013-09-15 03:06:26 +02:00
else:
2017-09-21 17:24:51 +02:00
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(
encodeVarint(addressVersionNumber) +
encodeVarint(streamNumber) + hash
).digest()).digest()
2013-09-15 03:06:26 +02:00
tag = doubleHashOfAddressData[32:]
privEncryptionKey = doubleHashOfAddressData[:32]
2017-09-21 17:24:51 +02:00
MyECSubscriptionCryptorObjects[tag] = \
highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))
2013-05-02 17:53:54 +02:00
def fixPotentiallyInvalidUTF8Data(text):
try:
2017-09-21 17:24:51 +02:00
unicode(text, 'utf-8')
return text
except:
2017-09-21 17:24:51 +02:00
return 'Part of the message is corrupt. The message cannot be' \
' displayed the normal way.\n\n' + repr(text)
2017-09-21 17:24:51 +02:00
# Checks sensitive file permissions for inappropriate umask
# during keys.dat creation. (Or unwise subsequent chmod.)
2013-07-10 10:43:18 +02:00
#
2013-06-27 12:44:49 +02:00
# 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.
2013-06-27 12:44:49 +02:00
return True
2013-11-29 01:20:16 +01:00
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
else:
try:
2017-09-21 17:24:51 +02:00
# Skip known problems for non-Win32 filesystems
# without POSIX permissions.
fstype = subprocess.check_output(
'stat -f -c "%%T" %s' % (filename),
shell=True,
stderr=subprocess.STDOUT
)
if 'fuseblk' in fstype:
2017-09-21 17:24:51 +02:00
logger.info(
'Skipping file permissions check for %s.'
' Filesystem fuseblk detected.', filename)
return True
except:
# Swallow exception here, but we might run into trouble later!
logger.error('Could not determine filesystem type. %s', filename)
2013-06-27 12:44:49 +02:00
present_permissions = os.stat(filename)[0]
disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO
return present_permissions & disallowed_permissions == 0
2017-09-21 17:24:51 +02:00
# Fixes permissions on a sensitive file.
2013-06-27 12:44:49 +02:00
def fixSensitiveFilePermissions(filename, hasEnabledKeys):
if hasEnabledKeys:
2017-09-21 17:24:51 +02:00
logger.warning(
'Keyfile had insecure permissions, and there were enabled'
' keys. The truly paranoid should stop using them immediately.')
2013-06-27 12:44:49 +02:00
else:
2017-09-21 17:24:51 +02:00
logger.warning(
'Keyfile had insecure permissions, but there were no enabled keys.'
)
2013-06-27 12:44:49 +02:00
try:
present_permissions = os.stat(filename)[0]
disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO
2017-09-21 17:24:51 +02:00
allowed_permissions = ((1 << 32) - 1) ^ disallowed_permissions
2013-06-27 12:44:49 +02:00
new_permissions = (
allowed_permissions & present_permissions)
os.chmod(filename, new_permissions)
logger.info('Keyfile permissions automatically fixed.')
2017-09-21 17:24:51 +02:00
except Exception:
logger.exception('Keyfile permissions could not be fixed.')
2013-06-27 12:44:49 +02:00
raise
2017-09-21 17:24:51 +02:00
def isBitSetWithinBitfield(fourByteString, n):
# Uses MSB 0 bit numbering across 4 bytes of data
n = 31 - n
x, = unpack('>L', fourByteString)
return x & 2**n != 0
2014-08-27 09:14:32 +02:00
def decryptAndCheckPubkeyPayload(data, address):
"""
2017-09-21 17:24:51 +02:00
Version 4 pubkeys are encrypted. This function is run when we
already have the address to which we want to try to send a message.
The 'data' may come either off of the wire or we might have had it
already in our inventory when we tried to send a msg to this
particular address.
2014-08-27 09:14:32 +02:00
"""
2013-09-18 06:04:01 +02:00
try:
2018-03-22 12:48:07 +01:00
# status
_, addressVersion, streamNumber, ripe = decodeAddress(address)
2017-09-21 17:24:51 +02:00
2014-08-27 09:14:32 +02:00
readPosition = 20 # bypass the nonce, time, and object type
2017-09-21 17:24:51 +02:00
embeddedAddressVersion, varintLength = \
decodeVarint(data[readPosition:readPosition + 10])
2014-08-27 09:14:32 +02:00
readPosition += varintLength
2017-09-21 17:24:51 +02:00
embeddedStreamNumber, varintLength = \
decodeVarint(data[readPosition:readPosition + 10])
2014-08-27 09:14:32 +02:00
readPosition += varintLength
2017-09-21 17:24:51 +02:00
# We'll store the address version and stream number
# (and some more) in the pubkeys table.
storedData = data[20:readPosition]
2014-08-27 09:14:32 +02:00
if addressVersion != embeddedAddressVersion:
2017-09-21 17:24:51 +02:00
logger.info(
'Pubkey decryption was UNsuccessful'
' due to address version mismatch.')
2013-09-18 06:04:01 +02:00
return 'failed'
2014-08-27 09:14:32 +02:00
if streamNumber != embeddedStreamNumber:
2017-09-21 17:24:51 +02:00
logger.info(
'Pubkey decryption was UNsuccessful'
' due to stream number mismatch.')
2014-08-27 09:14:32 +02:00
return 'failed'
2017-09-21 17:24:51 +02:00
2014-08-27 09:14:32 +02:00
tag = data[readPosition:readPosition + 32]
readPosition += 32
2017-09-21 17:24:51 +02:00
# the time through the tag. More data is appended onto
# signedData below after the decryption.
signedData = data[8:readPosition]
2014-08-27 09:14:32 +02:00
encryptedData = data[readPosition:]
2017-09-21 17:24:51 +02:00
2014-08-27 09:14:32 +02:00
# Let us try to decrypt the pubkey
toAddress, cryptorObject = state.neededPubkeys[tag]
2014-08-27 09:14:32 +02:00
if toAddress != address:
2017-09-21 17:24:51 +02:00
logger.critical(
'decryptAndCheckPubkeyPayload failed due to toAddress'
' mismatch. This is very peculiar.'
' toAddress: %s, address %s',
toAddress, address
)
# the only way I can think that this could happen
# is if someone encodes their address data two different ways.
# That sort of address-malleability should have been caught
# by the UI or API and an error given to the user.
2014-08-27 09:14:32 +02:00
return 'failed'
try:
decryptedData = cryptorObject.decrypt(encryptedData)
except:
# Someone must have encrypted some data with a different key
# but tagged it with a tag for which we are watching.
logger.info('Pubkey decryption was unsuccessful.')
return 'failed'
2017-09-21 17:24:51 +02:00
2014-08-27 09:14:32 +02:00
readPosition = 0
2017-09-21 17:24:51 +02:00
# bitfieldBehaviors = decryptedData[readPosition:readPosition + 4]
2014-08-27 09:14:32 +02:00
readPosition += 4
2017-09-21 17:24:51 +02:00
publicSigningKey = \
'\x04' + decryptedData[readPosition:readPosition + 64]
2014-08-27 09:14:32 +02:00
readPosition += 64
2017-09-21 17:24:51 +02:00
publicEncryptionKey = \
'\x04' + decryptedData[readPosition:readPosition + 64]
2014-08-27 09:14:32 +02:00
readPosition += 64
2017-09-21 17:24:51 +02:00
specifiedNonceTrialsPerByte, specifiedNonceTrialsPerByteLength = \
decodeVarint(decryptedData[readPosition:readPosition + 10])
2014-08-27 09:14:32 +02:00
readPosition += specifiedNonceTrialsPerByteLength
2017-09-21 17:24:51 +02:00
specifiedPayloadLengthExtraBytes, \
specifiedPayloadLengthExtraBytesLength = \
decodeVarint(decryptedData[readPosition:readPosition + 10])
2014-08-27 09:14:32 +02:00
readPosition += specifiedPayloadLengthExtraBytesLength
2014-12-25 09:57:34 +01:00
storedData += decryptedData[:readPosition]
signedData += decryptedData[:readPosition]
2017-09-21 17:24:51 +02:00
signatureLength, signatureLengthLength = \
decodeVarint(decryptedData[readPosition:readPosition + 10])
2014-08-27 09:14:32 +02:00
readPosition += signatureLengthLength
signature = decryptedData[readPosition:readPosition + signatureLength]
2017-09-21 17:24:51 +02:00
if not highlevelcrypto.verify(
2017-09-21 17:24:51 +02:00
signedData, signature, hexlify(publicSigningKey)):
logger.info(
'ECDSA verify failed (within decryptAndCheckPubkeyPayload)')
2014-12-25 09:57:34 +01:00
return 'failed'
2017-09-21 17:24:51 +02:00
logger.info(
'ECDSA verify passed (within decryptAndCheckPubkeyPayload)')
2014-08-27 09:14:32 +02:00
sha = hashlib.new('sha512')
sha.update(publicSigningKey + publicEncryptionKey)
ripeHasher = hashlib.new('ripemd160')
ripeHasher.update(sha.digest())
embeddedRipe = ripeHasher.digest()
2017-09-21 17:24:51 +02:00
2014-08-27 09:14:32 +02:00
if embeddedRipe != ripe:
2017-09-21 17:24:51 +02:00
# Although this pubkey object had the tag were were looking for
# and was encrypted with the correct encryption key,
# it doesn't contain the correct pubkeys. Someone is
# either being malicious or using buggy software.
logger.info(
'Pubkey decryption was UNsuccessful due to RIPE mismatch.')
2014-08-27 09:14:32 +02:00
return 'failed'
2017-09-21 17:24:51 +02:00
2014-08-27 09:14:32 +02:00
# Everything checked out. Insert it into the pubkeys table.
2017-09-21 17:24:51 +02:00
logger.info(
'within decryptAndCheckPubkeyPayload, '
'addressVersion: %s, streamNumber: %s\nripe %s\n'
'publicSigningKey in hex: %s\npublicEncryptionKey in hex: %s',
addressVersion, streamNumber, hexlify(ripe),
hexlify(publicSigningKey), hexlify(publicEncryptionKey)
)
2015-03-09 07:35:32 +01:00
t = (address, addressVersion, storedData, int(time.time()), 'yes')
2014-08-27 09:14:32 +02:00
sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t)
return 'successful'
2017-09-21 17:24:51 +02:00
except varintDecodeError:
logger.info(
'Pubkey decryption was UNsuccessful due to a malformed varint.')
2013-09-18 06:04:01 +02:00
return 'failed'
2017-09-21 17:24:51 +02:00
except Exception:
logger.critical(
'Pubkey decryption was UNsuccessful because of'
' an unhandled exception! This is definitely a bug! \n%s' %
traceback.format_exc()
)
2013-09-18 06:04:01 +02:00
return 'failed'
2017-09-21 17:24:51 +02:00
def openKeysFile():
if 'linux' in sys.platform:
subprocess.call(["xdg-open", state.appdata + 'keys.dat'])
else:
os.startfile(state.appdata + 'keys.dat')