class LANClient: def __init__(self, settings, key): if isinstance(settings, Settings): self.settings = settings.copy() else: self.settings = Settings(settings) self.key = key self.nonce_counter = 0 self.broadcast = (util.broadcast_address(), 30000) def configure(self, version): self.settings.set("pia.version", version) self.settings.set("pia.lan_version", self.lan_version(version)) self.settings.set("pia.station_extension", version < 50000) self.settings.set("pia.crypto_enabled", version >= 50900) def lan_version(self, version): if version >= 51100: return 2 if version >= 50900: return 1 return 0 def generate_challenge_key(self, key): aes = AES.new(self.key, AES.MODE_ECB) return aes.encrypt(key) def generate_nonce(self, counter): broadcast = socket.inet_aton(self.broadcast[0]) return broadcast + struct.pack(">Q", counter) def generate_challenge_reply(self, nonce, key, challenge): key = hmac.new(self.key, key, digestmod=hashlib.sha256).digest()[:16] data = hmac.new(self.key, challenge, digestmod=hashlib.sha256).digest()[:16] aes = AES.new(key, AES.MODE_GCM, nonce=nonce) ciphertext, tag = aes.encrypt_and_digest(data) return tag + ciphertext
response = common.RMCResponse() response.result = common.Result(0x10001) #Success response.connection_id = next(self.connection_id) response.public_station = station return response def register_ex(self, context, urls, login_data): return self.register(context, urls) class FriendsServer(friends.FriendsServer): pass #Implement friend server methods here settings = Settings("friends.cfg") settings.set("server.access_key", Friends.ACCESS_KEY) auth_server = service.RMCServer(settings) auth_server.register_protocol(AuthenticationServer(settings)) auth_server.start("", 1223) server_key = derive_key(get_user_by_name("Quazal Rendez-Vous")) secure_server = service.RMCServer(settings) secure_server.register_protocol(SecureConnectionServer()) secure_server.register_protocol(FriendsServer()) secure_server.start("", 1224, key=server_key) input("Press enter to exit...\n")
class PIASession: def __init__(self, settings=None): if isinstance(settings, Settings): self.settings = settings.copy() else: self.settings = Settings(settings) self.stations = StationTable() self.transport = MessageTransport(self) self.resender = ResendingTransport(self.transport) self.mesh = Mesh() self.session_key = None self.my_station = None self.session_settings = None self.protocols = {} def configure(self, version): self.settings.set("pia.version", version) self.settings.set("pia.station_extension", version < 50000) self.settings.set("pia.crypto_enabled", version >= 50900) if version >= 50900: self.settings.set("pia.encryption_method", 1) self.settings.set("pia.signature_method", 0) if version >= 51100: self.settings.set("pia.header_version", 4) if version >= 50900: self.settings.set("pia.protocol_type_revision", 1) self.settings.set("pia.message_version", self.derive_message_version(version)) def derive_message_version(self, pia_version): major_version = pia_version // 100 if major_version <= 503: return 0 if major_version in [509, 510]: return 1 if major_version == 511: return 2 if 514 <= major_version <= 517: return 3 if major_version == 518: return 4 raise ValueError("Unsupported PIA version: %i" % pia_version) def register_protocol(self, protocol): self.protocols[protocol.get_protocol_type()] = protocol def create_protocols(self): self.station_protocol = StationProtocol(self) self.mesh_protocol = MeshProtocol(self) self.unreliable_protocol = UnreliableProtocol(self) self.register_protocol(self.station_protocol) self.register_protocol(self.mesh_protocol) self.register_protocol(self.unreliable_protocol) def get_protocol(self, type): return self.protocols[type] def prepare(self, identification_info): logger.info("Initializing PIA session") self.create_protocols() self.transport.prepare() location = self.prepare_station_location() connection_info = StationConnectionInfo(location) self.my_station = self.stations.create() self.my_station.connection_state = ConnectionState.CONNECTED self.my_station.address = self.transport.local_address() self.my_station.connection_info = connection_info self.my_station.identification_info = identification_info self.my_station.id = self.build_station_id() self.event = scheduler.add_socket(self.handle_recv, self.transport) def cleanup(self): scheduler.remove(self.event) self.transport.cleanup() def create_mesh(self, session_key, settings): self.session_key = session_key self.session_settings = settings self.mesh.create(self.my_station) def join_mesh(self, session_key, host_location): self.session_key = session_key connection_info = StationConnectionInfo(host_location) address = host_location.local.address host = self.stations.create() host.address = address.host, address.port host.connection_info = connection_info self.connect_station(host) host.wait_connected() self.mesh_protocol.join(host) def leave_mesh(self): pass def connect_station(self, station): self.station_protocol.connect(station) def handle_recv(self, pair): station, message = pair protocol_id = message.protocol_id if protocol_id in self.protocols: self.protocols[protocol_id].handle(station, message) else: logger.warning("Unknown protocol id: 0x%X" % protocol_id) def get_mesh(self): return self.mesh def get_station_table(self): return self.stations def local_station(self): return self.my_station def host_station(self): index = self.mesh.get_host_index() return self.stations.find_by_index(index) def is_host(self): return self.local_station() == self.host_station() def get_game_mode(self): return self.session_settings.game_mode def get_attributes(self): return self.session_settings.attributes def get_num_participants(self): return self.mesh.get_num_participants() def get_min_participants(self): return self.session_settings.min_participants def get_max_participants(self): return self.session_settings.max_participants def get_session_type(self): return self.session_settings.session_type def get_application_data(self): return self.session_settings.application_data def get_session_key(self): return self.session_key def set_session_key(self, key): self.session_key = key def join(self, session): raise NotImplementedError def create(self): raise NotImplementedError def leave(self): raise NotImplementedError def generate_nonce(self, packet): raise NotImplementedError def get_session_id(self): raise NotImplementedError def build_station_id(self): raise NotImplementedError
class BackEndClient: def __init__(self, settings=None): if isinstance(settings, Settings): self.settings = settings.copy() else: self.settings = Settings(settings) self.auth_client = service.RMCClient(self.settings) self.secure_client = service.RMCClient(self.settings) self.auth_proto = authentication.AuthenticationClient(self.auth_client) self.secure_proto = secure.SecureConnectionClient(self.secure_client) if self.settings.get("kerberos.key_derivation") == 0: self.key_derivation = kerberos.KeyDerivationOld(65000, 1024) else: self.key_derivation = kerberos.KeyDerivationNew(1, 1) self.pid = None self.local_station = None self.public_station = None def configure(self, access_key, nex_version, client_version=None): self.settings.set("nex.access_key", access_key) self.settings.set("nex.version", nex_version) if nex_version >= 40500: if client_version is None: raise ValueError( "Must specify client version for NEX 4.5.0 or later") self.settings.set("nex.client_version", client_version) self.auth_client.set_access_key(access_key) self.secure_client.set_access_key(access_key) def connect(self, host, port): # Connect to authentication server if not self.auth_client.connect(host, port, 1): raise ConnectionError("Couldn't connect to authentication server") def close(self): self.auth_client.close() self.secure_client.close() def login(self, username, password=None, auth_info=None, login_data=None): if self.settings.get("nex.version") < 40500: result = self.login_normal(username, auth_info) else: result = self.login_with_param(username, auth_info) self.pid = result.pid secure_station = result.secure_station kerberos_key = result.ticket_key if not kerberos_key: if not password: raise ValueError("A password is required for this account") # Derive kerberos key from password kerberos_key = self.key_derivation.derive_key( password.encode(), self.pid) # Decrypt ticket from login response ticket = kerberos.ClientTicket() ticket.decrypt(result.ticket, kerberos_key, self.settings) if ticket.target_pid != secure_station["PID"]: # Request ticket for secure server response = self.auth_proto.request_ticket(self.pid, secure_station["PID"]) # Check for errors and decrypt ticket response.result.raise_if_error() ticket = kerberos.ClientTicket() ticket.decrypt(response.ticket, kerberos_key, self.settings) ticket.source_pid = self.pid ticket.target_cid = secure_station["CID"] # The secure server may reside at the same # address as the authentication server host = secure_station["address"] port = secure_station["port"] if host == "0.0.0.1": host, port = self.auth_client.remote_address() # Connect to secure server server_sid = secure_station["sid"] if not self.secure_client.connect(host, port, server_sid, ticket): raise ConnectionError("Couldn't connect to secure server") # Create a stationurl for our local client address client_addr = self.secure_client.local_address() self.local_station = common.StationURL( address=client_addr[0], port=client_addr[1], sid=self.secure_client.stream_id(), natm=0, natf=0, upnp=0, pmp=0) # Register urls on secure server if login_data: response = self.secure_proto.register_ex([self.local_station], login_data) else: response = self.secure_proto.register([self.local_station]) # Check for errors and update urls response.result.raise_if_error() self.public_station = response.public_station self.public_station["RVCID"] = response.connection_id self.local_station["RVCID"] = response.connection_id def login_normal(self, username, auth_info): if auth_info: response = self.auth_proto.login_ex(username, auth_info) else: response = self.auth_proto.login(username) response.result.raise_if_error() return LoginResult(response.pid, response.ticket, None, response.connection_data.main_station) def login_with_param(self, username, auth_info): param = authentication.ValidateAndRequestTicketParam() param.username = username if auth_info: param.data = auth_info else: param.data = common.NullData() param.nex_version = self.settings.get("nex.version") param.client_version = self.settings.get("nex.client_version") response = self.auth_proto.login_with_param(param) return LoginResult(response.pid, response.ticket, bytes.fromhex(response.ticket_key), response.server_url) def login_guest(self): self.login("guest", "MMQea3n!fsik") def get_pid(self): return self.pid
response = common.RMCResponse() response.result = common.Result(0x10001) #Success response.connection_id = next(self.connection_id) response.public_station = station return response def register_ex(self, context, urls, login_data): return self.register(context, urls) class FriendsServer(friends.FriendsServer): pass #Implement friend server methods here settings = Settings("friends.cfg") settings.set("nex.access_key", Friends.ACCESS_KEY) auth_server = service.RMCServer(settings) auth_server.register_protocol(AuthenticationServer(settings)) auth_server.start("", 1223) server_key = derive_key(get_user_by_name("Quazal Rendez-Vous")) secure_server = service.RMCServer(settings) secure_server.register_protocol(SecureConnectionServer()) secure_server.register_protocol(FriendsServer()) secure_server.start("", 1224, key=server_key) input("Press enter to exit...\n")
class BackEndClient: def __init__(self, access_key, version, settings=None): if settings: self.settings = settings.copy() else: self.settings = Settings() self.settings.set("server.access_key", access_key) self.settings.set("server.version", version) self.auth_client = service.RMCClient(self.settings) self.secure_client = service.RMCClient(self.settings) self.auth_proto = authentication.AuthenticationClient(self.auth_client) self.secure_proto = secure.SecureConnectionClient(self.secure_client) if self.settings.get("kerberos.key_derivation") == 0: self.key_derivation = kerberos.KeyDerivationOld(65000, 1024) else: self.key_derivation = kerberos.KeyDerivationNew(1, 1) self.my_pid = None self.local_station = None self.public_station = None def connect(self, host, port): # Connect to authentication server if not self.auth_client.connect(host, port, 1): raise ConnectionError("Couldn't connect to authentication server") def close(self): self.auth_client.close() self.secure_client.close() def login(self, username, password, auth_info=None, login_data=None): # Call login method on authentication protocol if auth_info: response = self.auth_proto.login_ex(username, auth_info) else: response = self.auth_proto.login(username) # Check for errors response.result.raise_if_error() self.my_pid = response.pid secure_station = response.connection_data.main_station # Derive kerberos key from password kerberos_key = self.key_derivation.derive_key(password.encode("ascii"), response.pid) # Decrypt ticket from login response ticket = kerberos.ClientTicket() ticket.decrypt(response.ticket, kerberos_key, self.settings) if ticket.target_pid != secure_station["PID"]: # Request ticket for secure server response = self.auth_proto.request_ticket(self.my_pid, secure_station["PID"]) # Check for errors and decrypt ticket response.result.raise_if_error() ticket = kerberos.ClientTicket() ticket.decrypt(response.ticket, kerberos_key, self.settings) ticket.source_pid = self.my_pid ticket.target_cid = secure_station["CID"] # The secure server may reside at the same # address as the authentication server host = secure_station["address"] port = secure_station["port"] if host == "0.0.0.1": host, port = self.auth_client.remote_address() # Connect to secure server server_sid = secure_station["sid"] if not self.secure_client.connect(host, port, server_sid, ticket): raise ConnectionError("Couldn't connect to secure server") # Create a stationurl for our local client address client_addr = self.secure_client.local_address() self.local_station = common.StationURL( address=client_addr[0], port=client_addr[1], sid=self.secure_client.stream_id(), natm=0, natf=0, upnp=0, pmp=0) # Register urls on secure server if login_data: response = self.secure_proto.register_ex([self.local_station], login_data) else: response = self.secure_proto.register([self.local_station]) # Check for errors and update urls response.result.raise_if_error() self.public_station = response.public_station self.public_station["RVCID"] = response.connection_id self.local_station["RVCID"] = response.connection_id def login_guest(self): self.login("guest", "MMQea3n!fsik") def get_pid(self): return self.my_pid