def __init__(self, file_id: bytes, preloaded_audio_key: bool, audio_key_time: int): self.file_id = None if file_id is None else Utils.bytes_to_hex( file_id) self.preloaded_audio_key = preloaded_audio_key self.audio_key_time = audio_key_time if preloaded_audio_key and audio_key_time != -1: raise RuntimeError()
def __init__(self, device_type: Connect.DeviceType, device_name: str, preferred_locale: str, conf: Session.Configuration, device_id: str = None): self.preferred_locale = preferred_locale self.conf = conf self.device_type = device_type self.device_name = device_name self.device_id = device_id if device_id is not None else Utils.random_hex_string( 40)
def resolve_storage_interactive( self, file_id: bytes, preload: bool) -> StorageResolve.StorageResolveResponse: resp = self.session.api().send( "GET", (self.STORAGE_RESOLVE_INTERACTIVE_PREFETCH if preload else self.STORAGE_RESOLVE_INTERACTIVE).format( Utils.bytes_to_hex(file_id)), None, None) if resp.status_code != 200: raise RuntimeError(resp.status_code) body = resp.content if body is None: RuntimeError("Response body is empty!") storage_resolve_response = StorageResolve.StorageResolveResponse() storage_resolve_response.ParseFromString(body) return storage_resolve_response
def public_key_array(self): return Utils.to_byte_array(self.public_key)
def get_episode_gid(self): if self.episode_gid is None: raise RuntimeError("Not an episode!") return Utils.bytes_to_hex(self.episode_gid)
def get_file_id(self): if self.file_id is None: raise RuntimeError("Not a file!") return Utils.bytes_to_hex(self.file_id)
def run(self) -> None: self.session._LOGGER.debug("Session.Receiver started") while self.running: packet: Packet cmd: bytes try: # noinspection PyProtectedMember packet = self.session._cipherPair.receive_encoded( self.session._conn) cmd = Packet.Type.parse(packet.cmd) if cmd is None: self.session._LOGGER.info( "Skipping unknown command cmd: 0x{}, payload: {}". format(Utils.bytes_to_hex(packet.cmd), packet.payload)) continue except RuntimeError as ex: if self.running: self.session._LOGGER.fatal( "Failed reading packet! {}".format(ex)) # noinspection PyProtectedMember self.session._reconnect() break if not self.running: break if cmd == Packet.Type.ping: # noinspection PyProtectedMember if self.session._scheduledReconnect is not None: # noinspection PyProtectedMember self.session._scheduler.cancel( self.session._scheduledReconnect) def anonymous(): self.session._LOGGER.warning( "Socket timed out. Reconnecting...") self.session._reconnect() # noinspection PyProtectedMember self.session.scheduled_reconnect = self.session._scheduler.enter( 2 * 60 + 5, 1, anonymous) self.session.send(Packet.Type.pong, packet.payload) continue if cmd == Packet.Type.pong_ack: continue if cmd == Packet.Type.country_code: self.session.country_code = packet.payload.decode() self.session._LOGGER.info( "Received country_code: {}".format( self.session.country_code)) continue if cmd == Packet.Type.license_version: license_version = BytesInputStream(packet.payload) license_id = license_version.read_short() if license_id != 0: buffer = license_version.read() self.session._LOGGER.info( "Received license_version: {}, {}".format( license_id, buffer.decode())) else: self.session._LOGGER.info( "Received license_version: {}".format(license_id)) continue if cmd == Packet.Type.unknown_0x10: self.session._LOGGER.debug("Received 0x10: {}".format( Utils.bytes_to_hex(packet.payload))) continue if cmd == Packet.Type.mercury_sub or \ cmd == Packet.Type.mercury_unsub or \ cmd == Packet.Type.mercury_event or \ cmd == Packet.Type.mercury_req: self.session.mercury().dispatch(packet) continue if cmd == Packet.Type.aes_key or \ cmd == Packet.Type.aes_key_error: self.session.audio_key().dispatch(packet) continue if cmd == Packet.Type.channel_error or \ cmd == Packet.Type.stream_chunk_res: self.session.channel().dispatch(packet) continue if cmd == Packet.Type.product_info: # noinspection PyProtectedMember self.session._parse_product_info(packet.payload) continue self.session._LOGGER.info("Skipping {}".format( Utils.bytes_to_hex(cmd))) self.session._LOGGER.debug("Session.Receiver stopped")
def _connect(self) -> None: acc = Session.Accumulator() # Send ClientHello nonce = os.urandom(0x10) client_hello = Keyexchange.ClientHello( build_info=Version.standard_build_info(), cryptosuites_supported=[ Keyexchange.Cryptosuite.CRYPTO_SUITE_SHANNON ], login_crypto_hello=Keyexchange.LoginCryptoHelloUnion( diffie_hellman=Keyexchange.LoginCryptoDiffieHellmanHello( gc=self._keys.public_key_array(), server_keys_known=1), ), client_nonce=nonce, padding=bytes([0x1e])) client_hello_bytes = client_hello.SerializeToString() length = 2 + 4 + len(client_hello_bytes) self._conn.write_byte(0) self._conn.write_byte(4) self._conn.write_int(length) self._conn.write(client_hello_bytes) self._conn.flush() acc.write_byte(0) acc.write_byte(4) acc.write_int(length) acc.write(client_hello_bytes) # Read APResponseMessage length = self._conn.read_int() acc.write_int(length) buffer = self._conn.read(length - 4) acc.write(buffer) ap_response_message = Keyexchange.APResponseMessage() ap_response_message.ParseFromString(buffer) shared_key = Utils.to_byte_array( self._keys.compute_shared_key( ap_response_message.challenge.login_crypto_challenge. diffie_hellman.gs)) # Check gs_signature rsa = RSA.construct((int.from_bytes(self._serverKey, "big"), 65537)) pkcs1_v1_5 = PKCS1_v1_5.new(rsa) sha1 = SHA1.new() sha1.update(ap_response_message.challenge.login_crypto_challenge. diffie_hellman.gs) # noinspection PyTypeChecker if not pkcs1_v1_5.verify( sha1, ap_response_message.challenge.login_crypto_challenge. diffie_hellman.gs_signature): raise RuntimeError("Failed signature check!") # Solve challenge data = b"" for i in range(1, 6): # noinspection PyTypeChecker mac = HMAC.new(shared_key, digestmod=SHA1) mac.update(acc.array()) mac.update(bytes([i])) data += mac.digest() # noinspection PyTypeChecker mac = HMAC.new(data[:20], digestmod=SHA1) mac.update(acc.array()) challenge = mac.digest() client_response_plaintext = Keyexchange.ClientResponsePlaintext( login_crypto_response=Keyexchange.LoginCryptoResponseUnion( diffie_hellman=Keyexchange.LoginCryptoDiffieHellmanResponse( hmac=challenge)), pow_response=Keyexchange.PoWResponseUnion(), crypto_response=Keyexchange.CryptoResponseUnion()) client_response_plaintext_bytes = client_response_plaintext.SerializeToString( ) length = 4 + len(client_response_plaintext_bytes) self._conn.write_int(length) self._conn.write(client_response_plaintext_bytes) self._conn.flush() try: self._conn.set_timeout(1) scrap = self._conn.read(4) if 4 == len(scrap): length = (scrap[0] << 24) | (scrap[1] << 16) | ( scrap[2] << 8) | (scrap[3] & 0xff) payload = self._conn.read(length - 4) failed = Keyexchange.APResponseMessage() failed.ParseFromString(payload) raise RuntimeError(failed) except socket.timeout: pass finally: self._conn.set_timeout(0) with self._authLock: self._cipherPair = CipherPair(data[20:52], data[52:84]) self._authLockBool = True self._LOGGER.info("Connection successfully!")