From e898b402034ae90e9b3708b831498c707c9ee274 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Thu, 25 Dec 2014 03:57:34 -0500 Subject: [PATCH] Removed obsolete Protocol v2 code --- src/class_objectProcessor.py | 595 +++++++++++------------------------ src/class_singleWorker.py | 251 +++++---------- src/class_sqlThread.py | 22 ++ src/pyelliptic/ecc.py | 24 +- src/pyelliptic/hash.py | 26 ++ src/shared.py | 51 +-- 6 files changed, 326 insertions(+), 643 deletions(-) diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index 73572c34..0400ae7b 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -184,13 +184,15 @@ class objectProcessor(threading.Thread): readPosition += 4 publicSigningKey = data[readPosition:readPosition + 64] # Is it possible for a public key to be invalid such that trying to - # encrypt or sign with it will cause an error? If it is, we should - # probably test these keys here. + # encrypt or sign with it will cause an error? If it is, it would + # be easiest to test them here. readPosition += 64 publicEncryptionKey = data[readPosition:readPosition + 64] if len(publicEncryptionKey) < 64: logger.debug('publicEncryptionKey length less than 64. Sanity check failed.') return + readPosition += 64 + dataToStore = data[20:readPosition] # The data we'll store in the pubkeys table. sha = hashlib.new('sha512') sha.update( '\x04' + publicSigningKey + '\x04' + publicEncryptionKey) @@ -213,17 +215,6 @@ class objectProcessor(threading.Thread): queryreturn = sqlQuery( '''SELECT usedpersonally FROM pubkeys WHERE hash=? AND addressversion=? AND usedpersonally='yes' ''', ripe, addressVersion) - """ - With the changes in protocol v3, we have to be careful to store pubkey data - in the database the same way we did before to maintain backwards compatibility - with what is in people's databases already. This means that for v2 keys, we - must store the nonce, the time, and then everything else starting with the - address version. - """ - dataToStore = '\x00' * 8 # fake nonce - dataToStore += data[8:16] # the time - dataToStore += data[20:] # everything else - if queryreturn != []: # if this pubkey is already in our database and if we have used it personally: logger.info('We HAVE used this pubkey personally. Updating time.') t = (ripe, addressVersion, dataToStore, int(time.time()), 'yes') @@ -249,36 +240,16 @@ class objectProcessor(threading.Thread): data[readPosition:readPosition + 10]) readPosition += specifiedPayloadLengthExtraBytesLength endOfSignedDataPosition = readPosition + dataToStore = data[20:readPosition] # The data we'll store in the pubkeys table. signatureLength, signatureLengthLength = decodeVarint( data[readPosition:readPosition + 10]) readPosition += signatureLengthLength signature = data[readPosition:readPosition + signatureLength] - """ - With the changes in protocol v3, to maintain backwards compatibility, signatures will be sent - the 'old' way during an upgrade period and then a 'new' simpler way after that. We will therefore - check the sig both ways. - Old way: - signedData = timePubkeyWasSigned(4 bytes) + addressVersion through extra_bytes - New way: - signedData = all of the payload data, from the time down through the extra_bytes - - The timePubkeyWasSigned will be calculated by subtracting 28 days form the embedded expiresTime. - """ - expiresTime, = unpack('>Q', data[8:16]) - TTL = 28 * 24 * 60 * 60 - signedData = pack('>I', (expiresTime - TTL)) # the time that the pubkey was signed. 4 bytes. - signedData += data[20:endOfSignedDataPosition] # the address version down through the payloadLengthExtraBytes - - if highlevelcrypto.verify(signedData, signature, publicSigningKey.encode('hex')): - logger.info('ECDSA verify passed (within processpubkey, old method)') + if highlevelcrypto.verify(data[8:endOfSignedDataPosition], signature, publicSigningKey.encode('hex')): + logger.debug('ECDSA verify passed (within processpubkey)') else: - logger.warning('ECDSA verify failed (within processpubkey, old method)') - # let us try the newer signature method - if highlevelcrypto.verify(data[8:endOfSignedDataPosition], signature, publicSigningKey.encode('hex')): - logger.info('ECDSA verify passed (within processpubkey, new method)') - else: - logger.warning('ECDSA verify failed (within processpubkey, new method)') - return + logger.warning('ECDSA verify failed (within processpubkey)') + return sha = hashlib.new('sha512') sha.update(publicSigningKey + publicEncryptionKey) @@ -299,17 +270,6 @@ class objectProcessor(threading.Thread): ) - """ - With the changes in protocol v3, we have to be careful to store pubkey data - in the database the same way we did before to maintain backwards compatibility - with what is in people's databases already. This means that for v3 keys, we - must store the nonce, the time, and then everything else starting with the - address version. - """ - dataToStore = '\x00' * 8 # fake nonce - dataToStore += data[8:16] # the time - dataToStore += data[20:] # everything else - queryreturn = sqlQuery('''SELECT usedpersonally FROM pubkeys WHERE hash=? AND addressversion=? AND usedpersonally='yes' ''', ripe, addressVersion) if queryreturn != []: # if this pubkey is already in our database and if we have used it personally: logger.info('We HAVE used this pubkey personally. Updating time.') @@ -350,14 +310,11 @@ class objectProcessor(threading.Thread): shared.UISignalQueue.put(( 'updateNumberOfMessagesProcessed', 'no data')) readPosition = 20 # bypass the nonce, time, and object type - - """ - In protocol v2, the next byte(s) was the streamNumber. But starting after - the protocol v3 upgrade period, the next byte(s) will be a msg version - number followed by the streamNumber. - """ - #msgVersionOutsideEncryption, msgVersionOutsideEncryptionLength = decodeVarint(data[readPosition:readPosition + 9]) - #readPosition += msgVersionOutsideEncryptionLength + 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]) @@ -379,34 +336,16 @@ class objectProcessor(threading.Thread): # 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. - # This can be simplified quite a bit after 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT for key, cryptorObject in shared.myECCryptorObjects.items(): try: 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. msg did NOT specify version.' % key.encode('hex')) - - # We didn't bypass a msg version above as it is commented out. - # But the decryption was successful. Which means that there - # wasn't a msg version byte include in this msg. - msgObjectContainedVersion = False + logger.info('EC decryption successful using key associated with ripe hash: %s.' % key.encode('hex')) + #msgObjectContainedVersion = False break except Exception as err: - # What if a client sent us a msg with - # a msg version included? We didn't bypass it above. So - # let's try to decrypt the msg assuming that it is present. - try: - decryptedData = cryptorObject.decrypt(data[readPosition+1:]) # notice that we offset by 1 byte compared to the attempt above. - 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. msg DID specifiy version.' % key.encode('hex')) - - # There IS a msg version byte include in this msg. - msgObjectContainedVersion = True - break - except Exception as err: - pass + 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,)) @@ -416,15 +355,6 @@ class objectProcessor(threading.Thread): toAddress = shared.myAddressesByHash[ toRipe] # Look up my address based on the RIPE hash. readPosition = 0 - if not msgObjectContainedVersion: # by which I mean "if the msg object didn't have the msg version outside of the encryption". This confusingness will be removed after the protocol v3 upgrade period. - messageVersionWithinEncryption, messageVersionWithinEncryptionLength = decodeVarint( - decryptedData[readPosition:readPosition + 10]) - readPosition += messageVersionWithinEncryptionLength - if messageVersionWithinEncryption != 1: - logger.info('Cannot understand message versions other than one. Ignoring message.') - return - else: - messageVersionWithinEncryptionLength = 0 # This variable can disappear after the protocol v3 upgrade period is complete. sendersAddressVersionNumber, sendersAddressVersionNumberLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += sendersAddressVersionNumberLength @@ -489,12 +419,7 @@ class objectProcessor(threading.Thread): readPosition += signatureLengthLength signature = decryptedData[ readPosition:readPosition + signatureLength] - if not msgObjectContainedVersion: - # protocol v2. This can be removed after the end of the protocol v3 upgrade period. - signedData = decryptedData[:positionOfBottomOfAckData] - else: - # protocol v3 - signedData = data[8:20] + encodeVarint(1) + encodeVarint(streamNumberAsClaimedByMsg) + decryptedData[:positionOfBottomOfAckData] + signedData = data[8:20] + encodeVarint(1) + encodeVarint(streamNumberAsClaimedByMsg) + decryptedData[:positionOfBottomOfAckData] if not highlevelcrypto.verify(signedData, signature, pubSigningKey.encode('hex')): logger.debug('ECDSA verify failed') @@ -511,32 +436,25 @@ class objectProcessor(threading.Thread): 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 (?,?,?,?,?)''', + ripe.digest(), + 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. if sendersAddressVersionNumber <= 3: - sqlExecute( - '''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', - ripe.digest(), - sendersAddressVersionNumber, - '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + '\xFF\xFF\xFF\xFF' + decryptedData[messageVersionWithinEncryptionLength:endOfThePublicKeyPosition], - int(time.time()), - 'yes') - # This will 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(ripe=ripe.digest()) elif sendersAddressVersionNumber >= 4: - sqlExecute( - '''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', - ripe.digest(), - sendersAddressVersionNumber, - '\x00\x00\x00\x00\x00\x00\x00\x01' + decryptedData[messageVersionWithinEncryptionLength:endOfThePublicKeyPosition], - int(time.time()), - 'yes') - # This will 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(address = 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 @@ -665,22 +583,19 @@ class objectProcessor(threading.Thread): broadcastVersion, broadcastVersionLength = decodeVarint( data[readPosition:readPosition + 9]) readPosition += broadcastVersionLength - if broadcastVersion < 1 or broadcastVersion > 5: - logger.info('Cannot decode incoming broadcast versions higher than 5. Assuming the sender isn\'t being silly, you should upgrade Bitmessage because this message shall be ignored.') + 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 - if broadcastVersion == 1: - logger.info('Version 1 broadcasts are no longer supported. Not processing it at all.') - if broadcastVersion in [2,4]: + cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint( + data[readPosition:readPosition + 10]) + readPosition += cleartextStreamNumberLength + if broadcastVersion == 4: """ - v2 (and later v4) broadcasts are encrypted the same way the msgs were encrypted. To see if we are interested in a - v2 broadcast, we try to decrypt it. This was replaced with v3 (and later v5) broadcasts which include a tag which + 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. - v2 and v3 broadcasts should be completely obsolete after the protocol v3 upgrade period and some code can be simplified. """ - cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint( - data[readPosition:readPosition + 10]) - readPosition += cleartextStreamNumberLength - signedData = data[8:readPosition] # This doesn't end up being used if the broadcastVersion is 2 + signedData = data[8:readPosition] initialDecryptionSuccessful = False for key, cryptorObject in shared.MyECSubscriptionCryptorObjects.items(): try: @@ -694,145 +609,9 @@ class objectProcessor(threading.Thread): # print 'cryptorObject.decrypt Exception:', err if not initialDecryptionSuccessful: # This is not a broadcast I am interested in. - logger.debug('Length of time program spent failing to decrypt this v2 broadcast: %s seconds.' % (time.time() - messageProcessingStartTime,)) + logger.debug('Length of time program spent failing to decrypt this v4 broadcast: %s seconds.' % (time.time() - messageProcessingStartTime,)) return - # At this point this is a broadcast I have decrypted and thus am - # interested in. - readPosition = 0 - if broadcastVersion == 2: - signedBroadcastVersion, signedBroadcastVersionLength = decodeVarint( - decryptedData[:10]) - readPosition += signedBroadcastVersionLength - - beginningOfPubkeyPosition = readPosition # used when we add the pubkey to our pubkey table. This variable can be disposed of after the protocol v3 upgrade period because it will necessarily be at the beginning of the decryptedData; ie it will definitely equal 0 - sendersAddressVersion, sendersAddressVersionLength = decodeVarint( - decryptedData[readPosition:readPosition + 9]) - if sendersAddressVersion < 2 or sendersAddressVersion > 3: - logger.info('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 - 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 - behaviorBitfield = decryptedData[readPosition:readPosition + 4] - 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) - ripe = hashlib.new('ripemd160') - ripe.update(sha.digest()) - - if toRipe != ripe.digest(): - logger.info('The 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] - if broadcastVersion == 2: # this can be removed after the protocol v3 upgrade period - signedData = decryptedData[:readPositionAtBottomOfMessage] - else: - signedData += decryptedData[:readPositionAtBottomOfMessage] - if not highlevelcrypto.verify(signedData, signature, sendersPubSigningKey.encode('hex')): - logger.debug('ECDSA verify failed') - return - logger.debug('ECDSA verify passed') - # verify passed - - # Let's store the public key in case we want to reply to this - # person. - sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', - ripe.digest(), - sendersAddressVersion, - '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + '\xFF\xFF\xFF\xFF' + decryptedData[beginningOfPubkeyPosition:endOfPubkeyPosition], - int(time.time()), - 'yes') - # shared.workerQueue.put(('newpubkey',(sendersAddressVersion,sendersStream,ripe.digest()))) - # This will 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(ripe=ripe.digest()) - - fromAddress = encodeAddress( - sendersAddressVersion, sendersStream, ripe.digest()) - with shared.printLock: - print 'fromAddress:', fromAddress - - if messageEncodingType == 2: - subject, body = self.decodeType2Message(message) - logger.info('Broadcast subject (first 100 characters): %s' % repr(subject)[:100]) - elif messageEncodingType == 1: - body = message - subject = '' - elif messageEncodingType == 0: - logger.info('messageEncodingType == 0. Doing nothing with the message.') - else: - body = 'Unknown encoding type.\n\n' + repr(message) - subject = '' - - toAddress = '[Broadcast subscribers]' - if messageEncodingType != 0: - if helper_inbox.isMessageAlreadyInInbox(toAddress, fromAddress, subject, body, messageEncodingType): - logger.info('This broadcast is already in our inbox. Ignoring it.') - else: - t = (inventoryHash, toAddress, fromAddress, subject, int( - time.time()), body, 'inbox', messageEncodingType, 0) - helper_inbox.insert(t) - - shared.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 shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'): - try: - apiNotifyPath = shared.config.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,)) - - if broadcastVersion in [3,5]: - # broadcast version 3 should be completely obsolete after the end of the protocol v3 upgrade period - cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint( - data[readPosition:readPosition + 10]) - readPosition += cleartextStreamNumberLength + elif broadcastVersion == 5: embeddedTag = data[readPosition:readPosition+32] readPosition += 32 if embeddedTag not in shared.MyECSubscriptionCryptorObjects: @@ -847,153 +626,161 @@ class objectProcessor(threading.Thread): except Exception as err: logger.debug('Broadcast version %s decryption Unsuccessful.' % broadcastVersion) return - - # broadcast version 3 includes the broadcast version at the beginning - # of the decryptedData. Broadcast version 5 doesn't. - readPosition = 0 - if broadcastVersion == 3: # This section can be removed after the protocol v3 upgrade period - signedBroadcastVersion, signedBroadcastVersionLength = decodeVarint( - decryptedData[:10]) - readPosition += signedBroadcastVersionLength - - beginningOfPubkeyPosition = readPosition # used when we add the pubkey to our pubkey table - sendersAddressVersion, sendersAddressVersionLength = decodeVarint( - decryptedData[readPosition:readPosition + 9]) + # 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 3 or 4. Assuming the sender isn\'t being silly, you should upgrade Bitmessage because this message shall be ignored.') + 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.') + 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 + behaviorBitfield = decryptedData[readPosition:readPosition + 4] + 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 - readPosition += sendersStreamLength - behaviorBitfield = decryptedData[readPosition:readPosition + 4] - 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() - + 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] - if broadcastVersion == 3: # broadcastVersion 3 should be completely unused after the end of the protocol v3 upgrade period - signedData = decryptedData[:readPositionAtBottomOfMessage] - elif broadcastVersion == 5: - signedData += decryptedData[:readPositionAtBottomOfMessage] - if not highlevelcrypto.verify(signedData, signature, sendersPubSigningKey.encode('hex')): - logger.debug('ECDSA verify failed') - return - logger.debug('ECDSA verify passed') - # verify passed + 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, sendersPubSigningKey.encode('hex')): + logger.debug('ECDSA verify failed') + return + logger.debug('ECDSA verify passed') - fromAddress = encodeAddress( - sendersAddressVersion, sendersStream, calculatedRipe) - logger.info('fromAddress: %s' % fromAddress) + 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 (?,?,?,?,?)''', - calculatedRipe, - sendersAddressVersion, - '\x00\x00\x00\x00\x00\x00\x00\x01' + decryptedData[beginningOfPubkeyPosition:endOfPubkeyPosition], - int(time.time()), - 'yes') - # This will 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(address = fromAddress) + # Let's store the public key in case we want to reply to this person. + sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', + calculatedRipe, + sendersAddressVersion, + decryptedData[:endOfPubkeyPosition], + int(time.time()), + 'yes') - if messageEncodingType == 2: - subject, body = self.decodeType2Message(message) - logger.info('Broadcast subject (first 100 characters): %s' % repr(subject)[:100]) - elif messageEncodingType == 1: - body = message - subject = '' - elif messageEncodingType == 0: - logger.info('messageEncodingType == 0. Doing nothing with the message.') + # 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. + if broadcastVersion == 4: + self.possibleNewPubkey(ripe=calculatedRipe) + elif broadcastVersion == 5: + self.possibleNewPubkey(address=fromAddress) + + fromAddress = encodeAddress( + sendersAddressVersion, sendersStream, calculatedRipe) + with shared.printLock: + print 'fromAddress:', fromAddress + + if messageEncodingType == 2: + subject, body = self.decodeType2Message(message) + logger.info('Broadcast subject (first 100 characters): %s' % repr(subject)[:100]) + elif messageEncodingType == 1: + body = message + subject = '' + elif messageEncodingType == 0: + logger.info('messageEncodingType == 0. Doing nothing with the message.') + else: + body = 'Unknown encoding type.\n\n' + repr(message) + subject = '' + + toAddress = '[Broadcast subscribers]' + if messageEncodingType != 0: + if helper_inbox.isMessageAlreadyInInbox(toAddress, fromAddress, subject, body, messageEncodingType): + logger.info('This broadcast is already in our inbox. Ignoring it.') else: - body = 'Unknown encoding type.\n\n' + repr(message) - subject = '' + t = (inventoryHash, toAddress, fromAddress, subject, int( + time.time()), body, 'inbox', messageEncodingType, 0) + helper_inbox.insert(t) - toAddress = '[Broadcast subscribers]' - if messageEncodingType != 0: - if helper_inbox.isMessageAlreadyInInbox(toAddress, fromAddress, subject, body, messageEncodingType): - logger.info('This broadcast is already in our inbox. Ignoring it.') - else: - t = (inventoryHash, toAddress, fromAddress, subject, int( - time.time()), body, 'inbox', messageEncodingType, 0) - helper_inbox.insert(t) - - shared.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 shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'): - try: - apiNotifyPath = shared.config.get( - 'bitmessagesettings', 'apinotifypath') - except: - apiNotifyPath = '' - if apiNotifyPath != '': - call([apiNotifyPath, "newBroadcast"]) + shared.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 shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'): + try: + apiNotifyPath = shared.config.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,)) - # Display timing data - logger.debug('Time spent processing this interesting broadcast: %s' % (time.time() - messageProcessingStartTime,)) # We have inserted a pubkey into our pubkey table which we received from a # pubkey, msg, or broadcast message. It might be one that we have been # waiting for. Let's check. def possibleNewPubkey(self, ripe=None, address=None): + """ + We have put a new pubkey in our pubkeys table. Let's see if we have been + awaiting it. + """ # For address versions <= 3, we wait on a key with the correct ripe hash if ripe != None: if ripe in shared.neededPubkeys: - logger.info('We have been awaiting the arrival of this pubkey.') del shared.neededPubkeys[ripe] - sqlExecute( - '''UPDATE sent SET status='doingmsgpow' WHERE toripe=? AND (status='awaitingpubkey' or status='doingpubkeypow') and folder='sent' ''', - ripe) - shared.workerQueue.put(('sendmessage', '')) + self.sendMessages(ripe) else: logger.debug('We don\'t need this pub key. We didn\'t ask for it. Pubkey hash: %s' % ripe.encode('hex')) # For address versions >= 4, we wait on a pubkey with the correct tag. @@ -1004,12 +791,20 @@ class objectProcessor(threading.Thread): tag = hashlib.sha512(hashlib.sha512(encodeVarint( addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()[32:] if tag in shared.neededPubkeys: - logger.info('We have been awaiting the arrival of this pubkey.') del shared.neededPubkeys[tag] - sqlExecute( - '''UPDATE sent SET status='doingmsgpow' WHERE toripe=? AND (status='awaitingpubkey' or status='doingpubkeypow') and folder='sent' ''', - ripe) - shared.workerQueue.put(('sendmessage', '')) + self.sendMessages(ripe) + + def sendMessages(self, ripe): + """ + This function is called by the possibleNewPubkey function when + that function sees that we now have the necessary pubkey + to send one or more messages. + """ + logger.info('We have been awaiting the arrival of this pubkey.') + sqlExecute( + '''UPDATE sent SET status='doingmsgpow' WHERE toripe=? AND (status='awaitingpubkey' or status='doingpubkeypow') and folder='sent' ''', + ripe) + shared.workerQueue.put(('sendmessage', '')) def ackDataHasAVaildHeader(self, ackData): if len(ackData) < shared.Header.size: @@ -1040,22 +835,6 @@ class objectProcessor(threading.Thread): return False return True - def decodeType2Message(self, message): - bodyPositionIndex = string.find(message, '\nBody:') - if bodyPositionIndex > 1: - subject = message[8:bodyPositionIndex] - # Only save and show the first 500 characters of the subject. - # Any more is probably an attack. - subject = subject[:500] - body = message[bodyPositionIndex + 6:] - else: - subject = '' - body = message - # Throw away any extra lines (headers) after the subject. - if subject: - subject = subject.splitlines()[0] - return subject, body - def addMailingListNameToSubject(self, subject, mailingListName): subject = subject.strip() if subject[:3] == 'Re:' or subject[:3] == 'RE:': diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index 541ecd98..4a88f6c0 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -219,12 +219,7 @@ class singleWorker(threading.Thread): payload += encodeVarint(shared.config.getint( myAddress, 'payloadlengthextrabytes')) - if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - signedData = pack('>I', signedTimeForProtocolV2) + payload[12:] - else: - signedData = payload - - signature = highlevelcrypto.sign(signedData, privSigningKeyHex) + signature = highlevelcrypto.sign(payload, privSigningKeyHex) payload += encodeVarint(len(signature)) payload += signature @@ -309,8 +304,6 @@ class singleWorker(threading.Thread): dataToEncrypt += encodeVarint(shared.config.getint( myAddress, 'payloadlengthextrabytes')) - - # When we encrypt, we'll use a hash of the data # contained in an address as a decryption key. This way in order to # read the public keys in a pubkey message, a node must know the address @@ -320,23 +313,7 @@ class singleWorker(threading.Thread): doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest() payload += doubleHashOfAddressData[32:] # the tag - - """ - According to the protocol specification, the expiresTime along with the pubkey information is - signed. But to be backwards compatible during the upgrade period, we shall sign not the - expiresTime but rather the current time. There must be precisely a 28 day difference - between the two. After the upgrade period we'll switch to signing the whole payload from - above appended with dataToEncrypt. - """ - if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - dataToSign = pack('>Q', (embeddedTime - TTL)) - dataToSign += encodeVarint(addressVersionNumber) # Address version number - dataToSign += encodeVarint(streamNumber) - dataToSign += dataToEncrypt - else: - dataToSign = payload + dataToEncrypt - - signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex) + signature = highlevelcrypto.sign(payload + dataToEncrypt, privSigningKeyHex) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature @@ -411,17 +388,11 @@ class singleWorker(threading.Thread): embeddedTime = int(time.time() + TTL) payload = pack('>Q', embeddedTime) payload += '\x00\x00\x00\x03' # object type: broadcast - - if int(time.time()) < 1416175200: # Before Sun, 16 Nov 2014 22:00:00 GMT - if addressVersionNumber <= 3: - payload += encodeVarint(2) # broadcast version - else: - payload += encodeVarint(3) # broadcast version - else: # After Sun, 16 Nov 2014 22:00:00 GMT - if addressVersionNumber <= 3: - payload += encodeVarint(4) # broadcast version - else: - payload += encodeVarint(5) # broadcast version + + if addressVersionNumber <= 3: + payload += encodeVarint(4) # broadcast version + else: + payload += encodeVarint(5) # broadcast version payload += encodeVarint(streamNumber) if addressVersionNumber >= 4: @@ -432,14 +403,7 @@ class singleWorker(threading.Thread): else: tag = '' - dataToEncrypt = "" - # the broadcast version is not included here after the end of the protocol v3 upgrade period - if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - if addressVersionNumber <= 3: - dataToEncrypt += encodeVarint(2) # broadcast version - else: - dataToEncrypt += encodeVarint(3) # broadcast version - dataToEncrypt += encodeVarint(addressVersionNumber) + dataToEncrypt = encodeVarint(addressVersionNumber) dataToEncrypt += encodeVarint(streamNumber) dataToEncrypt += '\x00\x00\x00\x01' # behavior bitfield dataToEncrypt += pubSigningKey[1:] @@ -450,10 +414,7 @@ class singleWorker(threading.Thread): dataToEncrypt += '\x02' # message encoding type dataToEncrypt += encodeVarint(len('Subject:' + subject + '\n' + 'Body:' + body)) #Type 2 is simple UTF-8 message encoding per the documentation on the wiki. dataToEncrypt += 'Subject:' + subject + '\n' + 'Body:' + body - if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - dataToSign = dataToEncrypt - else: - dataToSign = payload + dataToEncrypt + dataToSign = payload + dataToEncrypt signature = highlevelcrypto.sign( dataToSign, privSigningKeyHex) @@ -646,25 +607,16 @@ class singleWorker(threading.Thread): for row in queryreturn: pubkeyPayload, = row - # The pubkey message is stored the way we originally received it - # under protocol version 2 - # which means that we need to read beyond things like the nonce and - # time to get to the actual public keys. - if toAddressVersionNumber <= 3: - readPosition = 8 # to bypass the nonce - elif toAddressVersionNumber >= 4: - readPosition = 0 # the nonce is not included here so we don't need to skip over it. - pubkeyEmbeddedTime, = unpack( - '>I', pubkeyPayload[readPosition:readPosition + 4]) - # This section is used for the transition from 32 bit time to 64 - # bit time in the protocol. - if pubkeyEmbeddedTime == 0: - pubkeyEmbeddedTime, = unpack( - '>Q', pubkeyPayload[readPosition:readPosition + 8]) - readPosition += 8 - else: - readPosition += 4 - readPosition += 1 # to bypass the address version whose length is definitely 1 + # The pubkey message is stored with the following items all appended: + # -address version + # -stream number + # -behavior bitfield + # -pub signing key + # -pub encryption key + # -nonce trials per byte (if address version is >= 3) + # -length extra bytes (if address version is >= 3) + + readPosition = 1 # to bypass the address version whose length is definitely 1 streamNumber, streamNumberLength = decodeVarint( pubkeyPayload[readPosition:readPosition + 10]) readPosition += streamNumberLength @@ -679,9 +631,7 @@ class singleWorker(threading.Thread): # if the human changes their setting and then sends another message or restarts their client, this one will send at that time. continue readPosition += 4 # to bypass the bitfield of behaviors - # pubSigningKeyBase256 = - # pubkeyPayload[readPosition:readPosition+64] #We don't use this - # key for anything here. + # pubSigningKeyBase256 = pubkeyPayload[readPosition:readPosition+64] # We don't use this key for anything here. readPosition += 64 pubEncryptionKeyBase256 = pubkeyPayload[ readPosition:readPosition + 64] @@ -740,91 +690,38 @@ class singleWorker(threading.Thread): shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, tr.translateText("MainWindow", "Doing work necessary to send message.")))) - if fromAddressVersionNumber == 2: - payload = "" - if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - payload += '\x01' # Message version. - payload += encodeVarint(fromAddressVersionNumber) - payload += encodeVarint(fromStreamNumber) - payload += '\x00\x00\x00\x01' # Bitfield of features and behaviors that can be expected from me. (See https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features ) + # Now we can start to assemble our message. + payload = encodeVarint(fromAddressVersionNumber) + payload += encodeVarint(fromStreamNumber) + payload += '\x00\x00\x00\x01' # Bitfield of features and behaviors that can be expected from me. (See https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features ) - # We need to convert our private keys to public keys in order - # to include them. - try: - privSigningKeyBase58 = shared.config.get( - fromaddress, 'privsigningkey') - privEncryptionKeyBase58 = shared.config.get( - fromaddress, 'privencryptionkey') - except: - shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( - ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) - continue + # We need to convert our private keys to public keys in order + # to include them. + try: + privSigningKeyBase58 = shared.config.get( + fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = shared.config.get( + fromaddress, 'privencryptionkey') + except: + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) + continue - privSigningKeyHex = shared.decodeWalletImportFormat( - privSigningKeyBase58).encode('hex') - privEncryptionKeyHex = shared.decodeWalletImportFormat( - privEncryptionKeyBase58).encode('hex') + privSigningKeyHex = shared.decodeWalletImportFormat( + privSigningKeyBase58).encode('hex') + privEncryptionKeyHex = shared.decodeWalletImportFormat( + privEncryptionKeyBase58).encode('hex') - pubSigningKey = highlevelcrypto.privToPub( - privSigningKeyHex).decode('hex') - pubEncryptionKey = highlevelcrypto.privToPub( - privEncryptionKeyHex).decode('hex') + pubSigningKey = highlevelcrypto.privToPub( + privSigningKeyHex).decode('hex') + pubEncryptionKey = highlevelcrypto.privToPub( + privEncryptionKeyHex).decode('hex') - payload += pubSigningKey[ - 1:] # The \x04 on the beginning of the public keys are not sent. This way there is only one acceptable way to encode and send a public key. - payload += pubEncryptionKey[1:] - - payload += toRipe # This hash will be checked by the receiver of the message to verify that toRipe belongs to them. This prevents a Surreptitious Forwarding Attack. - payload += '\x02' # Type 2 is simple UTF-8 message encoding as specified on the Protocol Specification on the Bitmessage Wiki. - messageToTransmit = 'Subject:' + \ - subject + '\n' + 'Body:' + message - payload += encodeVarint(len(messageToTransmit)) - payload += messageToTransmit - fullAckPayload = self.generateFullAckMessage( - ackdata, toStreamNumber) # The fullAckPayload is a normal msg protocol message with the proof of work already completed that the receiver of this message can easily send out. - payload += encodeVarint(len(fullAckPayload)) - payload += fullAckPayload - if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - dataToSign = payload - else: - dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + encodeVarint(1) + encodeVarint(toStreamNumber) + payload - signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex) - payload += encodeVarint(len(signature)) - payload += signature + payload += pubSigningKey[ + 1:] # The \x04 on the beginning of the public keys are not sent. This way there is only one acceptable way to encode and send a public key. + payload += pubEncryptionKey[1:] if fromAddressVersionNumber >= 3: - payload = "" - if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - payload += '\x01' # Message version. - payload += encodeVarint(fromAddressVersionNumber) - payload += encodeVarint(fromStreamNumber) - payload += '\x00\x00\x00\x01' # Bitfield of features and behaviors that can be expected from me. (See https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features ) - - # We need to convert our private keys to public keys in order - # to include them. - try: - privSigningKeyBase58 = shared.config.get( - fromaddress, 'privsigningkey') - privEncryptionKeyBase58 = shared.config.get( - fromaddress, 'privencryptionkey') - except: - shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( - ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) - continue - - privSigningKeyHex = shared.decodeWalletImportFormat( - privSigningKeyBase58).encode('hex') - privEncryptionKeyHex = shared.decodeWalletImportFormat( - privEncryptionKeyBase58).encode('hex') - - pubSigningKey = highlevelcrypto.privToPub( - privSigningKeyHex).decode('hex') - pubEncryptionKey = highlevelcrypto.privToPub( - privEncryptionKeyHex).decode('hex') - - payload += pubSigningKey[ - 1:] # The \x04 on the beginning of the public keys are not sent. This way there is only one acceptable way to encode and send a public key. - payload += pubEncryptionKey[1:] # If the receiver of our message is in our address book, # subscriptions list, or whitelist then we will allow them to # do the network-minimum proof of work. Let us check to see if @@ -840,34 +737,30 @@ class singleWorker(threading.Thread): payload += encodeVarint(shared.config.getint( fromaddress, 'payloadlengthextrabytes')) - payload += toRipe # This hash will be checked by the receiver of the message to verify that toRipe belongs to them. This prevents a Surreptitious Forwarding Attack. - payload += '\x02' # Type 2 is simple UTF-8 message encoding as specified on the Protocol Specification on the Bitmessage Wiki. - messageToTransmit = 'Subject:' + \ - subject + '\n' + 'Body:' + message - payload += encodeVarint(len(messageToTransmit)) - payload += messageToTransmit - if shared.config.has_section(toaddress): - with shared.printLock: - print 'Not bothering to include ackdata because we are sending to ourselves or a chan.' - fullAckPayload = '' - elif not shared.isBitSetWithinBitfield(behaviorBitfield,31): - with shared.printLock: - print 'Not bothering to include ackdata because the receiver said that they won\'t relay it anyway.' - fullAckPayload = '' - else: - fullAckPayload = self.generateFullAckMessage( - ackdata, toStreamNumber) # The fullAckPayload is a normal msg protocol message with the proof of work already completed that the receiver of this message can easily send out. - payload += encodeVarint(len(fullAckPayload)) - payload += fullAckPayload - if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - dataToSign = payload - else: - dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + encodeVarint(1) + encodeVarint(toStreamNumber) + payload - signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex) - payload += encodeVarint(len(signature)) - payload += signature + payload += toRipe # This hash will be checked by the receiver of the message to verify that toRipe belongs to them. This prevents a Surreptitious Forwarding Attack. + payload += '\x02' # Type 2 is simple UTF-8 message encoding as specified on the Protocol Specification on the Bitmessage Wiki. + messageToTransmit = 'Subject:' + \ + subject + '\n' + 'Body:' + message + payload += encodeVarint(len(messageToTransmit)) + payload += messageToTransmit + if shared.config.has_section(toaddress): + with shared.printLock: + print 'Not bothering to include ackdata because we are sending to ourselves or a chan.' + fullAckPayload = '' + elif not shared.isBitSetWithinBitfield(behaviorBitfield,31): + with shared.printLock: + print 'Not bothering to include ackdata because the receiver said that they won\'t relay it anyway.' + fullAckPayload = '' + else: + fullAckPayload = self.generateFullAckMessage( + ackdata, toStreamNumber) # The fullAckPayload is a normal msg protocol message with the proof of work already completed that the receiver of this message can easily send out. + payload += encodeVarint(len(fullAckPayload)) + payload += fullAckPayload + dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + encodeVarint(1) + encodeVarint(toStreamNumber) + payload + signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex) + payload += encodeVarint(len(signature)) + payload += signature - print 'using pubEncryptionKey:', pubEncryptionKeyBase256.encode('hex') # We have assembled the data that will be encrypted. try: encrypted = highlevelcrypto.encrypt(payload,"04"+pubEncryptionKeyBase256.encode('hex')) @@ -878,8 +771,7 @@ class singleWorker(threading.Thread): encryptedPayload = pack('>Q', embeddedTime) encryptedPayload += '\x00\x00\x00\x02' # object type: msg - if int(time.time()) >= 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - encryptedPayload += encodeVarint(1) # msg version + encryptedPayload += encodeVarint(1) # msg version encryptedPayload += encodeVarint(toStreamNumber) + encrypted target = 2 ** 64 / (requiredAverageProofOfWorkNonceTrialsPerByte*(len(encryptedPayload) + 8 + requiredPayloadLengthExtraBytes + ((TTL*(len(encryptedPayload)+8+requiredPayloadLengthExtraBytes))/(2 ** 16)))) with shared.printLock: @@ -1019,8 +911,7 @@ class singleWorker(threading.Thread): embeddedTime = int(time.time() + TTL) payload = pack('>Q', (embeddedTime)) payload += '\x00\x00\x00\x02' # object type: msg - if int(time.time()) >= 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - payload += encodeVarint(1) # msg version + payload += encodeVarint(1) # msg version payload += encodeVarint(toStreamNumber) + ackdata target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 26c08154..ce4f837d 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -346,6 +346,28 @@ class sqlThread(threading.Thread): shared.config.set('bitmessagesettings', 'settingsversion', '10') with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) + + + # The format of data stored in the pubkeys table has changed. Let's + # clear it, and the pubkeys from inventory, so that they'll be re-downloaded. + item = '''SELECT value FROM settings WHERE key='version';''' + parameters = '' + self.cur.execute(item, parameters) + currentVersion = int(self.cur.fetchall()[0][0]) + if currentVersion == 7: + logger.debug('In messages.dat database, clearing pubkeys table because the data format has been updated.') + self.cur.execute( + '''delete from inventory where objecttype = 1;''') + self.cur.execute( + '''delete from pubkeys;''') + # Any sending messages for which we *thought* that we had the pubkey must + # be rechecked. + self.cur.execute( + '''UPDATE sent SET status='msgqueued' WHERE status='doingmsgpow' or status='badkey';''') + query = '''update settings set value=? WHERE key='version';''' + parameters = (8,) + self.cur.execute(query, parameters) + logger.debug('Finished clearing currently held pubkeys.') # Are you hoping to add a new option to the keys.dat file of existing diff --git a/src/pyelliptic/ecc.py b/src/pyelliptic/ecc.py index 31bcb1e7..44020be2 100644 --- a/src/pyelliptic/ecc.py +++ b/src/pyelliptic/ecc.py @@ -7,7 +7,7 @@ from hashlib import sha512 from pyelliptic.openssl import OpenSSL from pyelliptic.cipher import Cipher -from pyelliptic.hash import hmac_sha256 +from pyelliptic.hash import hmac_sha256, equals from struct import pack, unpack @@ -436,16 +436,9 @@ class ECC: pubkey = ephem.get_pubkey() iv = OpenSSL.rand(OpenSSL.get_cipher(ciphername).get_blocksize()) ctx = Cipher(key_e, iv, 1, ciphername) - import time - if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - ciphertext = ctx.ciphering(data) - else: - ciphertext = iv + pubkey + ctx.ciphering(data) # Everyone should be using this line after the Bitmessage protocol v3 upgrade period + ciphertext = iv + pubkey + ctx.ciphering(data) mac = hmac_sha256(key_m, ciphertext) - if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT - return iv + pubkey + ciphertext + mac - else: - return ciphertext + mac # Everyone should be using this line after the Bitmessage protocol v3 upgrade period + return ciphertext + mac def decrypt(self, data, ciphername='aes-256-cbc'): """ @@ -461,14 +454,7 @@ class ECC: mac = data[i:] key = sha512(self.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest() key_e, key_m = key[:32], key[32:] - """ - pyelliptic was changed slightly so that the hmac covers the - iv and pubkey. So let's have an upgrade period where we support - both the old and the new hmac'ing algorithms. - https://github.com/yann2192/pyelliptic/issues/17 - """ - if hmac_sha256(key_m, ciphertext) != mac: - if hmac_sha256(key_m, data[:len(data) - 32]) != mac: - raise RuntimeError("Fail to verify data") + if not equals(hmac_sha256(key_m, data[:len(data) - 32]), mac): + raise RuntimeError("Fail to verify data") ctx = Cipher(key_e, iv, 0, ciphername) return ctx.ciphering(ciphertext) diff --git a/src/pyelliptic/hash.py b/src/pyelliptic/hash.py index d8ade000..fb910dd4 100644 --- a/src/pyelliptic/hash.py +++ b/src/pyelliptic/hash.py @@ -7,6 +7,32 @@ from pyelliptic.openssl import OpenSSL +# For python3 +def _equals_bytes(a, b): + if len(a) != len(b): + return False + result = 0 + for x, y in zip(a, b): + result |= x ^ y + return result == 0 + + +def _equals_str(a, b): + if len(a) != len(b): + return False + result = 0 + for x, y in zip(a, b): + result |= ord(x) ^ ord(y) + return result == 0 + + +def equals(a, b): + if isinstance(a, str): + return _equals_str(a, b) + else: + return _equals_bytes(a, b) + + def hmac_sha256(k, m): """ Compute the key and the message with HMAC SHA5256 diff --git a/src/shared.py b/src/shared.py index 0a103ed1..f31683ed 100644 --- a/src/shared.py +++ b/src/shared.py @@ -472,20 +472,10 @@ def isBitSetWithinBitfield(fourByteString, n): def decryptAndCheckPubkeyPayload(data, address): """ - With the changes in protocol v3, to maintain backwards compatibility, signatures will be sent - the 'old' way during an upgrade period and then a 'new' simpler way after that. We will therefore - check the sig both ways. - Old way: - signedData = timePubkeyWasSigned(8 bytes) + addressVersion + streamNumber + the decrypted data down through the payloadLengthExtraBytes - New way: - signedData = all of the payload data from the time to the tag + the decrypted data down through the payloadLengthExtraBytes - - The timePubkeyWasSigned will be calculated by subtracting 28 days form the embedded expiresTime. - """ - - """ - The time, address version, and stream number are not encrypted so let's - keep that data here for now. + 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. """ try: status, addressVersion, streamNumber, ripe = decodeAddress(address) @@ -495,6 +485,7 @@ def decryptAndCheckPubkeyPayload(data, address): readPosition += varintLength embeddedStreamNumber, varintLength = decodeVarint(data[readPosition:readPosition + 10]) readPosition += varintLength + storedData = data[20:readPosition] # We'll store the address version and stream number (and some more) in the pubkeys table. if addressVersion != embeddedAddressVersion: logger.info('Pubkey decryption was UNsuccessful due to address version mismatch.') @@ -503,16 +494,9 @@ def decryptAndCheckPubkeyPayload(data, address): logger.info('Pubkey decryption was UNsuccessful due to stream number mismatch.') return 'failed' - expiresTime, = unpack('>Q', data[8:16]) - TTL = 28 * 24 * 60 * 60 - signedDataOldMethod = pack('>Q', (expiresTime - TTL)) # the time that the pubkey was signed. 8 bytes. - signedDataOldMethod += data[20:readPosition] # the address version and stream number - tag = data[readPosition:readPosition + 32] readPosition += 32 - - signedDataNewMethod = data[8:readPosition] # the time through the tag - + signedData = data[8:readPosition] # the time through the tag. More data is appended onto signedData below after the decryption. encryptedData = data[readPosition:] # Let us try to decrypt the pubkey @@ -520,7 +504,7 @@ def decryptAndCheckPubkeyPayload(data, address): if toAddress != address: 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 prevented earlier. + # That sort of address-malleability should have been caught by the UI or API and an error given to the user. return 'failed' try: decryptedData = cryptorObject.decrypt(encryptedData) @@ -543,23 +527,18 @@ def decryptAndCheckPubkeyPayload(data, address): specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += specifiedPayloadLengthExtraBytesLength - signedDataOldMethod += decryptedData[:readPosition] - signedDataNewMethod += decryptedData[:readPosition] + storedData += decryptedData[:readPosition] + signedData += decryptedData[:readPosition] signatureLength, signatureLengthLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += signatureLengthLength signature = decryptedData[readPosition:readPosition + signatureLength] - if highlevelcrypto.verify(signedDataOldMethod, signature, publicSigningKey.encode('hex')): - logger.info('ECDSA verify passed (within decryptAndCheckPubkeyPayload, old method)') + if highlevelcrypto.verify(signedData, signature, publicSigningKey.encode('hex')): + logger.info('ECDSA verify passed (within decryptAndCheckPubkeyPayload)') else: - logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload, old method)') - # Try the protocol v3 signing method - if highlevelcrypto.verify(signedDataNewMethod, signature, publicSigningKey.encode('hex')): - logger.info('ECDSA verify passed (within decryptAndCheckPubkeyPayload, new method)') - else: - logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload, new method)') - return 'failed' + logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload)') + return 'failed' sha = hashlib.new('sha512') sha.update(publicSigningKey + publicEncryptionKey) @@ -570,7 +549,7 @@ def decryptAndCheckPubkeyPayload(data, address): if embeddedRipe != ripe: # 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 keys. Someone is either being malicious or using buggy software. + # correct pubkeys. Someone is either being malicious or using buggy software. logger.info('Pubkey decryption was UNsuccessful due to RIPE mismatch.') return 'failed' @@ -587,7 +566,7 @@ def decryptAndCheckPubkeyPayload(data, address): ) ) - t = (ripe, addressVersion, signedDataOldMethod, int(time.time()), 'yes') + t = (ripe, addressVersion, storedData, int(time.time()), 'yes') sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) return 'successful' except varintDecodeError as e: