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)
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)
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 )
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)