Example #1
0
class CryptoTransportLayer(TransportLayer):

    def __init__(self, my_ip, my_port, market_id, db, bm_user=None, bm_pass=None,
                 bm_port=None, seed_mode=0, dev_mode=False, disable_ip_update=False):

        self.log = logging.getLogger(
            '[%s] %s' % (market_id, self.__class__.__name__)
        )
        requests_log = logging.getLogger("requests")
        requests_log.setLevel(logging.WARNING)

        # Connect to database
        self.db = db

        self.bitmessage_api = None
        if (bm_user, bm_pass, bm_port) != (None, None, None):
            if not self._connect_to_bitmessage(bm_user, bm_pass, bm_port):
                self.log.info('Bitmessage not installed or started')

        try:
            socket.inet_pton(socket.AF_INET6, my_ip)
            my_uri = "tcp://[%s]:%s" % (my_ip, my_port)
        except (socket.error, ValueError):
            my_uri = "tcp://%s:%s" % (my_ip, my_port)

        self.market_id = market_id
        self.nick_mapping = {}
        self.uri = my_uri
        self.ip = my_ip
        self.nickname = ""
        self._dev_mode = dev_mode

        # Set up
        self._setup_settings()

        self.dht = DHT(self, self.market_id, self.settings, self.db)

        # self._myself = ec.ECC(pubkey=self.pubkey.decode('hex'),
        #                       privkey=self.secret.decode('hex'),
        #                       curve='secp256k1')

        TransportLayer.__init__(self, market_id, my_ip, my_port,
                                self.guid, self.nickname)

        self.setup_callbacks()
        self.listen(self.pubkey)

        if seed_mode == 0 and not dev_mode and not disable_ip_update:
            self.start_ip_address_checker()

    def setup_callbacks(self):
        self.add_callbacks([('hello', self._ping),
                            ('findNode', self._find_node),
                            ('findNodeResponse', self._find_node_response),
                            ('store', self._store_value)])

    def start_ip_address_checker(self):
        '''Checks for possible public IP change'''
        self.caller = PeriodicCallback(self._ip_updater_periodic_callback, 5000, ioloop.IOLoop.instance())
        self.caller.start()

    def _ip_updater_periodic_callback(self):
        try:
            r = requests.get('http://ipv4.icanhazip.com')

            if r and hasattr(r, 'text'):
                ip = r.text
                ip = ip.strip(' \t\n\r')
                if ip != self.ip:
                    self.ip = ip
                    try:
                        socket.inet_pton(socket.AF_INET6, self.ip)
                        my_uri = 'tcp://[%s]:%s' % (self.ip, self.port)
                    except (socket.error, ValueError):
                        my_uri = 'tcp://%s:%s' % (self.ip, self.port)
                    self.uri = my_uri
                    self.stream.close()
                    self.listen(self.pubkey)

                    self.dht._iterativeFind(self.guid, [], 'findNode')
            else:
                self.log.error('Could not get IP')
        except Exception as e:
            self.log.error('[Requests] error: %s' % e)

    def save_peer_to_db(self, peer_tuple):
        uri = peer_tuple[0]
        pubkey = peer_tuple[1]
        guid = peer_tuple[2]
        nickname = peer_tuple[3]

        # Update query
        self.db.deleteEntries("peers", {"uri": uri, "guid": guid}, "OR")
        # if len(results) > 0:
        #     self.db.updateEntries("peers", {"id": results[0]['id']}, {"market_id": self.market_id, "uri": uri, "pubkey": pubkey, "guid": guid, "nickname": nickname})
        # else:
        if guid is not None:
            self.db.insertEntry("peers", {
                "uri": uri,
                "pubkey": pubkey,
                "guid": guid,
                "nickname": nickname,
                "market_id": self.market_id
            })

    def _connect_to_bitmessage(self, bm_user, bm_pass, bm_port):
        # Get bitmessage going
        # First, try to find a local instance
        result = False
        try:
            self.log.info('[_connect_to_bitmessage] Connecting to Bitmessage on port %s' % bm_port)
            self.bitmessage_api = xmlrpclib.ServerProxy("http://{}:{}@localhost:{}/".format(bm_user, bm_pass, bm_port), verbose=0)
            result = self.bitmessage_api.add(2, 3)
            self.log.info("[_connect_to_bitmessage] Bitmessage API is live: %s", result)
        # If we failed, fall back to starting our own
        except Exception as e:
            self.log.info("Failed to connect to bitmessage instance: {}".format(e))
            self.bitmessage_api = None
            # self._log.info("Spawning internal bitmessage instance")
            # # Add bitmessage submodule path
            # sys.path.insert(0, os.path.join(
            #     os.path.dirname(__file__), '..', 'pybitmessage', 'src'))
            # import bitmessagemain as bitmessage
            # bitmessage.logger.setLevel(logging.WARNING)
            # bitmessage_instance = bitmessage.Main()
            # bitmessage_instance.start(daemon=True)
            # bminfo = bitmessage_instance.getApiAddress()
            # if bminfo is not None:
            #     self._log.info("Started bitmessage daemon at %s:%s".format(
            #         bminfo['address'], bminfo['port']))
            #     bitmessage_api = xmlrpclib.ServerProxy("http://{}:{}@{}:{}/".format(
            #         bm_user, bm_pass, bminfo['address'], bminfo['port']))
            # else:
            #     self._log.info("Failed to start bitmessage dameon")
            #     self._bitmessage_api = None
        return result

    def _checkok(self, msg):
        self.log.info('Check ok')

    def get_guid(self):
        return self.guid

    def get_dht(self):
        return self.dht

    def get_bitmessage_api(self):
        return self.bitmessage_api

    def get_market_id(self):
        return self.market_id

    # def get_myself(self):
    #     return self._myself

    def _ping(self, msg):

        self.log.info('Pinged %s ' % json.dumps(msg, ensure_ascii=False))
        #
        # pinger = CryptoPeerConnection(self, msg['uri'], msg['pubkey'], msg['senderGUID'])
        # pinger.send_raw(json.dumps(
        #     {"type": "hello_response",
        #      "senderGUID": self.guid,
        #      "uri": self.uri,
        #      "senderNick": self.nickname,
        #      "pubkey": self.pubkey,
        #     }))

    def _store_value(self, msg):
        self.dht._on_storeValue(msg)

    def _find_node(self, msg):
        self.dht.on_find_node(msg)

    def _find_node_response(self, msg):
        self.dht.on_findNodeResponse(self, msg)

    def _setup_settings(self):

        try:
            self.settings = self.db.selectEntries("settings", {"market_id": self.market_id})
        except (OperationalError, DatabaseError) as e:
            print e
            raise SystemExit("database file %s corrupt or empty - cannot continue" % self.db.db_path)

        if len(self.settings) == 0:
            self.settings = {"market_id": self.market_id, "welcome": "enable"}
            self.db.insertEntry("settings", self.settings)
        else:
            self.settings = self.settings[0]

        # Generate PGP key during initial setup or if previous PGP gen failed
        if not ('PGPPubKey' in self.settings and self.settings["PGPPubKey"]):
            try:
                self.log.info('Generating PGP keypair. This may take several minutes...')
                print 'Generating PGP keypair. This may take several minutes...'
                gpg = gnupg.GPG()
                input_data = gpg.gen_key_input(key_type="RSA",
                                               key_length=2048,
                                               name_email='*****@*****.**',
                                               name_comment="Autogenerated by Open Bazaar",
                                               passphrase="P@ssw0rd")
                assert input_data is not None
                key = gpg.gen_key(input_data)
                assert key is not None

                pubkey_text = gpg.export_keys(key.fingerprint)
                newsettings = {"PGPPubKey": pubkey_text, "PGPPubkeyFingerprint": key.fingerprint}
                self.db.updateEntries("settings", {"market_id": self.market_id}, newsettings)
                self.settings.update(newsettings)

                self.log.info('PGP keypair generated.')
            except Exception as e:
                self.log.error("Encountered a problem with GPG: %s" % e)
                raise SystemExit("Encountered a problem with GPG: %s" % e)

        if not ('pubkey' in self.settings and self.settings['pubkey']):
            # Generate Bitcoin keypair
            self._generate_new_keypair()

        if not ('nickname' in self.settings and self.settings['nickname']):
            newsettings = {'nickname': 'Default'}
            self.db.updateEntries('settings', {"market_id": self.market_id}, newsettings)
            self.settings.update(newsettings)

        self.nickname = self.settings['nickname'] if 'nickname' in self.settings else ""
        self.secret = self.settings['secret'] if 'secret' in self.settings else ""
        self.pubkey = self.settings['pubkey'] if 'pubkey' in self.settings else ""
        self.privkey = self.settings.get('privkey')
        self.btc_pubkey = privkey_to_pubkey(self.privkey)
        self.guid = self.settings['guid'] if 'guid' in self.settings else ""
        self.sin = self.settings['sin'] if 'sin' in self.settings else ""
        self.bitmessage = self.settings['bitmessage'] if 'bitmessage' in self.settings else ""

        if not ('bitmessage' in self.settings and self.settings['bitmessage']):
            # Generate Bitmessage address
            if self.bitmessage_api is not None:
                self._generate_new_bitmessage_address()

        self._myself = ec.ECC(
            pubkey=pubkey_to_pyelliptic(self.pubkey).decode('hex'),
            raw_privkey=self.secret.decode('hex'),
            curve='secp256k1'
        )

        self.log.debug('Retrieved Settings: \n%s', pformat(self.settings))

    def _generate_new_keypair(self):
        secret = str(random.randrange(2 ** 256))
        self.secret = hashlib.sha256(secret).hexdigest()
        self.pubkey = privtopub(self.secret)
        self.privkey = random_key()
        print 'PRIVATE KEY: ', self.privkey
        self.btc_pubkey = privtopub(self.privkey)
        print 'PUBLIC KEY: ', self.btc_pubkey

        # Generate SIN
        sha_hash = hashlib.sha256()
        sha_hash.update(self.pubkey)
        ripe_hash = hashlib.new('ripemd160')
        ripe_hash.update(sha_hash.digest())

        self.guid = ripe_hash.digest().encode('hex')
        self.sin = obelisk.EncodeBase58Check('\x0F\x02%s' + ripe_hash.digest())

        newsettings = {
            "secret": self.secret,
            "pubkey": self.pubkey,
            "privkey": self.privkey,
            "guid": self.guid,
            "sin": self.sin
        }
        self.db.updateEntries("settings", {"market_id": self.market_id}, newsettings)
        self.settings.update(newsettings)

    def _generate_new_bitmessage_address(self):
        # Use the guid generated previously as the key
        self.bitmessage = self.bitmessage_api.createRandomAddress(
            self.guid.encode('base64'),
            False,
            1.05,
            1.1111
        )
        newsettings = {"bitmessage": self.bitmessage}
        self.db.updateEntries("settings", {"market_id": self.market_id}, newsettings)
        self.settings.update(newsettings)

    def join_network(self, seed_peers=None, callback=lambda msg: None):
        if seed_peers is None:
            seed_peers = []

        self.log.info('Joining network')

        known_peers = []

        # Connect up through seed servers
        for idx, seed in enumerate(seed_peers):
            try:
                socket.inet_pton(socket.AF_INET6, seed)
                seed_peers[idx] = "tcp://[%s]:12345" % seed
            except (socket.error, ValueError):
                seed_peers[idx] = "tcp://%s:12345" % seed

        # Connect to persisted peers
        db_peers = self.get_past_peers()

        known_peers = list(set(seed_peers)) + list(set(db_peers))

        self.connect_to_peers(known_peers)

        # TODO: This needs rethinking. Normally we can search for ourselves
        #       but because we are not connected to them quick enough this
        #       will always fail. Need @gubatron to review
        # Populate routing table by searching for self
        # if len(known_peers) > 0:
        #     self.search_for_my_node()

        if callback is not None:
            callback('Joined')

    def get_past_peers(self):
        peers = []
        result = self.db.selectEntries("peers", {"market_id": self.market_id})
        for peer in result:
            peers.append(peer['uri'])
        return peers

    def search_for_my_node(self):
        print 'Searching for myself'
        self.dht._iterativeFind(self.guid, self.dht.knownNodes, 'findNode')

    def connect_to_peers(self, known_peers):
        for known_peer in known_peers:
            t = Thread(target=self.dht.add_peer, args=(self, known_peer,))
            t.start()

    def get_crypto_peer(self, guid=None, uri=None, pubkey=None, nickname=None,
                        callback=None):
        if guid == self.guid:
            self.log.error('Cannot get CryptoPeerConnection for your own node')
            return

        self.log.debug('Getting CryptoPeerConnection' +
                       '\nGUID:%s\nURI:%s\nPubkey:%s\nNickname:%s' %
                       (guid, uri, pubkey, nickname))

        return connection.CryptoPeerConnection(self,
                                               uri,
                                               pubkey,
                                               guid=guid,
                                               nickname=nickname)

    def addCryptoPeer(self, peer_to_add):

        foundOutdatedPeer = False
        for idx, peer in enumerate(self.dht.activePeers):

            if (peer.address, peer.guid, peer.pub) == \
               (peer_to_add.address, peer_to_add.guid, peer_to_add.pub):
                self.log.info('Found existing peer, not adding.')
                return

            if peer.guid == peer_to_add.guid or \
               peer.pub == peer_to_add.pub or \
               peer.address == peer_to_add.address:

                foundOutdatedPeer = True
                self.log.info('Found an outdated peer')

                # Update existing peer
                self.activePeers[idx] = peer_to_add
                self.dht.add_peer(self,
                                  peer_to_add.address,
                                  peer_to_add.pub,
                                  peer_to_add.guid,
                                  peer_to_add.nickname)

        if not foundOutdatedPeer and peer_to_add.guid != self.guid:
            self.log.info('Adding crypto peer at %s' % peer_to_add.nickname)
            self.dht.add_peer(self,
                              peer_to_add.address,
                              peer_to_add.pub,
                              peer_to_add.guid,
                              peer_to_add.nickname)

    def get_profile(self):
        peers = {}

        self.settings = self.db.selectEntries("settings", {"market_id": self.market_id})[0]
        for uri, peer in self.peers.iteritems():
            if peer.pub:
                peers[uri] = peer.pub.encode('hex')
        return {'uri': self.uri,
                'pub': self._myself.get_pubkey().encode('hex'),
                'nickname': self.nickname,
                'peers': peers}

    def respond_pubkey_if_mine(self, nickname, ident_pubkey):

        if ident_pubkey != self.pubkey:
            self.log.info("Public key does not match your identity")
            return

        # Return signed pubkey
        pubkey = self._myself.pubkey
        ec_key = obelisk.EllipticCurveKey()
        ec_key.set_secret(self.secret)
        digest = obelisk.Hash(pubkey)
        signature = ec_key.sign(digest)

        # Send array of nickname, pubkey, signature to transport layer
        self.send(proto_response_pubkey(nickname, pubkey, signature))

    def pubkey_exists(self, pub):

        for peer in self.peers.itervalues():
            self.log.info(
                'PEER: %s Pub: %s' % (
                    peer.pub.encode('hex'), pub.encode('hex')
                )
            )
            if peer.pub.encode('hex') == pub.encode('hex'):
                return True

        return False

    def create_peer(self, uri, pub, node_guid):

        if pub:
            pub = pub.decode('hex')

        # Create the peer if public key is not already in the peer list
        # if not self.pubkey_exists(pub):
        self.peers[uri] = connection.CryptoPeerConnection(self, uri, pub, node_guid)

        # Call 'peer' callbacks on listeners
        self.trigger_callbacks('peer', self.peers[uri])

        # else:
        #    print 'Pub Key is already in peer list'

    def send(self, data, send_to=None, callback=lambda msg: None):

        self.log.debug("Outgoing Data: %s %s" % (data, send_to))

        # Directed message
        if send_to is not None:

            peer = self.dht.routingTable.getContact(send_to)
            if not peer:
                for activePeer in self.dht.activePeers:
                    if activePeer.guid == send_to:
                        peer = activePeer
                        break

            # peer = CryptoPeerConnection(msg['uri'])
            if peer:
                self.log.debug('Directed Data (%s): %s' % (send_to, data))
                try:
                    peer.send(data, callback=callback)
                except Exception as e:
                    self.log.error('Not sending message directly to peer %s' % e)
            else:
                self.log.error('No peer found')

        else:
            # FindKey and then send

            for peer in self.dht.activePeers:
                try:
                    peer = self.dht.routingTable.getContact(peer.guid)
                    data['senderGUID'] = self.guid
                    data['pubkey'] = self.pubkey

                    def cb(msg):
                        self.log.debug('Message Back: \n%s' % pformat(msg))

                    peer.send(data, cb)

                except:
                    self.log.info("Error sending over peer!")
                    traceback.print_exc()

    def send_enc(self, uri, msg):
        peer = self.peers[uri]
        pub = peer.pub

        # Now send a hello message to the peer
        if pub:
            self.log.info(
                "Sending encrypted [%s] message to %s" % (
                    msg['type'], uri
                )
            )
            peer.send(msg)
        else:
            # Will send clear profile on initial if no pub
            self.log.info(
                "Sending unencrypted [%s] message to %s" % (
                    msg['type'], uri
                )
            )
            self.peers[uri].send_raw(json.dumps(msg))

    def _init_peer(self, msg):

        uri = msg['uri']
        pub = msg.get('pub')
        nickname = msg.get('nickname')
        msg_type = msg.get('type')
        guid = msg['guid']

        if not self.valid_peer_uri(uri):
            self.log.error("Invalid Peer: %s " % uri)
            return

        if uri not in self.peers:
            # Unknown peer
            self.log.info('Add New Peer: %s' % uri)
            self.create_peer(uri, pub, guid)

            if not msg_type:
                self.send_enc(uri, hello_request(self.get_profile()))
            elif msg_type == 'hello_request':
                self.send_enc(uri, hello_response(self.get_profile()))

        else:
            # Known peer
            if pub:
                # test if we have to update the pubkey
                if not self.peers[uri].pub:
                    self.log.info("Setting public key for seed node")
                    self.peers[uri].pub = pub.decode('hex')
                    self.trigger_callbacks('peer', self.peers[uri])

                if self.peers[uri].pub != pub.decode('hex'):
                    self.log.info("Updating public key for node")
                    self.peers[uri].nickname = nickname
                    self.peers[uri].pub = pub.decode('hex')

                    self.trigger_callbacks('peer', self.peers[uri])

            if msg_type == 'hello_request':
                # reply only if necessary
                self.send_enc(uri, hello_response(self.get_profile()))

    def _on_message(self, msg):

        # here goes the application callbacks
        # we get a "clean" msg which is a dict holding whatever
        # self.log.info("[On Message] Data received: %s" % msg)

        pubkey = msg.get('pubkey')
        uri = msg.get('uri')
        ip = urlparse(uri).hostname
        port = urlparse(uri).port
        guid = msg.get('senderGUID')
        nickname = msg.get('senderNick')[:120]

        self.dht.add_known_node((ip, port, guid, nickname))
        self.log.info('On Message: %s' % json.dumps(msg, ensure_ascii=False))
        self.dht.add_peer(self, uri, pubkey, guid, nickname)
        t = Thread(target=self.trigger_callbacks, args=(msg['type'], msg,))
        t.start()

    def _on_raw_message(self, serialized):
        try:

            msg = json.loads(serialized)
            self.log.info("Message Received [%s]" % msg.get('type', 'unknown'))

            if msg.get('type') is None:

                data = msg.get('data').decode('hex')
                sig = msg.get('sig').decode('hex')

                try:
                    cryptor = makePrivCryptor(self.secret)

                    try:
                        data = cryptor.decrypt(data)
                    except Exception as e:
                        self.log.info('Exception: %s' % e)

                    self.log.debug('Signature: %s' % sig.encode('hex'))
                    self.log.debug('Signed Data: %s' % data)

                    # Check signature
                    data_json = json.loads(data)
                    sigCryptor = makePubCryptor(data_json['pubkey'])
                    if sigCryptor.verify(sig, data):
                        self.log.info('Verified')
                    else:
                        self.log.error('Message signature could not be verified %s' % msg)
                        # return

                    msg = json.loads(data)
                    self.log.debug('Message Data %s ' % msg)
                except Exception as e:
                    self.log.error('Could not decrypt message properly %s' % e)

        except ValueError:
            try:
                # Encrypted?
                try:
                    msg = self._myself.decrypt(serialized)
                    msg = json.loads(msg)

                    self.log.info(
                        "Decrypted Message [%s]" % msg.get('type', 'unknown')
                    )
                except:
                    self.log.error("Could not decrypt message: %s" % msg)
                    return
            except:
                self.log.error('Message probably sent using incorrect pubkey')

                return

        if msg.get('type') is not None:
            self._on_message(msg)
        else:
            self.log.error('Received a message with no type')

    def shutdown(self):
        print "CryptoTransportLayer.shutdown()!"
        try:
            TransportLayer.shutdown(self)
            print "CryptoTransportLayer.shutdown(): ZMQ sockets destroyed."
        except Exception as e:
            self.log.error("Transport shutdown error: " + e.message)

        print "Notice: explicit DHT Shutdown not implemented."

        try:
            self.bitmessage_api.close()
        except Exception as e:
            # might not even be open, not much more we can do on our way out if exception thrown here.
            self.log.error("Could not shutdown bitmessage_api's ServerProxy. " + e.message)
