Merge pull request #750 from Atheros1/master

Removed obsolete Protocol v2 code
This commit is contained in:
Jonathan Warren 2014-12-25 19:16:37 -05:00
commit 607acf74f5
6 changed files with 326 additions and 643 deletions

View File

@ -184,13 +184,15 @@ class objectProcessor(threading.Thread):
readPosition += 4 readPosition += 4
publicSigningKey = data[readPosition:readPosition + 64] publicSigningKey = data[readPosition:readPosition + 64]
# Is it possible for a public key to be invalid such that trying to # 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 # encrypt or sign with it will cause an error? If it is, it would
# probably test these keys here. # be easiest to test them here.
readPosition += 64 readPosition += 64
publicEncryptionKey = data[readPosition:readPosition + 64] publicEncryptionKey = data[readPosition:readPosition + 64]
if len(publicEncryptionKey) < 64: if len(publicEncryptionKey) < 64:
logger.debug('publicEncryptionKey length less than 64. Sanity check failed.') logger.debug('publicEncryptionKey length less than 64. Sanity check failed.')
return return
readPosition += 64
dataToStore = data[20:readPosition] # The data we'll store in the pubkeys table.
sha = hashlib.new('sha512') sha = hashlib.new('sha512')
sha.update( sha.update(
'\x04' + publicSigningKey + '\x04' + publicEncryptionKey) '\x04' + publicSigningKey + '\x04' + publicEncryptionKey)
@ -213,17 +215,6 @@ class objectProcessor(threading.Thread):
queryreturn = sqlQuery( queryreturn = sqlQuery(
'''SELECT usedpersonally FROM pubkeys WHERE hash=? AND addressversion=? AND usedpersonally='yes' ''', ripe, addressVersion) '''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: 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.') logger.info('We HAVE used this pubkey personally. Updating time.')
t = (ripe, addressVersion, dataToStore, int(time.time()), 'yes') t = (ripe, addressVersion, dataToStore, int(time.time()), 'yes')
@ -249,36 +240,16 @@ class objectProcessor(threading.Thread):
data[readPosition:readPosition + 10]) data[readPosition:readPosition + 10])
readPosition += specifiedPayloadLengthExtraBytesLength readPosition += specifiedPayloadLengthExtraBytesLength
endOfSignedDataPosition = readPosition endOfSignedDataPosition = readPosition
dataToStore = data[20:readPosition] # The data we'll store in the pubkeys table.
signatureLength, signatureLengthLength = decodeVarint( signatureLength, signatureLengthLength = decodeVarint(
data[readPosition:readPosition + 10]) data[readPosition:readPosition + 10])
readPosition += signatureLengthLength readPosition += signatureLengthLength
signature = data[readPosition:readPosition + signatureLength] signature = data[readPosition:readPosition + signatureLength]
""" if highlevelcrypto.verify(data[8:endOfSignedDataPosition], signature, publicSigningKey.encode('hex')):
With the changes in protocol v3, to maintain backwards compatibility, signatures will be sent logger.debug('ECDSA verify passed (within processpubkey)')
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)')
else: else:
logger.warning('ECDSA verify failed (within processpubkey, old method)') logger.warning('ECDSA verify failed (within processpubkey)')
# let us try the newer signature method return
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
sha = hashlib.new('sha512') sha = hashlib.new('sha512')
sha.update(publicSigningKey + publicEncryptionKey) 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) 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: 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.') logger.info('We HAVE used this pubkey personally. Updating time.')
@ -350,14 +310,11 @@ class objectProcessor(threading.Thread):
shared.UISignalQueue.put(( shared.UISignalQueue.put((
'updateNumberOfMessagesProcessed', 'no data')) 'updateNumberOfMessagesProcessed', 'no data'))
readPosition = 20 # bypass the nonce, time, and object type readPosition = 20 # bypass the nonce, time, and object type
msgVersion, msgVersionLength = decodeVarint(data[readPosition:readPosition + 9])
""" if msgVersion != 1:
In protocol v2, the next byte(s) was the streamNumber. But starting after logger.info('Cannot understand message versions other than one. Ignoring message.')
the protocol v3 upgrade period, the next byte(s) will be a msg version return
number followed by the streamNumber. readPosition += msgVersionLength
"""
#msgVersionOutsideEncryption, msgVersionOutsideEncryptionLength = decodeVarint(data[readPosition:readPosition + 9])
#readPosition += msgVersionOutsideEncryptionLength
streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = decodeVarint( streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = decodeVarint(
data[readPosition:readPosition + 9]) 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 # 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. # 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(): for key, cryptorObject in shared.myECCryptorObjects.items():
try: try:
decryptedData = cryptorObject.decrypt(data[readPosition:]) 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. 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 initialDecryptionSuccessful = True
logger.info('EC decryption successful using key associated with ripe hash: %s. msg did NOT specify version.' % key.encode('hex')) logger.info('EC decryption successful using key associated with ripe hash: %s.' % key.encode('hex'))
#msgObjectContainedVersion = False
# 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
break break
except Exception as err: except Exception as err:
# What if a client sent us a msg with pass
# 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
if not initialDecryptionSuccessful: if not initialDecryptionSuccessful:
# This is not a message bound for me. # 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,)) 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[ toAddress = shared.myAddressesByHash[
toRipe] # Look up my address based on the RIPE hash. toRipe] # Look up my address based on the RIPE hash.
readPosition = 0 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( sendersAddressVersionNumber, sendersAddressVersionNumberLength = decodeVarint(
decryptedData[readPosition:readPosition + 10]) decryptedData[readPosition:readPosition + 10])
readPosition += sendersAddressVersionNumberLength readPosition += sendersAddressVersionNumberLength
@ -489,12 +419,7 @@ class objectProcessor(threading.Thread):
readPosition += signatureLengthLength readPosition += signatureLengthLength
signature = decryptedData[ signature = decryptedData[
readPosition:readPosition + signatureLength] readPosition:readPosition + signatureLength]
if not msgObjectContainedVersion: signedData = data[8:20] + encodeVarint(1) + encodeVarint(streamNumberAsClaimedByMsg) + decryptedData[:positionOfBottomOfAckData]
# 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]
if not highlevelcrypto.verify(signedData, signature, pubSigningKey.encode('hex')): if not highlevelcrypto.verify(signedData, signature, pubSigningKey.encode('hex')):
logger.debug('ECDSA verify failed') logger.debug('ECDSA verify failed')
@ -511,32 +436,25 @@ class objectProcessor(threading.Thread):
ripe.update(sha.digest()) ripe.update(sha.digest())
fromAddress = encodeAddress( fromAddress = encodeAddress(
sendersAddressVersionNumber, sendersStreamNumber, ripe.digest()) sendersAddressVersionNumber, sendersStreamNumber, ripe.digest())
# Let's store the public key in case we want to reply to this # Let's store the public key in case we want to reply to this
# person. # 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: 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()) self.possibleNewPubkey(ripe=ripe.digest())
elif sendersAddressVersionNumber >= 4: 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) self.possibleNewPubkey(address = fromAddress)
# If this message is bound for one of my version 3 addresses (or # 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 # 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 # proof of work requirement. If this is bound for one of my chan
@ -665,22 +583,19 @@ class objectProcessor(threading.Thread):
broadcastVersion, broadcastVersionLength = decodeVarint( broadcastVersion, broadcastVersionLength = decodeVarint(
data[readPosition:readPosition + 9]) data[readPosition:readPosition + 9])
readPosition += broadcastVersionLength readPosition += broadcastVersionLength
if broadcastVersion < 1 or broadcastVersion > 5: if broadcastVersion < 4 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.') 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 return
if broadcastVersion == 1: cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint(
logger.info('Version 1 broadcasts are no longer supported. Not processing it at all.') data[readPosition:readPosition + 10])
if broadcastVersion in [2,4]: 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 v4 broadcasts are encrypted the same way the msgs are 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 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. 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( signedData = data[8:readPosition]
data[readPosition:readPosition + 10])
readPosition += cleartextStreamNumberLength
signedData = data[8:readPosition] # This doesn't end up being used if the broadcastVersion is 2
initialDecryptionSuccessful = False initialDecryptionSuccessful = False
for key, cryptorObject in shared.MyECSubscriptionCryptorObjects.items(): for key, cryptorObject in shared.MyECSubscriptionCryptorObjects.items():
try: try:
@ -694,145 +609,9 @@ class objectProcessor(threading.Thread):
# print 'cryptorObject.decrypt Exception:', err # print 'cryptorObject.decrypt Exception:', err
if not initialDecryptionSuccessful: if not initialDecryptionSuccessful:
# This is not a broadcast I am interested in. # 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 return
# At this point this is a broadcast I have decrypted and thus am elif broadcastVersion == 5:
# 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
embeddedTag = data[readPosition:readPosition+32] embeddedTag = data[readPosition:readPosition+32]
readPosition += 32 readPosition += 32
if embeddedTag not in shared.MyECSubscriptionCryptorObjects: if embeddedTag not in shared.MyECSubscriptionCryptorObjects:
@ -847,153 +626,161 @@ class objectProcessor(threading.Thread):
except Exception as err: except Exception as err:
logger.debug('Broadcast version %s decryption Unsuccessful.' % broadcastVersion) logger.debug('Broadcast version %s decryption Unsuccessful.' % broadcastVersion)
return return
# At this point this is a broadcast I have decrypted and am
# broadcast version 3 includes the broadcast version at the beginning # interested in.
# of the decryptedData. Broadcast version 5 doesn't. readPosition = 0
readPosition = 0 sendersAddressVersion, sendersAddressVersionLength = decodeVarint(
if broadcastVersion == 3: # This section can be removed after the protocol v3 upgrade period decryptedData[readPosition:readPosition + 9])
signedBroadcastVersion, signedBroadcastVersionLength = decodeVarint( if broadcastVersion == 4:
decryptedData[:10]) if sendersAddressVersion < 2 or sendersAddressVersion > 3:
readPosition += signedBroadcastVersionLength 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
beginningOfPubkeyPosition = readPosition # used when we add the pubkey to our pubkey table elif broadcastVersion == 5:
sendersAddressVersion, sendersAddressVersionLength = decodeVarint(
decryptedData[readPosition:readPosition + 9])
if sendersAddressVersion < 4: 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 return
readPosition += sendersAddressVersionLength readPosition += sendersAddressVersionLength
sendersStream, sendersStreamLength = decodeVarint( sendersStream, sendersStreamLength = decodeVarint(
decryptedData[readPosition:readPosition + 9]) decryptedData[readPosition:readPosition + 9])
if sendersStream != cleartextStreamNumber: 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.') 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 return
readPosition += sendersStreamLength elif broadcastVersion == 5:
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()
calculatedTag = hashlib.sha512(hashlib.sha512(encodeVarint( calculatedTag = hashlib.sha512(hashlib.sha512(encodeVarint(
sendersAddressVersion) + encodeVarint(sendersStream) + calculatedRipe).digest()).digest()[32:] sendersAddressVersion) + encodeVarint(sendersStream) + calculatedRipe).digest()).digest()[32:]
if calculatedTag != embeddedTag: 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.') 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 return
messageEncodingType, messageEncodingTypeLength = decodeVarint( messageEncodingType, messageEncodingTypeLength = decodeVarint(
decryptedData[readPosition:readPosition + 9]) decryptedData[readPosition:readPosition + 9])
if messageEncodingType == 0: if messageEncodingType == 0:
return return
readPosition += messageEncodingTypeLength readPosition += messageEncodingTypeLength
messageLength, messageLengthLength = decodeVarint( messageLength, messageLengthLength = decodeVarint(
decryptedData[readPosition:readPosition + 9]) decryptedData[readPosition:readPosition + 9])
readPosition += messageLengthLength readPosition += messageLengthLength
message = decryptedData[readPosition:readPosition + messageLength] message = decryptedData[readPosition:readPosition + messageLength]
readPosition += messageLength readPosition += messageLength
readPositionAtBottomOfMessage = readPosition readPositionAtBottomOfMessage = readPosition
signatureLength, signatureLengthLength = decodeVarint( signatureLength, signatureLengthLength = decodeVarint(
decryptedData[readPosition:readPosition + 9]) decryptedData[readPosition:readPosition + 9])
readPosition += signatureLengthLength readPosition += signatureLengthLength
signature = decryptedData[ signature = decryptedData[
readPosition:readPosition + signatureLength] readPosition:readPosition + signatureLength]
if broadcastVersion == 3: # broadcastVersion 3 should be completely unused after the end of the protocol v3 upgrade period signedData += decryptedData[:readPositionAtBottomOfMessage]
signedData = decryptedData[:readPositionAtBottomOfMessage] if not highlevelcrypto.verify(signedData, signature, sendersPubSigningKey.encode('hex')):
elif broadcastVersion == 5: logger.debug('ECDSA verify failed')
signedData += decryptedData[:readPositionAtBottomOfMessage] return
if not highlevelcrypto.verify(signedData, signature, sendersPubSigningKey.encode('hex')): logger.debug('ECDSA verify passed')
logger.debug('ECDSA verify failed')
return
logger.debug('ECDSA verify passed')
# verify passed
fromAddress = encodeAddress( fromAddress = encodeAddress(
sendersAddressVersion, sendersStream, calculatedRipe) sendersAddressVersion, sendersStream, calculatedRipe)
logger.info('fromAddress: %s' % fromAddress) logger.info('fromAddress: %s' % fromAddress)
# Let's store the public key in case we want to reply to this person. # Let's store the public key in case we want to reply to this person.
sqlExecute( sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''',
'''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', calculatedRipe,
calculatedRipe, sendersAddressVersion,
sendersAddressVersion, decryptedData[:endOfPubkeyPosition],
'\x00\x00\x00\x00\x00\x00\x00\x01' + decryptedData[beginningOfPubkeyPosition:endOfPubkeyPosition], int(time.time()),
int(time.time()), 'yes')
'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 messageEncodingType == 2: # Check to see whether we happen to be awaiting this
subject, body = self.decodeType2Message(message) # pubkey in order to send a message. If we are, it will do the POW
logger.info('Broadcast subject (first 100 characters): %s' % repr(subject)[:100]) # and send it.
elif messageEncodingType == 1: if broadcastVersion == 4:
body = message self.possibleNewPubkey(ripe=calculatedRipe)
subject = '' elif broadcastVersion == 5:
elif messageEncodingType == 0: self.possibleNewPubkey(address=fromAddress)
logger.info('messageEncodingType == 0. Doing nothing with the message.')
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: else:
body = 'Unknown encoding type.\n\n' + repr(message) t = (inventoryHash, toAddress, fromAddress, subject, int(
subject = '' time.time()), body, 'inbox', messageEncodingType, 0)
helper_inbox.insert(t)
toAddress = '[Broadcast subscribers]' shared.UISignalQueue.put(('displayNewInboxMessage', (
if messageEncodingType != 0: inventoryHash, toAddress, fromAddress, subject, body)))
if helper_inbox.isMessageAlreadyInInbox(toAddress, fromAddress, subject, body, messageEncodingType):
logger.info('This broadcast is already in our inbox. Ignoring it.') # If we are behaving as an API then we might need to run an
else: # outside command to let some program know that a new message
t = (inventoryHash, toAddress, fromAddress, subject, int( # has arrived.
time.time()), body, 'inbox', messageEncodingType, 0) if shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'):
helper_inbox.insert(t) try:
apiNotifyPath = shared.config.get(
shared.UISignalQueue.put(('displayNewInboxMessage', ( 'bitmessagesettings', 'apinotifypath')
inventoryHash, toAddress, fromAddress, subject, body))) except:
apiNotifyPath = ''
# If we are behaving as an API then we might need to run an if apiNotifyPath != '':
# outside command to let some program know that a new message call([apiNotifyPath, "newBroadcast"])
# has arrived.
if shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'): # Display timing data
try: logger.info('Time spent processing this interesting broadcast: %s' % (time.time() - messageProcessingStartTime,))
apiNotifyPath = shared.config.get(
'bitmessagesettings', 'apinotifypath')
except:
apiNotifyPath = ''
if apiNotifyPath != '':
call([apiNotifyPath, "newBroadcast"])
# 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 # 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 # pubkey, msg, or broadcast message. It might be one that we have been
# waiting for. Let's check. # waiting for. Let's check.
def possibleNewPubkey(self, ripe=None, address=None): 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 # For address versions <= 3, we wait on a key with the correct ripe hash
if ripe != None: if ripe != None:
if ripe in shared.neededPubkeys: if ripe in shared.neededPubkeys:
logger.info('We have been awaiting the arrival of this pubkey.')
del shared.neededPubkeys[ripe] del shared.neededPubkeys[ripe]
sqlExecute( self.sendMessages(ripe)
'''UPDATE sent SET status='doingmsgpow' WHERE toripe=? AND (status='awaitingpubkey' or status='doingpubkeypow') and folder='sent' ''',
ripe)
shared.workerQueue.put(('sendmessage', ''))
else: else:
logger.debug('We don\'t need this pub key. We didn\'t ask for it. Pubkey hash: %s' % ripe.encode('hex')) 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. # 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( tag = hashlib.sha512(hashlib.sha512(encodeVarint(
addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()[32:] addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()[32:]
if tag in shared.neededPubkeys: if tag in shared.neededPubkeys:
logger.info('We have been awaiting the arrival of this pubkey.')
del shared.neededPubkeys[tag] del shared.neededPubkeys[tag]
sqlExecute( self.sendMessages(ripe)
'''UPDATE sent SET status='doingmsgpow' WHERE toripe=? AND (status='awaitingpubkey' or status='doingpubkeypow') and folder='sent' ''',
ripe) def sendMessages(self, ripe):
shared.workerQueue.put(('sendmessage', '')) """
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): def ackDataHasAVaildHeader(self, ackData):
if len(ackData) < shared.Header.size: if len(ackData) < shared.Header.size:
@ -1040,22 +835,6 @@ class objectProcessor(threading.Thread):
return False return False
return True 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): def addMailingListNameToSubject(self, subject, mailingListName):
subject = subject.strip() subject = subject.strip()
if subject[:3] == 'Re:' or subject[:3] == 'RE:': if subject[:3] == 'Re:' or subject[:3] == 'RE:':

View File

@ -219,12 +219,7 @@ class singleWorker(threading.Thread):
payload += encodeVarint(shared.config.getint( payload += encodeVarint(shared.config.getint(
myAddress, 'payloadlengthextrabytes')) myAddress, 'payloadlengthextrabytes'))
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT signature = highlevelcrypto.sign(payload, privSigningKeyHex)
signedData = pack('>I', signedTimeForProtocolV2) + payload[12:]
else:
signedData = payload
signature = highlevelcrypto.sign(signedData, privSigningKeyHex)
payload += encodeVarint(len(signature)) payload += encodeVarint(len(signature))
payload += signature payload += signature
@ -309,8 +304,6 @@ class singleWorker(threading.Thread):
dataToEncrypt += encodeVarint(shared.config.getint( dataToEncrypt += encodeVarint(shared.config.getint(
myAddress, 'payloadlengthextrabytes')) myAddress, 'payloadlengthextrabytes'))
# When we encrypt, we'll use a hash of the data # When we encrypt, we'll use a hash of the data
# contained in an address as a decryption key. This way in order to # 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 # 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( doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint(
addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest() addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest()
payload += doubleHashOfAddressData[32:] # the tag payload += doubleHashOfAddressData[32:] # the tag
signature = highlevelcrypto.sign(payload + dataToEncrypt, privSigningKeyHex)
"""
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)
dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += encodeVarint(len(signature))
dataToEncrypt += signature dataToEncrypt += signature
@ -411,17 +388,11 @@ class singleWorker(threading.Thread):
embeddedTime = int(time.time() + TTL) embeddedTime = int(time.time() + TTL)
payload = pack('>Q', embeddedTime) payload = pack('>Q', embeddedTime)
payload += '\x00\x00\x00\x03' # object type: broadcast 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:
if addressVersionNumber <= 3: payload += encodeVarint(4) # broadcast version
payload += encodeVarint(2) # broadcast version else:
else: payload += encodeVarint(5) # broadcast version
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
payload += encodeVarint(streamNumber) payload += encodeVarint(streamNumber)
if addressVersionNumber >= 4: if addressVersionNumber >= 4:
@ -432,14 +403,7 @@ class singleWorker(threading.Thread):
else: else:
tag = '' tag = ''
dataToEncrypt = "" dataToEncrypt = encodeVarint(addressVersionNumber)
# 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(streamNumber) dataToEncrypt += encodeVarint(streamNumber)
dataToEncrypt += '\x00\x00\x00\x01' # behavior bitfield dataToEncrypt += '\x00\x00\x00\x01' # behavior bitfield
dataToEncrypt += pubSigningKey[1:] dataToEncrypt += pubSigningKey[1:]
@ -450,10 +414,7 @@ class singleWorker(threading.Thread):
dataToEncrypt += '\x02' # message encoding type 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 += 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 dataToEncrypt += 'Subject:' + subject + '\n' + 'Body:' + body
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT dataToSign = payload + dataToEncrypt
dataToSign = dataToEncrypt
else:
dataToSign = payload + dataToEncrypt
signature = highlevelcrypto.sign( signature = highlevelcrypto.sign(
dataToSign, privSigningKeyHex) dataToSign, privSigningKeyHex)
@ -646,25 +607,16 @@ class singleWorker(threading.Thread):
for row in queryreturn: for row in queryreturn:
pubkeyPayload, = row pubkeyPayload, = row
# The pubkey message is stored the way we originally received it # The pubkey message is stored with the following items all appended:
# under protocol version 2 # -address version
# which means that we need to read beyond things like the nonce and # -stream number
# time to get to the actual public keys. # -behavior bitfield
if toAddressVersionNumber <= 3: # -pub signing key
readPosition = 8 # to bypass the nonce # -pub encryption key
elif toAddressVersionNumber >= 4: # -nonce trials per byte (if address version is >= 3)
readPosition = 0 # the nonce is not included here so we don't need to skip over it. # -length extra bytes (if address version is >= 3)
pubkeyEmbeddedTime, = unpack(
'>I', pubkeyPayload[readPosition:readPosition + 4]) readPosition = 1 # to bypass the address version whose length is definitely 1
# 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
streamNumber, streamNumberLength = decodeVarint( streamNumber, streamNumberLength = decodeVarint(
pubkeyPayload[readPosition:readPosition + 10]) pubkeyPayload[readPosition:readPosition + 10])
readPosition += streamNumberLength 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. # if the human changes their setting and then sends another message or restarts their client, this one will send at that time.
continue continue
readPosition += 4 # to bypass the bitfield of behaviors readPosition += 4 # to bypass the bitfield of behaviors
# pubSigningKeyBase256 = # pubSigningKeyBase256 = pubkeyPayload[readPosition:readPosition+64] # We don't use this key for anything here.
# pubkeyPayload[readPosition:readPosition+64] #We don't use this
# key for anything here.
readPosition += 64 readPosition += 64
pubEncryptionKeyBase256 = pubkeyPayload[ pubEncryptionKeyBase256 = pubkeyPayload[
readPosition:readPosition + 64] readPosition:readPosition + 64]
@ -740,91 +690,38 @@ class singleWorker(threading.Thread):
shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (
ackdata, tr.translateText("MainWindow", "Doing work necessary to send message.")))) ackdata, tr.translateText("MainWindow", "Doing work necessary to send message."))))
if fromAddressVersionNumber == 2: # Now we can start to assemble our message.
payload = "" payload = encodeVarint(fromAddressVersionNumber)
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT payload += encodeVarint(fromStreamNumber)
payload += '\x01' # Message version. 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 )
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 # We need to convert our private keys to public keys in order
# to include them. # to include them.
try: try:
privSigningKeyBase58 = shared.config.get( privSigningKeyBase58 = shared.config.get(
fromaddress, 'privsigningkey') fromaddress, 'privsigningkey')
privEncryptionKeyBase58 = shared.config.get( privEncryptionKeyBase58 = shared.config.get(
fromaddress, 'privencryptionkey') fromaddress, 'privencryptionkey')
except: except:
shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (
ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) ackdata, tr.translateText("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file."))))
continue continue
privSigningKeyHex = shared.decodeWalletImportFormat( privSigningKeyHex = shared.decodeWalletImportFormat(
privSigningKeyBase58).encode('hex') privSigningKeyBase58).encode('hex')
privEncryptionKeyHex = shared.decodeWalletImportFormat( privEncryptionKeyHex = shared.decodeWalletImportFormat(
privEncryptionKeyBase58).encode('hex') privEncryptionKeyBase58).encode('hex')
pubSigningKey = highlevelcrypto.privToPub( pubSigningKey = highlevelcrypto.privToPub(
privSigningKeyHex).decode('hex') privSigningKeyHex).decode('hex')
pubEncryptionKey = highlevelcrypto.privToPub( pubEncryptionKey = highlevelcrypto.privToPub(
privEncryptionKeyHex).decode('hex') privEncryptionKeyHex).decode('hex')
payload += pubSigningKey[ 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. 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 += 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
if fromAddressVersionNumber >= 3: 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, # If the receiver of our message is in our address book,
# subscriptions list, or whitelist then we will allow them to # subscriptions list, or whitelist then we will allow them to
# do the network-minimum proof of work. Let us check to see if # 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( payload += encodeVarint(shared.config.getint(
fromaddress, 'payloadlengthextrabytes')) 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 += 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. payload += '\x02' # Type 2 is simple UTF-8 message encoding as specified on the Protocol Specification on the Bitmessage Wiki.
messageToTransmit = 'Subject:' + \ messageToTransmit = 'Subject:' + \
subject + '\n' + 'Body:' + message subject + '\n' + 'Body:' + message
payload += encodeVarint(len(messageToTransmit)) payload += encodeVarint(len(messageToTransmit))
payload += messageToTransmit payload += messageToTransmit
if shared.config.has_section(toaddress): if shared.config.has_section(toaddress):
with shared.printLock: with shared.printLock:
print 'Not bothering to include ackdata because we are sending to ourselves or a chan.' print 'Not bothering to include ackdata because we are sending to ourselves or a chan.'
fullAckPayload = '' fullAckPayload = ''
elif not shared.isBitSetWithinBitfield(behaviorBitfield,31): elif not shared.isBitSetWithinBitfield(behaviorBitfield,31):
with shared.printLock: with shared.printLock:
print 'Not bothering to include ackdata because the receiver said that they won\'t relay it anyway.' print 'Not bothering to include ackdata because the receiver said that they won\'t relay it anyway.'
fullAckPayload = '' fullAckPayload = ''
else: else:
fullAckPayload = self.generateFullAckMessage( 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. 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 += encodeVarint(len(fullAckPayload))
payload += fullAckPayload payload += fullAckPayload
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + encodeVarint(1) + encodeVarint(toStreamNumber) + payload
dataToSign = payload signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex)
else: payload += encodeVarint(len(signature))
dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + encodeVarint(1) + encodeVarint(toStreamNumber) + payload payload += signature
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. # We have assembled the data that will be encrypted.
try: try:
encrypted = highlevelcrypto.encrypt(payload,"04"+pubEncryptionKeyBase256.encode('hex')) encrypted = highlevelcrypto.encrypt(payload,"04"+pubEncryptionKeyBase256.encode('hex'))
@ -878,8 +771,7 @@ class singleWorker(threading.Thread):
encryptedPayload = pack('>Q', embeddedTime) encryptedPayload = pack('>Q', embeddedTime)
encryptedPayload += '\x00\x00\x00\x02' # object type: msg 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 encryptedPayload += encodeVarint(toStreamNumber) + encrypted
target = 2 ** 64 / (requiredAverageProofOfWorkNonceTrialsPerByte*(len(encryptedPayload) + 8 + requiredPayloadLengthExtraBytes + ((TTL*(len(encryptedPayload)+8+requiredPayloadLengthExtraBytes))/(2 ** 16)))) target = 2 ** 64 / (requiredAverageProofOfWorkNonceTrialsPerByte*(len(encryptedPayload) + 8 + requiredPayloadLengthExtraBytes + ((TTL*(len(encryptedPayload)+8+requiredPayloadLengthExtraBytes))/(2 ** 16))))
with shared.printLock: with shared.printLock:
@ -1019,8 +911,7 @@ class singleWorker(threading.Thread):
embeddedTime = int(time.time() + TTL) embeddedTime = int(time.time() + TTL)
payload = pack('>Q', (embeddedTime)) payload = pack('>Q', (embeddedTime))
payload += '\x00\x00\x00\x02' # object type: msg 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 payload += encodeVarint(toStreamNumber) + ackdata
target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) target = 2 ** 64 / (shared.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + shared.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+shared.networkDefaultPayloadLengthExtraBytes))/(2 ** 16))))

View File

@ -346,6 +346,28 @@ class sqlThread(threading.Thread):
shared.config.set('bitmessagesettings', 'settingsversion', '10') shared.config.set('bitmessagesettings', 'settingsversion', '10')
with open(shared.appdata + 'keys.dat', 'wb') as configfile: with open(shared.appdata + 'keys.dat', 'wb') as configfile:
shared.config.write(configfile) shared.config.write(configfile)
# 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 # Are you hoping to add a new option to the keys.dat file of existing

View File

@ -7,7 +7,7 @@
from hashlib import sha512 from hashlib import sha512
from pyelliptic.openssl import OpenSSL from pyelliptic.openssl import OpenSSL
from pyelliptic.cipher import Cipher from pyelliptic.cipher import Cipher
from pyelliptic.hash import hmac_sha256 from pyelliptic.hash import hmac_sha256, equals
from struct import pack, unpack from struct import pack, unpack
@ -436,16 +436,9 @@ class ECC:
pubkey = ephem.get_pubkey() pubkey = ephem.get_pubkey()
iv = OpenSSL.rand(OpenSSL.get_cipher(ciphername).get_blocksize()) iv = OpenSSL.rand(OpenSSL.get_cipher(ciphername).get_blocksize())
ctx = Cipher(key_e, iv, 1, ciphername) ctx = Cipher(key_e, iv, 1, ciphername)
import time ciphertext = iv + pubkey + ctx.ciphering(data)
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
mac = hmac_sha256(key_m, ciphertext) mac = hmac_sha256(key_m, ciphertext)
if int(time.time()) < 1416175200: # Sun, 16 Nov 2014 22:00:00 GMT return ciphertext + mac
return iv + pubkey + ciphertext + mac
else:
return ciphertext + mac # Everyone should be using this line after the Bitmessage protocol v3 upgrade period
def decrypt(self, data, ciphername='aes-256-cbc'): def decrypt(self, data, ciphername='aes-256-cbc'):
""" """
@ -461,14 +454,7 @@ class ECC:
mac = data[i:] mac = data[i:]
key = sha512(self.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest() key = sha512(self.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest()
key_e, key_m = key[:32], key[32:] key_e, key_m = key[:32], key[32:]
""" if not equals(hmac_sha256(key_m, data[:len(data) - 32]), mac):
pyelliptic was changed slightly so that the hmac covers the raise RuntimeError("Fail to verify data")
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")
ctx = Cipher(key_e, iv, 0, ciphername) ctx = Cipher(key_e, iv, 0, ciphername)
return ctx.ciphering(ciphertext) return ctx.ciphering(ciphertext)

View File

@ -7,6 +7,32 @@
from pyelliptic.openssl import OpenSSL 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): def hmac_sha256(k, m):
""" """
Compute the key and the message with HMAC SHA5256 Compute the key and the message with HMAC SHA5256

View File

@ -472,20 +472,10 @@ def isBitSetWithinBitfield(fourByteString, n):
def decryptAndCheckPubkeyPayload(data, address): def decryptAndCheckPubkeyPayload(data, address):
""" """
With the changes in protocol v3, to maintain backwards compatibility, signatures will be sent Version 4 pubkeys are encrypted. This function is run when we already have the
the 'old' way during an upgrade period and then a 'new' simpler way after that. We will therefore address to which we want to try to send a message. The 'data' may come either
check the sig both ways. off of the wire or we might have had it already in our inventory when we tried
Old way: to send a msg to this particular address.
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.
""" """
try: try:
status, addressVersion, streamNumber, ripe = decodeAddress(address) status, addressVersion, streamNumber, ripe = decodeAddress(address)
@ -495,6 +485,7 @@ def decryptAndCheckPubkeyPayload(data, address):
readPosition += varintLength readPosition += varintLength
embeddedStreamNumber, varintLength = decodeVarint(data[readPosition:readPosition + 10]) embeddedStreamNumber, varintLength = decodeVarint(data[readPosition:readPosition + 10])
readPosition += varintLength 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: if addressVersion != embeddedAddressVersion:
logger.info('Pubkey decryption was UNsuccessful due to address version mismatch.') 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.') logger.info('Pubkey decryption was UNsuccessful due to stream number mismatch.')
return 'failed' 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] tag = data[readPosition:readPosition + 32]
readPosition += 32 readPosition += 32
signedData = data[8:readPosition] # the time through the tag. More data is appended onto signedData below after the decryption.
signedDataNewMethod = data[8:readPosition] # the time through the tag
encryptedData = data[readPosition:] encryptedData = data[readPosition:]
# Let us try to decrypt the pubkey # Let us try to decrypt the pubkey
@ -520,7 +504,7 @@ def decryptAndCheckPubkeyPayload(data, address):
if toAddress != address: if toAddress != address:
logger.critical('decryptAndCheckPubkeyPayload failed due to toAddress mismatch. This is very peculiar. toAddress: %s, address %s' % (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. # 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' return 'failed'
try: try:
decryptedData = cryptorObject.decrypt(encryptedData) decryptedData = cryptorObject.decrypt(encryptedData)
@ -543,23 +527,18 @@ def decryptAndCheckPubkeyPayload(data, address):
specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint( specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint(
decryptedData[readPosition:readPosition + 10]) decryptedData[readPosition:readPosition + 10])
readPosition += specifiedPayloadLengthExtraBytesLength readPosition += specifiedPayloadLengthExtraBytesLength
signedDataOldMethod += decryptedData[:readPosition] storedData += decryptedData[:readPosition]
signedDataNewMethod += decryptedData[:readPosition] signedData += decryptedData[:readPosition]
signatureLength, signatureLengthLength = decodeVarint( signatureLength, signatureLengthLength = decodeVarint(
decryptedData[readPosition:readPosition + 10]) decryptedData[readPosition:readPosition + 10])
readPosition += signatureLengthLength readPosition += signatureLengthLength
signature = decryptedData[readPosition:readPosition + signatureLength] signature = decryptedData[readPosition:readPosition + signatureLength]
if highlevelcrypto.verify(signedDataOldMethod, signature, publicSigningKey.encode('hex')): if highlevelcrypto.verify(signedData, signature, publicSigningKey.encode('hex')):
logger.info('ECDSA verify passed (within decryptAndCheckPubkeyPayload, old method)') logger.info('ECDSA verify passed (within decryptAndCheckPubkeyPayload)')
else: else:
logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload, old method)') logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload)')
# Try the protocol v3 signing method return 'failed'
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'
sha = hashlib.new('sha512') sha = hashlib.new('sha512')
sha.update(publicSigningKey + publicEncryptionKey) sha.update(publicSigningKey + publicEncryptionKey)
@ -570,7 +549,7 @@ def decryptAndCheckPubkeyPayload(data, address):
if embeddedRipe != ripe: if embeddedRipe != ripe:
# Although this pubkey object had the tag were were looking for and was # Although this pubkey object had the tag were were looking for and was
# encrypted with the correct encryption key, it doesn't contain the # 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.') logger.info('Pubkey decryption was UNsuccessful due to RIPE mismatch.')
return 'failed' 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) sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t)
return 'successful' return 'successful'
except varintDecodeError as e: except varintDecodeError as e: