def getKey(self): with newkey_lock[self.serverid]: if self.serverid not in keys: for _ in range(2): logger.debug('hxsocks getKey') host, port, usn, psw = (self.hxsServer.hostname, self.hxsServer.port, self.hxsServer.username, self.hxsServer.password) if self._sock is None: logger.debug('hxsocks connect') from connection import create_connection self._sock = create_connection((host, port), self.timeout, parentproxy=self.parentproxy, tunnel=True) self.pskcipher = encrypt.Encryptor(self.PSK, self.method) acipher = ECC(self.pskcipher.key_len) pubk = acipher.get_pub_key() logger.debug('hxsocks send key exchange request') ts = struct.pack('>I', int(time.time())) padding_len = random.randint(64, 255) data = chr(10) + ts + chr(len(pubk)) + pubk + hmac.new(psw.encode(), ts + pubk + usn.encode(), hashlib.sha256).digest()\ + chr(padding_len) + b'\x00' * padding_len self._sock.sendall(self.pskcipher.encrypt(data)) fp = self._sock.makefile('rb', 0) resp_len = 1 if self.pskcipher.decipher else self.pskcipher.iv_len + 1 resp = ord(self.pskcipher.decrypt(fp.read(resp_len))) if resp == 0: logger.debug('hxsocks read key exchange respond') pklen = ord(self.pskcipher.decrypt(fp.read(1))) server_key = self.pskcipher.decrypt(fp.read(pklen)) auth = self.pskcipher.decrypt(fp.read(32)) pklen = ord(self.pskcipher.decrypt(fp.read(1))) server_cert = self.pskcipher.decrypt(fp.read(pklen)) sig_len = ord(self.pskcipher.decrypt(fp.read(1))) signature = self.pskcipher.decrypt(fp.read(sig_len)) pad_len = ord(self.pskcipher.decrypt(fp.read(1))) self.pskcipher.decrypt(fp.read(pad_len)) # TODO: ask user if a certificate should be accepted or not. if host not in known_hosts: logger.info('hxs: server %s new cert %s saved.' % (host, hashlib.sha256(server_cert).hexdigest()[:8])) with open('./.hxs_known_hosts/' + host + '.cert', 'wb') as f: f.write(server_cert) known_hosts[host] = server_cert elif known_hosts[host] != server_cert: logger.error('hxs: server %s certificate mismatch! PLEASE CHECK!' % host) raise OSError(0, 'hxs: bad certificate') if auth == hmac.new(psw.encode(), pubk + server_key + usn.encode(), hashlib.sha256).digest(): if ECC.verify_with_pub_key(server_cert, auth, signature, self.hash_algo): shared_secret = acipher.get_dh_key(server_key) keys[self.serverid] = (hashlib.md5(pubk).digest(), shared_secret) self.cipher = encrypt.AEncryptor(keys[self.serverid][1], self.method, SALT, CTX, 0) logger.debug('hxs key exchange success') return else: logger.error('hxs getKey Error: server auth failed, bad signature') else: logger.error('hxs getKey Error: server auth failed, bad username or password') else: fp.read(ord(self.pskcipher.decrypt(fp.read(1)))) logger.error('hxs getKey Error. bad password or timestamp.') else: raise IOError(0, 'hxs getKey Error') else: self.cipher = encrypt.AEncryptor(keys[self.serverid][1], self.method, SALT, CTX, 0)
def Authenticate(self, PubKey, PubKeySignature): ecc = ECC(pubkey_data=PubKey) if not ecc.verifyAddress(PubKeySignature, PubKey): raise ValueError('Failed to verify public key data.') self.stream.remote_ecc = ecc self.auth = True self.stream.node.add_stream(self.stream) return True
def create_key(cls, user, client_pkey, klen): if cls.notvalid(user, client_pkey): return 0, 0 if len(cls.userpkeys[user]) > 3: cls.del_key(cls.userpkeys[user][0]) dh = ECC(klen) shared_secret = dh.get_dh_key(client_pkey) client_pkey = hashlib.md5(client_pkey).digest() cls.userpkeys[user].append(client_pkey) cls.pkeyuser[client_pkey] = user cls.pkeykey[client_pkey] = shared_secret cls.pkeytime[client_pkey] = time.time() return dh.get_pub_key(), USER_PASS[user]
def A2MXAccessStore(node, data): b58node = ECC.b58(node) if mongoclient == None or b58node not in mongoclient.database_names(): raise A2MXAccessException('Unknown node {}'.format(b58node)) # FIXME check if data already in DB _id = mongoclient[b58node]['inbox'].insert({ 'timestamp': datetime.datetime.now(datetime.timezone.utf), 'data': data }) rid = int(0).to_bytes(4) data = rid + BSON.encode({ '_id': _id }) clients = connected_clients.online(node) for client in clients: client.sendfun(data)
def __init__(self, selectloop): self.selectloop = selectloop self.pathlist = a2mxpath.PathList() self.streams = [] self.ecc = ECC(keyfile=config['keyfile']) self.connected_nodes = { self.ecc.pubkeyHash(): self } mypub = self.ecc.pubkeyHashBase58() if sys.stdout.isatty(): cwd = os.getcwd().rsplit('/', 1)[1] sys.stdout.write("\x1b]2;{}: {}\x07".format(cwd, mypub)) print("I am", mypub) self.selectloop.tadd(90, self.find_new_peers)
def process_bson(self, bs): if self.ecc == None: self.ecc = ECC(pubkey_data=bs['access']) if self.ecc.pubkeyHash() != self.node.ecc.pubkeyHash(): if mongoclient == None or self.ecc.pubkeyHashBase58() not in mongoclient.database_names(): return { 'error': 'Unknown Node {}'.format(self.ecc.pubkeyHashBase58()) } self.db = mongoclient[self.ecc.pubkeyHashBase58()] print("access request to", self.ecc.pubkeyHashBase58()) else: print("access to me") self.auth = now() return { 'auth': self.auth, 'pubkey': self.node.ecc.pubkeyAddress() } if isinstance(self.auth, datetime.datetime): sigdata = BSON.encode({ 'auth': self.auth }) verify = self.ecc.verifyAddress(bs['sig'], sigdata) lsig = self.node.ecc.signAddress(sigdata) if not verify: return { 'error': 'Not authenticated' } self.auth = True connected_clients.add(self) return { 'sig': lsig } if self.auth != True: return { 'error': 'Not authenticated' } if len(bs) != 1: raise A2MXAccessException('Only one command at a time supported') for k, v in bs.items(): f = getattr(self, k, None) if getattr(f, 'A2MXAccessRequest__marker__', False) != True: raise A2MXAccessException('Invalid request {}'.format(k)) if isinstance(v, (dict, OrderedDict)): return f(**v) elif v == None: return f() raise A2MXAccessException('Invalid argument '.format(v))
def main(): logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', filemode='a+') hello = 'hxsocks-server %s' % __version__ if gevent: hello += ' with gevent %s' % gevent.__version__ print(hello) print('by v3aqb') global SERVER_CERT try: SERVER_CERT = ECC(from_file=os.path.join( os.path.dirname(os.path.abspath(__file__)), 'cert.pem')) except: logging.warning('server cert not found, creating...') SERVER_CERT = ECC(key_len=32) SERVER_CERT.save( os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cert.pem')) servers = ['hxs://0.0.0.0:9000'] forward = [] if os.path.exists( os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.json')): global USER_PASS d = json.loads( open( os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.json')).read()) USER_PASS = d['users'] servers = d['servers'] forward = d.get('forward', []) for s in servers: logging.info('starting server: %s' % s) ssserver = HXSocksServer(s, forward, HXSocksHandler) threading.Thread(target=ssserver.serve_forever).start()
def getKey(self): with newkey_lock[self.serverid]: if self.serverid not in keys: for _ in range(2): logger.debug('hxsocks getKey') host, port, usn, psw = (self.hxsServer.hostname, self.hxsServer.port, self.hxsServer.username, self.hxsServer.password) if self._sock is None: logger.debug('hxsocks connect') from connection import create_connection self._sock = create_connection( (host, port), self.timeout, parentproxy=self.parentproxy, tunnel=True) self.pskcipher = encrypt.Encryptor( self.PSK, self.method) self._rfile = self._sock.makefile('rb') self._header_sent = False self._header_received = False acipher = ECC(self.pskcipher.key_len) pubk = acipher.get_pub_key() logger.debug('hxsocks send key exchange request') ts = struct.pack('>I', int(time.time())) padding_len = random.randint(64, 255) data = b''.join([ ts, chr(len(pubk)).encode('latin1'), pubk, hmac.new(psw.encode(), ts + pubk + usn.encode(), hashlib.sha256).digest(), b'\x00' * padding_len ]) data = chr(10).encode() + struct.pack('>H', len(data)) + data self._sock_sendall(self.pskcipher.encrypt(data)) resp_len = 2 if self.pskcipher.decipher else self.pskcipher.iv_len + 2 resp_len = self.pskcipher.decrypt( self._rfile_read(resp_len)) resp_len = struct.unpack('>H', resp_len)[0] data = self.pskcipher.decrypt(self._rfile_read(resp_len)) data = io.BytesIO(data) resp_code = byte2int(data.read(1)) if resp_code == 0: logger.debug('hxsocks read key exchange respond') pklen = byte2int(data.read(1)) scertlen = byte2int(data.read(1)) siglen = byte2int(data.read(1)) server_key = data.read(pklen) auth = data.read(32) server_cert = data.read(scertlen) signature = data.read(siglen) # TODO: ask user if a certificate should be accepted or not. if host not in known_hosts: logger.info( 'hxs: server %s new cert %s saved.' % (host, hashlib.sha256(server_cert).hexdigest()[:8])) with open('./.hxs_known_hosts/' + host + '.cert', 'wb') as f: f.write(server_cert) known_hosts[host] = server_cert elif known_hosts[host] != server_cert: logger.error( 'hxs: server %s certificate mismatch! PLEASE CHECK!' % host) raise OSError(0, 'hxs: bad certificate') if auth == hmac.new(psw.encode(), pubk + server_key + usn.encode(), hashlib.sha256).digest(): if ECC.verify_with_pub_key(server_cert, auth, signature, self.hash_algo): shared_secret = acipher.get_dh_key(server_key) keys[self.serverid] = ( hashlib.md5(pubk).digest(), shared_secret) self.cipher = encrypt.AEncryptor( keys[self.serverid][1], self.method, SALT, CTX, 0, MAC_LEN) logger.debug('hxs key exchange success') return else: logger.error( 'hxs getKey Error: server auth failed, bad signature' ) else: logger.error( 'hxs getKey Error: server auth failed, bad username or password' ) else: logger.error( 'hxs getKey Error. bad password or timestamp.') else: raise IOError(0, 'hxs getKey Error') else: self.cipher = encrypt.AEncryptor(keys[self.serverid][1], self.method, SALT, CTX, 0, MAC_LEN)
class A2MXAccess(): def __init__(self, node, sendfun): self.node = node self.sendfun = sendfun self.ecc = None self.auth = False def disconnected(self): if self.auth: connected_clients.remove(self) print("A2MXAccess disconnect", self.ecc.pubkeyHashBase58() if self.ecc else "unknown", "authenticated" if self.auth == True else "not authenticated") def process(self, data): rid = data[:4] bs = BSON.decode(bytes(data[4:]), tz_aware=True) try: value = self.process_bson(bs) except Exception as e: import traceback traceback.print_exc() value = None error = 'Exception occured: {} {}'.format(str(type(e)), str(e)) else: error = None response = {} if isinstance(value, (dict, OrderedDict)): response = value else: if value != None: response = { 'data': value } if error != None: response = { 'error': error } if len(response): bv = BSON.encode(response) self.sendfun(rid + bv) def process_bson(self, bs): if self.ecc == None: self.ecc = ECC(pubkey_data=bs['access']) if self.ecc.pubkeyHash() != self.node.ecc.pubkeyHash(): if mongoclient == None or self.ecc.pubkeyHashBase58() not in mongoclient.database_names(): return { 'error': 'Unknown Node {}'.format(self.ecc.pubkeyHashBase58()) } self.db = mongoclient[self.ecc.pubkeyHashBase58()] print("access request to", self.ecc.pubkeyHashBase58()) else: print("access to me") self.auth = now() return { 'auth': self.auth, 'pubkey': self.node.ecc.pubkeyAddress() } if isinstance(self.auth, datetime.datetime): sigdata = BSON.encode({ 'auth': self.auth }) verify = self.ecc.verifyAddress(bs['sig'], sigdata) lsig = self.node.ecc.signAddress(sigdata) if not verify: return { 'error': 'Not authenticated' } self.auth = True connected_clients.add(self) return { 'sig': lsig } if self.auth != True: return { 'error': 'Not authenticated' } if len(bs) != 1: raise A2MXAccessException('Only one command at a time supported') for k, v in bs.items(): f = getattr(self, k, None) if getattr(f, 'A2MXAccessRequest__marker__', False) != True: raise A2MXAccessException('Invalid request {}'.format(k)) if isinstance(v, (dict, OrderedDict)): return f(**v) elif v == None: return f() raise A2MXAccessException('Invalid argument '.format(v)) @A2MXAccessRequest def getpath(self): p = A2MXPath(self.node.ecc, self.ecc, no_URI=True) return p.data @A2MXAccessRequest def setpath(self, **kwargs): p = A2MXPath(**kwargs) assert p.isComplete self.db['path'].remove() self.db['path'].insert(p.data) self.node.new_path(p) return True @A2MXAccessRequest def paths(self): paths = [ p.data for p in self.node.paths ] return paths @A2MXAccessRequest def find(self, query, rep): return [ x for x in self.db['inbox'].find(query, rep) ] @A2MXAccessRequest def save(self, doc): return self.db['inbox'].save(doc) @A2MXAccessRequest def find_routes(self, src, dst, min_hops, max_hops, max_count): if not src or src == b'\x00': src = self.node.ecc.pubkeyHash() routes = self.node.find_routes_from(src, dst, max_hops) send = [] for route in routes: if len(route) < min_hops: continue send.append(route.routes) if len(send) >= max_count: break return send @A2MXAccessRequest def sendto(self, node, data): return self.node.sendto(node, data)
def getKey(self): logger.debug('hxsocks2 getKey') host, port, usn, psw = (self.hxsServer.hostname, self.hxsServer.port, self.hxsServer.username, self.hxsServer.password) logger.debug('hxsocks2 connect to server') from connection import create_connection self._sock = create_connection((host, port), self.timeout, parentproxy=self.parentproxy, tunnel=True) self._rfile = self._sock.makefile('rb') acipher = ECC(self.__pskcipher._key_len) pubk = acipher.get_pub_key() logger.debug('hxsocks2 send key exchange request') ts = int(time.time()) // 30 ts = struct.pack('>I', ts) padding_len = random.randint(64, 255) data = b''.join([chr(len(pubk)).encode('latin1'), pubk, hmac.new(psw.encode(), ts + pubk + usn.encode(), hashlib.sha256).digest(), b'\x00' * padding_len]) data = chr(20).encode() + struct.pack('>H', len(data)) + data ct = self.__pskcipher.encrypt(data) self._sock.sendall(ct) self.__pskcipher.decrypt(self._rfile.read(self.__pskcipher._iv_len)) if encrypt.is_aead(self.method): ct_len = self.__pskcipher.decrypt(self._rfile.read(18)) ct_len, = struct.unpack('!H', ct_len) ct = self.__pskcipher.decrypt(self._rfile.read(ct_len + 16)) data = ct[2:] else: resp_len = self.__pskcipher.decrypt(self._rfile.read(2)) resp_len, = struct.unpack('>H', resp_len) data = self.__pskcipher.decrypt(self._rfile.read(resp_len)) data = io.BytesIO(data) resp_code = byte2int(data.read(1)) if resp_code == 0: logger.debug('hxsocks read key exchange respond') pklen = byte2int(data.read(1)) scertlen = byte2int(data.read(1)) siglen = byte2int(data.read(1)) server_key = data.read(pklen) auth = data.read(32) server_cert = data.read(scertlen) signature = data.read(siglen) # TODO: ask user if a certificate should be accepted or not. server_id = '%s_%d' % (host, port) if server_id not in known_hosts: logger.info('hxs: server %s new cert %s saved.' % (server_id, hashlib.sha256(server_cert).hexdigest()[:8])) with open('./.hxs_known_hosts/' + server_id + '.cert', 'wb') as f: f.write(server_cert) known_hosts[server_id] = server_cert elif known_hosts[server_id] != server_cert: logger.error('hxs: server %s certificate mismatch! PLEASE CHECK!' % server_id) raise OSError(0, 'hxs: bad certificate') if auth == hmac.new(psw.encode(), pubk + server_key + usn.encode(), hashlib.sha256).digest(): if ECC.verify_with_pub_key(server_cert, auth, signature, self.hash_algo): shared_secret = acipher.get_dh_key(server_key) logger.debug('hxs key exchange success') self.__cipher = AEncryptor(shared_secret, self.method, CTX) return else: logger.error('hxs getKey Error: server auth failed, bad signature') else: logger.error('hxs getKey Error: server auth failed, bad username or password') else: logger.error('hxs getKey Error. bad password or timestamp.') raise OSError(0, 'hxs getKey Error')
if len(sys.argv) == 1: host = 'localhost' port = config['bind'][0][1] else: hostport = sys.argv[1].split(':') if len(hostport) == 1: host = hostport[0] port = 0xA22 elif len(hostport) == 2: host = hostport[0] port = int(hostport[1]) else: raise ValueError('invalid arguments') ecc = ECC(pkcs8_der_keyfile_address=config['address.pkcs8.der'], pkcs8_der_keyfile_sign=config['sign.pkcs8.der'], pkcs8_der_keyfile_encrypt=config['encrypt.pkcs8.der']) print("I am", ecc.pubkeyHashBase58()) def SSL(sock): context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) context.verify_mode = ssl.CERT_NONE context.set_ecdh_curve('secp521r1') context.options = ssl.OP_SINGLE_DH_USE | ssl.OP_SINGLE_ECDH_USE context.set_ciphers('ECDHE-ECDSA-AES256-SHA') return context.wrap_socket(sock, do_handshake_on_connect=True) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) sock.connect((host, port)) sock = SSL(sock)
def __init__(self, A=None, B=None, T=None, UA=None, UB=None, M=None, PB=None, PF=None, PD=None, P=None, SA=None, SB=None, D=None, DS=None): # A = node A public key data (address, sign and encrypt compressed public keys) # B = node B public key data # T = timestamp # UA = AX URI node A # UB = AX URI node B # M = MaxSize # PB = POW broadcast # PF = POW forward # PD = POW direct # P = path proof of work # SA = node A signature (over A, B, T and UA if present) # SB = node B signature (over A, B, T and UB if present) # D = deleted timestamp # DS = deleted signature (over A, B, T, SA, SB and D) # A must always be the smaller binary value if not isinstance(A, ECC): self.__a = ECC(pubkey_data=A) else: self.__a = A if not SA and self.__a.hasPrivkey() and not PB < 0: UA = config['publish_axuri'] if not isinstance(B, ECC): self.__b = ECC(pubkey_data=B) else: self.__b = B if not SB and self.__b.hasPrivkey() and not PB < 0: UB = config['publish_axuri'] def testURI(uri): if uri == None: return if len(uri) > 32: raise ValueError('URI too long') try: host, port = uri.split(':') except ValueError: raise ValueError('Invalid URI') try: int(port) except ValueError: raise ValueError('Invalid URI') validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789' if not all(c in validChars for c in host): raise ValueError('Invalid chars in URI') testURI(UA) testURI(UB) self.__ua = UA self.__ub = UB if self.__a.pubkeyData() > self.__b.pubkeyData(): self.__a, self.__b = self.__b, self.__a self.__ua, self.__ub = self.__ub, self.__ua if T: if T > datetime.datetime.now(datetime.timezone.utc): raise ValueError('timestamp is in the future') self.__t = T else: self.__t = now() assert isinstance(M, int) assert isinstance(PB, float) assert isinstance(PF, float) assert isinstance(PD, float) self.__maxsize = M self.__pb = PB self.__pf = PF self.__pd = PD self.__sigod = OrderedDict() self.__sigod['A'] = self.__a.pubkeyData() self.__sigod['B'] = self.__b.pubkeyData() self.__sigod['T'] = self.__t self.__sigod['M'] = self.__maxsize self.__sigod['PB'] = PB self.__sigod['PF'] = PF self.__sigod['PD'] = PD if self.__ua: self.__sigod['UA'] = self.__ua sigdata_a = BSON.encode(self.__sigod) del self.__sigod['UA'] else: sigdata_a = BSON.encode(self.__sigod) if self.__ub: self.__sigod['UB'] = self.__ub sigdata_b = BSON.encode(self.__sigod) del self.__sigod['UB'] else: sigdata_b = BSON.encode(self.__sigod) self.__sa = SA if SA == None: if self.__a.hasPrivkey(): self.__sa = self.__a.signAddress(sigdata_a) else: verify = self.__a.verifyAddress(SA, sigdata_a) if not verify: raise InvalidDataException('SA signature verify failed.') self.__sb = SB if SB == None: if self.__b.hasPrivkey(): self.__sb = self.__b.signAddress(sigdata_b) else: verify = self.__b.verifyAddress(SB, sigdata_b) if not verify: raise InvalidDataException('SA signature verify failed.') if not (self.__sa or self.__sb): raise ValueError('Invalid signatures.') pow_data = BSON.encode(self.__sigod) if P == True: self.pow_done = None def setpow(nonce): self.__pow = nonce if self.pow_done: self.pow_done() calculatePOW(message=pow_data, difficulty=3.0, callback=setpow) else: self.__pow = P if isinstance(P, int): if not checkPOW(message=pow_data, difficulty=3.0, nonce=P): raise ValueError('Invalid POW') elif P != None: raise ValueError('Invalid POW value') self.__d = D self.__ds = DS if self.__d: if self.__d > datetime.datetime.now(datetime.timezone.utc): raise ValueError('Deleted timestamp is in the future.') if self.__d < self.__t: raise ValueError('Deleted timestamp is older than timestamp.') if not self.isComplete: raise ValueError('Deleted path may not be incomplete.') self.__sigod['SA'] = self.__sa self.__sigod['SB'] = self.__sb self.__sigod['D'] = self.__d sigdata = BSON.encode(self.__sigod) verify = self.__a.verifyAddress(self.__ds, sigdata) or self.__b.verifyAddress(self.__ds, sigdata) if not verify: raise InvalidDataException('DS signature verify failed.') self.__hash = hash(self.AHash + self.BHash) self.__longhash = hashlib.sha256(BSON.encode(self.__sigod)).digest()
class A2MXPath(): def __init__(self, A=None, B=None, T=None, UA=None, UB=None, M=None, PB=None, PF=None, PD=None, P=None, SA=None, SB=None, D=None, DS=None): # A = node A public key data (address, sign and encrypt compressed public keys) # B = node B public key data # T = timestamp # UA = AX URI node A # UB = AX URI node B # M = MaxSize # PB = POW broadcast # PF = POW forward # PD = POW direct # P = path proof of work # SA = node A signature (over A, B, T and UA if present) # SB = node B signature (over A, B, T and UB if present) # D = deleted timestamp # DS = deleted signature (over A, B, T, SA, SB and D) # A must always be the smaller binary value if not isinstance(A, ECC): self.__a = ECC(pubkey_data=A) else: self.__a = A if not SA and self.__a.hasPrivkey() and not PB < 0: UA = config['publish_axuri'] if not isinstance(B, ECC): self.__b = ECC(pubkey_data=B) else: self.__b = B if not SB and self.__b.hasPrivkey() and not PB < 0: UB = config['publish_axuri'] def testURI(uri): if uri == None: return if len(uri) > 32: raise ValueError('URI too long') try: host, port = uri.split(':') except ValueError: raise ValueError('Invalid URI') try: int(port) except ValueError: raise ValueError('Invalid URI') validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789' if not all(c in validChars for c in host): raise ValueError('Invalid chars in URI') testURI(UA) testURI(UB) self.__ua = UA self.__ub = UB if self.__a.pubkeyData() > self.__b.pubkeyData(): self.__a, self.__b = self.__b, self.__a self.__ua, self.__ub = self.__ub, self.__ua if T: if T > datetime.datetime.now(datetime.timezone.utc): raise ValueError('timestamp is in the future') self.__t = T else: self.__t = now() assert isinstance(M, int) assert isinstance(PB, float) assert isinstance(PF, float) assert isinstance(PD, float) self.__maxsize = M self.__pb = PB self.__pf = PF self.__pd = PD self.__sigod = OrderedDict() self.__sigod['A'] = self.__a.pubkeyData() self.__sigod['B'] = self.__b.pubkeyData() self.__sigod['T'] = self.__t self.__sigod['M'] = self.__maxsize self.__sigod['PB'] = PB self.__sigod['PF'] = PF self.__sigod['PD'] = PD if self.__ua: self.__sigod['UA'] = self.__ua sigdata_a = BSON.encode(self.__sigod) del self.__sigod['UA'] else: sigdata_a = BSON.encode(self.__sigod) if self.__ub: self.__sigod['UB'] = self.__ub sigdata_b = BSON.encode(self.__sigod) del self.__sigod['UB'] else: sigdata_b = BSON.encode(self.__sigod) self.__sa = SA if SA == None: if self.__a.hasPrivkey(): self.__sa = self.__a.signAddress(sigdata_a) else: verify = self.__a.verifyAddress(SA, sigdata_a) if not verify: raise InvalidDataException('SA signature verify failed.') self.__sb = SB if SB == None: if self.__b.hasPrivkey(): self.__sb = self.__b.signAddress(sigdata_b) else: verify = self.__b.verifyAddress(SB, sigdata_b) if not verify: raise InvalidDataException('SA signature verify failed.') if not (self.__sa or self.__sb): raise ValueError('Invalid signatures.') pow_data = BSON.encode(self.__sigod) if P == True: self.pow_done = None def setpow(nonce): self.__pow = nonce if self.pow_done: self.pow_done() calculatePOW(message=pow_data, difficulty=3.0, callback=setpow) else: self.__pow = P if isinstance(P, int): if not checkPOW(message=pow_data, difficulty=3.0, nonce=P): raise ValueError('Invalid POW') elif P != None: raise ValueError('Invalid POW value') self.__d = D self.__ds = DS if self.__d: if self.__d > datetime.datetime.now(datetime.timezone.utc): raise ValueError('Deleted timestamp is in the future.') if self.__d < self.__t: raise ValueError('Deleted timestamp is older than timestamp.') if not self.isComplete: raise ValueError('Deleted path may not be incomplete.') self.__sigod['SA'] = self.__sa self.__sigod['SB'] = self.__sb self.__sigod['D'] = self.__d sigdata = BSON.encode(self.__sigod) verify = self.__a.verifyAddress(self.__ds, sigdata) or self.__b.verifyAddress(self.__ds, sigdata) if not verify: raise InvalidDataException('DS signature verify failed.') self.__hash = hash(self.AHash + self.BHash) self.__longhash = hashlib.sha256(BSON.encode(self.__sigod)).digest() def __getstate__(self): state = { 'A': self.__a.pubkeyData(), 'B': self.__b.pubkeyData(), 'T': self.__t, 'SA': self.__sa, 'SB': self.__sb, 'M': self.__maxsize, 'PB': self.__pb, 'PF': self.__pf, 'PD': self.__pd } if self.__ua: state['UA'] = self.__ua if self.__ub: state['UB'] = self.__ub if self.__pow: state['P'] = self.__pow if self.__d: state['D'] = self.__d state['DS'] = self.__ds return state def __setstate__(self, state): return A2MXPath.__init__(self, **state) def __hash__(self): return self.__hash @property def longHash(self): return self.__longhash @property def isComplete(self): return self.__sa != None and self.__sb != None and self.__pow != None @property def data(self): return self.__getstate__() @property def A(self): return self.__a.pubkeyData() @property def AHash(self): return self.__a.pubkeyHash() @property def AURI(self): return self.__ua @property def B(self): return self.__b.pubkeyData() @property def BHash(self): return self.__b.pubkeyHash() @property def BURI(self): return self.__ub @property def deleted(self): return self.__d @property def timestamp(self): return self.__t @property def newest_timestamp(self): return self.__d or self.__t def markdelete(self): assert self.__d == None assert 'SA' not in self.__sigod assert 'SB' not in self.__sigod assert 'D' not in self.__sigod self.__sigod['SA'] = self.__sa self.__sigod['SB'] = self.__sb self.__d = now() self.__sigod['D'] = self.__d sigdata = BSON.encode(self.__sigod) if self.__a.hasPrivkey(): self.__ds = self.__a.signAddress(sigdata) elif self.__b.hasPrivkey(): self.__ds = self.__b.signAddress(sigdata) else: raise ValueError('Cannot mark path as deleted without private key.') def __eq__(self, other): if not isinstance(other, A2MXPath): return False return self.A == other.A and self.B == other.B def equal(self, other): if not isinstance(other, A2MXPath): return False return self.data == other.data def otherHash(self, otherHash): if otherHash == self.AHash: return self.BHash elif otherHash == self.BHash: return self.AHash raise ValueError('otherHash is neither A or B.') def ecc(self, h): if h == self.AHash: return self.A if h == self.BHash: return self.B raise ValueError("Hash is neither A nor B") @property def hashes(self): return (self.AHash, self.BHash) def is_better_than(self, other): if self != other: raise ValueError('Cannot compare paths with different nodes') return self.newest_timestamp > other.newest_timestamp def __str__(self): return 'A: {}{} B: {}{} Timestamp: {} M: {} PB: {} PF: {} PD: {} POW: {} Deleted: {}{}'.format( self.__a.pubkeyHashBase58(), " ({})".format(self.__ua) if self.__ua else "", self.__b.pubkeyHashBase58(), " ({})".format(self.__ub) if self.__ub else "", self.__t.isoformat(), self.__maxsize, self.__pb, self.__pf, self.__pd, self.__pow, self.__d.isoformat() if self.__d else False, "" if self.isComplete else " Incomplete")
class A2MXNode(): def __init__(self, selectloop): self.selectloop = selectloop self.pathlist = a2mxpath.PathList() self.streams = [] self.ecc = ECC(keyfile=config['keyfile']) self.connected_nodes = { self.ecc.pubkeyHash(): self } mypub = self.ecc.pubkeyHashBase58() if sys.stdout.isatty(): cwd = os.getcwd().rsplit('/', 1)[1] sys.stdout.write("\x1b]2;{}: {}\x07".format(cwd, mypub)) print("I am", mypub) self.selectloop.tadd(90, self.find_new_peers) def add(self, selectable): self.selectloop.add(selectable) def remove(self, selectable): self.selectloop.remove(selectable) def wadd(self, selectable): self.selectloop.wadd(selectable) def wremove(self, selectable): self.selectloop.wremove(selectable) def add_stream(self, stream): # if stream.remote_ecc.pubkeyHash() in self.connected_nodes: # return False assert stream not in self.streams self.streams.append(stream) self.connected_nodes[stream.remote_ecc.pubkeyHash()] = stream return True def del_stream(self, stream): if stream not in self.streams: return self.streams.remove(stream) assert stream.remote_ecc.pubkeyHash() in self.connected_nodes and self.connected_nodes[stream.remote_ecc.pubkeyHash()] == stream del self.connected_nodes[stream.remote_ecc.pubkeyHash()] if stream.path and stream.path.isComplete: self.del_path(stream.path) def new_path(self, path, stream=None): fromhash = stream.remote_ecc.pubkeyHashBase58() if stream else 'myself' if self.pathlist.new(path, fromhash): self.send_path(path, stream) print(self.pathlist) def send_path(self, path, stream=None): for ostream in self.streams: if ostream == stream: continue if hasattr(ostream, 'forward'): ostream.forward.sendCall('path', path.data) def del_path(self, path): self.pathlist.delete(path) self.send_path(path) def update_nodes(self, path): def up(h): if h not in self.nodes: self.nodes[h] = [] nl = self.nodes[h] if path in nl: nl.remove(path) nl.append(path) up(path.AHash) up(path.BHash) def sendto(self, node, data): if node == self.ecc.pubkeyHash(): data = self.ecc.decrypt(bytes(data)) self.request.parseRequest(data) return # if node not in self.connected_nodes: # try: # a2mxaccess.A2MXAccessStore(node, data) # print("stored data for {}".format(ECC.b58(node))) # except a2mxaccess.A2MXAccessException: # print("cannot send to node {}".format(ECC.b58(node))) # return False self.connected_nodes[node].raw_send(data) return True def find_new_peers(self): self.selectloop.tadd(random.randint(30, 90), self.find_new_peers) if len(self.streams) >= config['connections']: return try: new_hash = random.sample(self.pathlist.axuris.keys() - self.connected_nodes.keys(), 1)[0] except ValueError: return A2MXStream(self, uri='ax://' + self.pathlist.axuris[new_hash], pubkey_hash=new_hash) def find_routes_from(self, src, dst, maxhops=None): if dst not in self.nodes: return [] pathlist = self.nodes[dst] if len(pathlist) == 0: return [] dst_pubc = pathlist[0].pubkeyCompressed(dst) def find_path(pathlist, lasthop, thispath=None, step=1): if maxhops != None and step >= maxhops: return if thispath == None: thispath = [dst_pubc] for path in pathlist: if path.deleted: continue nexthop = path.otherHash(lasthop) nexthop_pubc = path.pubkeyCompressed(nexthop) if nexthop == src: ytp = thispath[:] ytp.append(nexthop_pubc) yield A2MXRoute(ytp) continue if nexthop == dst: continue if nexthop_pubc in thispath: continue tp = thispath[:] tp.append(nexthop_pubc) for p in find_path(self.nodes[lasthop], lasthop, tp, step+1): yield p return find_path(pathlist, dst) def shortest_route(self, src, dst): try: routes = [ x for x in self.find_routes_from(src, dst) ] except KeyError: return A2MXRoute([]) if len(routes) == 0: return A2MXRoute([]) return min(routes, key=len)
def __str__(self): s = 'A2MXRoute' for r in self.routes: s += ' ' + ECC.b58(r).decode('ascii') return s