Example #2
0
class CryptoTransportLayer(TransportLayer):
    def __init__(self,
                 my_ip,
                 my_port,
                 market_id,
                 db,
                 bm_user=None,
                 bm_pass=None,
                 bm_port=None,
                 seed_mode=0,
                 dev_mode=False):

        self.log = logging.getLogger('[%s] %s' %
                                     (market_id, self.__class__.__name__))
        requests_log = logging.getLogger("requests")
        requests_log.setLevel(logging.WARNING)

        # Connect to database
        self.db = db

        self.bitmessage_api = None
        if (bm_user, bm_pass, bm_port) != (None, None, None):
            if not self._connect_to_bitmessage(bm_user, bm_pass, bm_port):
                self.log.info('Bitmessage not installed or started')

        try:
            socket.inet_pton(socket.AF_INET6, my_ip)
            my_uri = "tcp://[%s]:%s" % (my_ip, my_port)
        except socket.error:
            my_uri = "tcp://%s:%s" % (my_ip, my_port)

        self.market_id = market_id
        self.nick_mapping = {}
        self.uri = my_uri
        self.ip = my_ip
        self.nickname = ""
        self._dev_mode = dev_mode

        # Set up
        self._setup_settings()

        self.dht = DHT(self, self.market_id, self.settings, self.db)

        # self._myself = ec.ECC(pubkey=self.pubkey.decode('hex'),
        #                       privkey=self.secret.decode('hex'),
        #                       curve='secp256k1')

        TransportLayer.__init__(self, market_id, my_ip, my_port, self.guid,
                                self.nickname)

        self.setup_callbacks()
        self.listen(self.pubkey)

        if seed_mode == 0 and not dev_mode:
            self.start_ip_address_checker()

    def setup_callbacks(self):
        self.add_callbacks([('hello', self._ping),
                            ('findNode', self._find_node),
                            ('findNodeResponse', self._find_node_response),
                            ('store', self._store_value)])

    def start_ip_address_checker(self):
        '''Checks for possible public IP change'''
        self.caller = PeriodicCallback(self._ip_updater_periodic_callback,
                                       5000, ioloop.IOLoop.instance())
        self.caller.start()

    def _ip_updater_periodic_callback(self):
        try:
            r = requests.get('https://icanhazip.com')

            if r and hasattr(r, 'text'):
                ip = r.text
                ip = ip.strip(' \t\n\r')
                if ip != self.ip:
                    self.ip = ip
                    try:
                        socket.inet_pton(socket.AF_INET6, self.ip)
                        my_uri = 'tcp://[%s]:%s' % (self.ip, self.port)
                    except socket.error:
                        my_uri = 'tcp://%s:%s' % (self.ip, self.port)
                    self.uri = my_uri
                    self.stream.close()
                    self.listen(self.pubkey)

                    self.dht._iterativeFind(self.guid, [], 'findNode')
            else:
                self.log.error('Could not get IP')
        except Exception as e:
            self.log.error('[Requests] error: %s' % e)

    def save_peer_to_db(self, peer_tuple):
        uri = peer_tuple[0]
        pubkey = peer_tuple[1]
        guid = peer_tuple[2]
        nickname = peer_tuple[3]

        # Update query
        self.db.deleteEntries("peers", {"uri": uri, "guid": guid}, "OR")
        # if len(results) > 0:
        #     self.db.updateEntries("peers", {"id": results[0]['id']}, {"market_id": self.market_id, "uri": uri, "pubkey": pubkey, "guid": guid, "nickname": nickname})
        # else:
        if guid is not None:
            self.db.insertEntry(
                "peers", {
                    "uri": uri,
                    "pubkey": pubkey,
                    "guid": guid,
                    "nickname": nickname,
                    "market_id": self.market_id
                })

    def _connect_to_bitmessage(self, bm_user, bm_pass, bm_port):
        # Get bitmessage going
        # First, try to find a local instance
        result = False
        try:
            self.log.info(
                '[_connect_to_bitmessage] Connecting to Bitmessage on port %s'
                % bm_port)
            self.bitmessage_api = xmlrpclib.ServerProxy(
                "http://{}:{}@localhost:{}/".format(bm_user, bm_pass, bm_port),
                verbose=0)
            result = self.bitmessage_api.add(2, 3)
            self.log.info(
                "[_connect_to_bitmessage] Bitmessage API is live".format(
                    result))
        # If we failed, fall back to starting our own
        except Exception as e:
            self.log.info(
                "Failed to connect to bitmessage instance: {}".format(e))
            self.bitmessage_api = None
            # self._log.info("Spawning internal bitmessage instance")
            # # Add bitmessage submodule path
            # sys.path.insert(0, os.path.join(
            #     os.path.dirname(__file__), '..', 'pybitmessage', 'src'))
            # import bitmessagemain as bitmessage
            # bitmessage.logger.setLevel(logging.WARNING)
            # bitmessage_instance = bitmessage.Main()
            # bitmessage_instance.start(daemon=True)
            # bminfo = bitmessage_instance.getApiAddress()
            # if bminfo is not None:
            #     self._log.info("Started bitmessage daemon at %s:%s".format(
            #         bminfo['address'], bminfo['port']))
            #     bitmessage_api = xmlrpclib.ServerProxy("http://{}:{}@{}:{}/".format(
            #         bm_user, bm_pass, bminfo['address'], bminfo['port']))
            # else:
            #     self._log.info("Failed to start bitmessage dameon")
            #     self._bitmessage_api = None
        return result

    def _checkok(self, msg):
        self.log.info('Check ok')

    def get_guid(self):
        return self.guid

    def get_dht(self):
        return self.dht

    def get_bitmessage_api(self):
        return self.bitmessage_api

    def get_market_id(self):
        return self.market_id

    # def get_myself(self):
    #     return self._myself

    def _ping(self, msg):

        self.log.info('Pinged %s ' % json.dumps(msg, ensure_ascii=False))
        #
        # pinger = CryptoPeerConnection(self, msg['uri'], msg['pubkey'], msg['senderGUID'])
        # pinger.send_raw(json.dumps(
        #     {"type": "hello_response",
        #      "senderGUID": self.guid,
        #      "uri": self.uri,
        #      "senderNick": self.nickname,
        #      "pubkey": self.pubkey,
        #     }))

    def _store_value(self, msg):
        self.dht._on_storeValue(msg)

    def _find_node(self, msg):
        self.dht.on_find_node(msg)

    def _find_node_response(self, msg):
        self.dht.on_findNodeResponse(self, msg)

    def _setup_settings(self):

        try:
            self.settings = self.db.selectEntries(
                "settings", {"market_id": self.market_id})
        except (OperationalError, DatabaseError) as e:
            print e
            raise SystemExit(
                "database file %s corrupt or empty - cannot continue" %
                self.db.db_path)

        if len(self.settings) == 0:
            self.settings = {"market_id": self.market_id, "welcome": "enable"}
            self.db.insertEntry("settings", self.settings)
        else:
            self.settings = self.settings[0]

        # Generate PGP key during initial setup or if previous PGP gen failed
        if not ('PGPPubKey' in self.settings and self.settings["PGPPubKey"]):
            try:
                self.log.info(
                    'Generating PGP keypair. This may take several minutes...')
                print 'Generating PGP keypair. This may take several minutes...'
                gpg = gnupg.GPG()
                input_data = gpg.gen_key_input(
                    key_type="RSA",
                    key_length=2048,
                    name_email='*****@*****.**',
                    name_comment="Autogenerated by Open Bazaar",
                    passphrase="P@ssw0rd")
                assert input_data is not None
                key = gpg.gen_key(input_data)
                assert key is not None

                pubkey_text = gpg.export_keys(key.fingerprint)
                newsettings = {
                    "PGPPubKey": pubkey_text,
                    "PGPPubkeyFingerprint": key.fingerprint
                }
                self.db.updateEntries("settings",
                                      {"market_id": self.market_id},
                                      newsettings)
                self.settings.update(newsettings)

                self.log.info('PGP keypair generated.')
            except Exception as e:
                self.log.error("Encountered a problem with GPG: %s" % e)
                raise SystemExit("Encountered a problem with GPG: %s" % e)

        if not ('pubkey' in self.settings and self.settings['pubkey']):
            # Generate Bitcoin keypair
            self._generate_new_keypair()

        if not ('bitmessage' in self.settings and self.settings['bitmessage']):
            # Generate Bitmessage address
            if self.bitmessage_api is not None:
                self._generate_new_bitmessage_address()

        if not ('nickname' in self.settings and self.settings['nickname']):
            newsettings = {'nickname': 'Default'}
            self.db.updateEntries('settings', {"market_id": self.market_id},
                                  newsettings)
            self.settings.update(newsettings)

        self.nickname = self.settings[
            'nickname'] if 'nickname' in self.settings else ""
        self.secret = self.settings[
            'secret'] if 'secret' in self.settings else ""
        self.pubkey = self.settings[
            'pubkey'] if 'pubkey' in self.settings else ""
        self.privkey = self.settings.get('privkey')
        self.btc_pubkey = privkey_to_pubkey(self.privkey)
        self.guid = self.settings['guid'] if 'guid' in self.settings else ""
        self.sin = self.settings['sin'] if 'sin' in self.settings else ""
        self.bitmessage = self.settings[
            'bitmessage'] if 'bitmessage' in self.settings else ""

        self._myself = ec.ECC(pubkey=pubkey_to_pyelliptic(
            self.pubkey).decode('hex'),
                              raw_privkey=self.secret.decode('hex'),
                              curve='secp256k1')

        self.log.debug('Retrieved Settings: \n%s', pformat(self.settings))

    def _generate_new_keypair(self):
        secret = str(random.randrange(2**256))
        self.secret = hashlib.sha256(secret).hexdigest()
        self.pubkey = privtopub(self.secret)
        self.privkey = random_key()
        print 'PRIVATE KEY: ', self.privkey
        self.btc_pubkey = privtopub(self.privkey)
        print 'PUBLIC KEY: ', self.btc_pubkey

        # Generate SIN
        sha_hash = hashlib.sha256()
        sha_hash.update(self.pubkey)
        ripe_hash = hashlib.new('ripemd160')
        ripe_hash.update(sha_hash.digest())

        self.guid = ripe_hash.digest().encode('hex')
        self.sin = obelisk.EncodeBase58Check('\x0F\x02%s' + ripe_hash.digest())

        newsettings = {
            "secret": self.secret,
            "pubkey": self.pubkey,
            "privkey": self.privkey,
            "guid": self.guid,
            "sin": self.sin
        }
        self.db.updateEntries("settings", {"market_id": self.market_id},
                              newsettings)
        self.settings.update(newsettings)

    def _generate_new_bitmessage_address(self):
        # Use the guid generated previously as the key
        self.bitmessage = self.bitmessage_api.createRandomAddress(
            self.guid.encode('base64'), False, 1.05, 1.1111)
        newsettings = {"bitmessage": self.bitmessage}
        self.db.updateEntries("settings", {"market_id": self.market_id},
                              newsettings)
        self.settings.update(newsettings)

    def join_network(self, seed_peers=[], callback=lambda msg: None):

        self.log.info('Joining network')

        known_peers = []

        # Connect up through seed servers
        for idx, seed in enumerate(seed_peers):
            try:
                socket.inet_pton(socket.AF_INET6, seed)
                seed_peers[idx] = "tcp://[%s]:12345" % seed
            except socket.error:
                seed_peers[idx] = "tcp://%s:12345" % seed

        # Connect to persisted peers
        db_peers = self.get_past_peers()

        known_peers = list(set(seed_peers)) + list(set(db_peers))

        print 'known_peers', known_peers

        self.connect_to_peers(known_peers)

        # Populate routing table by searching for self
        if len(known_peers) > 0:
            self.search_for_my_node()

        if callback is not None:
            callback('Joined')

    def get_past_peers(self):
        peers = []
        result = self.db.selectEntries("peers", {"market_id": self.market_id})
        for peer in result:
            peers.append(peer['uri'])
        return peers

    def search_for_my_node(self):
        print 'Searching for myself'
        self.dht._iterativeFind(self.guid, self.dht.knownNodes, 'findNode')

    def connect_to_peers(self, known_peers):
        for known_peer in known_peers:
            t = Thread(target=self.dht.add_peer, args=(
                self,
                known_peer,
            ))
            t.start()

    def get_crypto_peer(self,
                        guid=None,
                        uri=None,
                        pubkey=None,
                        nickname=None,
                        callback=None):
        if guid == self.guid:
            self.log.error('Cannot get CryptoPeerConnection for your own node')
            return

        self.log.debug('Getting CryptoPeerConnection' +
                       '\nGUID:%s\nURI:%s\nPubkey:%s\nNickname:%s' %
                       (guid, uri, pubkey, nickname))

        return connection.CryptoPeerConnection(self,
                                               uri,
                                               pubkey,
                                               guid=guid,
                                               nickname=nickname,
                                               callback=callback)

    def addCryptoPeer(self, peer_to_add):

        foundOutdatedPeer = False
        for idx, peer in enumerate(self.dht.activePeers):

            if (peer.address, peer.guid, peer.pub) == \
               (peer_to_add.address, peer_to_add.guid, peer_to_add.pub):
                self.log.info('Found existing peer, not adding.')
                return

            if peer.guid == peer_to_add.guid or \
               peer.pub == peer_to_add.pub or \
               peer.address == peer_to_add.address:

                foundOutdatedPeer = True
                self.log.info('Found an outdated peer')

                # Update existing peer
                self.activePeers[idx] = peer_to_add
                self.dht.add_peer(self, peer_to_add.address, peer_to_add.pub,
                                  peer_to_add.guid, peer_to_add.nickname)

        if not foundOutdatedPeer and peer_to_add.guid != self.guid:
            self.log.info('Adding crypto peer at %s' % peer_to_add.nickname)
            self.dht.add_peer(self, peer_to_add.address, peer_to_add.pub,
                              peer_to_add.guid, peer_to_add.nickname)

    def get_profile(self):
        peers = {}

        self.settings = self.db.selectEntries("settings",
                                              {"market_id": self.market_id})[0]
        for uri, peer in self.peers.iteritems():
            if peer.pub:
                peers[uri] = peer.pub.encode('hex')
        return {
            'uri': self.uri,
            'pub': self._myself.get_pubkey().encode('hex'),
            'nickname': self.nickname,
            'peers': peers
        }

    def respond_pubkey_if_mine(self, nickname, ident_pubkey):

        if ident_pubkey != self.pubkey:
            self.log.info("Public key does not match your identity")
            return

        # Return signed pubkey
        pubkey = self._myself.pubkey
        ec_key = obelisk.EllipticCurveKey()
        ec_key.set_secret(self.secret)
        digest = obelisk.Hash(pubkey)
        signature = ec_key.sign(digest)

        # Send array of nickname, pubkey, signature to transport layer
        self.send(proto_response_pubkey(nickname, pubkey, signature))

    def pubkey_exists(self, pub):

        for uri, peer in self.peers.iteritems():
            self.log.info('PEER: %s Pub: %s' %
                          (peer.pub.encode('hex'), pub.encode('hex')))
            if peer.pub.encode('hex') == pub.encode('hex'):
                return True

        return False

    def create_peer(self, uri, pub, node_guid):

        if pub:
            pub = pub.decode('hex')

        # Create the peer if public key is not already in the peer list
        # if not self.pubkey_exists(pub):
        self.peers[uri] = connection.CryptoPeerConnection(
            self, uri, pub, node_guid)

        # Call 'peer' callbacks on listeners
        self.trigger_callbacks('peer', self.peers[uri])

        # else:
        #    print 'Pub Key is already in peer list'

    def send(self, data, send_to=None, callback=lambda msg: None):

        self.log.debug("Outgoing Data: %s %s" % (data, send_to))

        # Directed message
        if send_to is not None:

            peer = self.dht.routingTable.getContact(send_to)
            if not peer:
                for activePeer in self.dht.activePeers:
                    if activePeer.guid == send_to:
                        peer = activePeer
                        break

            # peer = CryptoPeerConnection(msg['uri'])
            if peer:
                self.log.debug('Directed Data (%s): %s' % (send_to, data))
                try:
                    peer.send(data, callback=callback)
                except Exception as e:
                    self.log.error('Not sending message directly to peer %s' %
                                   e)
            else:
                self.log.error('No peer found')

        else:
            # FindKey and then send

            for peer in self.dht.activePeers:
                try:
                    peer = self.dht.routingTable.getContact(peer.guid)
                    data['senderGUID'] = self.guid
                    data['pubkey'] = self.pubkey

                    def cb(msg):
                        self.log.debug('Message Back: \n%s' % pformat(msg))

                    peer.send(data, cb)

                except:
                    self.log.info("Error sending over peer!")
                    traceback.print_exc()

    def send_enc(self, uri, msg):
        peer = self.peers[uri]
        pub = peer.pub

        # Now send a hello message to the peer
        if pub:
            self.log.info("Sending encrypted [%s] message to %s" %
                          (msg['type'], uri))
            peer.send(msg)
        else:
            # Will send clear profile on initial if no pub
            self.log.info("Sending unencrypted [%s] message to %s" %
                          (msg['type'], uri))
            self.peers[uri].send_raw(json.dumps(msg))

    def _init_peer(self, msg):

        uri = msg['uri']
        pub = msg.get('pub')
        nickname = msg.get('nickname')
        msg_type = msg.get('type')
        guid = msg['guid']

        if not self.valid_peer_uri(uri):
            self.log.error("Invalid Peer: %s " % uri)
            return

        if uri not in self.peers:
            # Unknown peer
            self.log.info('Add New Peer: %s' % uri)
            self.create_peer(uri, pub, guid)

            if not msg_type:
                self.send_enc(uri, hello_request(self.get_profile()))
            elif msg_type == 'hello_request':
                self.send_enc(uri, hello_response(self.get_profile()))

        else:
            # Known peer
            if pub:
                # test if we have to update the pubkey
                if not self.peers[uri].pub:
                    self.log.info("Setting public key for seed node")
                    self.peers[uri].pub = pub.decode('hex')
                    self.trigger_callbacks('peer', self.peers[uri])

                if self.peers[uri].pub != pub.decode('hex'):
                    self.log.info("Updating public key for node")
                    self.peers[uri].nickname = nickname
                    self.peers[uri].pub = pub.decode('hex')

                    self.trigger_callbacks('peer', self.peers[uri])

            if msg_type == 'hello_request':
                # reply only if necessary
                self.send_enc(uri, hello_response(self.get_profile()))

    def _on_message(self, msg):

        # here goes the application callbacks
        # we get a "clean" msg which is a dict holding whatever
        # self.log.info("[On Message] Data received: %s" % msg)

        pubkey = msg.get('pubkey')
        uri = msg.get('uri')
        ip = urlparse(uri).hostname
        port = urlparse(uri).port
        guid = msg.get('senderGUID')
        nickname = msg.get('senderNick')[:120]

        self.dht.add_known_node((ip, port, guid, nickname))
        self.log.info('ON MESSAGE %s' % json.dumps(msg, ensure_ascii=False))

        self.dht.add_peer(self, uri, pubkey, guid, nickname)
        self.log.debug('Callbacks %s' % self.callbacks)
        t = Thread(target=self.trigger_callbacks, args=(
            msg['type'],
            msg,
        ))
        t.start()

    def _on_raw_message(self, serialized):
        try:

            # Decompress message
            serialized = zlib.decompress(serialized)

            msg = json.loads(serialized)
            self.log.info("Message Received [%s]" % msg.get('type', 'unknown'))

            if msg.get('type') is None:

                data = msg.get('data').decode('hex')
                sig = msg.get('sig').decode('hex')

                try:
                    cryptor = makePrivCryptor(self.secret)

                    try:
                        data = cryptor.decrypt(data)
                    except Exception as e:
                        self.log.info('Exception: %s' % e)

                    self.log.debug('Signature: %s' % sig.encode('hex'))
                    self.log.debug('Signed Data: %s' % data)

                    # Check signature
                    data_json = json.loads(data)
                    sigCryptor = makePubCryptor(data_json['pubkey'])
                    if sigCryptor.verify(sig, data):
                        self.log.info('Verified')
                    else:
                        self.log.error(
                            'Message signature could not be verified %s' % msg)
                        # return

                    msg = json.loads(data)
                    self.log.debug('Message Data %s ' % msg)
                except Exception as e:
                    self.log.error('Could not decrypt message properly %s' % e)

        except ValueError:
            try:
                # Encrypted?
                try:
                    msg = self._myself.decrypt(serialized)
                    msg = json.loads(msg)

                    self.log.info("Decrypted Message [%s]" %
                                  msg.get('type', 'unknown'))
                except:
                    self.log.error("Could not decrypt message: %s" % msg)
                    return
            except:
                self.log.error('Message probably sent using incorrect pubkey')

                return

        if msg.get('type') is not None:
            self._on_message(msg)
        else:
            self.log.error('Received a message with no type')

    def shutdown(self):
        print "CryptoTransportLayer.shutdown()!"
        try:
            TransportLayer.shutdown(self)
            print "CryptoTransportLayer.shutdown(): ZMQ sockets destroyed."
        except Exception as e:
            self.log.error("Transport shutdown error: " + e.message)

        print "Notice: explicit DHT Shutdown not implemented."

        try:
            self.bitmessage_api.close()
        except Exception as e:
            # might not even be open, not much more we can do on our way out if exception thrown here.
            self.log.error(
                "Could not shutdown bitmessage_api's ServerProxy. " +
                e.message)
