def processmsg(self, data): messageProcessingStartTime = time.time() shared.numberOfMessagesProcessed += 1 # queues.UISignalQueue.put(( # 'updateNumberOfMessagesProcessed', 'no data')) readPosition = 20 # bypass the nonce, time, and object type msgVersion, msgVersionLength = decodeVarint(data[readPosition:readPosition + 9]) if msgVersion != 1: logger.info('Cannot understand message versions other than one. Ignoring message.') return readPosition += msgVersionLength streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = decodeVarint( data[readPosition:readPosition + 9]) readPosition += streamNumberAsClaimedByMsgLength inventoryHash = calculateInventoryHash(data) initialDecryptionSuccessful = False # This is not an acknowledgement bound for me. See if it is a message # bound for me by trying to decrypt it with my private keys. for key, cryptorObject in shared.myECCryptorObjects.items(): try: if initialDecryptionSuccessful: # continue decryption attempts to avoid timing attacks cryptorObject.decrypt(data[readPosition:]) else: decryptedData = cryptorObject.decrypt(data[readPosition:]) toRipe = key # This is the RIPE hash of my pubkeys. We need this below to compare to the destination_ripe included in the encrypted data. initialDecryptionSuccessful = True logger.info('EC decryption successful using key associated with ripe hash: %s.' % hexlify(key)) except Exception as err: pass if not initialDecryptionSuccessful: # This is not a message bound for me. logger.info('Length of time program spent failing to decrypt this message: %s seconds.' % (time.time() - messageProcessingStartTime,)) return # This is a message bound for me. toAddress = shared.myAddressesByHash[ toRipe] # Look up my address based on the RIPE hash. readPosition = 0 sendersAddressVersionNumber, sendersAddressVersionNumberLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += sendersAddressVersionNumberLength if sendersAddressVersionNumber == 0: logger.info('Cannot understand sendersAddressVersionNumber = 0. Ignoring message.') return if sendersAddressVersionNumber > 4: logger.info('Sender\'s address version number %s not yet supported. Ignoring message.' % sendersAddressVersionNumber) return if len(decryptedData) < 170: logger.info('Length of the unencrypted data is unreasonably short. Sanity check failed. Ignoring message.') return sendersStreamNumber, sendersStreamNumberLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) if sendersStreamNumber == 0: logger.info('sender\'s stream number is 0. Ignoring message.') return readPosition += sendersStreamNumberLength behaviorBitfield = decryptedData[readPosition:readPosition + 4] readPosition += 4 pubSigningKey = '\x04' + decryptedData[ readPosition:readPosition + 64] readPosition += 64 pubEncryptionKey = '\x04' + decryptedData[ readPosition:readPosition + 64] readPosition += 64 if sendersAddressVersionNumber >= 3: requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += varintLength logger.info('sender\'s requiredAverageProofOfWorkNonceTrialsPerByte is %s' % requiredAverageProofOfWorkNonceTrialsPerByte) requiredPayloadLengthExtraBytes, varintLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += varintLength logger.info('sender\'s requiredPayloadLengthExtraBytes is %s' % requiredPayloadLengthExtraBytes) endOfThePublicKeyPosition = readPosition # needed for when we store the pubkey in our database of pubkeys for later use. if toRipe != decryptedData[readPosition:readPosition + 20]: logger.info('The original sender of this message did not send it to you. Someone is attempting a Surreptitious Forwarding Attack.\n\ See: http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html \n\ your toRipe: %s\n\ embedded destination toRipe: %s' % (hexlify(toRipe), hexlify(decryptedData[readPosition:readPosition + 20])) ) return readPosition += 20 messageEncodingType, messageEncodingTypeLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += messageEncodingTypeLength messageLength, messageLengthLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += messageLengthLength message = decryptedData[readPosition:readPosition + messageLength] # print 'First 150 characters of message:', repr(message[:150]) readPosition += messageLength ackLength, ackLengthLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += ackLengthLength ackData = decryptedData[readPosition:readPosition + ackLength] readPosition += ackLength positionOfBottomOfAckData = readPosition # needed to mark the end of what is covered by the signature signatureLength, signatureLengthLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += signatureLengthLength signature = decryptedData[ readPosition:readPosition + signatureLength] signedData = data[8:20] + encodeVarint(1) + encodeVarint(streamNumberAsClaimedByMsg) + decryptedData[:positionOfBottomOfAckData] if not highlevelcrypto.verify(signedData, signature, hexlify(pubSigningKey)): logger.debug('ECDSA verify failed') return logger.debug('ECDSA verify passed') logger.debug('As a matter of intellectual curiosity, here is the Bitcoin address associated with the keys owned by the other person: %s ..and here is the testnet address: %s. The other person must take their private signing key from Bitmessage and import it into Bitcoin (or a service like Blockchain.info) for it to be of any use. Do not use this unless you know what you are doing.' % (helper_bitcoin.calculateBitcoinAddressFromPubkey(pubSigningKey), helper_bitcoin.calculateTestnetAddressFromPubkey(pubSigningKey)) ) sigHash = hashlib.sha512(hashlib.sha512(signature).digest()).digest()[32:] # Used to detect and ignore duplicate messages in our inbox # calculate the fromRipe. sha = hashlib.new('sha512') sha.update(pubSigningKey + pubEncryptionKey) ripe = hashlib.new('ripemd160') ripe.update(sha.digest()) fromAddress = encodeAddress( sendersAddressVersionNumber, sendersStreamNumber, ripe.digest()) # Let's store the public key in case we want to reply to this # person. sqlExecute( '''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', fromAddress, sendersAddressVersionNumber, decryptedData[:endOfThePublicKeyPosition], int(time.time()), 'yes') # Check to see whether we happen to be awaiting this # pubkey in order to send a message. If we are, it will do the POW # and send it. self.possibleNewPubkey(fromAddress) # If this message is bound for one of my version 3 addresses (or # higher), then we must check to make sure it meets our demanded # proof of work requirement. If this is bound for one of my chan # addresses then we skip this check; the minimum network POW is # fine. if decodeAddress(toAddress)[1] >= 3 and not BMConfigParser().safeGetBoolean(toAddress, 'chan'): # If the toAddress version number is 3 or higher and not one of my chan addresses: if not shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist(fromAddress): # If I'm not friendly with this person: requiredNonceTrialsPerByte = BMConfigParser().getint( toAddress, 'noncetrialsperbyte') requiredPayloadLengthExtraBytes = BMConfigParser().getint( toAddress, 'payloadlengthextrabytes') if not protocol.isProofOfWorkSufficient(data, requiredNonceTrialsPerByte, requiredPayloadLengthExtraBytes): logger.info('Proof of work in msg is insufficient only because it does not meet our higher requirement.') return blockMessage = False # Gets set to True if the user shouldn't see the message according to black or white lists. if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': # If we are using a blacklist queryreturn = sqlQuery( '''SELECT label FROM blacklist where address=? and enabled='1' ''', fromAddress) if queryreturn != []: logger.info('Message ignored because address is in blacklist.') blockMessage = True else: # We're using a whitelist queryreturn = sqlQuery( '''SELECT label FROM whitelist where address=? and enabled='1' ''', fromAddress) if queryreturn == []: logger.info('Message ignored because address not in whitelist.') blockMessage = True toLabel = BMConfigParser().get(toAddress, 'label') if toLabel == '': toLabel = toAddress decodedMessage = helper_msgcoding.MsgDecode(messageEncodingType, message) subject = decodedMessage.subject body = decodedMessage.body # Let us make sure that we haven't already received this message if helper_inbox.isMessageAlreadyInInbox(sigHash): logger.info('This msg is already in our inbox. Ignoring it.') blockMessage = True if not blockMessage: if messageEncodingType != 0: t = (inventoryHash, toAddress, fromAddress, subject, int( time.time()), body, 'inbox', messageEncodingType, 0, sigHash) helper_inbox.insert(t) queues.UISignalQueue.put(('displayNewInboxMessage', ( inventoryHash, toAddress, fromAddress, subject, body))) # If we are behaving as an API then we might need to run an # outside command to let some program know that a new message # has arrived. if BMConfigParser().safeGetBoolean('bitmessagesettings', 'apienabled'): try: apiNotifyPath = BMConfigParser().get( 'bitmessagesettings', 'apinotifypath') except: apiNotifyPath = '' if apiNotifyPath != '': call([apiNotifyPath, "newMessage"]) # Let us now check and see whether our receiving address is # behaving as a mailing list if BMConfigParser().safeGetBoolean(toAddress, 'mailinglist') and messageEncodingType != 0: try: mailingListName = BMConfigParser().get( toAddress, 'mailinglistname') except: mailingListName = '' # Let us send out this message as a broadcast subject = self.addMailingListNameToSubject( subject, mailingListName) # Let us now send this message out as a broadcast message = time.strftime("%a, %Y-%m-%d %H:%M:%S UTC", time.gmtime( )) + ' Message ostensibly from ' + fromAddress + ':\n\n' + body fromAddress = toAddress # The fromAddress for the broadcast that we are about to send is the toAddress (my address) for the msg message we are currently processing. ackdataForBroadcast = OpenSSL.rand( 32) # We don't actually need the ackdataForBroadcast for acknowledgement since this is a broadcast message but we can use it to update the user interface when the POW is done generating. toAddress = '[Broadcast subscribers]' ripe = '' # We really should have a discussion about how to # set the TTL for mailing list broadcasts. This is obviously # hard-coded. TTL = 2*7*24*60*60 # 2 weeks t = ('', toAddress, ripe, fromAddress, subject, message, ackdataForBroadcast, int(time.time()), # sentTime (this doesn't change) int(time.time()), # lastActionTime 0, 'broadcastqueued', 0, 'sent', messageEncodingType, TTL) helper_sent.insert(t) queues.UISignalQueue.put(('displayNewSentMessage', ( toAddress, '[Broadcast subscribers]', fromAddress, subject, message, ackdataForBroadcast))) queues.workerQueue.put(('sendbroadcast', '')) # Don't send ACK if invalid, blacklisted senders, invisible messages, disabled or chan if self.ackDataHasAValidHeader(ackData) and \ not blockMessage and \ messageEncodingType != 0 and \ not BMConfigParser().safeGetBoolean(toAddress, 'dontsendack') and \ not BMConfigParser().safeGetBoolean(toAddress, 'chan'): shared.checkAndShareObjectWithPeers(ackData[24:]) # Display timing data timeRequiredToAttemptToDecryptMessage = time.time( ) - messageProcessingStartTime shared.successfullyDecryptMessageTimings.append( timeRequiredToAttemptToDecryptMessage) sum = 0 for item in shared.successfullyDecryptMessageTimings: sum += item logger.debug('Time to decrypt this message successfully: %s\n\ Average time for all message decryption successes since startup: %s.' % (timeRequiredToAttemptToDecryptMessage, sum / len(shared.successfullyDecryptMessageTimings)) )
def processbroadcast(self, data): messageProcessingStartTime = time.time() shared.numberOfBroadcastsProcessed += 1 queues.UISignalQueue.put( ('updateNumberOfBroadcastsProcessed', 'no data')) inventoryHash = calculateInventoryHash(data) readPosition = 20 # bypass the nonce, time, and object type broadcastVersion, broadcastVersionLength = decodeVarint( data[readPosition:readPosition + 9]) readPosition += broadcastVersionLength if broadcastVersion < 4 or broadcastVersion > 5: logger.info( 'Cannot decode incoming broadcast versions less than 4' ' or higher than 5. Assuming the sender isn\'t being silly,' ' you should upgrade Bitmessage because this message shall' ' be ignored.') return cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint( data[readPosition:readPosition + 10]) readPosition += cleartextStreamNumberLength if broadcastVersion == 4: # v4 broadcasts are encrypted the same way the msgs are # encrypted. To see if we are interested in a v4 broadcast, # we try to decrypt it. This was replaced with v5 broadcasts # which include a tag which we check instead, just like we do # with v4 pubkeys. signedData = data[8:readPosition] initialDecryptionSuccessful = False for key, cryptorObject in sorted( shared.MyECSubscriptionCryptorObjects.items(), key=lambda x: random.random()): try: # continue decryption attempts to avoid timing attacks if initialDecryptionSuccessful: cryptorObject.decrypt(data[readPosition:]) else: decryptedData = cryptorObject.decrypt( data[readPosition:]) # This is the RIPE hash of the sender's pubkey. # We need this below to compare to the RIPE hash # of the sender's address to verify that it was # encrypted by with their key rather than some # other key. toRipe = key initialDecryptionSuccessful = True logger.info( 'EC decryption successful using key associated' ' with ripe hash: %s', hexlify(key)) except Exception: logger.debug('cryptorObject.decrypt Exception:', exc_info=True) if not initialDecryptionSuccessful: # This is not a broadcast I am interested in. logger.debug( 'Length of time program spent failing to decrypt this' ' v4 broadcast: %s seconds.', time.time() - messageProcessingStartTime) return elif broadcastVersion == 5: embeddedTag = data[readPosition:readPosition + 32] readPosition += 32 if embeddedTag not in shared.MyECSubscriptionCryptorObjects: logger.debug('We\'re not interested in this broadcast.') return # We are interested in this broadcast because of its tag. # We're going to add some more data which is signed further down. signedData = data[8:readPosition] cryptorObject = shared.MyECSubscriptionCryptorObjects[embeddedTag] try: decryptedData = cryptorObject.decrypt(data[readPosition:]) logger.debug('EC decryption successful') except Exception: logger.debug('Broadcast version %s decryption Unsuccessful.', broadcastVersion) return # At this point this is a broadcast I have decrypted and am # interested in. readPosition = 0 sendersAddressVersion, sendersAddressVersionLength = decodeVarint( decryptedData[readPosition:readPosition + 9]) if broadcastVersion == 4: if sendersAddressVersion < 2 or sendersAddressVersion > 3: logger.warning( 'Cannot decode senderAddressVersion other than 2 or 3.' ' Assuming the sender isn\'t being silly, you should' ' upgrade Bitmessage because this message shall be' ' ignored.') return elif broadcastVersion == 5: if sendersAddressVersion < 4: logger.info( 'Cannot decode senderAddressVersion less than 4 for' ' broadcast version number 5. Assuming the sender' ' isn\'t being silly, you should upgrade Bitmessage' ' because this message shall be ignored.') return readPosition += sendersAddressVersionLength sendersStream, sendersStreamLength = decodeVarint( decryptedData[readPosition:readPosition + 9]) if sendersStream != cleartextStreamNumber: logger.info( 'The stream number outside of the encryption on which the' ' POW was completed doesn\'t match the stream number' ' inside the encryption. Ignoring broadcast.') return readPosition += sendersStreamLength readPosition += 4 sendersPubSigningKey = '\x04' + \ decryptedData[readPosition:readPosition + 64] readPosition += 64 sendersPubEncryptionKey = '\x04' + \ decryptedData[readPosition:readPosition + 64] readPosition += 64 if sendersAddressVersion >= 3: requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = \ decodeVarint(decryptedData[readPosition:readPosition + 10]) readPosition += varintLength logger.debug( 'sender\'s requiredAverageProofOfWorkNonceTrialsPerByte' ' is %s', requiredAverageProofOfWorkNonceTrialsPerByte) requiredPayloadLengthExtraBytes, varintLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += varintLength logger.debug('sender\'s requiredPayloadLengthExtraBytes is %s', requiredPayloadLengthExtraBytes) endOfPubkeyPosition = readPosition sha = hashlib.new('sha512') sha.update(sendersPubSigningKey + sendersPubEncryptionKey) ripeHasher = hashlib.new('ripemd160') ripeHasher.update(sha.digest()) calculatedRipe = ripeHasher.digest() if broadcastVersion == 4: if toRipe != calculatedRipe: logger.info('The encryption key used to encrypt this message' ' doesn\'t match the keys inbedded in the message' ' itself. Ignoring message.') return elif broadcastVersion == 5: calculatedTag = hashlib.sha512( hashlib.sha512( encodeVarint(sendersAddressVersion) + encodeVarint(sendersStream) + calculatedRipe).digest()).digest()[32:] if calculatedTag != embeddedTag: logger.debug('The tag and encryption key used to encrypt this' ' message doesn\'t match the keys inbedded in the' ' message itself. Ignoring message.') return messageEncodingType, messageEncodingTypeLength = decodeVarint( decryptedData[readPosition:readPosition + 9]) if messageEncodingType == 0: return readPosition += messageEncodingTypeLength messageLength, messageLengthLength = decodeVarint( decryptedData[readPosition:readPosition + 9]) readPosition += messageLengthLength message = decryptedData[readPosition:readPosition + messageLength] readPosition += messageLength readPositionAtBottomOfMessage = readPosition signatureLength, signatureLengthLength = decodeVarint( decryptedData[readPosition:readPosition + 9]) readPosition += signatureLengthLength signature = decryptedData[readPosition:readPosition + signatureLength] signedData += decryptedData[:readPositionAtBottomOfMessage] if not highlevelcrypto.verify(signedData, signature, hexlify(sendersPubSigningKey)): logger.debug('ECDSA verify failed') return logger.debug('ECDSA verify passed') # Used to detect and ignore duplicate messages in our inbox sigHash = hashlib.sha512( hashlib.sha512(signature).digest()).digest()[32:] fromAddress = encodeAddress(sendersAddressVersion, sendersStream, calculatedRipe) logger.info('fromAddress: %s' % fromAddress) # Let's store the public key in case we want to reply to this person. sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', fromAddress, sendersAddressVersion, decryptedData[:endOfPubkeyPosition], int(time.time()), 'yes') # Check to see whether we happen to be awaiting this # pubkey in order to send a message. If we are, it will do the POW # and send it. self.possibleNewPubkey(fromAddress) fromAddress = encodeAddress(sendersAddressVersion, sendersStream, calculatedRipe) logger.debug('fromAddress: ' + fromAddress) try: decodedMessage = helper_msgcoding.MsgDecode( messageEncodingType, message) except helper_msgcoding.MsgDecodeException: return subject = decodedMessage.subject body = decodedMessage.body toAddress = '[Broadcast subscribers]' if helper_inbox.isMessageAlreadyInInbox(sigHash): logger.info('This broadcast is already in our inbox. Ignoring it.') return t = (inventoryHash, toAddress, fromAddress, subject, int(time.time()), body, 'inbox', messageEncodingType, 0, sigHash) helper_inbox.insert(t) queues.UISignalQueue.put( ('displayNewInboxMessage', (inventoryHash, toAddress, fromAddress, subject, body))) # If we are behaving as an API then we might need to run an # outside command to let some program know that a new message # has arrived. if BMConfigParser().safeGetBoolean('bitmessagesettings', 'apienabled'): try: apiNotifyPath = BMConfigParser().get('bitmessagesettings', 'apinotifypath') except: apiNotifyPath = '' if apiNotifyPath != '': call([apiNotifyPath, "newBroadcast"]) # Display timing data logger.info('Time spent processing this interesting broadcast: %s', time.time() - messageProcessingStartTime)