/ src / protocol.py
protocol.py
  1  """
  2  Low-level protocol-related functions.
  3  """
  4  # pylint: disable=too-many-boolean-expressions,too-many-return-statements
  5  # pylint: disable=too-many-locals,too-many-statements
  6  
  7  import base64
  8  import hashlib
  9  import random
 10  import socket
 11  import sys
 12  import time
 13  from binascii import hexlify
 14  from struct import Struct, pack, unpack
 15  
 16  import defaults
 17  import highlevelcrypto
 18  import state
 19  from addresses import (decodeAddress, decodeVarint, encodeVarint,
 20                         varintDecodeError)
 21  from bmconfigparser import config
 22  from debug import logger
 23  from helper_sql import sqlExecute
 24  from network.node import Peer
 25  from version import softwareVersion
 26  
 27  # Network constants
 28  magic = 0xE9BEB4D9
 29  #: protocol specification says max 1000 addresses in one addr command
 30  MAX_ADDR_COUNT = 1000
 31  #: address is online if online less than this many seconds ago
 32  ADDRESS_ALIVE = 10800
 33  #: ~1.6 MB which is the maximum possible size of an inv message.
 34  MAX_MESSAGE_SIZE = 1600100
 35  #: 2**18 = 256kB is the maximum size of an object payload
 36  MAX_OBJECT_PAYLOAD_SIZE = 2**18
 37  #: protocol specification says max 50000 objects in one inv command
 38  MAX_OBJECT_COUNT = 50000
 39  #: maximum time offset
 40  MAX_TIME_OFFSET = 3600
 41  
 42  # Service flags
 43  #: This is a normal network node
 44  NODE_NETWORK = 1
 45  #: This node supports SSL/TLS in the current connect (python < 2.7.9
 46  #: only supports an SSL client, so in that case it would only have this
 47  #: on when the connection is a client).
 48  NODE_SSL = 2
 49  # (Proposal) This node may do PoW on behalf of some its peers
 50  # (PoW offloading/delegating), but it doesn't have to. Clients may have
 51  # to meet additional requirements (e.g. TLS authentication)
 52  # NODE_POW = 4
 53  #: Node supports dandelion
 54  NODE_DANDELION = 8
 55  
 56  # Bitfield flags
 57  BITFIELD_DOESACK = 1
 58  
 59  # Error types
 60  STATUS_WARNING = 0
 61  STATUS_ERROR = 1
 62  STATUS_FATAL = 2
 63  
 64  # Object types
 65  OBJECT_GETPUBKEY = 0
 66  OBJECT_PUBKEY = 1
 67  OBJECT_MSG = 2
 68  OBJECT_BROADCAST = 3
 69  OBJECT_ONIONPEER = 0x746f72
 70  OBJECT_I2P = 0x493250
 71  OBJECT_ADDR = 0x61646472
 72  
 73  eightBytesOfRandomDataUsedToDetectConnectionsToSelf = pack(
 74      '>Q', random.randrange(1, 18446744073709551615))  # nosec B311
 75  
 76  # Compiled struct for packing/unpacking headers
 77  # New code should use CreatePacket instead of Header.pack
 78  Header = Struct('!L12sL4s')
 79  
 80  VersionPacket = Struct('>LqQ20s4s36sH')
 81  
 82  # Bitfield
 83  
 84  
 85  def getBitfield(address):
 86      """Get a bitfield from an address"""
 87      # bitfield of features supported by me (see the wiki).
 88      bitfield = 0
 89      # send ack
 90      if not config.safeGetBoolean(address, 'dontsendack'):
 91          bitfield |= BITFIELD_DOESACK
 92      return pack('>I', bitfield)
 93  
 94  
 95  def checkBitfield(bitfieldBinary, flags):
 96      """Check if a bitfield matches the given flags"""
 97      bitfield, = unpack('>I', bitfieldBinary)
 98      return (bitfield & flags) == flags
 99  