Example #3
0
class CryptoTransportLayer(TransportLayer):

    def __init__(self, ob_ctx, db):

        self.ob_ctx = ob_ctx

        self.log = logging.getLogger(
            '[%s] %s' % (ob_ctx.market_id, self.__class__.__name__)
        )
        requests_log = logging.getLogger("requests")
        requests_log.setLevel(logging.WARNING)

        self.db = db

        self.bitmessage_api = None
        if (ob_ctx.bm_user, ob_ctx.bm_pass, ob_ctx.bm_port) != (None, None, -1):
            if not self._connect_to_bitmessage():
                self.log.info('Bitmessage not installed or started')

        self.market_id = ob_ctx.market_id
        self.nick_mapping = {}
        self.uri = network_util.get_peer_url(ob_ctx.server_ip, ob_ctx.server_port)
        self.ip = ob_ctx.server_ip
        self.nickname = ""
        self.dev_mode = ob_ctx.dev_mode

        self.all_messages = (
            'hello',
            'findNode',
            'findNodeResponse',
            'store'
        )

        self._setup_settings()
        ob_ctx.market_id = self.market_id
        self.dht = DHT(self, self.market_id, self.settings, self.db)
        TransportLayer.__init__(self, ob_ctx, self.guid, self.nickname)
        self.start_listener()

        if ob_ctx.enable_ip_checker and not ob_ctx.seed_mode and not ob_ctx.dev_mode:
            self.start_ip_address_checker()

    def start_listener(self):
        self.add_callbacks([
            (
                msg,
                {
                    'cb': getattr(self, 'on_%s' % msg),
                    'validator_cb': getattr(self, 'validate_on_%s' % msg)
                }
            )
            for msg in self.all_messages
        ])

        self.listener = connection.CryptoPeerListener(
            self.ip, self.port, self.pubkey, self.secret, self.ctx,
            self.guid,
            self._on_message
        )

        self.listener.set_ok_msg({
            'type': 'ok',
            'senderGUID': self.guid,
            'pubkey': self.pubkey,
            'senderNick': self.nickname
        })
        self.listener.listen()

    def start_ip_address_checker(self):
        '''Checks for possible public IP change'''
        if self.ob_ctx.enable_ip_checker:
            self.caller = PeriodicCallback(self._ip_updater_periodic_callback, 5000, ioloop.IOLoop.instance())
            self.caller.start()
            self.log.info("IP_CHECKER_ENABLED: Periodic IP Address Checker started.")

    def _ip_updater_periodic_callback(self):
        if self.ob_ctx.enable_ip_checker:
            new_ip = network_util.get_my_ip()

            if not new_ip or new_ip == self.ip:
                return

            self.ob_ctx.server_ip = new_ip
            self.ip = new_ip

            if self.listener is not None:
                self.listener.set_ip_address(new_ip)

            self.dht._iterativeFind(self.guid, [], 'findNode')

    def save_peer_to_db(self, peer_tuple):
        uri = peer_tuple[0]
        pubkey = peer_tuple[1]
        guid = peer_tuple[2]
        nickname = peer_tuple[3]

        # Update query
        self.db.deleteEntries("peers", {"uri": uri, "guid": guid}, "OR")
        if guid is not None:
            self.db.insertEntry("peers", {
                "uri": uri,
                "pubkey": pubkey,
                "guid": guid,
                "nickname": nickname,
                "market_id": self.market_id
            })

    def _connect_to_bitmessage(self):
        # Get bitmessage going
        # First, try to find a local instance
        result = False
        bm_user = self.ob_ctx.bm_user
        bm_pass = self.ob_ctx.bm_pass
        bm_port = self.ob_ctx.bm_port
        try:
            self.log.info(
                '[_connect_to_bitmessage] Connecting to Bitmessage on port %s',
                bm_port
            )
            self.bitmessage_api = xmlrpclib.ServerProxy(
                "http://{}:{}@localhost:{}/".format(bm_user, bm_pass, bm_port),
                verbose=0
            )
            result = self.bitmessage_api.add(2, 3)
            self.log.info(
                "[_connect_to_bitmessage] Bitmessage API is live: %s",
                result
            )
        # If we failed, fall back to starting our own
        except Exception as e:
            self.log.info("Failed to connect to bitmessage instance: %s", e)
            self.bitmessage_api = None
        return result

    def validate_on_hello(self, msg):
        self.log.debug('Validating ping message.')
        return True

    def on_hello(self, msg):
        self.log.info('Pinged %s', json.dumps(msg, ensure_ascii=False))

    def validate_on_store(self, msg):
        self.log.debug('Validating store value message.')
        return True

    def on_store(self, msg):
        self.dht._on_storeValue(msg)

    def validate_on_findNode(self, msg):
        self.log.debug('Validating find node message.')
        return True

    def on_findNode(self, msg):
        self.dht.on_find_node(msg)

    def validate_on_findNodeResponse(self, msg):
        self.log.debug('Validating find node response message.')
        return True

    def on_findNodeResponse(self, msg):
        self.dht.on_findNodeResponse(self, msg)

    def _setup_settings(self):
        try:
            self.settings = self.db.selectEntries("settings", {"market_id": self.market_id})
        except (OperationalError, DatabaseError) as e:
            print e
            raise SystemExit("database file %s corrupt or empty - cannot continue" % self.db.db_path)

        if len(self.settings) == 0:
            self.settings = {"market_id": self.market_id, "welcome": "enable"}
            self.db.insertEntry("settings", self.settings)
        else:
            self.settings = self.settings[0]

        # Generate PGP key during initial setup or if previous PGP gen failed
        if not self.settings.get('PGPPubKey'):
            try:
                self.log.info('Generating PGP keypair. This may take several minutes...')
                print 'Generating PGP keypair. This may take several minutes...'
                gpg = gnupg.GPG()
                input_data = gpg.gen_key_input(key_type="RSA",
                                               key_length=2048,
                                               name_email='*****@*****.**',
                                               name_comment="Autogenerated by Open Bazaar",
                                               passphrase="P@ssw0rd")
                assert input_data is not None
                key = gpg.gen_key(input_data)
                assert key is not None

                pubkey_text = gpg.export_keys(key.fingerprint)
                newsettings = {"PGPPubKey": pubkey_text, "PGPPubkeyFingerprint": key.fingerprint}
                self.db.updateEntries("settings", newsettings, {"market_id": self.market_id})
                self.settings.update(newsettings)

                self.log.info('PGP keypair generated.')
            except Exception as e:
                sys.exit("Encountered a problem with GPG: %s" % e)

        if not self.settings.get('pubkey'):
            # Generate Bitcoin keypair
            self._generate_new_keypair()

        if not self.settings.get('nickname'):
            newsettings = {'nickname': 'Default'}
            self.db.updateEntries('settings', newsettings, {"market_id": self.market_id})
            self.settings.update(newsettings)

        self.nickname = self.settings.get('nickname', '')
        self.secret = self.settings.get('secret', '')
        self.pubkey = self.settings.get('pubkey', '')
        self.privkey = self.settings.get('privkey')
        self.btc_pubkey = privkey_to_pubkey(self.privkey)
        self.guid = self.settings.get('guid', '')
        self.sin = self.settings.get('sin', '')
        self.bitmessage = self.settings.get('bitmessage', '')

        if not self.settings.get('bitmessage'):
            # Generate Bitmessage address
            if self.bitmessage_api is not None:
                self._generate_new_bitmessage_address()

        self.cryptor = Cryptor(pubkey_hex=self.pubkey, privkey_hex=self.secret)

        # In case user wants to override with command line passed bitmessage values
        if self.ob_ctx.bm_user is not None and \
           self.ob_ctx.bm_pass is not None and \
           self.ob_ctx.bm_port is not None:
            self._connect_to_bitmessage()

    def _generate_new_keypair(self):
        secret = str(random.randrange(2 ** 256))
        self.secret = hashlib.sha256(secret).hexdigest()
        self.pubkey = privkey_to_pubkey(self.secret)
        self.privkey = random_key()
        self.btc_pubkey = privkey_to_pubkey(self.privkey)
        print 'PUBLIC KEY: ', self.btc_pubkey

        # Generate SIN
        sha_hash = hashlib.sha256()
        sha_hash.update(self.pubkey)
        ripe_hash = hashlib.new('ripemd160')
        ripe_hash.update(sha_hash.digest())

        self.guid = ripe_hash.hexdigest()
        self.sin = obelisk.EncodeBase58Check('\x0F\x02%s' % ripe_hash.digest())

        newsettings = {
            "secret": self.secret,
            "pubkey": self.pubkey,
            "privkey": self.privkey,
            "guid": self.guid,
            "sin": self.sin
        }
        self.db.updateEntries("settings", newsettings, {"market_id": self.market_id})
        self.settings.update(newsettings)

    def _generate_new_bitmessage_address(self):
        # Use the guid generated previously as the key
        self.bitmessage = self.bitmessage_api.createRandomAddress(
            self.guid.encode('base64'),
            False,
            1.05,
            1.1111
        )
        newsettings = {"bitmessage": self.bitmessage}
        self.db.updateEntries("settings", newsettings, {"market_id": self.market_id})
        self.settings.update(newsettings)

    def join_network(self, seeds=None, callback=None):
        if seeds is None:
            seeds = []

        self.log.info('Joining network')

        # Connect up through seed servers
        for idx, seed in enumerate(seeds):
            seeds[idx] = network_util.get_peer_url(seed, "12345")

        # Connect to persisted peers
        db_peers = self.get_past_peers()

        known_peers = list(set(seeds).union(db_peers))

        for known_peer in known_peers:
            self.dht.add_peer(self, known_peer)

        # Populate routing table by searching for self
        if known_peers:
            # Check every one second if we are connected
            # We could use a PeriodicCallback but I think this is simpler
            # since this will be repeated in most cases less than 10 times
            def join_callback():
                # If we are not connected to any node, reschedule a check
                if not self.dht.activePeers:
                    ioloop.IOLoop.instance().call_later(1, join_callback)
                else:
                    self.search_for_my_node()
            join_callback()

        if callback is not None:
            callback('Joined')

    def get_past_peers(self):
        result = self.db.selectEntries("peers", {"market_id": self.market_id})
        return [peer['uri'] for peer in result]

    def search_for_my_node(self):
        self.log.info('Searching for myself')
        self.dht._iterativeFind(self.guid, self.dht.knownNodes, 'findNode')

    def get_crypto_peer(self, guid=None, uri=None, pubkey=None, nickname=None):
        if guid == self.guid:
            self.log.error('Cannot get CryptoPeerConnection for your own node')
            return

        self.log.debug(
            'Getting CryptoPeerConnection'
            '\nGUID: %s'
            '\nURI: %s'
            '\nPubkey:%s'
            '\nNickname:%s',
            guid, uri, pubkey, nickname
        )

        return connection.CryptoPeerConnection(
            self, uri, pubkey, guid=guid, nickname=nickname
        )

    def respond_pubkey_if_mine(self, nickname, ident_pubkey):

        if ident_pubkey != self.pubkey:
            self.log.info("Public key does not match your identity")
            return

        # Return signed pubkey
        pubkey = self.cryptor.pubkey  # XXX: A Cryptor does not have such a field.
        ec_key = obelisk.EllipticCurveKey()
        ec_key.set_secret(self.secret)
        digest = obelisk.Hash(pubkey)
        signature = ec_key.sign(digest)

        # Send array of nickname, pubkey, signature to transport layer
        self.send(proto_response_pubkey(nickname, pubkey, signature))

    def send(self, data, send_to=None, callback=None):

        self.log.debug("Outgoing Data: %s %s", data, send_to)

        # Directed message
        if send_to is not None:

            peer = self.dht.routingTable.getContact(send_to)
            if peer is None:
                for activePeer in self.dht.activePeers:
                    if activePeer.guid == send_to:
                        peer = activePeer
                        break

            if peer:
                self.log.debug('Directed Data (%s): %s', send_to, data)
                try:
                    peer.send(data, callback=callback)
                except Exception as e:
                    self.log.error('Not sending message directly to peer %s', e)
            else:
                self.log.error('No peer found')

        else:
            # FindKey and then send

            for peer in self.dht.activePeers:
                try:
                    routing_peer = self.dht.routingTable.getContact(peer.guid)

                    if routing_peer is None:
                        self.dht.routingTable.addContact(peer)
                        routing_peer = peer

                    data['senderGUID'] = self.guid
                    data['pubkey'] = self.pubkey

                    def cb(msg):
                        self.log.debug('Message Back: \n%s', pformat(msg))

                    routing_peer.send(data, cb)

                except Exception:
                    self.log.info("Error sending over peer!")
                    traceback.print_exc()

    def _on_message(self, msg):

        # here goes the application callbacks
        # we get a "clean" msg which is a dict holding whatever

        pubkey = msg.get('pubkey')
        uri = msg.get('uri')
        guid = msg.get('senderGUID')
        nickname = msg.get('senderNick')[:120]

        self.log.info('On Message: %s', json.dumps(msg, ensure_ascii=False))
        self.dht.add_peer(self, uri, pubkey, guid, nickname)
        t = Thread(target=self.trigger_callbacks, args=(msg['type'], msg,))
        t.start()

    def store(self, *args, **kwargs):
        """
        Store or republish data.

        Refer to the dht module (iterativeStore()) for further details.
        """
        self.dht.iterativeStore(*args, **kwargs)

    def shutdown(self):
        print "CryptoTransportLayer.shutdown()!"
        print "Notice: explicit DHT Shutdown not implemented."

        try:
            if self.bitmessage_api is not None:
                self.bitmessage_api.close()
        except Exception as e:
            # It might not even be open; we can't do much more on our
            # way out if exception is thrown here.
            self.log.error(
                "Could not shutdown bitmessage_api's ServerProxy: %s", e.message
            )
