async def finish_pairing(self, username: str, pin_code: int) -> bool: """Finish authentication process. A username (generated by new_credentials) and the PIN code shown on screen must be provided. """ # Step 1 self.srp.step1(pin_code) pub_key, proof = self.srp.step2(self._atv_pub_key, self._atv_salt) data = { hap_tlv8.TlvValue.SeqNo: b"\x03", hap_tlv8.TlvValue.PublicKey: pub_key, hap_tlv8.TlvValue.Proof: proof, } await self.http.post("/pair-setup", body=hap_tlv8.write_tlv(data), headers=_AIRPLAY_HEADERS) data = { hap_tlv8.TlvValue.SeqNo: b"\x05", hap_tlv8.TlvValue.EncryptedData: self.srp.step3(), } resp = await self.http.post("/pair-setup", body=hap_tlv8.write_tlv(data), headers=_AIRPLAY_HEADERS) pairing_data = hap_tlv8.read_tlv(resp.body) encrypted_data = pairing_data[hap_tlv8.TlvValue.EncryptedData] return self.srp.step4(encrypted_data)
async def verify_credentials(self) -> bool: """Verify credentials with device.""" _, public_key = self.srp.initialize() resp = await self.protocol.exchange_auth( FrameType.PV_Start, { PAIRING_DATA_KEY: write_tlv( {TlvValue.SeqNo: b"\x01", TlvValue.PublicKey: public_key} ), "_auTy": 4, }, ) pairing_data = _get_pairing_data(resp) server_pub_key = pairing_data[TlvValue.PublicKey] encrypted = pairing_data[TlvValue.EncryptedData] log_binary(_LOGGER, "Device", Public=self.credentials.ltpk, Encrypted=encrypted) encrypted_data = self.srp.verify1(self.credentials, server_pub_key, encrypted) await self.protocol.exchange_auth( FrameType.PV_Next, { PAIRING_DATA_KEY: write_tlv( {TlvValue.SeqNo: b"\x03", TlvValue.EncryptedData: encrypted_data} ), }, ) # TODO: check status code return True
async def verify_credentials(self) -> bool: """Verify if device is allowed to use AirPlau.""" self.srp.initialize() await self.http.post("/pair-pin-start", headers=_AIRPLAY_HEADERS) data = { hap_tlv8.TlvValue.Method: b"\x00", hap_tlv8.TlvValue.SeqNo: b"\x01" } resp = await self.http.post("/pair-setup", body=hap_tlv8.write_tlv(data), headers=_AIRPLAY_HEADERS) pairing_data = hap_tlv8.read_tlv(resp.body) atv_salt = pairing_data[hap_tlv8.TlvValue.Salt] atv_pub_key = pairing_data[hap_tlv8.TlvValue.PublicKey] self.srp.step1(_TRANSIENT_PIN) pub_key, proof = self.srp.step2(atv_pub_key, atv_salt) data = { hap_tlv8.TlvValue.SeqNo: b"\x03", hap_tlv8.TlvValue.PublicKey: pub_key, hap_tlv8.TlvValue.Proof: proof, } await self.http.post("/pair-setup", body=hap_tlv8.write_tlv(data), headers=_AIRPLAY_HEADERS) return True
async def finish_pairing(self, username: str, pin_code: int) -> HapCredentials: """Finish pairing process.""" self.srp.step1(pin_code) pub_key, proof = self.srp.step2(self._atv_pub_key, self._atv_salt) resp = await self.protocol.exchange_auth( FrameType.PS_Next, { PAIRING_DATA_KEY: write_tlv( { TlvValue.SeqNo: b"\x03", TlvValue.PublicKey: pub_key, TlvValue.Proof: proof, } ), "_pwTy": 1, }, ) pairing_data = _get_pairing_data(resp) atv_proof = pairing_data[TlvValue.Proof] log_binary(_LOGGER, "Device", Proof=atv_proof) # TODO: Dummy data: what to set? needed at all? additional_data = { "altIRK": b"-\x54\xe0\x7a\x88*en\x11\xab\x82v-'%\xc5", "accountID": "DC6A7CB6-CA1A-4BF4-880D-A61B717814DB", "model": "AppleTV6,2", "wifiMAC": b"@\xff\xa1\x8f\xa1\xb9", "name": "Living Room", "mac": b"@\xc4\xff\x8f\xb1\x99", } encrypted_data = self.srp.step3( additional_data={17: opack.pack(additional_data)} ) resp = await self.protocol.exchange_auth( FrameType.PS_Next, { PAIRING_DATA_KEY: write_tlv( { TlvValue.SeqNo: b"\x05", TlvValue.EncryptedData: encrypted_data, } ), "_pwTy": 1, }, ) pairing_data = _get_pairing_data(resp) encrypted_data = pairing_data[TlvValue.EncryptedData] return self.srp.step4(encrypted_data)
def _m5_setup(self, pairing_data): session_key = hkdf_expand( "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", binascii.unhexlify(self.session.key), ) acc_device_x = hkdf_expand( "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info", binascii.unhexlify(self.session.key), ) chacha = chacha20.Chacha20Cipher(session_key, session_key) decrypted_tlv_bytes = chacha.decrypt( pairing_data[TlvValue.EncryptedData], nounce="PS-Msg05".encode()) _LOGGER.debug("MSG5 EncryptedData=%s", read_tlv(decrypted_tlv_bytes)) other = { "altIRK": b"-\x54\xe0\x7a\x88*en\x11\xab\x82v-'%\xc5", "accountID": "DC6A7CB6-CA1A-4BF4-880D-A61B717814DB", "model": "AppleTV6,2", "wifiMAC": b"@\xff\xa1\x8f\xa1\xb9", "name": "Living Room", "mac": b"@\xc4\xff\x8f\xb1\x99", } device_info = acc_device_x + self.unique_id + self.keys.auth_pub signature = self.keys.sign.sign(device_info) tlv = { TlvValue.Identifier: self.unique_id, TlvValue.PublicKey: self.keys.auth_pub, TlvValue.Signature: signature, 17: opack.pack(other), } tlv = write_tlv(tlv) chacha = chacha20.Chacha20Cipher(session_key, session_key) encrypted = chacha.encrypt(tlv, nounce="PS-Msg06".encode()) tlv = write_tlv({ TlvValue.SeqNo: b"\x06", TlvValue.EncryptedData: encrypted }) self.send_to_client(FrameType.PS_Next, {"_pd": tlv}) self.has_paired()
def _m3_setup(self, pairing_data): pubkey = binascii.hexlify(pairing_data[TlvValue.PublicKey]) self.session.process(pubkey, self.salt) if self.session.verify_proof(binascii.hexlify(pairing_data[TlvValue.Proof])): proof = binascii.unhexlify(self.session.key_proof_hash) tlv = {TlvValue.SeqNo: b"\x04", TlvValue.Proof: proof} else: tlv = { TlvValue.SeqNo: b"\x04", TlvValue.Error: bytes([ErrorCode.Authentication]), } write_shared_key = hkdf_expand( "Control-Salt", "Control-Write-Encryption-Key", binascii.unhexlify(self.session.key), ) read_shared_key = hkdf_expand( "Control-Salt", "Control-Read-Encryption-Key", binascii.unhexlify(self.session.key), ) self.enable_encryption(write_shared_key, read_shared_key) self.has_paired() return write_tlv(tlv)
def _m5_setup(self, _): session_key = hkdf_expand( "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", binascii.unhexlify(self.session.key), ) acc_device_x = hkdf_expand( "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info", binascii.unhexlify(self.session.key), ) device_info = acc_device_x + self.unique_id + self.keys.auth_pub signature = self.keys.sign.sign(device_info) tlv = write_tlv({ TlvValue.Identifier: self.unique_id, TlvValue.PublicKey: self.keys.auth_pub, TlvValue.Signature: signature, }) chacha = chacha20.Chacha20Cipher(session_key, session_key) encrypted = chacha.encrypt(tlv, nounce="PS-Msg06".encode()) msg = messages.crypto_pairing({ TlvValue.SeqNo: b"\x06", TlvValue.EncryptedData: encrypted }) self.has_paired = True self.send_to_client(msg)
def step3(self, additional_data=None): """Third pairing step.""" ios_device_x = hkdf_expand( "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info", binascii.unhexlify(self._session.key), ) self._session_key = hkdf_expand( "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", binascii.unhexlify(self._session.key), ) device_info = ios_device_x + self.pairing_id + self._auth_public device_signature = self._signing_key.sign(device_info) tlv = { TlvValue.Identifier: self.pairing_id, TlvValue.PublicKey: self._auth_public, TlvValue.Signature: device_signature, } if additional_data: tlv.update(additional_data) chacha = chacha20.Chacha20Cipher(self._session_key, self._session_key) encrypted_data = chacha.encrypt(write_tlv(tlv), nounce="PS-Msg05".encode()) log_binary(_LOGGER, "Data", Encrypted=encrypted_data) return encrypted_data
def _m1_setup(self, pairing_data): return write_tlv( { TlvValue.SeqNo: b"\x02", TlvValue.Salt: binascii.unhexlify(self.salt), TlvValue.PublicKey: binascii.unhexlify(self.session.public), } )
def crypto_pairing(pairing_data, is_pairing=False): """Create a new CRYPTO_PAIRING_MESSAGE.""" message = create(protobuf.CRYPTO_PAIRING_MESSAGE) crypto = message.inner() crypto.status = 0 crypto.pairingData = hap_tlv8.write_tlv(pairing_data) # Hardcoded values for now, might have to be changed crypto.isRetrying = False crypto.isUsingSystemPairing = False crypto.state = 2 if is_pairing else 0 return message
def _m1_setup(self, pairing_data): tlv = write_tlv({ TlvValue.SeqNo: b"\x02", TlvValue.Salt: binascii.unhexlify(self.salt), TlvValue.PublicKey: binascii.unhexlify(self.session.public), 27: b"\x01", }) self.send_to_client(FrameType.PS_Next, {"_pd": tlv, "_pwTy": 1})
def _m1_verify(self, pairing_data): server_pub_key = self.keys.verify_pub.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) client_pub_key = pairing_data[TlvValue.PublicKey] shared_key = self.keys.verify.exchange( X25519PublicKey.from_public_bytes(client_pub_key)) session_key = hkdf_expand("Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", shared_key) info = server_pub_key + self.unique_id + client_pub_key signature = self.keys.sign.sign(info) tlv = write_tlv({ TlvValue.Identifier: self.unique_id, TlvValue.Signature: signature }) chacha = chacha20.Chacha20Cipher(session_key, session_key) encrypted = chacha.encrypt(tlv, nounce="PV-Msg02".encode()) tlv = write_tlv({ TlvValue.SeqNo: b"\x02", TlvValue.PublicKey: server_pub_key, TlvValue.EncryptedData: encrypted, }) self.output_key = hkdf_expand("", "ServerEncrypt-main", shared_key) self.input_key = hkdf_expand("", "ClientEncrypt-main", shared_key) log_binary(_LOGGER, "Keys", Output=self.output_key, Input=self.input_key) self.send_to_client(FrameType.PV_Next, {"_pd": tlv})
def _m3_setup(self, pairing_data): pubkey = binascii.hexlify(pairing_data[TlvValue.PublicKey]).decode() self.session.process(pubkey, self.salt) if self.session.verify_proof( binascii.hexlify(pairing_data[TlvValue.Proof])): proof = binascii.unhexlify(self.session.key_proof_hash) tlv = {TlvValue.Proof: proof, TlvValue.SeqNo: b"\x04"} else: tlv = { TlvValue.Error: bytes([ErrorCode.Authentication]), TlvValue.SeqNo: b"\x04", } self.send_to_client(FrameType.PS_Next, {"_pd": write_tlv(tlv)})
async def start_pairing(self) -> None: """Start the authentication process. This method will show the expected PIN on screen. """ self.srp.initialize() await self.http.post("/pair-pin-start", headers=_AIRPLAY_HEADERS) data = { hap_tlv8.TlvValue.Method: b"\x00", hap_tlv8.TlvValue.SeqNo: b"\x01" } resp = await self.http.post("/pair-setup", body=hap_tlv8.write_tlv(data), headers=_AIRPLAY_HEADERS) pairing_data = hap_tlv8.read_tlv(resp.body) self._atv_salt = pairing_data[hap_tlv8.TlvValue.Salt] self._atv_pub_key = pairing_data[hap_tlv8.TlvValue.PublicKey]
def verify1(self, credentials, session_pub_key, encrypted): """First verification step.""" self._shared = self._verify_private.exchange( X25519PublicKey.from_public_bytes(session_pub_key)) session_key = hkdf_expand("Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", self._shared) chacha = chacha20.Chacha20Cipher(session_key, session_key) decrypted_tlv = read_tlv( chacha.decrypt(encrypted, nounce="PV-Msg02".encode())) identifier = decrypted_tlv[TlvValue.Identifier] signature = decrypted_tlv[TlvValue.Signature] if identifier != credentials.atv_id: raise exceptions.AuthenticationError("incorrect device response") info = session_pub_key + bytes(identifier) + self._public_bytes ltpk = Ed25519PublicKey.from_public_bytes(bytes(credentials.ltpk)) try: ltpk.verify(bytes(signature), bytes(info)) except InvalidSignature as ex: raise exceptions.AuthenticationError("signature error") from ex device_info = self._public_bytes + credentials.client_id + session_pub_key device_signature = Ed25519PrivateKey.from_private_bytes( credentials.ltsk).sign(device_info) tlv = write_tlv({ TlvValue.Identifier: credentials.client_id, TlvValue.Signature: device_signature, }) return chacha.encrypt(tlv, nounce="PV-Msg03".encode())
async def start_pairing(self) -> None: """Start pairing procedure.""" self.srp.initialize() await self.protocol.start() resp = await self.protocol.exchange_auth( FrameType.PS_Start, { PAIRING_DATA_KEY: write_tlv( {TlvValue.Method: b"\x00", TlvValue.SeqNo: b"\x01"} ), "_pwTy": 1, }, ) pairing_data = _get_pairing_data(resp) self._atv_salt = pairing_data[TlvValue.Salt] self._atv_pub_key = pairing_data[TlvValue.PublicKey] log_binary( _LOGGER, "Got pub key and salt", Salt=self._atv_salt, PubKey=self._atv_pub_key, )
async def _send(self, data: Dict[Any, Any]) -> HttpResponse: headers = copy(_AIRPLAY_HEADERS) headers["Content-Type"] = "application/octet-stream" return await self.http.post("/pair-verify", body=hap_tlv8.write_tlv(data), headers=headers)
def _m3_verify(self, pairing_data): self.send_to_client(FrameType.PV_Next, {"_pd": write_tlv({TlvValue.SeqNo: b"\x04"})}) self.enable_encryption(self.output_key, self.input_key)
def test_write_single_key(): assert write_tlv(SINGLE_KEY_IN) == SINGLE_KEY_OUT
def test_write_two_keys(): assert write_tlv(DOUBLE_KEY_IN) == DOUBLE_KEY_OUT
def test_write_key_larger_than_255_bytes(): # This will actually result in two serialized TLVs, one being 255 bytes # and the next one will contain the remaining one byte assert write_tlv(LARGE_KEY_IN) == LARGE_KEY_OUT