100  
101  def isBitSetWithinBitfield(fourByteString, n):
102      """Check if a particular bit is set in a bitfeld"""
103      # Uses MSB 0 bit numbering across 4 bytes of data
104      n = 31 - n
105      x, = unpack('>L', fourByteString)
106      return x & 2**n != 0
107  
108  # Streams
109  
110  
111  MIN_VALID_STREAM = 1
112  MAX_VALID_STREAM = 2**63 - 1
113  
114  # IP addresses
115  
116  
117  def encodeHost(host):
118      """Encode a given host to be used in low-level socket operations"""
119      if host.endswith('.onion'):
120          return b'\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode(
121              host.split(".")[0], True)
122      elif host.find(':') == -1:
123          return b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
124              socket.inet_aton(host)
125      return socket.inet_pton(socket.AF_INET6, host)
126  
127  
128  def networkType(host):
129      """Determine if a host is IPv4, IPv6 or an onion address"""
130      if host.endswith('.onion'):
131          return 'onion'
132      elif host.find(':') == -1:
133          return 'IPv4'
134      return 'IPv6'
135  
136  
137  def network_group(host):
138      """Canonical identifier of network group
139         simplified, borrowed from
140         GetGroup() in src/netaddresses.cpp in bitcoin core"""
141      if not isinstance(host, str):
142          return None
143      network_type = networkType(host)
144      try:
145          raw_host = encodeHost(host)
146      except socket.error:
147          return host
148      if network_type == 'IPv4':
149          decoded_host = checkIPv4Address(raw_host[12:], True)
150          if decoded_host:
151              # /16 subnet
152              return raw_host[12:14]
153      elif network_type == 'IPv6':
154          decoded_host = checkIPv6Address(raw_host, True)
155          if decoded_host:
156              # /32 subnet
157              return raw_host[0:12]
158      else:
159          # just host, e.g. for tor
160          return host
161      # global network type group for local, private, unroutable
162      return network_type
163  
164  
165  def checkIPAddress(host, private=False):
166      """
167      Returns hostStandardFormat if it is a valid IP address,
168      otherwise returns False
169      """
170      if host[0:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
171          hostStandardFormat = socket.inet_ntop(socket.AF_INET, host[12:])
172          return checkIPv4Address(host[12:], hostStandardFormat, private)
173      elif host[0:6] == b'\xfd\x87\xd8\x7e\xeb\x43':
174          # Onion, based on BMD/bitcoind
175          hostStandardFormat = base64.b32encode(host[6:]).lower() + ".onion"
176          if private:
177              return False
178          return hostStandardFormat
179      else:
180          try:
181              hostStandardFormat = socket.inet_ntop(socket.AF_INET6, host)
182          except ValueError:
183              return False
184          if len(hostStandardFormat) == 0:
185              # This can happen on Windows systems which are
186              # not 64-bit compatible so let us drop the IPv6 address.
187              return False
188          return checkIPv6Address(host, hostStandardFormat, private)
189  
190  
191  def checkIPv4Address(host, hostStandardFormat, private=False):
192      """
193      Returns hostStandardFormat if it is an IPv4 address,
194      otherwise returns False
195      """
196      if host[0:1] == b'\x7F':  # 127/8
197          if not private:
198              logger.debug(
199                  'Ignoring IP address in loopback range: %s',
200                  hostStandardFormat)
201          return hostStandardFormat if private else False
202      if host[0:1] == b'\x0A':  # 10/8
203          if not private:
204              logger.debug(
205                  'Ignoring IP address in private range: %s', hostStandardFormat)
206          return hostStandardFormat if private else False
207      if host[0:2] == b'\xC0\xA8':  # 192.168/16
208          if not private:
209              logger.debug(
210                  'Ignoring IP address in private range: %s', hostStandardFormat)
211          return hostStandardFormat if private else False
212      if host[0:2] >= b'\xAC\x10' and host[0:2] < b'\xAC\x20':  # 172.16/12
213          if not private:
214              logger.debug(
215                  'Ignoring IP address in private range: %s', hostStandardFormat)
216          return hostStandardFormat if private else False
217      return False if private else hostStandardFormat
218  
219  
220  def checkIPv6Address(host, hostStandardFormat, private=False):
221      """
222      Returns hostStandardFormat if it is an IPv6 address,
223      otherwise returns False
224      """
225      if host == b'\x00' * 15 + b'\x01':
226          if not private:
227              logger.debug('Ignoring loopback address: %s', hostStandardFormat)
228          return False
229      try:
230          host = [ord(c) for c in host[:2]]
231      except TypeError:  # python3 has ints already
232          pass
233      if host[0] == 0xfe and host[1] & 0xc0 == 0x80:
234          if not private:
235              logger.debug('Ignoring local address: %s', hostStandardFormat)
236          return hostStandardFormat if private else False
237      if host[0] & 0xfe == 0xfc:
238          if not private:
239              logger.debug(
240                  'Ignoring unique local address: %s', hostStandardFormat)
241          return hostStandardFormat if private else False
242      return False if private else hostStandardFormat
243  
244  
245  def haveSSL(server=False):
246      """
247      Predicate to check if ECDSA server support is required and available
248  
249      python < 2.7.9's ssl library does not support ECDSA server due to
250      missing initialisation of available curves, but client works ok
251      """
252      if not server:
253          return True
254      elif sys.version_info >= (2, 7, 9):
255          return True
256      return False
257  
258  
259  def checkSocksIP(host):
260      """Predicate to check if we're using a SOCKS proxy"""
261      sockshostname = config.safeGet(
262          'bitmessagesettings', 'sockshostname')
263      try:
264          if not state.socksIP:
265              state.socksIP = socket.gethostbyname(sockshostname)
266      except NameError:  # uninitialised
267          state.socksIP = socket.gethostbyname(sockshostname)
268      except (TypeError, socket.gaierror):  # None, resolving failure
269          state.socksIP = sockshostname
270      return state.socksIP == host
271  
272  
273  def isProofOfWorkSufficient(
274          data, nonceTrialsPerByte=0, payloadLengthExtraBytes=0, recvTime=0):
275      """
276      Validate an object's Proof of Work using method described
277      :doc:`here </pow>`
278  
279      Arguments:
280          int nonceTrialsPerByte (default: from `.defaults`)
281          int payloadLengthExtraBytes (default: from `.defaults`)
282          float recvTime (optional) UNIX epoch time when object was
283            received from the network (default: current system time)
284      Returns:
285          True if PoW valid and sufficient, False in all other cases
286      """
287      if nonceTrialsPerByte < defaults.networkDefaultProofOfWorkNonceTrialsPerByte:
288          nonceTrialsPerByte = defaults.networkDefaultProofOfWorkNonceTrialsPerByte
289      if payloadLengthExtraBytes < defaults.networkDefaultPayloadLengthExtraBytes:
290          payloadLengthExtraBytes = defaults.networkDefaultPayloadLengthExtraBytes
291      endOfLifeTime, = unpack('>Q', data[8:16])
292      TTL = endOfLifeTime - int(recvTime if recvTime else time.time())
293      if TTL < 300:
294          TTL = 300
295      POW, = unpack('>Q', highlevelcrypto.double_sha512(
296          data[:8] + hashlib.sha512(data[8:]).digest())[0:8])
297      return POW <= 2 ** 64 / (
298          nonceTrialsPerByte * (
299              len(data) + payloadLengthExtraBytes
300              + ((TTL * (len(data) + payloadLengthExtraBytes)) / (2 ** 16))))
301  
302  
303  # Packet creation
304  
305  
306  def CreatePacket(command, payload=b''):
307      """Construct and return a packet"""
308      payload_length = len(payload)
309      checksum = hashlib.sha512(payload).digest()[0:4]
310  
311      b = bytearray(Header.size + payload_length)
312      Header.pack_into(b, 0, magic, command, payload_length, checksum)
313      b[Header.size:] = payload
314      return bytes(b)
315  
316  
317  def assembleAddrMessage(peerList):
318      """Create address command"""
319      if isinstance(peerList, Peer):
320          peerList = [peerList]
321      if not peerList:
322          return b''
323      retval = b''
324      for i in range(0, len(peerList), MAX_ADDR_COUNT):
325          payload = encodeVarint(len(peerList[i:i + MAX_ADDR_COUNT]))
326          for stream, peer, timestamp in peerList[i:i + MAX_ADDR_COUNT]:
327              # 64-bit time
328              payload += pack('>Q', timestamp)
329              payload += pack('>I', stream)
330              # service bit flags offered by this node
331              payload += pack('>q', 1)
332              payload += encodeHost(peer.host)
333              # remote port
334              payload += pack('>H', peer.port)
335          retval += CreatePacket(b'addr', payload)
336      return retval
337  
338  
339  def assembleVersionMessage(
340      remoteHost, remotePort, participatingStreams,
341      dandelion_enabled=True, server=False, nodeid=None
342  ):
343      """
344      Construct the payload of a version message,
345      return the resulting bytes of running `CreatePacket` on it
346      """
347      payload = b''
348      payload += pack('>L', 3)  # protocol version.
349      # bitflags of the services I offer.
350      payload += pack(
351          '>q',
352          NODE_NETWORK
353          | (NODE_SSL if haveSSL(server) else 0)
354          | (NODE_DANDELION if dandelion_enabled else 0)
355      )
356      payload += pack('>q', int(time.time()))
357  
358      # boolservices of remote connection; ignored by the remote host.
359      payload += pack('>q', 1)
360      if checkSocksIP(remoteHost) and server:
361          # prevent leaking of tor outbound IP
362          payload += encodeHost('127.0.0.1')
363          payload += pack('>H', 8444)
364      else:
365          # use first 16 bytes if host data is longer
366          # for example in case of onion v3 service
367          try:
368              payload += encodeHost(remoteHost)[:16]
369          except socket.error:
370              payload += encodeHost('127.0.0.1')
371          payload += pack('>H', remotePort)  # remote IPv6 and port
372  
373      # bitflags of the services I offer.
374      payload += pack(
375          '>q',
376          NODE_NETWORK
377          | (NODE_SSL if haveSSL(server) else 0)
378          | (NODE_DANDELION if dandelion_enabled else 0)
379      )
380      # = 127.0.0.1. This will be ignored by the remote host.
381      # The actual remote connected IP will be used.
382      payload += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack(
383          '>L', 2130706433)
384      # we have a separate extPort and incoming over clearnet
385      # or outgoing through clearnet
386      extport = config.safeGetInt('bitmessagesettings', 'extport')
387      if (
388          extport and ((server and not checkSocksIP(remoteHost)) or (
389              config.get('bitmessagesettings', 'socksproxytype')
390              == 'none' and not server))
391      ):
392          payload += pack('>H', extport)
393      elif checkSocksIP(remoteHost) and server:  # incoming connection over Tor
394          payload += pack(
395              '>H', config.getint('bitmessagesettings', 'onionport'))
396      else:  # no extport and not incoming over Tor
397          payload += pack(
398              '>H', config.getint('bitmessagesettings', 'port'))
399  
400      if nodeid is not None:
401          payload += nodeid[0:8]
402      else:
403          payload += eightBytesOfRandomDataUsedToDetectConnectionsToSelf
404      userAgent = ('/PyBitmessage:%s/' % softwareVersion).encode('utf-8')
405      payload += encodeVarint(len(userAgent))
406      payload += userAgent
407  
408      # Streams
409      payload += encodeVarint(len(participatingStreams))
410      count = 0
411      for stream in sorted(participatingStreams):
412          payload += encodeVarint(stream)
413          count += 1
414          # protocol limit, see specification
415          if count >= 160000:
416              break
417  
418      return CreatePacket(b'version', payload)
419  
420  
421  def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''):
422      """
423      Construct the payload of an error message,
424      return the resulting bytes of running `CreatePacket` on it
425      """
426      payload = encodeVarint(fatal)
427      payload += encodeVarint(banTime)
428      payload += encodeVarint(len(inventoryVector))
429      payload += inventoryVector
430      payload += encodeVarint(len(errorText))
431      payload += errorText
432      return CreatePacket(b'error', payload)
433  
434  
435  # Packet decoding
436  
437  
438  def decodeObjectParameters(data):
439      """Decode the parameters of a raw object needed to put it in inventory"""
440      # BMProto.decode_payload_content("QQIvv")
441      expiresTime = unpack('>Q', data[8:16])[0]
442      objectType = unpack('>I', data[16:20])[0]
443      parserPos = 20 + decodeVarint(data[20:30])[1]
444      toStreamNumber = decodeVarint(data[parserPos:parserPos + 10])[0]
445  
446      return objectType, toStreamNumber, expiresTime
447  
448  
449  def decryptAndCheckPubkeyPayload(data, address):
450      """
451      Version 4 pubkeys are encrypted. This function is run when we
452      already have the address to which we want to try to send a message.
453      The 'data' may come either off of the wire or we might have had it
454      already in our inventory when we tried to send a msg to this
455      particular address.
456      """
457      try:
458          addressVersion, streamNumber, ripe = decodeAddress(address)[1:]
459  
460          readPosition = 20  # bypass the nonce, time, and object type
461          embeddedAddressVersion, varintLength = decodeVarint(
462              data[readPosition:readPosition + 10])
463          readPosition += varintLength
464          embeddedStreamNumber, varintLength = decodeVarint(
465              data[readPosition:readPosition + 10])
466          readPosition += varintLength
467          # We'll store the address version and stream number
468          # (and some more) in the pubkeys table.
469          storedData = data[20:readPosition]
470  
471          if addressVersion != embeddedAddressVersion:
472              logger.info(
473                  'Pubkey decryption was UNsuccessful'
474                  ' due to address version mismatch.')
475              return 'failed'
476          if streamNumber != embeddedStreamNumber:
477              logger.info(
478                  'Pubkey decryption was UNsuccessful'
479                  ' due to stream number mismatch.')
480              return 'failed'
481  
482          tag = data[readPosition:readPosition + 32]
483          readPosition += 32
484          # the time through the tag. More data is appended onto
485          # signedData below after the decryption.
486          signedData = data[8:readPosition]
487          encryptedData = data[readPosition:]
488  
489          # Let us try to decrypt the pubkey
490          toAddress, cryptorObject = state.neededPubkeys[tag]
491          if toAddress != address:
492              logger.critical(
493                  'decryptAndCheckPubkeyPayload failed due to toAddress'
494                  ' mismatch. This is very peculiar.'
495                  ' toAddress: %s, address %s',
496                  toAddress, address
497              )
498              # the only way I can think that this could happen
499              # is if someone encodes their address data two different ways.
500              # That sort of address-malleability should have been caught
501              # by the UI or API and an error given to the user.
502              return 'failed'
503          try:
504              decryptedData = cryptorObject.decrypt(encryptedData)
505          except:  # noqa:E722
506              # FIXME: use a proper exception after `pyelliptic.ecc` is refactored.
507              # Someone must have encrypted some data with a different key
508              # but tagged it with a tag for which we are watching.
509              logger.info('Pubkey decryption was unsuccessful.')
510              return 'failed'
511  
512          readPosition = 0
513          # bitfieldBehaviors = decryptedData[readPosition:readPosition + 4]
514          readPosition += 4
515          pubSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64]
516          readPosition += 64
517          pubEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64]
518          readPosition += 64
519          specifiedNonceTrialsPerByteLength = decodeVarint(
520              decryptedData[readPosition:readPosition + 10])[1]
521          readPosition += specifiedNonceTrialsPerByteLength
522          specifiedPayloadLengthExtraBytesLength = decodeVarint(
523              decryptedData[readPosition:readPosition + 10])[1]
524          readPosition += specifiedPayloadLengthExtraBytesLength
525          storedData += decryptedData[:readPosition]
526          signedData += decryptedData[:readPosition]
527          signatureLength, signatureLengthLength = decodeVarint(
528              decryptedData[readPosition:readPosition + 10])
529          readPosition += signatureLengthLength
530          signature = decryptedData[readPosition:readPosition + signatureLength]
531  
532          if not highlevelcrypto.verify(
533                  signedData, signature, hexlify(pubSigningKey)):
534              logger.info(
535                  'ECDSA verify failed (within decryptAndCheckPubkeyPayload)')
536              return 'failed'
537  
538          logger.info(
539              'ECDSA verify passed (within decryptAndCheckPubkeyPayload)')
540  
541          embeddedRipe = highlevelcrypto.to_ripe(pubSigningKey, pubEncryptionKey)
542  
543          if embeddedRipe != ripe:
544              # Although this pubkey object had the tag were were looking for
545              # and was encrypted with the correct encryption key,
546              # it doesn't contain the correct pubkeys. Someone is
547              # either being malicious or using buggy software.
548              logger.info(
549                  'Pubkey decryption was UNsuccessful due to RIPE mismatch.')
550              return 'failed'
551  
552          # Everything checked out. Insert it into the pubkeys table.
553  
554          logger.info(
555              'within decryptAndCheckPubkeyPayload, '
556              'addressVersion: %s, streamNumber: %s\nripe %s\n'
557              'publicSigningKey in hex: %s\npublicEncryptionKey in hex: %s',
558              addressVersion, streamNumber, hexlify(ripe),
559              hexlify(pubSigningKey), hexlify(pubEncryptionKey)
560          )
561  
562          t = (address, addressVersion, storedData, int(time.time()), 'yes')
563          sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t)
564          return 'successful'
565      except varintDecodeError:
566          logger.info(
567              'Pubkey decryption was UNsuccessful due to a malformed varint.')
568          return 'failed'
569      except Exception:
570          logger.critical(
571              'Pubkey decryption was UNsuccessful because of'
572              ' an unhandled exception! This is definitely a bug!',
573              exc_info=True
574          )
575          return 'failed'