Example #4
0
class CryptoTransportLayer(TransportLayer):
    def __init__(self, ob_ctx, db):

        self.ob_ctx = ob_ctx

        self.log = logging.getLogger(
            '[%s] %s' % (ob_ctx.market_id, self.__class__.__name__))
        requests_log = logging.getLogger("requests")
        requests_log.setLevel(logging.WARNING)

        self.db = db

        self.bitmessage_api = None
        if (ob_ctx.bm_user, ob_ctx.bm_pass, ob_ctx.bm_port) != (None, None,
                                                                -1):
            if not self._connect_to_bitmessage():
                self.log.info('Bitmessage not installed or started')

        self.market_id = ob_ctx.market_id
        self.nick_mapping = {}
        self.uri = network_util.get_peer_url(ob_ctx.server_ip,
                                             ob_ctx.server_port)
        self.ip = ob_ctx.server_ip
        self.nickname = ""
        self.dev_mode = ob_ctx.dev_mode

        self.all_messages = ('hello', 'findNode', 'findNodeResponse', 'store')

        self._setup_settings()
        ob_ctx.market_id = self.market_id
        self.dht = DHT(self, self.market_id, self.settings, self.db)
        TransportLayer.__init__(self, ob_ctx, self.guid, self.nickname)
        self.start_listener()

        if ob_ctx.enable_ip_checker and not ob_ctx.seed_mode and not ob_ctx.dev_mode:
            self.start_ip_address_checker()

    def start_listener(self):
        self.add_callbacks([(msg, {
            'cb':
            getattr(self, 'on_%s' % msg),
            'validator_cb':
            getattr(self, 'validate_on_%s' % msg)
        }) for msg in self.all_messages])

        self.listener = connection.CryptoPeerListener(self.ip, self.port,
                                                      self.pubkey, self.secret,
                                                      self.ctx, self.guid,
                                                      self._on_message)

        self.listener.set_ok_msg({
            'type': 'ok',
            'senderGUID': self.guid,
            'pubkey': self.pubkey,
            'senderNick': self.nickname
        })
        self.listener.listen()

    def start_ip_address_checker(self):
        '''Checks for possible public IP change'''
        if self.ob_ctx.enable_ip_checker:
            self.caller = PeriodicCallback(self._ip_updater_periodic_callback,
                                           5000, ioloop.IOLoop.instance())
            self.caller.start()
            self.log.info(
                "IP_CHECKER_ENABLED: Periodic IP Address Checker started.")

    def _ip_updater_periodic_callback(self):
        if self.ob_ctx.enable_ip_checker:
            new_ip = network_util.get_my_ip()

            if not new_ip or new_ip == self.ip:
                return

            self.ob_ctx.server_ip = new_ip
            self.ip = new_ip

            if self.listener is not None:
                self.listener.set_ip_address(new_ip)

            self.dht._iterativeFind(self.guid, [], 'findNode')

    def save_peer_to_db(self, peer_tuple):
        uri = peer_tuple[0]
        pubkey = peer_tuple[1]
        guid = peer_tuple[2]
        nickname = peer_tuple[3]

        # Update query
        self.db.deleteEntries("peers", {"uri": uri, "guid": guid}, "OR")
        if guid is not None:
            self.db.insertEntry(
                "peers", {
                    "uri": uri,
                    "pubkey": pubkey,
                    "guid": guid,
                    "nickname": nickname,
                    "market_id": self.market_id
                })

    def _connect_to_bitmessage(self):
        # Get bitmessage going
        # First, try to find a local instance
        result = False
        bm_user = self.ob_ctx.bm_user
        bm_pass = self.ob_ctx.bm_pass
        bm_port = self.ob_ctx.bm_port
        try:
            self.log.info(
                '[_connect_to_bitmessage] Connecting to Bitmessage on port %s',
                bm_port)
            self.bitmessage_api = xmlrpclib.ServerProxy(
                "http://{}:{}@localhost:{}/".format(bm_user, bm_pass, bm_port),
                verbose=0)
            result = self.bitmessage_api.add(2, 3)
            self.log.info(
                "[_connect_to_bitmessage] Bitmessage API is live: %s", result)
        # If we failed, fall back to starting our own
        except Exception as e:
            self.log.info("Failed to connect to bitmessage instance: %s", e)
            self.bitmessage_api = None
        return result

    def validate_on_hello(self, msg):
        self.log.debug('Validating ping message.')
        return True

    def on_hello(self, msg):
        self.log.info('Pinged %s', json.dumps(msg, ensure_ascii=False))

    def validate_on_store(self, msg):
        self.log.debug('Validating store value message.')
        return True

    def on_store(self, msg):
        self.dht._on_storeValue(msg)

    def validate_on_findNode(self, msg):
        self.log.debug('Validating find node message.')
        return True

    def on_findNode(self, msg):
        self.dht.on_find_node(msg)

    def validate_on_findNodeResponse(self, msg):
        self.log.debug('Validating find node response message.')
        return True

    def on_findNodeResponse(self, msg):
        self.dht.on_findNodeResponse(self, msg)

    def _setup_settings(self):
        try:
            self.settings = self.db.selectEntries(
                "settings", {"market_id": self.market_id})
        except (OperationalError, DatabaseError) as e:
            print e
            raise SystemExit(
                "database file %s corrupt or empty - cannot continue" %
                self.db.db_path)

        if len(self.settings) == 0:
            self.settings = {"market_id": self.market_id, "welcome": "enable"}
            self.db.insertEntry("settings", self.settings)
        else:
            self.settings = self.settings[0]

        # Generate PGP key during initial setup or if previous PGP gen failed
        if not self.settings.get('PGPPubKey'):
            try:
                self.log.info(
                    'Generating PGP keypair. This may take several minutes...')
                print 'Generating PGP keypair. This may take several minutes...'
                gpg = gnupg.GPG()
                input_data = gpg.gen_key_input(
                    key_type="RSA",
                    key_length=2048,
                    name_email='*****@*****.**',
                    name_comment="Autogenerated by Open Bazaar",
                    passphrase="P@ssw0rd")
                assert input_data is not None
                key = gpg.gen_key(input_data)
                assert key is not None

                pubkey_text = gpg.export_keys(key.fingerprint)
                newsettings = {
                    "PGPPubKey": pubkey_text,
                    "PGPPubkeyFingerprint": key.fingerprint
                }
                self.db.updateEntries("settings", newsettings,
                                      {"market_id": self.market_id})
                self.settings.update(newsettings)

                self.log.info('PGP keypair generated.')
            except Exception as e:
                sys.exit("Encountered a problem with GPG: %s" % e)

        if not self.settings.get('pubkey'):
            # Generate Bitcoin keypair
            self._generate_new_keypair()

        if not self.settings.get('nickname'):
            newsettings = {'nickname': 'Default'}
            self.db.updateEntries('settings', newsettings,
                                  {"market_id": self.market_id})
            self.settings.update(newsettings)

        self.nickname = self.settings.get('nickname', '')
        self.secret = self.settings.get('secret', '')
        self.pubkey = self.settings.get('pubkey', '')
        self.privkey = self.settings.get('privkey')
        self.btc_pubkey = privkey_to_pubkey(self.privkey)
        self.guid = self.settings.get('guid', '')
        self.sin = self.settings.get('sin', '')
        self.bitmessage = self.settings.get('bitmessage', '')

        if not self.settings.get('bitmessage'):
            # Generate Bitmessage address
            if self.bitmessage_api is not None:
                self._generate_new_bitmessage_address()

        self.cryptor = Cryptor(pubkey_hex=self.pubkey, privkey_hex=self.secret)

        # In case user wants to override with command line passed bitmessage values
        if self.ob_ctx.bm_user is not None and \
           self.ob_ctx.bm_pass is not None and \
           self.ob_ctx.bm_port is not None:
            self._connect_to_bitmessage()

    def _generate_new_keypair(self):
        secret = str(random.randrange(2**256))
        self.secret = hashlib.sha256(secret).hexdigest()
        self.pubkey = privkey_to_pubkey(self.secret)
        self.privkey = random_key()
        self.btc_pubkey = privkey_to_pubkey(self.privkey)
        print 'PUBLIC KEY: ', self.btc_pubkey

        # Generate SIN
        sha_hash = hashlib.sha256()
        sha_hash.update(self.pubkey)
        ripe_hash = hashlib.new('ripemd160')
        ripe_hash.update(sha_hash.digest())

        self.guid = ripe_hash.hexdigest()
        self.sin = obelisk.EncodeBase58Check('\x0F\x02%s' % ripe_hash.digest())

        newsettings = {
            "secret": self.secret,
            "pubkey": self.pubkey,
            "privkey": self.privkey,
            "guid": self.guid,
            "sin": self.sin
        }
        self.db.updateEntries("settings", newsettings,
                              {"market_id": self.market_id})
        self.settings.update(newsettings)

    def _generate_new_bitmessage_address(self):
        # Use the guid generated previously as the key
        self.bitmessage = self.bitmessage_api.createRandomAddress(
            self.guid.encode('base64'), False, 1.05, 1.1111)
        newsettings = {"bitmessage": self.bitmessage}
        self.db.updateEntries("settings", newsettings,
                              {"market_id": self.market_id})
        self.settings.update(newsettings)

    def join_network(self, seeds=None, callback=None):
        if seeds is None:
            seeds = []

        self.log.info('Joining network')

        # Connect up through seed servers
        for idx, seed in enumerate(seeds):
            seeds[idx] = network_util.get_peer_url(seed, "12345")

        # Connect to persisted peers
        db_peers = self.get_past_peers()

        known_peers = list(set(seeds).union(db_peers))

        for known_peer in known_peers:
            self.dht.add_peer(self, known_peer)

        # Populate routing table by searching for self
        if known_peers:
            # Check every one second if we are connected
            # We could use a PeriodicCallback but I think this is simpler
            # since this will be repeated in most cases less than 10 times
            def join_callback():
                # If we are not connected to any node, reschedule a check
                if not self.dht.activePeers:
                    ioloop.IOLoop.instance().call_later(1, join_callback)
                else:
                    self.search_for_my_node()

            join_callback()

        if callback is not None:
            callback('Joined')

    def get_past_peers(self):
        result = self.db.selectEntries("peers", {"market_id": self.market_id})
        return [peer['uri'] for peer in result]

    def search_for_my_node(self):
        self.log.info('Searching for myself')
        self.dht._iterativeFind(self.guid, self.dht.knownNodes, 'findNode')

    def get_crypto_peer(self, guid=None, uri=None, pubkey=None, nickname=None):
        if guid == self.guid:
            self.log.error('Cannot get CryptoPeerConnection for your own node')
            return

        self.log.debug(
            'Getting CryptoPeerConnection'
            '\nGUID: %s'
            '\nURI: %s'
            '\nPubkey:%s'
            '\nNickname:%s', guid, uri, pubkey, nickname)

        return connection.CryptoPeerConnection(self,
                                               uri,
                                               pubkey,
                                               guid=guid,
                                               nickname=nickname)

    def respond_pubkey_if_mine(self, nickname, ident_pubkey):

        if ident_pubkey != self.pubkey:
            self.log.info("Public key does not match your identity")
            return

        # Return signed pubkey
        pubkey = self.cryptor.pubkey  # XXX: A Cryptor does not have such a field.
        ec_key = obelisk.EllipticCurveKey()
        ec_key.set_secret(self.secret)
        digest = obelisk.Hash(pubkey)
        signature = ec_key.sign(digest)

        # Send array of nickname, pubkey, signature to transport layer
        self.send(proto_response_pubkey(nickname, pubkey, signature))

    def send(self, data, send_to=None, callback=None):

        self.log.debug("Outgoing Data: %s %s", data, send_to)

        # Directed message
        if send_to is not None:

            peer = self.dht.routingTable.getContact(send_to)
            if peer is None:
                for activePeer in self.dht.activePeers:
                    if activePeer.guid == send_to:
                        peer = activePeer
                        break

            if peer:
                self.log.debug('Directed Data (%s): %s', send_to, data)
                try:
                    peer.send(data, callback=callback)
                except Exception as e:
                    self.log.error('Not sending message directly to peer %s',
                                   e)
            else:
                self.log.error('No peer found')

        else:
            # FindKey and then send

            for peer in self.dht.activePeers:
                try:
                    routing_peer = self.dht.routingTable.getContact(peer.guid)

                    if routing_peer is None:
                        self.dht.routingTable.addContact(peer)
                        routing_peer = peer

                    data['senderGUID'] = self.guid
                    data['pubkey'] = self.pubkey

                    def cb(msg):
                        self.log.debug('Message Back: \n%s', pformat(msg))

                    routing_peer.send(data, cb)

                except Exception:
                    self.log.info("Error sending over peer!")
                    traceback.print_exc()

    def _on_message(self, msg):

        # here goes the application callbacks
        # we get a "clean" msg which is a dict holding whatever

        pubkey = msg.get('pubkey')
        uri = msg.get('uri')
        guid = msg.get('senderGUID')
        nickname = msg.get('senderNick')[:120]

        self.log.info('On Message: %s', json.dumps(msg, ensure_ascii=False))
        self.dht.add_peer(self, uri, pubkey, guid, nickname)
        t = Thread(target=self.trigger_callbacks, args=(
            msg['type'],
            msg,
        ))
        t.start()

    def store(self, *args, **kwargs):
        """
        Store or republish data.

        Refer to the dht module (iterativeStore()) for further details.
        """
        self.dht.iterativeStore(*args, **kwargs)

    def shutdown(self):
        print "CryptoTransportLayer.shutdown()!"
        print "Notice: explicit DHT Shutdown not implemented."

        try:
            if self.bitmessage_api is not None:
                self.bitmessage_api.close()
        except Exception as e:
            # It might not even be open; we can't do much more on our
            # way out if exception is thrown here.
            self.log.error(
                "Could not shutdown bitmessage_api's ServerProxy: %s",
                e.message)